From 1161a4114dde2fe0de23cc920a64c64c06f171f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 26 Sep 2024 18:56:22 +0200 Subject: [PATCH 001/215] Minor tweaks to simd codebase --- Source/Engine/Core/Math/BoundingFrustum.h | 18 +++++----- Source/Engine/Core/SIMD.h | 40 ++++++++++++++++++----- Source/Engine/Platform/Defines.h | 1 + 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h index f98bcafae..554019079 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.h +++ b/Source/Engine/Core/Math/BoundingFrustum.h @@ -101,7 +101,7 @@ public: } /// - /// Gets the far plane of the BoundingFrustum. + /// Gets the far plane of the frustum. /// FORCE_INLINE Plane GetFar() const { @@ -109,7 +109,7 @@ public: } /// - /// Gets the left plane of the BoundingFrustum. + /// Gets the left plane of the frustum. /// FORCE_INLINE Plane GetLeft() const { @@ -117,7 +117,7 @@ public: } /// - /// Gets the right plane of the BoundingFrustum. + /// Gets the right plane of the frustum. /// FORCE_INLINE Plane GetRight() const { @@ -125,7 +125,7 @@ public: } /// - /// Gets the top plane of the BoundingFrustum. + /// Gets the top plane of the frustum. /// FORCE_INLINE Plane GetTop() const { @@ -133,7 +133,7 @@ public: } /// - /// Gets the bottom plane of the BoundingFrustum. + /// Gets the bottom plane of the frustum. /// FORCE_INLINE Plane GetBottom() const { @@ -230,17 +230,17 @@ public: ContainmentType Contains(const BoundingSphere& sphere) const; /// - /// Checks whether the current BoundingFrustum intersects a BoundingSphere. + /// Checks whether the current frustum intersects a sphere. /// /// The sphere. - /// True if the current BoundingFrustum intersects a BoundingSphere, otherwise false. + /// True if the current frustum intersects a sphere, otherwise false. bool Intersects(const BoundingSphere& sphere) const; /// - /// Checks whether the current BoundingFrustum intersects a BoundingBox. + /// Checks whether the current frustum intersects a box. /// /// The box - /// True if the current BoundingFrustum intersects a BoundingBox, otherwise false. + /// True if the current frustum intersects a box, otherwise false. FORCE_INLINE bool Intersects(const BoundingBox& box) const { return CollisionsHelper::FrustumContainsBox(*this, box) != ContainmentType::Disjoint; diff --git a/Source/Engine/Core/SIMD.h b/Source/Engine/Core/SIMD.h index 0ce58f7e7..b1d0db3a7 100644 --- a/Source/Engine/Core/SIMD.h +++ b/Source/Engine/Core/SIMD.h @@ -13,7 +13,7 @@ #if PLATFORM_SIMD_SSE2 -// Vector of four floating point values stored in vector register. +// Vector of four floating point values stored in a vector register. typedef __m128 SimdVector4; namespace SIMD @@ -28,9 +28,15 @@ namespace SIMD return _mm_set_ps(w, z, y, x); } - FORCE_INLINE SimdVector4 Load(const void* src) + FORCE_INLINE SimdVector4 Load(const float* __restrict src) { - return _mm_load_ps((const float*)(src)); + return _mm_loadu_ps(src); + } + + FORCE_INLINE SimdVector4 LoadAligned(const float* __restrict src) + { + ASSERT_LOW_LAYER(((uintptr)src & 15) == 0); + return _mm_load_ps(src); } FORCE_INLINE SimdVector4 Splat(float value) @@ -38,9 +44,15 @@ namespace SIMD return _mm_set_ps1(value); } - FORCE_INLINE void Store(void* dst, SimdVector4 src) + FORCE_INLINE void Store(float* __restrict dst, SimdVector4 src) { - _mm_store_ps((float*)dst, src); + _mm_storeu_ps(dst, src); + } + + FORCE_INLINE void StoreAligned(float* __restrict dst, SimdVector4 src) + { + ASSERT_LOW_LAYER(((uintptr)dst & 15) == 0); + _mm_store_ps(dst, src); } FORCE_INLINE int MoveMask(SimdVector4 a) @@ -113,7 +125,12 @@ namespace SIMD return { x, y, z, w }; } - FORCE_INLINE SimdVector4 Load(const void* src) + FORCE_INLINE SimdVector4 Load(const float* __restrict src) + { + return *(const SimdVector4*)src; + } + + FORCE_INLINE SimdVector4 LoadAligned(const float* __restrict src) { return *(const SimdVector4*)src; } @@ -123,10 +140,15 @@ namespace SIMD return { value, value, value, value }; } - FORCE_INLINE void Store(void* dst, SimdVector4 src) - { + FORCE_INLINE void Store(float* __restrict dst, SimdVector4 src) + { (*(SimdVector4*)dst) = src; - } + } + + FORCE_INLINE void StoreAligned(float* __restrict dst, SimdVector4 src) + { + (*(SimdVector4*)dst) = src; + } FORCE_INLINE int MoveMask(SimdVector4 a) { diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h index 40b250983..1f12f63d2 100644 --- a/Source/Engine/Platform/Defines.h +++ b/Source/Engine/Platform/Defines.h @@ -228,6 +228,7 @@ API_ENUM() enum class ArchitectureType #if defined(_M_PPC) || defined(__CELLOS_LV2__) #define PLATFORM_SIMD_VMX 1 #endif +#define PLATFORM_SIMD (PLATFORM_SIMD_SSE2 || PLATFORM_SIMD_SSE3 || PLATFORM_SIMD_SSE4 || PLATFORM_SIMD_NEON || PLATFORM_SIMD_VMX) // Unicode text macro #if !defined(TEXT) From f74694048575160067734cbe27a023ad78a2c727 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Oct 2024 10:04:18 +0200 Subject: [PATCH 002/215] Fix typo --- Source/Engine/Platform/Android/AndroidFileSystem.cpp | 2 +- Source/Engine/Platform/Apple/AppleFileSystem.cpp | 2 +- Source/Engine/Platform/Linux/LinuxFileSystem.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp index 1cea6715e..88114c899 100644 --- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp +++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp @@ -80,7 +80,7 @@ namespace // Determinate a full path of an entry char full_path[256]; ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); + strcpy(full_path, pathStr); strcat(full_path, "/"); strcat(full_path, entry->d_name); diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp index 6c87428cc..437f09288 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -184,7 +184,7 @@ bool AppleFileSystem::GetChildDirectories(Array& results, const String& // Determinate a full path of an entry char fullPath[256]; ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); - strcpy(fullPath, path); + strcpy(fullPath, pathStr); strcat(fullPath, "/"); strcat(fullPath, entry->d_name); diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 728b2c41b..128716418 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -227,7 +227,7 @@ bool DeleteUnixPathTree(const char* path) // Determinate a full path of an entry char full_path[256]; ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); + strcpy(full_path, pathStr); strcat(full_path, "/"); strcat(full_path, entry->d_name); From a932d549f4e69f1d61905b17d1d8f6c0b528a8d7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Oct 2024 10:24:15 +0200 Subject: [PATCH 003/215] Debug Commands work in progress --- Source/Engine/Debug/DebugCommands.cpp | 293 ++++++++++++++++++++++++++ Source/Engine/Debug/DebugCommands.cs | 15 ++ Source/Engine/Debug/DebugCommands.h | 24 +++ Source/Engine/Engine/Engine.h | 2 +- Source/Engine/Graphics/Graphics.h | 2 +- 5 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 Source/Engine/Debug/DebugCommands.cpp create mode 100644 Source/Engine/Debug/DebugCommands.cs create mode 100644 Source/Engine/Debug/DebugCommands.h diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp new file mode 100644 index 000000000..e4af0268e --- /dev/null +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -0,0 +1,293 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "DebugCommands.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Engine/EngineService.h" +#include "Engine/Threading/Threading.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Scripting/BinaryModule.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/ManagedCLR/MAssembly.h" +#include "Engine/Scripting/ManagedCLR/MClass.h" +#include "Engine/Scripting/ManagedCLR/MMethod.h" +#include "Engine/Scripting/ManagedCLR/MField.h" +#include "Engine/Scripting/ManagedCLR/MProperty.h" +#include "FlaxEngine.Gen.h" + +struct CommandData +{ + String Name; + BinaryModule* Module; + void* Method = nullptr; + void* MethodGet = nullptr; + void* MethodSet = nullptr; + void* Field = nullptr; + + void Invoke(StringView args) const + { + PROFILE_CPU(); + + // Get command signature + Array> sigParams; + if (Method) + { + ScriptingTypeMethodSignature sig; + Module->GetMethodSignature(Method, sig); + sigParams = MoveTemp(sig.Params); + } + else if (Field) + { + ScriptingTypeFieldSignature sig; + Module->GetFieldSignature(Field, sig); + auto& p = sigParams.AddOne(); + p.IsOut = false; + p.Type = sig.ValueType; + } + else if (MethodSet && args.HasChars()) + { + ScriptingTypeMethodSignature sig; + Module->GetMethodSignature(MethodSet, sig); + sigParams = MoveTemp(sig.Params); + sigParams.Resize(1); + } + + // Parse arguments + Array params; + params.Resize(sigParams.Count()); + Array argsSeparated; + String argsStr(args); + argsStr.Split(' ', argsSeparated); + for (int32 i = 0; i < argsSeparated.Count() && i < params.Count(); i++) + { + params[i] = Variant::Parse(argsSeparated[i], sigParams[i].Type); + } + + // Call command + Variant result; + if (Method) + { + Module->InvokeMethod(Method, Variant::Null, ToSpan(params), result); + } + else if (Field) + { + if (args.IsEmpty()) + Module->GetFieldValue(Field, Variant::Null, result); + else + Module->SetFieldValue(Field, Variant::Null, params[0]); + } + else if (MethodGet && args.IsEmpty()) + { + Module->InvokeMethod(MethodGet, Variant::Null, ToSpan(params), result); + } + else if (MethodSet && args.HasChars()) + { + Module->InvokeMethod(MethodSet, Variant::Null, ToSpan(params), result); + } + + // Print result + if (result != Variant()) + { + LOG_STR(Info, result.ToString()); + } + } +}; + +namespace +{ + CriticalSection Locker; + bool Inited = false; + Array Commands; + + void OnBinaryModuleLoaded(BinaryModule* module) + { + if (module == GetBinaryModuleCorlib()) + return; + +#if USE_CSHARP + if (auto* managedModule = dynamic_cast(module)) + { + const MClass* attribute = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEngine.DebugCommand"); + ASSERT_LOW_LAYER(attribute); + const auto& classes = managedModule->Assembly->GetClasses(); + for (auto e : classes) + { + MClass* mclass = e.Value; + if (mclass->IsGeneric() || + mclass->IsInterface() || + mclass->IsEnum()) + continue; + const bool useClass = mclass->HasAttribute(attribute); + // TODO: optimize this via stack-based format buffer and then convert Ansi to UTF16 +#define BUILD_NAME(commandData, itemName) commandData.Name = String(mclass->GetName()) + TEXT(".") + String(itemName) + + // Process methods + const auto& methods = mclass->GetMethods(); + for (MMethod* method : methods) + { + if (!method->IsStatic()) + continue; + const StringAnsi& name = method->GetName(); + if (name.Contains("Internal_") || + mclass->GetFullName().Contains(".Interop.")) + continue; + if (name.StartsWith("get_") || + name.StartsWith("set_") || + name.StartsWith("op_") || + name.StartsWith("add_") || + name.StartsWith("remove_")) + continue; + if (!useClass && !method->HasAttribute(attribute)) + continue; + + auto& commandData = Commands.AddOne(); + BUILD_NAME(commandData, method->GetName()); + commandData.Module = module; + commandData.Method = method; + } + + // Process fields + const auto& fields = mclass->GetFields(); + for (MField* field : fields) + { + if (!field->IsStatic()) + continue; + if (!useClass && !field->HasAttribute(attribute)) + continue; + + auto& commandData = Commands.AddOne(); + BUILD_NAME(commandData, field->GetName()); + commandData.Module = module; + commandData.Field = field; + } + + // Process properties + const auto& properties = mclass->GetProperties(); + for (MProperty* property : properties) + { + if (!property->IsStatic()) + continue; + if (!useClass && !property->HasAttribute(attribute)) + continue; + + auto& commandData = Commands.AddOne(); + BUILD_NAME(commandData, property->GetName()); + commandData.Module = module; + commandData.MethodGet = property->GetGetMethod(); + commandData.MethodSet = property->GetSetMethod(); + } + } +#undef BUILD_NAME + } + else +#endif + { + // TODO: implement generic search for other modules (eg. Visual Scripts) + } + } + + void OnScriptsReloading() + { + // Reset + Inited = false; + Commands.Clear(); + } + + void InitCommands() + { + PROFILE_CPU(); + Inited = true; + const auto& modules = BinaryModule::GetModules(); + for (BinaryModule* module : modules) + { + OnBinaryModuleLoaded(module); + } + Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded); + Scripting::ScriptsReloading.Bind(&OnScriptsReloading); + } +} + +class DebugCommandsService : public EngineService +{ +public: + DebugCommandsService() + : EngineService(TEXT("DebugCommands"), 0) + { + } + + void Dispose() override + { + // Cleanup + ScopeLock lock(Locker); + Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded); + Scripting::ScriptsReloading.Unbind(&OnScriptsReloading); + Commands.Clear(); + Inited = true; + } +}; + +DebugCommandsService DebugCommandsServiceInstance; + +void DebugCommands::Execute(StringView command) +{ + // Preprocess command text + while (command.HasChars() && StringUtils::IsWhitespace(command[0])) + command = StringView(command.Get() + 1, command.Length() - 1); + while (command.HasChars() && StringUtils::IsWhitespace(command[command.Length() - 1])) + command = StringView(command.Get(), command.Length() - 1); + if (command.IsEmpty()) + return; + StringView name = command; + StringView args; + int32 argsStart = name.Find(' '); + if (argsStart != -1) + { + name = command.Left(argsStart); + args = command.Right(argsStart + 1); + } + + // Ensure that commands cache has been created + ScopeLock lock(Locker); + if (!Inited) + InitCommands(); + + // Find command to run + for (const CommandData& command : Commands) + { + if (name.Length() == command.Name.Length() && + StringUtils::CompareIgnoreCase(name.Get(), command.Name.Get(), name.Length()) == 0) + { + command.Invoke(args); + return; + } + } + + LOG(Error, "Unknown command '{}'", name); +} + +bool DebugCommands::Iterate(const StringView& searchText, int32& index) +{ + ScopeLock lock(Locker); + if (index >= 0) + { + if (!Inited) + InitCommands(); + while (index < Commands.Count()) + { + auto& command = Commands.Get()[index]; + if (command.Name.StartsWith(searchText, StringSearchCase::IgnoreCase)) + { + return true; + } + index++; + } + } + return false; +} + +String DebugCommands::GetCommandName(int32 index) +{ + ScopeLock lock(Locker); + CHECK_RETURN(Commands.IsValidIndex(index), String::Empty); + return Commands.Get()[index].Name; +} diff --git a/Source/Engine/Debug/DebugCommands.cs b/Source/Engine/Debug/DebugCommands.cs new file mode 100644 index 000000000..3c67ca31e --- /dev/null +++ b/Source/Engine/Debug/DebugCommands.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Marks static method as debug command that can be executed from the command line or via console. + /// + [Serializable] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class DebugCommand : Attribute + { + } +} diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h new file mode 100644 index 000000000..cedf39f57 --- /dev/null +++ b/Source/Engine/Debug/DebugCommands.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingType.h" + +/// +/// Debug commands and console variables system. +/// +API_CLASS(static) class FLAXENGINE_API DebugCommands +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(DebugCommands); + +public: + /// + /// Executees the command. + /// + /// The command line (optionally with arguments). + API_FUNCTION() static void Execute(StringView command); + +public: + static bool Iterate(const StringView& searchText, int32& index); + static String GetCommandName(int32 index); +}; diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 85ffefda0..2f1728aa2 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -97,7 +97,7 @@ public: /// Exits the engine. /// /// The exit code. - static void Exit(int32 exitCode = -1); + API_FUNCTION(Attributes="DebugCommand") static void Exit(int32 exitCode = -1); /// /// Requests normal engine exit. diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 4c5e0ad26..fdcc706f7 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -9,7 +9,7 @@ /// /// Graphics device manager that creates, manages and releases graphics device and related objects. /// -API_CLASS(Static) class FLAXENGINE_API Graphics +API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Graphics { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Graphics); public: From 2f0518a348b9b7b456f0ab5d3ee6fe3b22e23635 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Oct 2024 17:10:01 +0200 Subject: [PATCH 004/215] Fix managed interop bug --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 16 +++++++++------- Source/Engine/Scripting/Runtime/DotNet.cpp | 14 +++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 62fd95117..852dcb52c 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -319,14 +319,15 @@ namespace FlaxEngine.Interop var arr = (NativeMethodDefinitions*)NativeAlloc(methods.Count, Unsafe.SizeOf()); for (int i = 0; i < methods.Count; i++) { + var method = methods[i]; IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); var classMethod = new NativeMethodDefinitions { - name = NativeAllocStringAnsi(methods[i].Name), - numParameters = methods[i].GetParameters().Length, - methodAttributes = (uint)methods[i].Attributes, + name = NativeAllocStringAnsi(method.Name), + numParameters = method.GetParameters().Length, + methodAttributes = (uint)method.Attributes, }; - classMethod.typeHandle = GetMethodGCHandle(methods[i]); + classMethod.typeHandle = GetMethodGCHandle(method); Unsafe.Write(ptr.ToPointer(), classMethod); } *classMethods = arr; @@ -377,14 +378,15 @@ namespace FlaxEngine.Interop var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf()); for (int i = 0; i < properties.Length; i++) { + var property = properties[i]; IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); - var getterMethod = properties[i].GetGetMethod(true); - var setterMethod = properties[i].GetSetMethod(true); + var getterMethod = property.GetGetMethod(true); + var setterMethod = property.GetSetMethod(true); var classProperty = new NativePropertyDefinitions { - name = NativeAllocStringAnsi(properties[i].Name), + name = NativeAllocStringAnsi(property.Name), }; if (getterMethod != null) { diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 26b011855..c2155a148 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -1000,11 +1000,12 @@ const Array& MClass::GetMethods() const int methodsCount; static void* GetClassMethodsPtr = GetStaticMethodPointer(TEXT("GetClassMethods")); CallStaticMethod(GetClassMethodsPtr, _handle, &methods, &methodsCount); + _methods.Resize(methodsCount); for (int32 i = 0; i < methodsCount; i++) { NativeMethodDefinitions& definition = methods[i]; MMethod* method = New(const_cast(this), StringAnsi(definition.name), definition.handle, definition.numParameters, definition.methodAttributes); - _methods.Add(method); + _methods[i] = method; MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(methods); @@ -1036,11 +1037,12 @@ const Array& MClass::GetFields() const int numFields; static void* GetClassFieldsPtr = GetStaticMethodPointer(TEXT("GetClassFields")); CallStaticMethod(GetClassFieldsPtr, _handle, &fields, &numFields); + _fields.Resize(numFields); for (int32 i = 0; i < numFields; i++) { NativeFieldDefinitions& definition = fields[i]; MField* field = New(const_cast(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes); - _fields.Add(field); + _fields[i] = field; MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(fields); @@ -1083,11 +1085,12 @@ const Array& MClass::GetProperties() const int numProperties; static void* GetClassPropertiesPtr = GetStaticMethodPointer(TEXT("GetClassProperties")); CallStaticMethod(GetClassPropertiesPtr, _handle, &foundProperties, &numProperties); + _properties.Resize(numProperties); for (int i = 0; i < numProperties; i++) { const NativePropertyDefinitions& definition = foundProperties[i]; MProperty* property = New(const_cast(this), definition.name, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); - _properties.Add(property); + _properties[i] = property; MCore::GC::FreeMemory((void*)definition.name); } MCore::GC::FreeMemory(foundProperties); @@ -1108,10 +1111,11 @@ const Array& MClass::GetInterfaces() const int numInterfaces; static void* GetClassInterfacesPtr = GetStaticMethodPointer(TEXT("GetClassInterfaces")); CallStaticMethod(GetClassInterfacesPtr, _handle, &foundInterfaceTypes, &numInterfaces); + _interfaces.Resize(numInterfaces); for (int32 i = 0; i < numInterfaces; i++) { MClass* interfaceClass = GetOrCreateClass(foundInterfaceTypes[i]); - _interfaces.Add(interfaceClass); + _interfaces[i] = interfaceClass; } MCore::GC::FreeMemory(foundInterfaceTypes); @@ -1504,7 +1508,7 @@ MProperty::MProperty(MClass* parentClass, const char* name, void* getterHandle, { _hasGetMethod = getterHandle != nullptr; if (_hasGetMethod) - _getMethod = New(parentClass, StringAnsi("get_" + _name), getterHandle, 1, getterAttributes); + _getMethod = New(parentClass, StringAnsi("get_" + _name), getterHandle, 0, getterAttributes); else _getMethod = nullptr; _hasSetMethod = setterHandle != nullptr; From 85915d787ad8193246e903505f1602e1ec86d622 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Oct 2024 17:10:34 +0200 Subject: [PATCH 005/215] Add Variant enum parsing by name and fix enum printing to string --- Source/Engine/Core/Types/Variant.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 3aea34368..221f6f660 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -2740,12 +2740,12 @@ String Variant::ToString() const const auto items = typeHandle.GetType().Enum.Items; for (int32 i = 0; items[i].Name; i++) { - if (items[i].Value == AsUint) + if (items[i].Value == AsUint64) return String(items[i].Name); } } } - return StringUtils::ToString(AsUint); + return StringUtils::ToString(AsUint64); case VariantType::Int64: return StringUtils::ToString(AsInt64); case VariantType::Uint64: @@ -3098,7 +3098,27 @@ Variant Variant::Parse(const StringView& text, const VariantType& type) break; case VariantType::Uint64: case VariantType::Enum: - StringUtils::Parse(text.Get(), &result.AsUint64); + if (!StringUtils::Parse(text.Get(), &result.AsUint64)) + { + } + else if (type.TypeName) + { + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(type.TypeName)); + if (typeHandle && typeHandle.GetType().Type == ScriptingTypes::Enum) + { + const auto items = typeHandle.GetType().Enum.Items; + StringAsANSI<32> textAnsi(text.Get(), text.Length()); + StringAnsiView textAnsiView(textAnsi.Get()); + for (int32 i = 0; items[i].Name; i++) + { + if (textAnsiView == items[i].Name) + { + result.AsUint64 = items[i].Value; + break; + } + } + } + } break; case VariantType::Float: StringUtils::Parse(text.Get(), &result.AsFloat); From adbe578aa107e53f2b76f95b82f63557a7ca734d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Oct 2024 15:57:41 +0200 Subject: [PATCH 006/215] Add attributes support for dotnet interop for methods, fields and properties --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 54 ++++++++- Source/Engine/Engine/NativeInterop.cs | 2 + .../Engine/Scripting/ManagedCLR/MProperty.h | 4 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 104 +++++++++--------- 4 files changed, 107 insertions(+), 57 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 852dcb52c..e3d66a2ef 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -49,6 +49,7 @@ namespace FlaxEngine.Interop internal struct NativePropertyDefinitions { internal IntPtr name; + internal ManagedHandle propertyHandle; internal ManagedHandle getterHandle; internal ManagedHandle setterHandle; internal uint getterAttributes; @@ -379,6 +380,17 @@ namespace FlaxEngine.Interop for (int i = 0; i < properties.Length; i++) { var property = properties[i]; + + ManagedHandle propertyHandle = ManagedHandle.Alloc(property); +#if FLAX_EDITOR + if (type.IsCollectible) + propertyHandleCacheCollectible.Add(propertyHandle); + else +#endif + { + propertyHandleCache.Add(propertyHandle); + } + IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); var getterMethod = property.GetGetMethod(true); @@ -387,6 +399,7 @@ namespace FlaxEngine.Interop var classProperty = new NativePropertyDefinitions { name = NativeAllocStringAnsi(property.Name), + propertyHandle = propertyHandle, }; if (getterMethod != null) { @@ -404,12 +417,8 @@ namespace FlaxEngine.Interop *classPropertiesCount = properties.Length; } - [UnmanagedCallersOnly] - internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount) + internal static void GetAttributes(object[] attributeValues, ManagedHandle** classAttributes, int* classAttributesCount) { - Type type = Unsafe.As(typeHandle.Target); - object[] attributeValues = type.GetCustomAttributes(false); - ManagedHandle* arr = (ManagedHandle*)NativeAlloc(attributeValues.Length, Unsafe.SizeOf()); for (int i = 0; i < attributeValues.Length; i++) { @@ -424,6 +433,38 @@ namespace FlaxEngine.Interop *classAttributesCount = attributeValues.Length; } + [UnmanagedCallersOnly] + internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount) + { + Type type = Unsafe.As(typeHandle.Target); + object[] attributeValues = type.GetCustomAttributes(false); + GetAttributes(attributeValues, classAttributes, classAttributesCount); + } + + [UnmanagedCallersOnly] + internal static void GetMethodAttributes(ManagedHandle methodHandle, ManagedHandle** classAttributes, int* classAttributesCount) + { + MethodHolder methodHolder = Unsafe.As(methodHandle.Target); + object[] attributeValues = methodHolder.method.GetCustomAttributes(false); + GetAttributes(attributeValues, classAttributes, classAttributesCount); + } + + [UnmanagedCallersOnly] + internal static void GetFieldAttributes(ManagedHandle fieldHandle, ManagedHandle** classAttributes, int* classAttributesCount) + { + FieldHolder field = Unsafe.As(fieldHandle.Target); + object[] attributeValues = field.field.GetCustomAttributes(false); + GetAttributes(attributeValues, classAttributes, classAttributesCount); + } + + [UnmanagedCallersOnly] + internal static void GetPropertyAttributes(ManagedHandle propertyHandle, ManagedHandle** classAttributes, int* classAttributesCount) + { + PropertyInfo property = Unsafe.As(propertyHandle.Target); + object[] attributeValues = property.GetCustomAttributes(false); + GetAttributes(attributeValues, classAttributes, classAttributesCount); + } + [UnmanagedCallersOnly] internal static ManagedHandle GetCustomAttribute(ManagedHandle typeHandle, ManagedHandle attributeHandle) { @@ -1027,6 +1068,9 @@ namespace FlaxEngine.Interop foreach (var handle in fieldHandleCacheCollectible) handle.Free(); fieldHandleCacheCollectible.Clear(); + foreach (var handle in propertyHandleCacheCollectible) + handle.Free(); + propertyHandleCacheCollectible.Clear(); _typeSizeCache.Clear(); diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 8317383cd..3591df299 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -38,11 +38,13 @@ namespace FlaxEngine.Interop private static ConcurrentDictionary cachedDelegates = new(); private static Dictionary managedTypes = new(new TypeComparer()); private static List fieldHandleCache = new(); + private static List propertyHandleCache = new(); #if FLAX_EDITOR private static List methodHandlesCollectible = new(); private static ConcurrentDictionary cachedDelegatesCollectible = new(); private static Dictionary managedTypesCollectible = new(new TypeComparer()); private static List fieldHandleCacheCollectible = new(); + private static List propertyHandleCacheCollectible = new(); #endif private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new(); diff --git a/Source/Engine/Scripting/ManagedCLR/MProperty.h b/Source/Engine/Scripting/ManagedCLR/MProperty.h index a9ce918f8..dbbed8ab3 100644 --- a/Source/Engine/Scripting/ManagedCLR/MProperty.h +++ b/Source/Engine/Scripting/ManagedCLR/MProperty.h @@ -16,6 +16,8 @@ class FLAXENGINE_API MProperty protected: #if USE_MONO MonoProperty* _monoProperty; +#elif USE_NETCORE + void* _handle; #endif mutable MMethod* _getMethod; @@ -34,7 +36,7 @@ public: #if USE_MONO explicit MProperty(MonoProperty* monoProperty, const char* name, MClass* parentClass); #elif USE_NETCORE - MProperty(MClass* parentClass, const char* name, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes); + MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes); #endif /// diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index c2155a148..4a71e9c2f 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -211,7 +211,25 @@ MClass* GetClass(MType* typeHandle); MClass* GetOrCreateClass(MType* typeHandle); MType* GetObjectType(MObject* obj); -void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass); +void* GetCustomAttribute(const Array& attributes, const MClass* attributeClass) +{ + for (MObject* attr : attributes) + { + MClass* attrClass = MCore::Object::GetClass(attr); + if (attrClass == attributeClass) + return attr; + } + return nullptr; +} + +void GetCustomAttributes(Array& result, void* handle, void* getAttributesFunc) +{ + MObject** attributes; + int numAttributes; + CallStaticMethod(getAttributesFunc, handle, &attributes, &numAttributes); + result.Set(attributes, numAttributes); + MCore::GC::FreeMemory(attributes); +} // Structures used to pass information from runtime, must match with the structures in managed side struct NativeClassDefinitions @@ -244,6 +262,7 @@ struct NativeFieldDefinitions struct NativePropertyDefinitions { const char* name; + void* propertyHandle; void* getterHandle; void* setterHandle; MMethodAttributes getterAttributes; @@ -1089,7 +1108,7 @@ const Array& MClass::GetProperties() const for (int i = 0; i < numProperties; i++) { const NativePropertyDefinitions& definition = foundProperties[i]; - MProperty* property = New(const_cast(this), definition.name, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); + MProperty* property = New(const_cast(this), definition.name, definition.propertyHandle, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes); _properties[i] = property; MCore::GC::FreeMemory((void*)definition.name); } @@ -1125,7 +1144,7 @@ const Array& MClass::GetInterfaces() const bool MClass::HasAttribute(const MClass* klass) const { - return GetCustomAttribute(this, klass) != nullptr; + return GetCustomAttribute(GetAttributes(), klass) != nullptr; } bool MClass::HasAttribute() const @@ -1135,7 +1154,7 @@ bool MClass::HasAttribute() const MObject* MClass::GetAttribute(const MClass* klass) const { - return (MObject*)GetCustomAttribute(this, klass); + return (MObject*)GetCustomAttribute(GetAttributes(), klass); } const Array& MClass::GetAttributes() const @@ -1145,14 +1164,8 @@ const Array& MClass::GetAttributes() const ScopeLock lock(BinaryModule::Locker); if (_hasCachedAttributes) return _attributes; - - MObject** attributes; - int numAttributes; static void* GetClassAttributesPtr = GetStaticMethodPointer(TEXT("GetClassAttributes")); - CallStaticMethod(GetClassAttributesPtr, _handle, &attributes, &numAttributes); - _attributes.Set(attributes, numAttributes); - MCore::GC::FreeMemory(attributes); - + GetCustomAttributes(_attributes, _handle, GetClassAttributesPtr); _hasCachedAttributes = true; return _attributes; } @@ -1191,17 +1204,17 @@ MMethod* MEvent::GetRemoveMethod() const bool MEvent::HasAttribute(const MClass* klass) const { - return false; // TODO: implement MEvent in .NET + return GetCustomAttribute(GetAttributes(), klass) != nullptr; } bool MEvent::HasAttribute() const { - return false; // TODO: implement MEvent in .NET + return !GetAttributes().IsEmpty(); } MObject* MEvent::GetAttribute(const MClass* klass) const { - return nullptr; // TODO: implement MEvent in .NET + return (MObject*)GetCustomAttribute(GetAttributes(), klass); } const Array& MEvent::GetAttributes() const @@ -1313,29 +1326,29 @@ void MField::SetValue(MObject* instance, void* value) const bool MField::HasAttribute(const MClass* klass) const { - // TODO: implement MField attributes in .NET - return false; + return GetCustomAttribute(GetAttributes(), klass) != nullptr; } bool MField::HasAttribute() const { - // TODO: implement MField attributes in .NET - return false; + return !GetAttributes().IsEmpty(); } MObject* MField::GetAttribute(const MClass* klass) const { - // TODO: implement MField attributes in .NET - return nullptr; + return (MObject*)GetCustomAttribute(GetAttributes(), klass); } const Array& MField::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + ScopeLock lock(BinaryModule::Locker); + if (_hasCachedAttributes) + return _attributes; + static void* GetFieldAttributesPtr = GetStaticMethodPointer(TEXT("GetFieldAttributes")); + GetCustomAttributes(_attributes, _handle, GetFieldAttributesPtr); _hasCachedAttributes = true; - - // TODO: implement MField attributes in .NET return _attributes; } @@ -1475,35 +1488,36 @@ bool MMethod::GetParameterIsOut(int32 paramIdx) const bool MMethod::HasAttribute(const MClass* klass) const { - // TODO: implement MMethod attributes in .NET - return false; + return GetCustomAttribute(GetAttributes(), klass) != nullptr; } bool MMethod::HasAttribute() const { - // TODO: implement MMethod attributes in .NET - return false; + return !GetAttributes().IsEmpty(); } MObject* MMethod::GetAttribute(const MClass* klass) const { - // TODO: implement MMethod attributes in .NET - return nullptr; + return (MObject*)GetCustomAttribute(GetAttributes(), klass); } const Array& MMethod::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + ScopeLock lock(BinaryModule::Locker); + if (_hasCachedAttributes) + return _attributes; + static void* GetMethodAttributesPtr = GetStaticMethodPointer(TEXT("GetMethodAttributes")); + GetCustomAttributes(_attributes, _handle, GetMethodAttributesPtr); _hasCachedAttributes = true; - - // TODO: implement MMethod attributes in .NET return _attributes; } -MProperty::MProperty(MClass* parentClass, const char* name, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes) +MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes) : _parentClass(parentClass) , _name(name) + , _handle(handle) , _hasCachedAttributes(false) { _hasGetMethod = getterHandle != nullptr; @@ -1552,29 +1566,29 @@ void MProperty::SetValue(MObject* instance, void* value, MObject** exception) co bool MProperty::HasAttribute(const MClass* klass) const { - // TODO: implement MProperty attributes in .NET - return false; + return GetCustomAttribute(GetAttributes(), klass) != nullptr; } bool MProperty::HasAttribute() const { - // TODO: implement MProperty attributes in .NET - return false; + return !GetAttributes().IsEmpty(); } MObject* MProperty::GetAttribute(const MClass* klass) const { - // TODO: implement MProperty attributes in .NET - return nullptr; + return (MObject*)GetCustomAttribute(GetAttributes(), klass); } const Array& MProperty::GetAttributes() const { if (_hasCachedAttributes) return _attributes; + ScopeLock lock(BinaryModule::Locker); + if (_hasCachedAttributes) + return _attributes; + static void* GetPropertyAttributesPtr = GetStaticMethodPointer(TEXT("GetPropertyAttributes")); + GetCustomAttributes(_attributes, _handle, GetPropertyAttributesPtr); _hasCachedAttributes = true; - - // TODO: implement MProperty attributes in .NET return _attributes; } @@ -1637,18 +1651,6 @@ MType* GetObjectType(MObject* obj) return (MType*)typeHandle; } -void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass) -{ - const Array& attributes = klass->GetAttributes(); - for (MObject* attr : attributes) - { - MClass* attrClass = MCore::Object::GetClass(attr); - if (attrClass == attributeClass) - return attr; - } - return nullptr; -} - #if DOTNET_HOST_CORECLR const char_t* NativeInteropTypeName = FLAX_CORECLR_TEXT("FlaxEngine.Interop.NativeInterop, FlaxEngine.CSharp"); From 777e65c17015d592ada9e599b472b535e48c6db8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Oct 2024 15:58:05 +0200 Subject: [PATCH 007/215] Fix crash when setting static dotnet field from unmanaged code --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index e3d66a2ef..71caee1e9 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -891,7 +891,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void FieldSetValue(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) { - object fieldOwner = fieldOwnerHandle.Target; + object fieldOwner = fieldOwnerHandle.IsAllocated ? fieldOwnerHandle.Target : null; FieldHolder field = Unsafe.As(fieldHandle.Target); object value = MarshalToManaged(valuePtr, field.field.FieldType); field.field.SetValue(fieldOwner, value); From 74d2f5d11565a7cfe79c02b359714dd924e3d492 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Oct 2024 15:58:27 +0200 Subject: [PATCH 008/215] Fix parsing numbers to support sign --- Source/Engine/Core/Types/Variant.cpp | 12 +-- Source/Engine/Platform/StringUtils.h | 130 ++++++++++++++++++--------- 2 files changed, 94 insertions(+), 48 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 221f6f660..95e815b81 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -3082,23 +3082,23 @@ Variant Variant::Parse(const StringView& text, const VariantType& type) result.AsBool = true; break; case VariantType::Int16: - StringUtils::Parse(text.Get(), &result.AsInt16); + StringUtils::Parse(text.Get(), text.Length(), &result.AsInt16); break; case VariantType::Uint16: - StringUtils::Parse(text.Get(), &result.AsUint16); + StringUtils::Parse(text.Get(), text.Length(), &result.AsUint16); break; case VariantType::Int: - StringUtils::Parse(text.Get(), &result.AsInt); + StringUtils::Parse(text.Get(), text.Length(), &result.AsInt); break; case VariantType::Uint: - StringUtils::Parse(text.Get(), &result.AsUint); + StringUtils::Parse(text.Get(), text.Length(), &result.AsUint); break; case VariantType::Int64: - StringUtils::Parse(text.Get(), &result.AsInt64); + StringUtils::Parse(text.Get(), text.Length(), &result.AsInt64); break; case VariantType::Uint64: case VariantType::Enum: - if (!StringUtils::Parse(text.Get(), &result.AsUint64)) + if (!StringUtils::Parse(text.Get(), text.Length(), &result.AsUint64)) { } else if (type.TypeName) diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h index af82f9f8b..eb567b651 100644 --- a/Source/Engine/Platform/StringUtils.h +++ b/Source/Engine/Platform/StringUtils.h @@ -195,30 +195,6 @@ public: // Converts hexadecimal character into the value. static int32 HexDigit(Char c); - // Parses text to unsigned integer value. Returns true if failed to convert the value. - template - static bool ParseHex(const CharType* str, uint32* result) - { - uint32 sum = 0; - const CharType* p = str; - if (*p == '0' && *(p + 1) == 'x') - p += 2; - while (*p) - { - int32 c = *p - '0'; - if (c < 0 || c > 9) - { - c = ToLower(*p) - 'a' + 10; - if (c < 10 || c > 15) - return true; - } - sum = 16 * sum + c; - p++; - } - *result = sum; - return false; - } - // Parses text to unsigned integer value. Returns true if failed to convert the value. template static bool ParseHex(const CharType* str, int32 length, uint32* result) @@ -244,28 +220,18 @@ public: return false; } - // Parses text to scalar integer value. Returns true if failed to convert the value. - template - static bool Parse(const T* str, U* result) + // Parses text to unsigned integer value. Returns true if failed to convert the value. + template + static bool ParseHex(const CharType* str, uint32* result) { - U sum = 0; - const T* p = str; - while (*p) - { - int32 c = *p++ - 48; - if (c < 0 || c > 9) - return true; - sum = 10 * sum + c; - } - *result = sum; - return false; + return ParseHex(str, Length(str), result); } - // Parses text to scalar integer value. Returns true if failed to convert the value. - template - static bool Parse(const T* str, uint32 length, U* result) + // Parses text to the unsigned integer value. Returns true if failed to convert the value. + template + static bool Parse(const T* str, int32 length, uint64* result) { - U sum = 0; + int64 sum = 0; const T* p = str; while (length--) { @@ -277,6 +243,86 @@ public: *result = sum; return false; } + template + static bool Parse(const T* str, uint32 length, uint32* result) + { + uint64 tmp; + const bool b = Parse(str, length, &tmp); + *result = (uint32)tmp; + return b; + } + template + static bool Parse(const T* str, uint32 length, uint16* result) + { + uint64 tmp; + const bool b = Parse(str, length, &tmp); + *result = (uint16)tmp; + return b; + } + template + static bool Parse(const T* str, uint32 length, uint8* result) + { + uint64 tmp; + const bool b = Parse(str, length, &tmp); + *result = (uint8)tmp; + return b; + } + + // Parses text to the integer value. Returns true if failed to convert the value. + template + static bool Parse(const T* str, int32 length, int64* result) + { + int64 sum = 0; + const T* p = str; + bool negate = false; + while (length--) + { + int32 c = *p++ - 48; + if (c == -3) + { + negate = true; + continue; + } + if (c < 0 || c > 9) + return true; + sum = 10 * sum + c; + } + if (negate) + sum = -sum; + *result = sum; + return false; + } + template + static bool Parse(const T* str, uint32 length, int32* result) + { + int64 tmp; + const bool b = Parse(str, length, &tmp); + *result = (int32)tmp; + return b; + } + template + static bool Parse(const T* str, uint32 length, int16* result) + { + int64 tmp; + const bool b = Parse(str, length, &tmp); + *result = (int16)tmp; + return b; + } + template + static bool Parse(const T* str, uint32 length, int8* result) + { + int64 tmp; + const bool b = Parse(str, length, &tmp); + *result = (int8)tmp; + return b; + } + + // Parses text to scalar integer value. Returns true if failed to convert the value. + template + static bool Parse(const T* str, U* result) + { + return Parse(str, Length(str), result); + } // Parses text to the scalar value. Returns true if failed to convert the value. static bool Parse(const Char* str, float* result); From c6958357c38cf44c1b6ad54d5d8e9aa85a2f0cdc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 4 Oct 2024 15:58:47 +0200 Subject: [PATCH 009/215] Allow placing `DebugCommand` attribute on fields and properties --- Source/Engine/Debug/DebugCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Debug/DebugCommands.cs b/Source/Engine/Debug/DebugCommands.cs index 3c67ca31e..d676ea979 100644 --- a/Source/Engine/Debug/DebugCommands.cs +++ b/Source/Engine/Debug/DebugCommands.cs @@ -8,7 +8,7 @@ namespace FlaxEngine /// Marks static method as debug command that can be executed from the command line or via console. /// [Serializable] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] public sealed class DebugCommand : Attribute { } From 32b09538bad836514564b112393e13c92d0f08b3 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sat, 5 Oct 2024 21:21:51 +0200 Subject: [PATCH 010/215] Hard `Nullable` refactor * --- Source/Engine/Core/Types/Nullable.h | 316 ++++++++++++++++++++-------- 1 file changed, 226 insertions(+), 90 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 9511f0df2..f291eb0dc 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -8,39 +8,132 @@ /// Represents a value type that can be assigned null. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. /// template -struct NullableBase +struct Nullable { -protected: +private: + union // Prevents default construction of T + { + T _value; + }; bool _hasValue; - T _value; + + /// + /// Ensures that the lifetime of the wrapped value ends correctly. This method is called when the state of the wrapper is no more needed. + /// + FORCE_INLINE void KillOld() + { + if (_hasValue) + { + _value.~T(); + } + } public: /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct with a null value. /// - NullableBase() + Nullable() + : _hasValue(false) { - _hasValue = false; + // Value is not initialized. + } + + ~Nullable() + { + KillOld(); } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct by copying the value. /// - /// The initial value. - NullableBase(const T& value) + /// The initial wrapped value. + Nullable(const T& value) + : _value(value) + , _hasValue(true) { - _value = value; - _hasValue = true; } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct by moving the value. /// - /// The other. - NullableBase(const NullableBase& other) + /// The initial wrapped value. + Nullable(T&& value) noexcept + : _value(MoveTemp(value)) + , _hasValue(true) + { + } + + /// + /// Initializes a new instance of the struct by copying the value from another instance. + /// + /// The wrapped value to be copied. + Nullable(const Nullable& other) + : _value(other._value) + , _hasValue(other._hasValue) + { + } + + /// + /// Initializes a new instance of the struct by moving the value from another instance. + /// + /// The wrapped value to be moved. + Nullable(Nullable&& other) noexcept { - _value = other._value; _hasValue = other._hasValue; + if (_hasValue) + { + new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) + } + + other.Reset(); + } + + auto operator=(const T& value) -> Nullable& + { + KillOld(); + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; + + return *this; + } + + auto operator=(T&& value) noexcept -> Nullable& + { + KillOld(); + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; + + return *this; + } + + auto operator=(const Nullable& other) -> Nullable& + { + KillOld(); + + _hasValue = other._hasValue; + if (_hasValue) + { + new (&_value) T(other._value); // Placement new (copy constructor) + } + + return *this; + } + + auto operator=(Nullable&& other) noexcept -> Nullable& + { + KillOld(); + + _hasValue = other._hasValue; + if (_hasValue) + { + new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) + + other.Reset(); + } + + return *this; } public: @@ -64,30 +157,61 @@ public: } /// - /// Gets the value of the current NullableBase{T} object if it has been assigned a valid underlying value. + /// Gets a reference to the value of the current NullableBase{T} object. + /// If is assumed that the value is valid, otherwise the behavior is undefined. /// - /// The value. - FORCE_INLINE T GetValue() + /// In the past, this value returned a copy of the stored value. Be careful. + /// Reference to the value. + FORCE_INLINE T& GetValue() { ASSERT(_hasValue); return _value; } /// - /// Sets the value. + /// Sets the wrapped value. /// - /// The value. - void SetValue(const T& value) + /// The value to be copied. + FORCE_INLINE void SetValue(const T& value) { - _value = value; + if (_hasValue) + { + _value.~T(); + } + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; + } + + /// + /// Sets the wrapped value. + /// + /// The value to be moved. + FORCE_INLINE void SetValue(T&& value) noexcept + { + KillOld(); + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; } /// /// Resets the value. /// - void Reset() + FORCE_INLINE void Reset() { + KillOld(); + _hasValue = false; + } + + /// + /// Moves the value from the current NullableBase{T} object and resets it. + /// + FORCE_INLINE void GetAndReset(T& value) + { + ASSERT(_hasValue); + value = MoveTemp(_value); + _value.~T(); // Move is not destructive. _hasValue = false; } @@ -97,14 +221,14 @@ public: /// /// The other object. /// True if both values are equal. - bool operator==(const NullableBase& other) const + FORCE_INLINE bool operator==(const Nullable& other) const { - if (_hasValue) + if (other._hasValue != _hasValue) { - return other._hasValue && _value == other._value; + return false; } - return !other._hasValue; + return _value == other._value; } /// @@ -112,43 +236,19 @@ public: /// /// The other object. /// True if both values are not equal. - FORCE_INLINE bool operator!=(const NullableBase& other) const + FORCE_INLINE bool operator!=(const Nullable& other) const { return !operator==(other); } -}; - -/// -/// Represents a value type that can be assigned null. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. -/// -template -struct Nullable : NullableBase -{ -public: - /// - /// Initializes a new instance of the struct. - /// - Nullable() - : NullableBase() - { - } /// - /// Initializes a new instance of the struct. + /// Explicit conversion to boolean value. /// - /// The initial value. - Nullable(const T& value) - : NullableBase(value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The other. - Nullable(const Nullable& other) - : NullableBase(other) + /// True if this object has a valid value, otherwise false + /// Hint: If-statements are able to use explicit cast implicitly (sic). + FORCE_INLINE explicit operator bool() const { + return _hasValue; } }; @@ -156,43 +256,81 @@ public: /// Nullable value container that contains a boolean value or null. /// template<> -struct Nullable : NullableBase +struct Nullable { -public: - /// - /// Initializes a new instance of the struct. - /// - Nullable() - : NullableBase() +private: + enum class Value : uint8 { - } + False = 0, + True = 1, + Null = 2, + }; - /// - /// Initializes a new instance of the struct. - /// - /// The initial value. - Nullable(bool value) - : NullableBase(value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The other. - Nullable(const Nullable& other) - : NullableBase(other) - { - } + Value _value = Value::Null; public: + Nullable() = default; + + ~Nullable() = default; + + + Nullable(Nullable&& value) = default; + + Nullable(const Nullable& value) = default; + + Nullable(const bool value) noexcept + { + _value = value ? Value::True : Value::False; + } + + + auto operator=(const bool value) noexcept -> Nullable& + { + _value = value ? Value::True : Value::False; + return *this; + } + + auto operator=(const Nullable& value) -> Nullable& = default; + + auto operator=(Nullable&& value) -> Nullable& = default; + + + FORCE_INLINE bool HasValue() const noexcept + { + return _value != Value::Null; + } + + FORCE_INLINE bool GetValue() const + { + ASSERT(_value != Value::Null); + return _value == Value::True; + } + + FORCE_INLINE void SetValue(const bool value) noexcept + { + _value = value ? Value::True : Value::False; + } + + FORCE_INLINE void Reset() noexcept + { + _value = Value::Null; + } + + FORCE_INLINE void GetAndReset(bool& value) noexcept + { + ASSERT(_value != Value::Null); + value = _value == Value::True; + _value = Value::Null; + } + + /// /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to true. /// /// true if this object has a valid value set to true; otherwise, false. FORCE_INLINE bool IsTrue() const { - return _hasValue && _value; + return _value == Value::True; } /// @@ -201,15 +339,13 @@ public: /// true if this object has a valid value set to false; otherwise, false. FORCE_INLINE bool IsFalse() const { - return _hasValue && !_value; + return _value == Value::False; } /// - /// Implicit conversion to boolean value. + /// Getting if provoke unacceptably ambiguous code. For template meta-programming use explicit HasValue() instead. /// - /// True if this object has a valid value set to true, otherwise false - FORCE_INLINE operator bool() const - { - return _hasValue && _value; - } + explicit operator bool() const = delete; + + // Note: Even though IsTrue and IsFalse have been added for convenience, but they may be used for performance reasons. }; From db06f4f72ec87002d095bf8994120cf75f862d0c Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sat, 5 Oct 2024 21:22:36 +0200 Subject: [PATCH 011/215] Fixed implicit type conversion for type specialization --- Source/Editor/Editor.cpp | 8 ++++---- Source/Editor/Windows/SplashScreen.cpp | 2 +- Source/Engine/Core/Log.cpp | 2 +- Source/Engine/Engine/Engine.cpp | 4 ++-- Source/Engine/Graphics/Graphics.cpp | 12 ++++++------ .../Graphics/Shaders/Cache/ShaderAssetBase.cpp | 4 ++-- .../Graphics/Shaders/Cache/ShaderCacheManager.cpp | 4 ++-- .../GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp | 10 +++++----- .../GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp | 8 ++++---- Source/Engine/Platform/Base/PlatformBase.cpp | 6 +++--- Source/Engine/Platform/Windows/WindowsPlatform.cpp | 2 +- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index d0bfe2765..8fd646da9 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -403,7 +403,7 @@ int32 Editor::LoadProduct() } // Create new project option - if (CommandLine::Options.NewProject) + if (CommandLine::Options.NewProject.IsTrue()) { Array projectFiles; FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly); @@ -428,7 +428,7 @@ int32 Editor::LoadProduct() } } } - if (CommandLine::Options.NewProject) + if (CommandLine::Options.NewProject.IsTrue()) { if (projectPath.IsEmpty()) projectPath = Platform::GetWorkingDirectory(); @@ -529,7 +529,7 @@ int32 Editor::LoadProduct() if (projectPath.IsEmpty()) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { Platform::Fatal(TEXT("Missing project path.")); return -1; @@ -657,7 +657,7 @@ Window* Editor::CreateMainWindow() bool Editor::Init() { // Scripts project files generation from command line - if (CommandLine::Options.GenProjectFiles) + if (CommandLine::Options.GenProjectFiles.IsTrue()) { const String customArgs = TEXT("-verbose -log -logfile=\"Cache/Intermediate/ProjectFileLog.txt\""); const bool failed = ScriptsBuilder::GenerateProject(customArgs); diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 49257d281..39f7691e7 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -147,7 +147,7 @@ SplashScreen::~SplashScreen() void SplashScreen::Show() { // Skip if already shown or in headless mode - if (IsVisible() || CommandLine::Options.Headless) + if (IsVisible() || CommandLine::Options.Headless.IsTrue()) return; LOG(Info, "Showing splash screen"); diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index c8cf4419e..38d591c51 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -119,7 +119,7 @@ void Log::Logger::Write(const StringView& msg) IsDuringLog = true; // Send message to standard process output - if (CommandLine::Options.Std) + if (CommandLine::Options.Std.IsTrue()) { #if PLATFORM_TEXT_IS_CHAR16 StringAnsi ansi(msg); diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index b26cbd25a..7fea9cd6d 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -631,9 +631,9 @@ void EngineImpl::InitPaths() FileSystem::CreateDirectory(Globals::ProjectContentFolder); if (!FileSystem::DirectoryExists(Globals::ProjectSourceFolder)) FileSystem::CreateDirectory(Globals::ProjectSourceFolder); - if (CommandLine::Options.ClearCache) + if (CommandLine::Options.ClearCache.IsTrue()) FileSystem::DeleteDirectory(Globals::ProjectCacheFolder, true); - else if (CommandLine::Options.ClearCookerCache) + else if (CommandLine::Options.ClearCookerCache.IsTrue()) FileSystem::DeleteDirectory(Globals::ProjectCacheFolder / TEXT("Cooker"), true); if (!FileSystem::DirectoryExists(Globals::ProjectCacheFolder)) FileSystem::CreateDirectory(Globals::ProjectCacheFolder); diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index a1640c0b4..25437d25d 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -104,7 +104,7 @@ bool GraphicsService::Init() GPUDevice* device = nullptr; // Null - if (!device && CommandLine::Options.Null) + if (!device && CommandLine::Options.Null.IsTrue()) { #if GRAPHICS_API_NULL device = CreateGPUDeviceNull(); @@ -114,7 +114,7 @@ bool GraphicsService::Init() } // Vulkan - if (!device && CommandLine::Options.Vulkan) + if (!device && CommandLine::Options.Vulkan.IsTrue()) { #if GRAPHICS_API_VULKAN device = CreateGPUDeviceVulkan(); @@ -124,7 +124,7 @@ bool GraphicsService::Init() } // DirectX 12 - if (!device && CommandLine::Options.D3D12) + if (!device && CommandLine::Options.D3D12.IsTrue()) { #if GRAPHICS_API_DIRECTX12 if (Platform::IsWindows10()) @@ -137,7 +137,7 @@ bool GraphicsService::Init() } // DirectX 11 and DirectX 10 - if (!device && (CommandLine::Options.D3D11 || CommandLine::Options.D3D10)) + if (!device && (CommandLine::Options.D3D11.IsTrue() || CommandLine::Options.D3D10.IsTrue())) { #if GRAPHICS_API_DIRECTX11 device = CreateGPUDeviceDX11(); @@ -193,10 +193,10 @@ bool GraphicsService::Init() // Initialize if (device->IsDebugToolAttached #if USE_EDITOR || !BUILD_RELEASE - || CommandLine::Options.ShaderProfile + || CommandLine::Options.ShaderProfile.IsTrue() #endif #if USE_EDITOR - || CommandLine::Options.ShaderDebug + || CommandLine::Options.ShaderDebug.IsTrue() #endif ) { diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 6f91ff4be..acc1dc7db 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -249,12 +249,12 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) options.SourceLength = sourceLength; options.Profile = shaderProfile; options.Output = &cacheStream; - if (CommandLine::Options.ShaderDebug) + if (CommandLine::Options.ShaderDebug.IsTrue()) { options.GenerateDebugData = true; options.NoOptimize = true; } - else if (CommandLine::Options.ShaderProfile) + else if (CommandLine::Options.ShaderProfile.IsTrue()) { options.GenerateDebugData = true; } diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp index b8adce351..112324b2c 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp @@ -193,8 +193,8 @@ bool ShaderCacheManagerService::Init() CacheVersion cacheVersion; const String cacheVerFile = rootDir / TEXT("CacheVersion"); #if USE_EDITOR - const bool shaderDebug = CommandLine::Options.ShaderDebug; - const bool shaderProfile = CommandLine::Options.ShaderProfile; + const bool shaderDebug = CommandLine::Options.ShaderDebug.IsTrue(); + const bool shaderProfile = CommandLine::Options.ShaderProfile.IsTrue(); #else const bool shaderDebug = false; #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 432a44ee8..724a5ea71 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -106,9 +106,9 @@ GPUDevice* GPUDeviceDX11::Create() #else D3D_FEATURE_LEVEL maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #endif - if (CommandLine::Options.D3D10) + if (CommandLine::Options.D3D10.IsTrue()) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_10_0; - else if (CommandLine::Options.D3D11) + else if (CommandLine::Options.D3D11.IsTrue()) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #if !USE_EDITOR && PLATFORM_WINDOWS auto winSettings = WindowsPlatformSettings::Get(); @@ -209,11 +209,11 @@ GPUDevice* GPUDeviceDX11::Create() } GPUAdapterDX selectedAdapter = adapters[selectedAdapterIndex]; uint32 vendorId = 0; - if (CommandLine::Options.NVIDIA) + if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; - else if (CommandLine::Options.AMD) + else if (CommandLine::Options.AMD.IsTrue()) vendorId = GPU_VENDOR_ID_AMD; - else if (CommandLine::Options.Intel) + else if (CommandLine::Options.Intel.IsTrue()) vendorId = GPU_VENDOR_ID_INTEL; if (vendorId != 0) { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index a33cd8194..78c047cfc 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -161,11 +161,11 @@ GPUDevice* GPUDeviceDX12::Create() } GPUAdapterDX selectedAdapter = adapters[selectedAdapterIndex]; uint32 vendorId = 0; - if (CommandLine::Options.NVIDIA) + if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; - else if (CommandLine::Options.AMD) + else if (CommandLine::Options.AMD.IsTrue()) vendorId = GPU_VENDOR_ID_AMD; - else if (CommandLine::Options.Intel) + else if (CommandLine::Options.Intel.IsTrue()) vendorId = GPU_VENDOR_ID_INTEL; if (vendorId != 0) { @@ -425,7 +425,7 @@ bool GPUDeviceDX12::Init() #if !BUILD_RELEASE // Prevent the GPU from overclocking or under-clocking to get consistent timings - if (CommandLine::Options.ShaderProfile) + if (CommandLine::Options.ShaderProfile.IsTrue()) { _device->SetStablePowerState(TRUE); } diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 6efa3c8b4..93b06b821 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -365,7 +365,7 @@ void PlatformBase::Fatal(const Char* msg, void* context) void PlatformBase::Error(const Char* msg) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { #if PLATFORM_TEXT_IS_CHAR16 StringAnsi ansi(msg); @@ -385,7 +385,7 @@ void PlatformBase::Error(const Char* msg) void PlatformBase::Warning(const Char* msg) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { std::cout << "Warning: " << msg << std::endl; } @@ -399,7 +399,7 @@ void PlatformBase::Warning(const Char* msg) void PlatformBase::Info(const Char* msg) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { std::cout << "Info: " << msg << std::endl; } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 0f1159fc5..57d46b1f8 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -616,7 +616,7 @@ bool WindowsPlatform::Init() return true; // Init console output (engine is linked with /SUBSYSTEM:WINDOWS so it lacks of proper console output on Windows) - if (CommandLine::Options.Std) + if (CommandLine::Options.Std.IsTrue()) { // Attaches output of application to parent console, returns true if running in console-mode // [Reference: https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application] From edfbeea0e696c0ae402643670a0b20f6f51b447e Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sat, 5 Oct 2024 21:46:30 +0200 Subject: [PATCH 012/215] `Nullable` utility functions --- Source/Engine/Core/Types/Nullable.h | 73 +++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index f291eb0dc..fe6db7e4a 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -80,6 +80,7 @@ public: Nullable(Nullable&& other) noexcept { _hasValue = other._hasValue; + if (_hasValue) { new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) @@ -112,31 +113,35 @@ public: { KillOld(); - _hasValue = other._hasValue; - if (_hasValue) + if (other._hasValue) { new (&_value) T(other._value); // Placement new (copy constructor) } + _hasValue = other._hasValue; // Set the flag AFTER the value is copied. return *this; } auto operator=(Nullable&& other) noexcept -> Nullable& { + if (this == &other) + { + return *this; + } + KillOld(); - _hasValue = other._hasValue; if (_hasValue) { new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) other.Reset(); } + _hasValue = other._hasValue; // Set the flag AFTER the value is moved. return *this; } -public: /// /// Gets a value indicating whether the current NullableBase{T} object has a valid value of its underlying type. /// @@ -168,6 +173,16 @@ public: return _value; } + FORCE_INLINE const T& GetValueOr(const T& defaultValue) const + { + return _hasValue ? _value : defaultValue; + } + + FORCE_INLINE T GetValueOr(T&& defaultValue) const noexcept + { + return _hasValue ? _value : defaultValue; + } + /// /// Sets the wrapped value. /// @@ -180,7 +195,7 @@ public: } new (&_value) T(value); // Placement new (copy constructor) - _hasValue = true; + _hasValue = true; // Set the flag AFTER the value is copied. } /// @@ -192,7 +207,31 @@ public: KillOld(); new (&_value) T(MoveTemp(value)); // Placement new (move constructor) - _hasValue = true; + _hasValue = true; // Set the flag AFTER the value is moved. + } + + FORCE_INLINE bool TrySet(T&& value) noexcept + { + if (_hasValue) + { + return false; + } + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; // Set the flag AFTER the value is moved. + return true; + } + + FORCE_INLINE bool TrySet(const T& value) + { + if (_hasValue) + { + return false; + } + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; // Set the flag AFTER the value is copied. + return true; } /// @@ -201,7 +240,7 @@ public: FORCE_INLINE void Reset() { KillOld(); - _hasValue = false; + _hasValue = false; // Reset the flag AFTER the value is (potentially) destructed. } /// @@ -211,11 +250,9 @@ public: { ASSERT(_hasValue); value = MoveTemp(_value); - _value.~T(); // Move is not destructive. - _hasValue = false; + Reset(); } -public: /// /// Indicates whether the current NullableBase{T} object is equal to a specified object. /// @@ -306,11 +343,27 @@ public: return _value == Value::True; } + FORCE_INLINE bool GetValueOr(const bool defaultValue) const noexcept + { + return _value == Value::Null ? defaultValue : _value == Value::True; + } + FORCE_INLINE void SetValue(const bool value) noexcept { _value = value ? Value::True : Value::False; } + FORCE_INLINE bool TrySet(const bool value) noexcept + { + if (_value != Value::Null) + { + return false; + } + + _value = value ? Value::True : Value::False; + return true; + } + FORCE_INLINE void Reset() noexcept { _value = Value::Null; From a2874a189e207fdfa5942ebffa0cfe28921a8d46 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sat, 5 Oct 2024 23:51:54 +0200 Subject: [PATCH 013/215] `Nullable` docs --- Source/Engine/Core/Types/Nullable.h | 174 +++++++++++++++++++--------- 1 file changed, 122 insertions(+), 52 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index fe6db7e4a..655233b85 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -5,20 +5,20 @@ #include "Engine/Platform/Platform.h" /// -/// Represents a value type that can be assigned null. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. +/// Wrapper for a value type that can be assigned null, controlling the lifetime of the wrapped value. /// template struct Nullable { private: - union // Prevents default construction of T + union { T _value; }; bool _hasValue; /// - /// Ensures that the lifetime of the wrapped value ends correctly. This method is called when the state of the wrapper is no more needed. + /// Ends the lifetime of the wrapped value by calling its destructor, if the lifetime has not ended yet. Otherwise, does nothing. /// FORCE_INLINE void KillOld() { @@ -30,7 +30,7 @@ private: public: /// - /// Initializes a new instance of the struct with a null value. + /// Initializes by setting the wrapped value to null. /// Nullable() : _hasValue(false) @@ -44,9 +44,9 @@ public: } /// - /// Initializes a new instance of the struct by copying the value. + /// Initializes by copying the wrapped value. /// - /// The initial wrapped value. + /// The initial wrapped value to be copied. Nullable(const T& value) : _value(value) , _hasValue(true) @@ -54,9 +54,9 @@ public: } /// - /// Initializes a new instance of the struct by moving the value. + /// Initializes by moving the wrapped value. /// - /// The initial wrapped value. + /// The initial wrapped value to be moved. Nullable(T&& value) noexcept : _value(MoveTemp(value)) , _hasValue(true) @@ -64,7 +64,7 @@ public: } /// - /// Initializes a new instance of the struct by copying the value from another instance. + /// Initializes by copying another . /// /// The wrapped value to be copied. Nullable(const Nullable& other) @@ -74,7 +74,7 @@ public: } /// - /// Initializes a new instance of the struct by moving the value from another instance. + /// Initializes by moving another . /// /// The wrapped value to be moved. Nullable(Nullable&& other) noexcept @@ -89,6 +89,9 @@ public: other.Reset(); } + /// + /// Reassigns the wrapped value by copying. + /// auto operator=(const T& value) -> Nullable& { KillOld(); @@ -99,6 +102,9 @@ public: return *this; } + /// + /// Reassigns the wrapped value by moving. + /// auto operator=(T&& value) noexcept -> Nullable& { KillOld(); @@ -109,6 +115,9 @@ public: return *this; } + /// + /// Reassigns the wrapped value by copying another . + /// auto operator=(const Nullable& other) -> Nullable& { KillOld(); @@ -122,6 +131,9 @@ public: return *this; } + /// + /// Reassigns the wrapped value by moving another . + /// auto operator=(Nullable&& other) noexcept -> Nullable& { if (this == &other) @@ -143,7 +155,7 @@ public: } /// - /// Gets a value indicating whether the current NullableBase{T} object has a valid value of its underlying type. + /// Checks if wrapped object has a valid value. /// /// true if this object has a valid value; otherwise, false. FORCE_INLINE bool HasValue() const @@ -152,9 +164,9 @@ public: } /// - /// Gets the value of the current NullableBase{T} object if it has been assigned a valid underlying value. + /// Gets a const reference to the wrapped value. If the value is not valid, the behavior is undefined. /// - /// The value. + /// Reference to the wrapped value. FORCE_INLINE const T& GetValue() const { ASSERT(_hasValue); @@ -162,29 +174,36 @@ public: } /// - /// Gets a reference to the value of the current NullableBase{T} object. - /// If is assumed that the value is valid, otherwise the behavior is undefined. + /// Gets a reference to the wrapped value. If the value is not valid, the behavior is undefined. + /// This method can be used to reassign the wrapped value. /// - /// In the past, this value returned a copy of the stored value. Be careful. - /// Reference to the value. + /// Reference to the wrapped value. FORCE_INLINE T& GetValue() { ASSERT(_hasValue); return _value; } + /// + /// Gets a const reference to the wrapped value or a default value if the value is not valid. + /// + /// Reference to the wrapped value or the default value. FORCE_INLINE const T& GetValueOr(const T& defaultValue) const { return _hasValue ? _value : defaultValue; } + /// + /// Gets an instance of the wrapped value or a default value based on r-value reference, if the wrapped value is not valid. + /// + /// Copy of the wrapped value or the default value. FORCE_INLINE T GetValueOr(T&& defaultValue) const noexcept { return _hasValue ? _value : defaultValue; } /// - /// Sets the wrapped value. + /// Sets the wrapped value by copying. /// /// The value to be copied. FORCE_INLINE void SetValue(const T& value) @@ -199,7 +218,7 @@ public: } /// - /// Sets the wrapped value. + /// Sets the wrapped value by moving. /// /// The value to be moved. FORCE_INLINE void SetValue(T&& value) noexcept @@ -210,18 +229,10 @@ public: _hasValue = true; // Set the flag AFTER the value is moved. } - FORCE_INLINE bool TrySet(T&& value) noexcept - { - if (_hasValue) - { - return false; - } - - new (&_value) T(MoveTemp(value)); // Placement new (move constructor) - _hasValue = true; // Set the flag AFTER the value is moved. - return true; - } - + /// + /// If the wrapped value is not valid, sets it by copying. Otherwise, does nothing. + /// + /// True if the wrapped value was changed, otherwise false. FORCE_INLINE bool TrySet(const T& value) { if (_hasValue) @@ -235,7 +246,23 @@ public: } /// - /// Resets the value. + /// If the wrapped value is not valid, sets it by moving. Otherwise, does nothing. + /// + /// True if the wrapped value was changed, otherwise false. + FORCE_INLINE bool TrySet(T&& value) noexcept + { + if (_hasValue) + { + return false; + } + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; // Set the flag AFTER the value is moved. + return true; + } + + /// + /// Disposes the wrapped value and sets the wrapped value to null. If the wrapped value is not valid, does nothing. /// FORCE_INLINE void Reset() { @@ -244,8 +271,9 @@ public: } /// - /// Moves the value from the current NullableBase{T} object and resets it. + /// Moves the wrapped value to the output parameter and sets the wrapped value to null. If the wrapped value is not valid, the behavior is undefined. /// + /// The output parameter that will receive the wrapped value. FORCE_INLINE void GetAndReset(T& value) { ASSERT(_hasValue); @@ -254,10 +282,10 @@ public: } /// - /// Indicates whether the current NullableBase{T} object is equal to a specified object. + /// Indicates whether this instance is equal to other one. /// /// The other object. - /// True if both values are equal. + /// true if both values are equal. FORCE_INLINE bool operator==(const Nullable& other) const { if (other._hasValue != _hasValue) @@ -269,20 +297,19 @@ public: } /// - /// Indicates whether the current NullableBase{T} object is not equal to a specified object. + /// Indicates whether this instance is NOT equal to other one. /// /// The other object. - /// True if both values are not equal. + /// true if both values are not equal. FORCE_INLINE bool operator!=(const Nullable& other) const { return !operator==(other); } /// - /// Explicit conversion to boolean value. + /// Explicit conversion to boolean value. Allows to check if the wrapped value is valid in if-statements without casting. /// - /// True if this object has a valid value, otherwise false - /// Hint: If-statements are able to use explicit cast implicitly (sic). + /// true if this object has a valid value, otherwise false FORCE_INLINE explicit operator bool() const { return _hasValue; @@ -290,69 +317,107 @@ public: }; /// -/// Nullable value container that contains a boolean value or null. +/// Specialization of for type. /// template<> struct Nullable { private: + /// + /// Underlying value of the nullable boolean. Uses only one byte to optimize memory usage. + /// enum class Value : uint8 { - False = 0, - True = 1, - Null = 2, + Null, + False, + True, }; Value _value = Value::Null; public: + /// + /// Initializes nullable boolean by setting the wrapped value to null. + /// Nullable() = default; ~Nullable() = default; - + /// + /// Initializes nullable boolean by moving another nullable boolean. + /// Nullable(Nullable&& value) = default; + /// + /// Initializes nullable boolean by copying another nullable boolean. + /// Nullable(const Nullable& value) = default; + /// + /// Initializes nullable boolean by implicitly casting a boolean value. + /// Nullable(const bool value) noexcept { _value = value ? Value::True : Value::False; } + /// + /// Reassigns the wrapped value by implicitly casting a boolean value. + /// auto operator=(const bool value) noexcept -> Nullable& { _value = value ? Value::True : Value::False; return *this; } + /// + /// Reassigns the wrapped value by copying another nullable boolean. + /// auto operator=(const Nullable& value) -> Nullable& = default; + /// + /// Reassigns the wrapped value by moving another nullable boolean. + /// auto operator=(Nullable&& value) -> Nullable& = default; + /// + /// Checks if wrapped bool has a valid value. + /// FORCE_INLINE bool HasValue() const noexcept { return _value != Value::Null; } + /// + /// Gets the wrapped boolean value. If the value is not valid, the behavior is undefined. + /// FORCE_INLINE bool GetValue() const { ASSERT(_value != Value::Null); return _value == Value::True; } + /// + /// Gets the wrapped boolean value. If the value is not valid, returns the default value. + /// FORCE_INLINE bool GetValueOr(const bool defaultValue) const noexcept { return _value == Value::Null ? defaultValue : _value == Value::True; } + /// + /// Sets the wrapped value to a valid boolean. + /// FORCE_INLINE void SetValue(const bool value) noexcept { _value = value ? Value::True : Value::False; } + /// + /// If the wrapped value is not valid, sets it to a valid boolean. + /// FORCE_INLINE bool TrySet(const bool value) noexcept { if (_value != Value::Null) @@ -364,11 +429,17 @@ public: return true; } + /// + /// Sets the wrapped bool to null. + /// FORCE_INLINE void Reset() noexcept { _value = Value::Null; } + /// + /// Moves the wrapped value to the output parameter and sets the wrapped value to null. If the wrapped value is not valid, the behavior is undefined. + /// FORCE_INLINE void GetAndReset(bool& value) noexcept { ASSERT(_value != Value::Null); @@ -378,27 +449,26 @@ public: /// - /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to true. + /// Checks if the current object has a valid value and it's set to true. If the value is false or not valid, the method returns false. /// - /// true if this object has a valid value set to true; otherwise, false. FORCE_INLINE bool IsTrue() const { return _value == Value::True; } /// - /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to false. + /// Checks if the current object has a valid value and it's set to false. If the value is true or not valid, the method returns false. /// - /// true if this object has a valid value set to false; otherwise, false. FORCE_INLINE bool IsFalse() const { return _value == Value::False; } /// - /// Getting if provoke unacceptably ambiguous code. For template meta-programming use explicit HasValue() instead. + /// Deletes implicit conversion to bool to prevent ambiguous code. /// + /// + /// Implicit cast from nullable bool to a bool produces unacceptably ambiguous code. For template meta-programming use explicit HasValue instead. + /// explicit operator bool() const = delete; - - // Note: Even though IsTrue and IsFalse have been added for convenience, but they may be used for performance reasons. }; From c9b1f6f516c445734e932a51def14197e837f4f9 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sun, 6 Oct 2024 01:57:12 +0200 Subject: [PATCH 014/215] `Nullable` fixes --- Source/Engine/Core/Types/Nullable.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 655233b85..0cf4b4361 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -79,12 +79,11 @@ public: /// The wrapped value to be moved. Nullable(Nullable&& other) noexcept { - _hasValue = other._hasValue; - - if (_hasValue) + if (other._hasValue) { new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) } + _hasValue = other._hasValue; other.Reset(); } @@ -194,10 +193,10 @@ public: } /// - /// Gets an instance of the wrapped value or a default value based on r-value reference, if the wrapped value is not valid. + /// Gets a mutable reference to the wrapped value or a default value if the value is not valid. /// - /// Copy of the wrapped value or the default value. - FORCE_INLINE T GetValueOr(T&& defaultValue) const noexcept + /// Reference to the wrapped value or the default value. + FORCE_INLINE T& GetValueOr(T& defaultValue) const { return _hasValue ? _value : defaultValue; } @@ -266,8 +265,8 @@ public: /// FORCE_INLINE void Reset() { + _hasValue = false; // Reset the flag BEFORE the value is (potentially) destructed. KillOld(); - _hasValue = false; // Reset the flag AFTER the value is (potentially) destructed. } /// From 23624aa7f8ae73866049e9ce663c2b96fdff5605 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sun, 6 Oct 2024 02:23:21 +0200 Subject: [PATCH 015/215] Fix type constraints --- Source/Engine/Core/Types/Nullable.h | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 0cf4b4361..9d8f3f65b 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -7,13 +7,19 @@ /// /// Wrapper for a value type that can be assigned null, controlling the lifetime of the wrapped value. /// +/// +/// The type of the wrapped value. It must be move-constructible but does not have to be copy-constructible. Value is never reassigned. +/// template struct Nullable { private: + struct Dummy { Dummy() {} }; + union { - T _value; + T _value; + Dummy _dummy; }; bool _hasValue; @@ -28,12 +34,18 @@ private: } } + /// + /// true if the wrapped type is copy constructible. + /// + constexpr static bool IsCopyConstructible = TIsCopyConstructible::Value; + public: /// /// Initializes by setting the wrapped value to null. /// Nullable() - : _hasValue(false) + : _dummy() + , _hasValue(false) { // Value is not initialized. } @@ -47,6 +59,7 @@ public: /// Initializes by copying the wrapped value. /// /// The initial wrapped value to be copied. + template::Type> Nullable(const T& value) : _value(value) , _hasValue(true) @@ -67,6 +80,7 @@ public: /// Initializes by copying another . /// /// The wrapped value to be copied. + template::Type> Nullable(const Nullable& other) : _value(other._value) , _hasValue(other._hasValue) @@ -91,6 +105,7 @@ public: /// /// Reassigns the wrapped value by copying. /// + template::Type> auto operator=(const T& value) -> Nullable& { KillOld(); @@ -117,6 +132,7 @@ public: /// /// Reassigns the wrapped value by copying another . /// + template::Type> auto operator=(const Nullable& other) -> Nullable& { KillOld(); @@ -205,6 +221,7 @@ public: /// Sets the wrapped value by copying. /// /// The value to be copied. + template::Type> FORCE_INLINE void SetValue(const T& value) { if (_hasValue) @@ -232,6 +249,7 @@ public: /// If the wrapped value is not valid, sets it by copying. Otherwise, does nothing. /// /// True if the wrapped value was changed, otherwise false. + template::Type> FORCE_INLINE bool TrySet(const T& value) { if (_hasValue) From 6f6348508a1a5b16cf10ad24f0a3db26cec71da0 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sun, 6 Oct 2024 02:53:11 +0200 Subject: [PATCH 016/215] `Nullable` implicit cast fix --- .../Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 6 +++--- Source/Engine/Platform/Linux/LinuxPlatform.cpp | 10 +++++----- Source/Engine/Platform/Mac/MacPlatform.cpp | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 9b6d4ba2c..5e11e1a86 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1222,11 +1222,11 @@ GPUDevice* GPUDeviceVulkan::Create() return nullptr; } uint32 vendorId = 0; - if (CommandLine::Options.NVIDIA) + if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; - else if (CommandLine::Options.AMD) + else if (CommandLine::Options.AMD.IsTrue()) vendorId = GPU_VENDOR_ID_AMD; - else if (CommandLine::Options.Intel) + else if (CommandLine::Options.Intel.IsTrue()) vendorId = GPU_VENDOR_ID_INTEL; if (vendorId != 0) { diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 2c951effa..681b60c37 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -653,7 +653,7 @@ static int X11_MessageBoxLoop(MessageBoxData* data) DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return DialogResult::None; // Setup for simple popup @@ -1369,7 +1369,7 @@ public: DragDropEffect LinuxWindow::DoDragDrop(const StringView& data) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return DragDropEffect::None; auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54); auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24); @@ -1673,7 +1673,7 @@ void LinuxClipboard::Clear() void LinuxClipboard::SetText(const StringView& text) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return; auto mainWindow = (LinuxWindow*)Engine::MainWindow; if (!mainWindow) @@ -1695,7 +1695,7 @@ void LinuxClipboard::SetFiles(const Array& files) String LinuxClipboard::GetText() { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return String::Empty; String result; auto mainWindow = (LinuxWindow*)Engine::MainWindow; @@ -2118,7 +2118,7 @@ bool LinuxPlatform::Init() Platform::MemoryClear(CursorsImg, sizeof(CursorsImg)); // Skip setup if running in headless mode (X11 might not be available on servers) - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return false; X11::XInitThreads(); diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 2f054f7a6..84279e194 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -54,7 +54,7 @@ String ComputerName; DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return DialogResult::None; NSAlert* alert = [[NSAlert alloc] init]; ASSERT(alert); From 077ececcf880409ccbf620539eb142aebf45e574 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:20:00 +0200 Subject: [PATCH 017/215] `Nullable` match --- Source/Engine/Core/Types/Nullable.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 9d8f3f65b..e1aae2ea3 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -331,6 +331,26 @@ public: { return _hasValue; } + + + /// + /// Matches the wrapped value with a handler for the value or a handler for the null value. + /// + /// Value visitor handling valid nullable value. + /// Null visitor handling invalid nullable value. + /// Result of the call of one of handlers. Handlers must share the same result type. + template + FORCE_INLINE auto Match(ValueVisitor valueHandler, NullVisitor nullHandler) const + { + if (_hasValue) + { + return valueHandler(_value); + } + else + { + return nullHandler(); + } + } }; /// From cda74f5cc472cccdf8e16bce5497ced652211b06 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:50:03 +0200 Subject: [PATCH 018/215] `Nullable` tests --- Source/Engine/Tests/TestNullable.cpp | 154 +++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Source/Engine/Tests/TestNullable.cpp diff --git a/Source/Engine/Tests/TestNullable.cpp b/Source/Engine/Tests/TestNullable.cpp new file mode 100644 index 000000000..945867a04 --- /dev/null +++ b/Source/Engine/Tests/TestNullable.cpp @@ -0,0 +1,154 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "Engine/Core/Types/Nullable.h" +#include + +TEST_CASE("Nullable") +{ + SECTION("Trivial Type") + { + Nullable a; + + REQUIRE(a.HasValue() == false); + REQUIRE(a.GetValueOr(2) == 2); + + a = 1; + + REQUIRE(a.HasValue() == true); + REQUIRE(a.GetValue() == 1); + REQUIRE(a.GetValueOr(2) == 1); + + a.Reset(); + + REQUIRE(a.HasValue() == false); + } + + SECTION("Move-Only Type") + { + struct MoveOnly + { + MoveOnly() = default; + ~MoveOnly() = default; + + MoveOnly(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; + + // MoveOnly& operator=(const MoveOnly&) = delete; + // MoveOnly& operator=(MoveOnly&&) = default; + }; + + Nullable a; + + REQUIRE(a.HasValue() == false); + + a = MoveOnly(); + + REQUIRE(a.HasValue() == true); + } + + SECTION("Bool Type") + { + + Nullable a; + + REQUIRE(a.HasValue() == false); + REQUIRE(a.GetValueOr(true) == true); + REQUIRE(a.IsTrue() == false); + REQUIRE(a.IsFalse() == false); + + a = false; + + REQUIRE(a.HasValue() == true); + REQUIRE(a.GetValue() == false); + REQUIRE(a.GetValueOr(true) == false); + + REQUIRE(a.IsTrue() == false); + REQUIRE(a.IsFalse() == true); + + a = true; + + REQUIRE(a.IsTrue() == true); + REQUIRE(a.IsFalse() == false); + + a.Reset(); + + REQUIRE(a.HasValue() == false); + } + + SECTION("Lifetime (No Construction)") + { + struct DoNotConstruct + { + DoNotConstruct() { FAIL("DoNotConstruct must not be constructed."); } + }; + + Nullable a; + + a.Reset(); + } + + SECTION("Lifetime") + { + int constructed = 0, destructed = 0; + + struct Lifetime + { + int& constructed; + int& destructed; + + Lifetime(int& constructed, int& destructed) + : constructed(constructed) + , destructed(destructed) + { + constructed++; + } + + Lifetime(const Lifetime& other) + : constructed(other.constructed) + , destructed(other.destructed) + { + constructed++; + } + + ~Lifetime() + { + destructed++; + } + }; + + { + Nullable a = Lifetime(constructed, destructed); + + REQUIRE(constructed == 1); + REQUIRE(destructed == 0); + + a.Reset(); + + REQUIRE(constructed == 1); + REQUIRE(destructed == 1); + } + + { + Nullable a = Lifetime(constructed, destructed); + } + + REQUIRE(constructed == 2); + REQUIRE(destructed == 2); + } + + SECTION("Matching") + { + Nullable a; + Nullable b = 2; + + a.Match( + [](int value) { FAIL("Null nullable must not match value handler."); }, + []() {} + ); + + b.Match( + [](int value) {}, + []() { FAIL("Nullable with valid value must not match null handler."); } + ); + } +}; From 44dad402f610f6dd01fe0ce32c65b51523625ef6 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Mon, 7 Oct 2024 02:41:07 +0200 Subject: [PATCH 019/215] `Nullable` dependency headers fix --- Source/Engine/Core/Types/Nullable.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index e1aae2ea3..6ca735d08 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Templates.h" #include "Engine/Platform/Platform.h" /// From 541ca67a061916f777c784d6b2a5cb02d4234bf4 Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Mon, 7 Oct 2024 03:11:50 +0200 Subject: [PATCH 020/215] `Nullable` sfinae fix --- Source/Engine/Core/Types/Nullable.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 6ca735d08..985e66090 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -35,11 +35,6 @@ private: } } - /// - /// true if the wrapped type is copy constructible. - /// - constexpr static bool IsCopyConstructible = TIsCopyConstructible::Value; - public: /// /// Initializes by setting the wrapped value to null. @@ -60,7 +55,7 @@ public: /// Initializes by copying the wrapped value. /// /// The initial wrapped value to be copied. - template::Type> + template::Value>::Type> Nullable(const T& value) : _value(value) , _hasValue(true) @@ -81,7 +76,7 @@ public: /// Initializes by copying another . /// /// The wrapped value to be copied. - template::Type> + template::Value>::Type> Nullable(const Nullable& other) : _value(other._value) , _hasValue(other._hasValue) @@ -106,7 +101,7 @@ public: /// /// Reassigns the wrapped value by copying. /// - template::Type> + template::Value>::Type> auto operator=(const T& value) -> Nullable& { KillOld(); @@ -133,7 +128,7 @@ public: /// /// Reassigns the wrapped value by copying another . /// - template::Type> + template::Value>::Type> auto operator=(const Nullable& other) -> Nullable& { KillOld(); @@ -222,7 +217,7 @@ public: /// Sets the wrapped value by copying. /// /// The value to be copied. - template::Type> + template::Value>::Type> FORCE_INLINE void SetValue(const T& value) { if (_hasValue) @@ -250,7 +245,7 @@ public: /// If the wrapped value is not valid, sets it by copying. Otherwise, does nothing. /// /// True if the wrapped value was changed, otherwise false. - template::Type> + template::Value>::Type> FORCE_INLINE bool TrySet(const T& value) { if (_hasValue) From f56207f1a40846749ea9122111d81b1dbf8a611b Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:17:23 +0200 Subject: [PATCH 021/215] `Nullable.Reset` fix, killing inlining --- Source/Engine/Core/Types/Nullable.h | 55 ++++++++++++++++++----------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 985e66090..43fe5342a 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -24,17 +24,6 @@ private: }; bool _hasValue; - /// - /// Ends the lifetime of the wrapped value by calling its destructor, if the lifetime has not ended yet. Otherwise, does nothing. - /// - FORCE_INLINE void KillOld() - { - if (_hasValue) - { - _value.~T(); - } - } - public: /// /// Initializes by setting the wrapped value to null. @@ -48,7 +37,10 @@ public: ~Nullable() { - KillOld(); + if (_hasValue) + { + _value.~T(); + } } /// @@ -104,7 +96,10 @@ public: template::Value>::Type> auto operator=(const T& value) -> Nullable& { - KillOld(); + if (_hasValue) + { + _value.~T(); + } new (&_value) T(value); // Placement new (copy constructor) _hasValue = true; @@ -117,7 +112,10 @@ public: /// auto operator=(T&& value) noexcept -> Nullable& { - KillOld(); + if (_hasValue) + { + _value.~T(); + } new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; @@ -131,12 +129,16 @@ public: template::Value>::Type> auto operator=(const Nullable& other) -> Nullable& { - KillOld(); + if (_hasValue) + { + _value.~T(); + } if (other._hasValue) { new (&_value) T(other._value); // Placement new (copy constructor) } + _hasValue = other._hasValue; // Set the flag AFTER the value is copied. return *this; @@ -152,14 +154,19 @@ public: return *this; } - KillOld(); - if (_hasValue) + { + _value.~T(); + } + + if (other._hasValue) { new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) - other.Reset(); + other._value.~T(); // Kill the old value in the source object. + other._hasValue = false; } + _hasValue = other._hasValue; // Set the flag AFTER the value is moved. return *this; @@ -235,7 +242,10 @@ public: /// The value to be moved. FORCE_INLINE void SetValue(T&& value) noexcept { - KillOld(); + if (_hasValue) + { + _value.~T(); + } new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; // Set the flag AFTER the value is moved. @@ -279,8 +289,13 @@ public: /// FORCE_INLINE void Reset() { + if (!_hasValue) + { + return; + } + _hasValue = false; // Reset the flag BEFORE the value is (potentially) destructed. - KillOld(); + _value.~T(); } /// From eda4f433d035917dc1c9e1cf84a5f5537ab8656e Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak <69864511+mtszkarbowiak@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:24:09 +0200 Subject: [PATCH 022/215] Update TestNullable.cpp --- Source/Engine/Tests/TestNullable.cpp | 155 ++++++++++++++------------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/Source/Engine/Tests/TestNullable.cpp b/Source/Engine/Tests/TestNullable.cpp index 945867a04..d02d98c0d 100644 --- a/Source/Engine/Tests/TestNullable.cpp +++ b/Source/Engine/Tests/TestNullable.cpp @@ -7,7 +7,7 @@ TEST_CASE("Nullable") { SECTION("Trivial Type") { - Nullable a; + Nullable a; REQUIRE(a.HasValue() == false); REQUIRE(a.GetValueOr(2) == 2); @@ -25,130 +25,139 @@ TEST_CASE("Nullable") SECTION("Move-Only Type") { - struct MoveOnly - { - MoveOnly() = default; + struct MoveOnly + { + MoveOnly() = default; ~MoveOnly() = default; - MoveOnly(const MoveOnly&) = delete; - MoveOnly(MoveOnly&&) = default; + MoveOnly(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; - // MoveOnly& operator=(const MoveOnly&) = delete; - // MoveOnly& operator=(MoveOnly&&) = default; - }; + MoveOnly& operator=(const MoveOnly&) = delete; + MoveOnly& operator=(MoveOnly&&) = default; + }; - Nullable a; + Nullable a; - REQUIRE(a.HasValue() == false); + REQUIRE(a.HasValue() == false); - a = MoveOnly(); + a = MoveOnly(); - REQUIRE(a.HasValue() == true); + REQUIRE(a.HasValue() == true); } SECTION("Bool Type") { - - Nullable a; + Nullable a; - REQUIRE(a.HasValue() == false); - REQUIRE(a.GetValueOr(true) == true); + REQUIRE(a.HasValue() == false); + REQUIRE(a.GetValueOr(true) == true); REQUIRE(a.IsTrue() == false); REQUIRE(a.IsFalse() == false); - a = false; + a = false; - REQUIRE(a.HasValue() == true); - REQUIRE(a.GetValue() == false); - REQUIRE(a.GetValueOr(true) == false); + REQUIRE(a.HasValue() == true); + REQUIRE(a.GetValue() == false); + REQUIRE(a.GetValueOr(true) == false); - REQUIRE(a.IsTrue() == false); - REQUIRE(a.IsFalse() == true); + REQUIRE(a.IsTrue() == false); + REQUIRE(a.IsFalse() == true); a = true; REQUIRE(a.IsTrue() == true); REQUIRE(a.IsFalse() == false); - a.Reset(); + a.Reset(); - REQUIRE(a.HasValue() == false); + REQUIRE(a.HasValue() == false); } SECTION("Lifetime (No Construction)") { - struct DoNotConstruct - { - DoNotConstruct() { FAIL("DoNotConstruct must not be constructed."); } - }; + struct DoNotConstruct + { + DoNotConstruct() { FAIL("DoNotConstruct must not be constructed."); } + }; Nullable a; - a.Reset(); } SECTION("Lifetime") - { - int constructed = 0, destructed = 0; - + { struct Lifetime - { - int& constructed; - int& destructed; + { + int* _constructed; + int* _destructed; - Lifetime(int& constructed, int& destructed) - : constructed(constructed) - , destructed(destructed) - { - constructed++; - } + Lifetime(int* constructed, int* destructed) + : _constructed(constructed) + , _destructed(destructed) + { + ++(*_constructed); + } - Lifetime(const Lifetime& other) - : constructed(other.constructed) - , destructed(other.destructed) - { - constructed++; - } + Lifetime(Lifetime&& other) noexcept + : _constructed(other._constructed) + , _destructed(other._destructed) + { + ++(*_constructed); + } - ~Lifetime() - { - destructed++; - } - }; + Lifetime() = delete; + Lifetime& operator=(const Lifetime&) = delete; + Lifetime& operator=(Lifetime&&) = delete; - { - Nullable a = Lifetime(constructed, destructed); + ~Lifetime() + { + ++(*_destructed); + } + }; - REQUIRE(constructed == 1); - REQUIRE(destructed == 0); + int constructed = 0, destructed = 0; + REQUIRE(constructed == destructed); - a.Reset(); + { - REQUIRE(constructed == 1); - REQUIRE(destructed == 1); - } + Nullable a = Lifetime(&constructed, &destructed); + REQUIRE(a.HasValue()); + REQUIRE(constructed == destructed + 1); - { - Nullable a = Lifetime(constructed, destructed); - } + a.Reset(); + REQUIRE(!a.HasValue()); + REQUIRE(constructed == destructed); + } + REQUIRE(constructed == destructed); - REQUIRE(constructed == 2); - REQUIRE(destructed == 2); - } + { + Nullable b = Lifetime(&constructed, &destructed); + REQUIRE(constructed == destructed + 1); + } + REQUIRE(constructed == destructed); + + { + Nullable c = Lifetime(&constructed, &destructed); + Nullable d = MoveTemp(c); + REQUIRE(constructed == destructed + 1); + } + REQUIRE(constructed == destructed); + } SECTION("Matching") { - Nullable a; - Nullable b = 2; + Nullable a; + Nullable b = 2; a.Match( - [](int value) { FAIL("Null nullable must not match value handler."); }, - []() {} + [](int) { FAIL("Null nullable must not match value handler."); }, + []() {} ); b.Match( - [](int value) {}, + [](int) {}, []() { FAIL("Nullable with valid value must not match null handler."); } - ); + ); } }; From 575a286e1e3cdde69c51c3f860ba8428e4879a14 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 11 Oct 2024 20:06:20 +0200 Subject: [PATCH 023/215] Add editor windows to quick actions via Content Finder tool --- Source/Editor/Modules/ContentFindingModule.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs index e3aa22b0e..b501d6686 100644 --- a/Source/Editor/Modules/ContentFindingModule.cs +++ b/Source/Editor/Modules/ContentFindingModule.cs @@ -192,7 +192,7 @@ namespace FlaxEditor.Modules /// Removes a quick action by name. /// /// The action's name. - /// True when it succeed, false if there is no Quick Action with this name. + /// True when it succeeds, false if there is no Quick Action with this name. public bool RemoveQuickAction(string name) { if (_quickActions == null) @@ -288,6 +288,16 @@ namespace FlaxEditor.Modules Profiler.EndEvent(); } + // Editor window + foreach (var window in Editor.Windows.Windows) + { + if (window is Windows.Assets.AssetEditorWindow) + continue; + var windowName = window.Title + " (window)"; + if (nameRegex.Match(windowName).Success) + matches.Add(new SearchResult { Name = windowName, Type = "Window", Item = window }); + } + Profiler.EndEvent(); return matches; } @@ -407,6 +417,9 @@ namespace FlaxEditor.Modules Editor.Instance.Windows.EditWin.Viewport.FocusSelection(); } break; + case Windows.EditorWindow window: + window.FocusOrShow(); + break; } } From ff495e1319348a0dcc7217dfe77288e5bec586aa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 11 Oct 2024 20:15:17 +0200 Subject: [PATCH 024/215] Add safety check to exit game in Release mode when running with graphics debugger attached --- Source/Engine/Graphics/Graphics.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index a1640c0b4..886728d95 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -200,6 +200,10 @@ bool GraphicsService::Init() #endif ) { +#if !USE_EDITOR && BUILD_RELEASE + // Block graphics debugging to protect contents + Platform::Fatal(TEXT("Graphics debugger attached.")); +#endif #if COMPILE_WITH_PROFILER // Auto-enable GPU events ProfilerGPU::EventsEnabled = true; From f8371d037b8543067552637e2058df03ef395257 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 11 Oct 2024 23:05:09 +0200 Subject: [PATCH 025/215] Refactor old `ContentLoadingManager` into `Content` for simplicity --- Source/Engine/Content/Asset.cpp | 86 +----- Source/Engine/Content/Assets/Model.cpp | 1 + Source/Engine/Content/Config.h | 4 +- Source/Engine/Content/Content.cpp | 259 +++++++++++++++++- Source/Engine/Content/Content.h | 3 +- .../Content/Loading/ContentLoadingManager.cpp | 237 ---------------- .../Content/Loading/ContentLoadingManager.h | 111 -------- Source/Engine/Content/Loading/LoadingThread.h | 50 ++++ .../Content/Loading/Tasks/LoadAssetDataTask.h | 1 + .../Graphics/Textures/StreamingTexture.cpp | 1 - 10 files changed, 314 insertions(+), 439 deletions(-) delete mode 100644 Source/Engine/Content/Loading/ContentLoadingManager.cpp delete mode 100644 Source/Engine/Content/Loading/ContentLoadingManager.h create mode 100644 Source/Engine/Content/Loading/LoadingThread.h diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index c772cb2d9..961ea7ed2 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -4,16 +4,12 @@ #include "Content.h" #include "SoftAssetReference.h" #include "Cache/AssetsCache.h" -#include "Loading/ContentLoadingManager.h" #include "Loading/Tasks/LoadAssetTask.h" #include "Engine/Core/Log.h" #include "Engine/Core/LogContext.h" -#include "Engine/Engine/Engine.h" -#include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Threading/MainThreadTask.h" -#include "Engine/Threading/ConcurrentTaskQueue.h" #include "Engine/Scripting/ManagedCLR/MCore.h" +#include "Engine/Threading/MainThreadTask.h" AssetReferenceBase::~AssetReferenceBase() { @@ -377,11 +373,6 @@ void Asset::Reload() } } -namespace ContentLoadingManagerImpl -{ - extern ConcurrentTaskQueue Tasks; -}; - bool Asset::WaitForLoaded(double timeoutInMilliseconds) const { // This function is used many time when some parts of the engine need to wait for asset loading end (it may fail but has to end). @@ -430,80 +421,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const PROFILE_CPU(); - // Check if call is made from the Loading Thread and task has not been taken yet - auto thread = ContentLoadingManager::GetCurrentLoadThread(); - if (thread != nullptr) - { - // Note: to reproduce this case just include material into material (use layering). - // So during loading first material it will wait for child materials loaded calling this function - - const double timeoutInSeconds = timeoutInMilliseconds * 0.001; - const double startTime = Platform::GetTimeSeconds(); - Task* task = loadingTask; - Array> localQueue; -#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds)) - do - { - // Try to execute content tasks - while (task->IsQueued() && CHECK_CONDITIONS()) - { - // Dequeue task from the loading queue - ContentLoadTask* tmp; - if (ContentLoadingManagerImpl::Tasks.try_dequeue(tmp)) - { - if (tmp == task) - { - if (localQueue.Count() != 0) - { - // Put back queued tasks - ContentLoadingManagerImpl::Tasks.enqueue_bulk(localQueue.Get(), localQueue.Count()); - localQueue.Clear(); - } - - thread->Run(tmp); - } - else - { - localQueue.Add(tmp); - } - } - else - { - // No task in queue but it's queued so other thread could have stolen it into own local queue - break; - } - } - if (localQueue.Count() != 0) - { - // Put back queued tasks - ContentLoadingManagerImpl::Tasks.enqueue_bulk(localQueue.Get(), localQueue.Count()); - localQueue.Clear(); - } - - // Check if task is done - if (task->IsEnded()) - { - // If was fine then wait for the next task - if (task->IsFinished()) - { - task = task->GetContinueWithTask(); - if (!task) - break; - } - else - { - // Failed or cancelled so this wait also fails - break; - } - } - } while (CHECK_CONDITIONS()); -#undef CHECK_CONDITIONS - } - else - { - // Wait for task end - loadingTask->Wait(timeoutInMilliseconds); - } + Content::WaitForTask(loadingTask, timeoutInMilliseconds); // If running on a main thread we can flush asset `Loaded` event if (IsInMainThread() && IsLoaded()) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 3da53ea6b..37e0d1cc8 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -974,6 +974,7 @@ Asset::LoadResult Model::load() auto chunk15 = GetChunk(15); if (chunk15 && chunk15->IsLoaded() && EnableModelSDF == 1) { + PROFILE_CPU_NAMED("SDF"); MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size()); int32 version; sdfStream.ReadInt32(&version); diff --git a/Source/Engine/Content/Config.h b/Source/Engine/Content/Config.h index de6f7d76d..361ec978a 100644 --- a/Source/Engine/Content/Config.h +++ b/Source/Engine/Content/Config.h @@ -7,13 +7,13 @@ // Amount of content loading threads per single physical CPU core #define LOADING_THREAD_PER_LOGICAL_CORE 0.5f -// Enable/disable additional assets metadata verification, note: we should disable it for release builds +// Enables additional assets metadata verification #define ASSETS_LOADING_EXTRA_VERIFICATION (BUILD_DEBUG || USE_EDITOR) // Maximum amount of data chunks used by the single asset #define ASSET_FILE_DATA_CHUNKS 16 -// Enables searching workspace for missing assets (should be disabled in the final builds where assets registry is solid) +// Enables searching workspace for missing assets #define ENABLE_ASSETS_DISCOVERY (USE_EDITOR) // Default extension for all asset files diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 37e77fa64..08a3a5f3f 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -3,20 +3,27 @@ #include "Content.h" #include "JsonAsset.h" #include "SceneReference.h" -#include "Engine/Serialization/Serialization.h" #include "Cache/AssetsCache.h" #include "Storage/ContentStorageManager.h" #include "Storage/JsonStorageProxy.h" #include "Factories/IAssetFactory.h" +#include "Loading/LoadingThread.h" +#include "Loading/ContentLoadTask.h" #include "Engine/Core/Log.h" #include "Engine/Core/LogContext.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/ObjectsRemovalService.h" -#include "Engine/Engine/EngineService.h" +#include "Engine/Serialization/Serialization.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Platform/ConditionVariable.h" +#include "Engine/Platform/Thread.h" +#include "Engine/Platform/CPUInfo.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/MainThreadTask.h" +#include "Engine/Threading/ConcurrentTaskQueue.h" #include "Engine/Graphics/Graphics.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Engine/EngineService.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/Globals.h" #include "Engine/Level/Types.h" @@ -30,6 +37,10 @@ #if ENABLE_ASSETS_DISCOVERY #include "Engine/Core/Collections/HashSet.h" #endif +#if USE_EDITOR && PLATFORM_WINDOWS +#include "Engine/Platform/Win32/IncludeWindowsHeaders.h" +#include +#endif TimeSpan Content::AssetsUpdateInterval = TimeSpan::FromMilliseconds(500); TimeSpan Content::AssetsUnloadInterval = TimeSpan::FromSeconds(10); @@ -64,6 +75,14 @@ namespace // Assets Registry Stuff AssetsCache Cache; + // Loading assets + THREADLOCAL LoadingThread* ThisLoadThread = nullptr; + LoadingThread* MainLoadThread = nullptr; + Array LoadThreads; + ConcurrentTaskQueue LoadTasks; + ConditionVariable LoadTasksSignal; + CriticalSection LoadTasksMutex; + // Unloading assets Dictionary UnloadQueue; TimeSpan LastUnloadCheckTime(0); @@ -90,6 +109,7 @@ public: bool Init() override; void Update() override; void LateUpdate() override; + void BeforeExit() override; void Dispose() override; }; @@ -100,6 +120,25 @@ bool ContentService::Init() // Load assets registry Cache.Init(); + // Create loading threads + const CPUInfo cpuInfo = Platform::GetCPUInfo(); + const int32 count = Math::Clamp(Math::CeilToInt(LOADING_THREAD_PER_LOGICAL_CORE * (float)cpuInfo.LogicalProcessorCount), 1, 12); + LOG(Info, "Creating {0} content loading threads...", count); + MainLoadThread = New(); + ThisLoadThread = MainLoadThread; + LoadThreads.EnsureCapacity(count); + for (int32 i = 0; i < count; i++) + { + auto thread = New(); + if (thread->Start(String::Format(TEXT("Load Thread {0}"), i))) + { + LOG(Fatal, "Cannot spawn content thread {0}/{1}", i, count); + Delete(thread); + return true; + } + LoadThreads.Add(thread); + } + return false; } @@ -173,6 +212,14 @@ void ContentService::LateUpdate() Cache.Save(); } +void ContentService::BeforeExit() +{ + // Signal threads to end work soon + for (auto thread : LoadThreads) + thread->NotifyExit(); + LoadTasksSignal.NotifyAll(); +} + void ContentService::Dispose() { IsExiting = true; @@ -198,6 +245,20 @@ void ContentService::Dispose() // NOW dispose graphics device - where there is no loaded assets at all Graphics::DisposeDevice(); + + // Exit all load threads + for (auto thread : LoadThreads) + thread->NotifyExit(); + LoadTasksSignal.NotifyAll(); + for (auto thread : LoadThreads) + thread->Join(); + LoadThreads.ClearDelete(); + Delete(MainLoadThread); + MainLoadThread = nullptr; + ThisLoadThread = nullptr; + + // Cancel all remaining tasks (no chance to execute them) + LoadTasks.CancelAll(); } IAssetFactory::Collection& IAssetFactory::Get() @@ -206,6 +267,122 @@ IAssetFactory::Collection& IAssetFactory::Get() return Factories; } +LoadingThread::LoadingThread() + : _exitFlag(false) + , _thread(nullptr) + , _totalTasksDoneCount(0) +{ +} + +LoadingThread::~LoadingThread() +{ + if (_thread != nullptr) + { + _thread->Kill(true); + Delete(_thread); + } +} + +void LoadingThread::NotifyExit() +{ + Platform::InterlockedIncrement(&_exitFlag); +} + +void LoadingThread::Join() +{ + auto thread = _thread; + if (thread) + thread->Join(); +} + +bool LoadingThread::Start(const String& name) +{ + ASSERT(_thread == nullptr && name.HasChars()); + + // Create new thread + auto thread = Thread::Create(this, name, ThreadPriority::Normal); + if (thread == nullptr) + return true; + + _thread = thread; + + return false; +} + +void LoadingThread::Run(ContentLoadTask* job) +{ + ASSERT(job); + + job->Execute(); + _totalTasksDoneCount++; +} + +String LoadingThread::ToString() const +{ + return String::Format(TEXT("Loading Thread {0}"), _thread ? _thread->GetID() : 0); +} + +int32 LoadingThread::Run() +{ +#if USE_EDITOR && PLATFORM_WINDOWS + // Initialize COM + // TODO: maybe add sth to Thread::Create to indicate that thread will use COM stuff + const auto result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(result)) + { + LOG(Error, "Failed to init COM for WIC texture importing! Result: {0:x}", static_cast(result)); + return -1; + } +#endif + + ContentLoadTask* task; + ThisLoadThread = this; + + while (Platform::AtomicRead(&_exitFlag) == 0) + { + if (LoadTasks.try_dequeue(task)) + { + Run(task); + } + else + { + LoadTasksMutex.Lock(); + LoadTasksSignal.Wait(LoadTasksMutex); + LoadTasksMutex.Unlock(); + } + } + + ThisLoadThread = nullptr; + return 0; +} + +void LoadingThread::Exit() +{ + LOG(Info, "Content thread '{0}' exited. Load calls: {1}", _thread->GetName(), _totalTasksDoneCount); +} + +String ContentLoadTask::ToString() const +{ + return String::Format(TEXT("Content Load Task ({})"), (int32)GetState()); +} + +void ContentLoadTask::Enqueue() +{ + LoadTasks.Add(this); + LoadTasksSignal.NotifyOne(); +} + +bool ContentLoadTask::Run() +{ + const auto result = run(); + const bool failed = result != Result::Ok; + if (failed) + { + LOG(Warning, "\'{0}\' failed with result: {1}", ToString(), ToString(result)); + } + return failed; +} + AssetsCache* Content::GetRegistry() { return &Cache; @@ -887,6 +1064,84 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type) return asset; } +void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMilliseconds) +{ + // Check if call is made from the Loading Thread and task has not been taken yet + auto thread = ThisLoadThread; + if (thread != nullptr) + { + // Note: to reproduce this case just include material into material (use layering). + // So during loading first material it will wait for child materials loaded calling this function + + const double timeoutInSeconds = timeoutInMilliseconds * 0.001; + const double startTime = Platform::GetTimeSeconds(); + Task* task = loadingTask; + Array> localQueue; +#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds)) + do + { + // Try to execute content tasks + while (task->IsQueued() && CHECK_CONDITIONS()) + { + // Dequeue task from the loading queue + ContentLoadTask* tmp; + if (LoadTasks.try_dequeue(tmp)) + { + if (tmp == task) + { + if (localQueue.Count() != 0) + { + // Put back queued tasks + LoadTasks.enqueue_bulk(localQueue.Get(), localQueue.Count()); + localQueue.Clear(); + } + + thread->Run(tmp); + } + else + { + localQueue.Add(tmp); + } + } + else + { + // No task in queue but it's queued so other thread could have stolen it into own local queue + break; + } + } + if (localQueue.Count() != 0) + { + // Put back queued tasks + LoadTasks.enqueue_bulk(localQueue.Get(), localQueue.Count()); + localQueue.Clear(); + } + + // Check if task is done + if (task->IsEnded()) + { + // If was fine then wait for the next task + if (task->IsFinished()) + { + task = task->GetContinueWithTask(); + if (!task) + break; + } + else + { + // Failed or cancelled so this wait also fails + break; + } + } + } while (CHECK_CONDITIONS()); +#undef CHECK_CONDITIONS + } + else + { + // Wait for task end + loadingTask->Wait(timeoutInMilliseconds); + } +} + void Content::tryCallOnLoaded(Asset* asset) { ScopeLock lock(LoadedAssetsToInvokeLocker); diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h index 5697c17b0..787b76054 100644 --- a/Source/Engine/Content/Content.h +++ b/Source/Engine/Content/Content.h @@ -362,11 +362,10 @@ public: API_EVENT() static Delegate AssetReloading; private: + static void WaitForTask(ContentLoadTask* loadingTask, double timeoutInMilliseconds); static void tryCallOnLoaded(Asset* asset); static void onAssetLoaded(Asset* asset); static void onAssetUnload(Asset* asset); static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId); - -private: static void deleteFileSafety(const StringView& path, const Guid& id); }; diff --git a/Source/Engine/Content/Loading/ContentLoadingManager.cpp b/Source/Engine/Content/Loading/ContentLoadingManager.cpp deleted file mode 100644 index d0efbd197..000000000 --- a/Source/Engine/Content/Loading/ContentLoadingManager.cpp +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#include "ContentLoadingManager.h" -#include "ContentLoadTask.h" -#include "Engine/Core/Log.h" -#include "Engine/Core/Math/Math.h" -#include "Engine/Core/Collections/Array.h" -#include "Engine/Platform/CPUInfo.h" -#include "Engine/Platform/Thread.h" -#include "Engine/Platform/ConditionVariable.h" -#include "Engine/Content/Config.h" -#include "Engine/Engine/EngineService.h" -#include "Engine/Threading/Threading.h" -#include "Engine/Threading/ConcurrentTaskQueue.h" -#if USE_EDITOR && PLATFORM_WINDOWS -#include "Engine/Platform/Win32/IncludeWindowsHeaders.h" -#include -#endif - -namespace ContentLoadingManagerImpl -{ - THREADLOCAL LoadingThread* ThisThread = nullptr; - LoadingThread* MainThread = nullptr; - Array Threads; - ConcurrentTaskQueue Tasks; - ConditionVariable TasksSignal; - CriticalSection TasksMutex; -}; - -using namespace ContentLoadingManagerImpl; - -class ContentLoadingManagerService : public EngineService -{ -public: - ContentLoadingManagerService() - : EngineService(TEXT("Content Loading Manager"), -500) - { - } - - bool Init() override; - void BeforeExit() override; - void Dispose() override; -}; - -ContentLoadingManagerService ContentLoadingManagerServiceInstance; - -LoadingThread::LoadingThread() - : _exitFlag(false) - , _thread(nullptr) - , _totalTasksDoneCount(0) -{ -} - -LoadingThread::~LoadingThread() -{ - // Check if has thread attached - if (_thread != nullptr) - { - _thread->Kill(true); - Delete(_thread); - } -} - -uint64 LoadingThread::GetID() const -{ - return _thread ? _thread->GetID() : 0; -} - -void LoadingThread::NotifyExit() -{ - Platform::InterlockedIncrement(&_exitFlag); -} - -void LoadingThread::Join() -{ - auto thread = _thread; - if (thread) - thread->Join(); -} - -bool LoadingThread::Start(const String& name) -{ - ASSERT(_thread == nullptr && name.HasChars()); - - // Create new thread - auto thread = Thread::Create(this, name, ThreadPriority::Normal); - if (thread == nullptr) - return true; - - _thread = thread; - - return false; -} - -void LoadingThread::Run(ContentLoadTask* job) -{ - ASSERT(job); - - job->Execute(); - _totalTasksDoneCount++; -} - -String LoadingThread::ToString() const -{ - return String::Format(TEXT("Loading Thread {0}"), GetID()); -} - -int32 LoadingThread::Run() -{ -#if USE_EDITOR && PLATFORM_WINDOWS - - // Initialize COM - // TODO: maybe add sth to Thread::Create to indicate that thread will use COM stuff - const auto result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if (FAILED(result)) - { - LOG(Error, "Failed to init COM for WIC texture importing! Result: {0:x}", static_cast(result)); - return -1; - } - -#endif - - ContentLoadTask* task; - ThisThread = this; - - while (HasExitFlagClear()) - { - if (Tasks.try_dequeue(task)) - { - Run(task); - } - else - { - TasksMutex.Lock(); - TasksSignal.Wait(TasksMutex); - TasksMutex.Unlock(); - } - } - - ThisThread = nullptr; - return 0; -} - -void LoadingThread::Exit() -{ - // Send info - ASSERT_LOW_LAYER(_thread); - LOG(Info, "Content thread '{0}' exited. Load calls: {1}", _thread->GetName(), _totalTasksDoneCount); -} - -LoadingThread* ContentLoadingManager::GetCurrentLoadThread() -{ - return ThisThread; -} - -int32 ContentLoadingManager::GetTasksCount() -{ - return Tasks.Count(); -} - -bool ContentLoadingManagerService::Init() -{ - ASSERT(ContentLoadingManagerImpl::Threads.IsEmpty() && IsInMainThread()); - - // Calculate amount of loading threads to use - const CPUInfo cpuInfo = Platform::GetCPUInfo(); - const int32 count = Math::Clamp(Math::CeilToInt(LOADING_THREAD_PER_LOGICAL_CORE * (float)cpuInfo.LogicalProcessorCount), 1, 12); - LOG(Info, "Creating {0} content loading threads...", count); - - // Create loading threads - MainThread = New(); - ThisThread = MainThread; - Threads.EnsureCapacity(count); - for (int32 i = 0; i < count; i++) - { - auto thread = New(); - if (thread->Start(String::Format(TEXT("Load Thread {0}"), i))) - { - LOG(Fatal, "Cannot spawn content thread {0}/{1}", i, count); - Delete(thread); - return true; - } - Threads.Add(thread); - } - - return false; -} - -void ContentLoadingManagerService::BeforeExit() -{ - // Signal threads to end work soon - for (int32 i = 0; i < Threads.Count(); i++) - Threads[i]->NotifyExit(); - TasksSignal.NotifyAll(); -} - -void ContentLoadingManagerService::Dispose() -{ - // Exit all threads - for (int32 i = 0; i < Threads.Count(); i++) - Threads[i]->NotifyExit(); - TasksSignal.NotifyAll(); - for (int32 i = 0; i < Threads.Count(); i++) - Threads[i]->Join(); - Threads.ClearDelete(); - Delete(MainThread); - MainThread = nullptr; - ThisThread = nullptr; - - // Cancel all remaining tasks (no chance to execute them) - Tasks.CancelAll(); -} - -String ContentLoadTask::ToString() const -{ - return String::Format(TEXT("Content Load Task ({})"), (int32)GetState()); -} - -void ContentLoadTask::Enqueue() -{ - Tasks.Add(this); - TasksSignal.NotifyOne(); -} - -bool ContentLoadTask::Run() -{ - // Perform an operation - const auto result = run(); - - // Process result - const bool failed = result != Result::Ok; - if (failed) - { - LOG(Warning, "\'{0}\' failed with result: {1}", ToString(), ToString(result)); - } - return failed; -} diff --git a/Source/Engine/Content/Loading/ContentLoadingManager.h b/Source/Engine/Content/Loading/ContentLoadingManager.h deleted file mode 100644 index ab04a5d52..000000000 --- a/Source/Engine/Content/Loading/ContentLoadingManager.h +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Threading/IRunnable.h" - -class Asset; -class LoadingThread; -class ContentLoadTask; - -/// -/// Resources loading thread -/// -class LoadingThread : public IRunnable -{ -protected: - volatile int64 _exitFlag; - Thread* _thread; - int32 _totalTasksDoneCount; - -public: - /// - /// Init - /// - LoadingThread(); - - /// - /// Destructor - /// - ~LoadingThread(); - -public: - /// - /// Gets the thread identifier. - /// - /// Thread ID - uint64 GetID() const; - -public: - /// - /// Returns true if thread has empty exit flag, so it can continue it's work - /// - /// True if exit flag is empty, otherwise false - FORCE_INLINE bool HasExitFlagClear() - { - return Platform::AtomicRead(&_exitFlag) == 0; - } - - /// - /// Set exit flag to true so thread must exit - /// - void NotifyExit(); - - /// - /// Stops the calling thread execution until the loading thread ends its execution. - /// - void Join(); - -public: - /// - /// Starts thread execution. - /// - /// The thread name. - /// True if cannot start, otherwise false - bool Start(const String& name); - - /// - /// Runs the specified task. - /// - /// The task. - void Run(ContentLoadTask* task); - -public: - // [IRunnable] - String ToString() const override; - int32 Run() override; - void Exit() override; -}; - -/// -/// Content loading manager. -/// -class ContentLoadingManager -{ - friend ContentLoadTask; - friend LoadingThread; - friend Asset; - -public: - /// - /// Checks if current execution context is thread used to load assets. - /// - /// True if execution is in Load Thread, otherwise false. - FORCE_INLINE static bool IsInLoadThread() - { - return GetCurrentLoadThread() != nullptr; - } - - /// - /// Gets content loading thread handle if current thread is one of them. - /// - /// Current load thread or null if current thread is different. - static LoadingThread* GetCurrentLoadThread(); - -public: - /// - /// Gets amount of enqueued tasks to perform. - /// - /// The tasks count. - static int32 GetTasksCount(); -}; diff --git a/Source/Engine/Content/Loading/LoadingThread.h b/Source/Engine/Content/Loading/LoadingThread.h new file mode 100644 index 000000000..1e78a5fc8 --- /dev/null +++ b/Source/Engine/Content/Loading/LoadingThread.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Threading/IRunnable.h" + +/// +/// Resources loading thread. +/// +class LoadingThread : public IRunnable +{ +protected: + volatile int64 _exitFlag; + Thread* _thread; + int32 _totalTasksDoneCount; + +public: + LoadingThread(); + ~LoadingThread(); + +public: + /// + /// Set exit flag to true so thread must exit + /// + void NotifyExit(); + + /// + /// Stops the calling thread execution until the loading thread ends its execution. + /// + void Join(); + + /// + /// Starts thread execution. + /// + /// The thread name. + /// True if cannot start, otherwise false + bool Start(const String& name); + + /// + /// Runs the specified task. + /// + /// The task. + void Run(class ContentLoadTask* task); + +public: + // [IRunnable] + String ToString() const override; + int32 Run() override; + void Exit() override; +}; diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h index 3fbd34835..43084009f 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h @@ -67,6 +67,7 @@ protected: #if TRACY_ENABLE ZoneScoped; ZoneName(*name, name.Length()); + ZoneValue(chunk->LocationInFile.Size / 1024); // Size in kB #endif if (ref->Storage->LoadAssetChunk(chunk)) { diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 2e1aaf457..a674f1989 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -4,7 +4,6 @@ #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "Engine/Streaming/StreamingGroup.h" -#include "Engine/Content/Loading/ContentLoadingManager.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTools.h" From 23ad24751a1b45a99b424b0262c3eba9e4ccc784 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 12 Oct 2024 00:00:02 +0200 Subject: [PATCH 026/215] Fix editor tables rows coloring to start with darker one --- Source/Editor/Windows/Profiler/Assets.cs | 2 +- Source/Editor/Windows/Profiler/CPU.cs | 2 +- Source/Editor/Windows/Profiler/GPU.cs | 2 +- Source/Editor/Windows/Profiler/MemoryGPU.cs | 2 +- Source/Editor/Windows/Profiler/Network.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs index 159392138..3b408587a 100644 --- a/Source/Editor/Windows/Profiler/Assets.cs +++ b/Source/Editor/Windows/Profiler/Assets.cs @@ -266,7 +266,7 @@ namespace FlaxEditor.Windows.Profiler // Add row to the table row.Width = _table.Width; - row.BackgroundColor = rowIndex % 2 == 0 ? rowColor2 : Color.Transparent; + row.BackgroundColor = rowIndex % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; rowIndex++; } diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 8c6ff5e78..ab25532d4 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -538,7 +538,7 @@ namespace FlaxEditor.Windows.Profiler row.Depth = e.Depth; row.Width = _table.Width; row.Visible = e.Depth < 2; - row.BackgroundColor = i % 2 == 0 ? rowColor2 : Color.Transparent; + row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; } } diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index 7b837768f..b1203787e 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -384,7 +384,7 @@ namespace FlaxEditor.Windows.Profiler row.Depth = e.Depth; row.Width = _table.Width; row.Visible = e.Depth < 3; - row.BackgroundColor = i % 2 == 0 ? rowColor2 : Color.Transparent; + row.BackgroundColor = i % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; } } diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index dd3c6d6ea..84853b275 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -326,7 +326,7 @@ namespace FlaxEditor.Windows.Profiler // Add row to the table row.Width = _table.Width; - row.BackgroundColor = rowIndex % 2 == 0 ? rowColor2 : Color.Transparent; + row.BackgroundColor = rowIndex % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = _table; rowIndex++; } diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index 2c064351a..c2418f590 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -220,7 +220,7 @@ namespace FlaxEditor.Windows.Profiler var table = isRpc ? _tableRpc : _tableRep; row.Width = table.Width; - row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 0 ? rowColor2 : Color.Transparent; + row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 1 ? rowColor2 : Color.Transparent; row.Parent = table; if (isRpc) rowCount.X++; From 443fe5dbcbea59f0a361058cffb9199bf030d520 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Oct 2024 12:11:20 +0200 Subject: [PATCH 027/215] Add network keys table to optimize ids and names sending over network #2815 --- Source/Engine/Networking/NetworkInternal.h | 1 + Source/Engine/Networking/NetworkManager.cpp | 362 +++++++++++++++++- Source/Engine/Networking/NetworkMessage.h | 65 +++- .../Engine/Networking/NetworkReplicator.cpp | 192 ++++++---- 4 files changed, 506 insertions(+), 114 deletions(-) diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 050ca67da..23738cfc8 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8 None = 0, Handshake, HandshakeReply, + Key, ObjectReplicate, ObjectReplicatePart, ObjectSpawn, diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index b9bb0125a..f2de18fef 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -30,11 +30,72 @@ Delegate NetworkManager::ClientConnecting; Delegate NetworkManager::ClientConnected; Delegate NetworkManager::ClientDisconnected; +PACK_STRUCT(struct NetworkMessageKey + { + NetworkMessageIDs ID = NetworkMessageIDs::Key; + byte Type; + uint32 Index; + }); + +struct NetworkKey +{ + enum Types + { + TypeNone = 0, + TypeId = 1, + TypeName = 2, + } Type; + + union + { + Guid Id; + StringAnsiView Name; + }; + + POD_COPYABLE(NetworkKey); + + NetworkKey() + { + Type = TypeNone; + } + + NetworkKey(const Guid& id) + { + Type = TypeId; + Id = id; + } + + NetworkKey(const StringAnsiView& name) + { + Type = TypeName; + Name = name; + } +}; + +struct NetworkKeys +{ + CriticalSection Lock; + Array Table; + Dictionary LookupId; + Dictionary LookupName; + Dictionary PendingIds; + Dictionary PendingNames; + + void SendPending(); + void SendAll(const NetworkConnection* target = nullptr); + void Clear(); + +private: + static void Send(const NetworkKey& key, uint32 index, const NetworkConnection* target = nullptr); +}; + namespace { uint32 GameProtocolVersion = 0; uint32 NextClientId = 0; double LastUpdateTime = 0; + Array ActiveConnections; + NetworkKeys Keys; } PACK_STRUCT(struct NetworkMessageHandshake @@ -55,6 +116,143 @@ PACK_STRUCT(struct NetworkMessageHandshakeReply int32 Result; }); +FORCE_INLINE StringAnsiView CloneAllocName(const StringAnsiView& name) +{ + StringAnsiView result; + if (name.Get()) + { + const int32 length = name.Length(); + char* str = (char*)Allocator::Allocate(length + 1); + Platform::MemoryCopy(str, name.Get(), length); + str[length] = 0; + result = StringAnsiView(str, length); + } + return result; +} + +FORCE_INLINE bool IsNetworkKeyValid(uint32 index) +{ + // TODO: use NetworkClientsMask to skip using network keys for clients that might not know it yet + // TODO: if key has been added within a last couple of frames then don't use it yet as it needs to be propagated across the peers + return true; +} + +void NetworkMessage::WriteNetworkId(const Guid& id) +{ + ScopeLock lock(Keys.Lock); + uint32 index = MAX_uint32; + bool hasIndex = Keys.LookupId.TryGet(id, index); + if (hasIndex) + hasIndex &= IsNetworkKeyValid(index); + WriteUInt32(index); + if (!hasIndex) + { + // No key cached locally so send the full data + WriteBytes((const uint8*)&id, sizeof(Guid)); + + // Add to the pending list (ignore on clients as server will automatically create a key once it gets full data) + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingIds.ContainsKey(id)) + { + Keys.PendingIds.Add(id, NetworkKey(id)); + } + } +} + +void NetworkMessage::ReadNetworkId(Guid& id) +{ + ScopeLock lock(Keys.Lock); + uint32 index = ReadUInt32(); + if (index != MAX_uint32) + { + if (index < (uint32)Keys.Table.Count()) + { + // Use cached key data + const NetworkKey& k = Keys.Table.Get()[index]; + ASSERT(k.Type == NetworkKey::TypeId); + id = k.Id; + } + else + { + // Incorrect data + // TODO: should we check if message comes before new key arrival? should sender assume that key needs confirmation of receive? + id = Guid::Empty; + } + } + else + { + // Read full data + ReadBytes((uint8*)&id, sizeof(Guid)); + + // When server receives unknown data then turn this into key so connected client will receive it + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingIds.ContainsKey(id) && + !Keys.LookupId.ContainsKey(id)) + { + Keys.PendingIds.Add(id, NetworkKey(id)); + } + } +} + +void NetworkMessage::WriteNetworkName(const StringAnsiView& name) +{ + ScopeLock lock(Keys.Lock); + uint32 index = MAX_uint32; + bool hasIndex = Keys.LookupName.TryGet(name, index); + if (hasIndex) + hasIndex &= IsNetworkKeyValid(index); + WriteUInt32(index); + if (!hasIndex) + { + // No key cached locally so send the full data + WriteStringAnsi(name); + + // Add to the pending list (ignore on clients as server will automatically create a key once it gets full data) + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingNames.ContainsKey(name)) + { + StringAnsiView newName = CloneAllocName(name); + Keys.PendingNames.Add(newName, NetworkKey(newName)); + } + } +} + +void NetworkMessage::ReadNetworkName(StringAnsiView& name) +{ + ScopeLock lock(Keys.Lock); + uint32 index = ReadUInt32(); + if (index != MAX_uint32) + { + if (index < (uint32)Keys.Table.Count()) + { + // Use cached key data + const NetworkKey& k = Keys.Table.Get()[index]; + ASSERT(k.Type == NetworkKey::TypeName); + name = k.Name; + } + else + { + // Incorrect data + // TODO: should we check if message comes before new key arrival? should sender assume that key needs confirmation of receive? + name = StringAnsiView::Empty; + } + } + else + { + // Read full data + name = ReadStringAnsi(); + + // When server receives unknown data then turn this into key so connected client will receive it + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingNames.ContainsKey(name) && + !Keys.LookupName.ContainsKey(name)) + { + StringAnsiView newName = CloneAllocName(name); + Keys.PendingNames.Add(newName, NetworkKey(newName)); + } + } +} + void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) { // Read client connection data @@ -94,6 +292,8 @@ void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, Netwo { client->State = NetworkConnectionState::Connected; LOG(Info, "Client id={0} connected", event.Sender.ConnectionId); + ActiveConnections.Add(event.Sender); + Keys.SendAll(&event.Sender); NetworkManager::ClientConnected(client); NetworkInternal::NetworkReplicatorClientConnected(client); } @@ -120,6 +320,44 @@ void OnNetworkMessageHandshakeReply(NetworkEvent& event, NetworkClient* client, NetworkManager::StateChanged(); } +void OnNetworkMessageKey(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) +{ + // Read key data + NetworkMessageKey msgData; + event.Message.ReadStructure(msgData); + Guid id; + StringAnsiView name; + if (msgData.Type == NetworkKey::TypeId) + event.Message.ReadBytes((uint8*)&id, sizeof(Guid)); + else + name = event.Message.ReadStringAnsi(); + + ScopeLock lock(Keys.Lock); + if (NetworkManager::IsClient()) + { + // Add new key + if (msgData.Index >= (uint32)Keys.Table.Count()) + Keys.Table.Resize(msgData.Index + 1); + NetworkKey& key = Keys.Table[msgData.Index]; + ASSERT_LOW_LAYER(key.Type == NetworkKey::TypeNone); + key.Type = (NetworkKey::Types)msgData.Type; + if (key.Type == NetworkKey::TypeId) + { + key.Id = id; + Keys.LookupId.Add(id, msgData.Index); + } + else + { + key.Name = CloneAllocName(name); + Keys.LookupName.Add(key.Name, msgData.Index); + } + } + else + { + // TODO: make new pending key if client explicitly sends it + } +} + namespace { // Network message handlers table @@ -128,6 +366,7 @@ namespace nullptr, OnNetworkMessageHandshake, OnNetworkMessageHandshakeReply, + OnNetworkMessageKey, NetworkInternal::OnNetworkMessageObjectReplicate, NetworkInternal::OnNetworkMessageObjectReplicatePart, NetworkInternal::OnNetworkMessageObjectSpawn, @@ -362,11 +601,98 @@ void NetworkManager::Stop() LocalClient = nullptr; } + // Clear local state + NextClientId = 0; + LastUpdateTime = 0; + ActiveConnections.Clear(); + Keys.Clear(); + State = NetworkConnectionState::Disconnected; Mode = NetworkManagerMode::Offline; StateChanged(); } +void NetworkKeys::SendPending() +{ + PROFILE_CPU(); + ScopeLock lock(Lock); + + // Add new keys + int32 initialCount = Table.Count(); + int32 sendIndex = initialCount; + for (auto& e : PendingIds) + { + const int32 key = sendIndex++; + LookupId.Add(e.Key, key); + Table.Add(e.Value); + } + for (auto& e : PendingNames) + { + const int32 key = sendIndex++; + LookupName.Add(e.Key, key); + Table.Add(e.Value); + } + + // Send new entries + sendIndex = initialCount; + for (auto& e : PendingIds) + Send(e.Value, sendIndex++); + for (auto& e : PendingNames) + Send(e.Value, sendIndex++); + + // Clear lists + PendingIds.Clear(); + PendingNames.Clear(); +} + +void NetworkKeys::SendAll(const NetworkConnection* target) +{ + PROFILE_CPU(); + ScopeLock lock(Lock); + int32 sendIndex = 0; + for (auto& e : Table) + Send(e, sendIndex++, target); +} + +void NetworkKeys::Clear() +{ + ScopeLock lock(Lock); + LookupId.Clear(); + LookupName.Clear(); + PendingNames.GetValues(Table); + PendingNames.Clear(); + for (auto& e : Table) + { + if (e.Type == NetworkKey::TypeName) + { + // Free allocated string + Allocator::Free((void*)e.Name.Get()); + } + } + Table.Clear(); +} + +void NetworkKeys::Send(const NetworkKey& key, uint32 index, const NetworkConnection* target) +{ + // TODO: optimize with batching multiple keys into a single message + auto peer = NetworkManager::Peer; + NetworkMessage msg = peer->BeginSendMessage(); + NetworkMessageKey msgData; + msgData.Type = key.Type; + msgData.Index = index; + msg.WriteStructure(msgData); + if (key.Type == NetworkKey::TypeId) + msg.WriteGuid(key.Id); + else + msg.WriteStringAnsi(key.Name); + if (NetworkManager::IsClient()) + peer->EndSendMessage(NetworkChannelType::Reliable, msg); + else if (target) + peer->EndSendMessage(NetworkChannelType::Reliable, msg, *target); + else + peer->EndSendMessage(NetworkChannelType::Reliable, msg, ActiveConnections); +} + void NetworkManagerService::Update() { const double currentTime = Time::Update.UnscaledTime.GetTotalSeconds(); @@ -450,27 +776,28 @@ void NetworkManagerService::Update() NetworkManager::ClientDisconnected(client); client->State = NetworkConnectionState::Disconnected; Delete(client); + ActiveConnections.Remove(event.Sender); } break; case NetworkEventType::Message: + { + // Process network message + NetworkClient* client = NetworkManager::GetClient(event.Sender); + if (!client && NetworkManager::Mode != NetworkManagerMode::Client) { - // Process network message - NetworkClient* client = NetworkManager::GetClient(event.Sender); - if (!client && NetworkManager::Mode != NetworkManagerMode::Client) - { - LOG(Error, "Unknown client"); - break; - } - uint8 id = *event.Message.Buffer; - if (id < (uint8)NetworkMessageIDs::MAX) - { - MessageHandlers[id](event, client, peer); - } - else - { - LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId); - } + LOG(Error, "Unknown client"); + break; } + uint8 id = *event.Message.Buffer; + if (id < (uint8)NetworkMessageIDs::MAX) + { + MessageHandlers[id](event, client, peer); + } + else + { + LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId); + } + } peer->RecycleMessage(event.Message); break; default: @@ -481,4 +808,7 @@ void NetworkManagerService::Update() // Update replication NetworkInternal::NetworkReplicatorUpdate(); + + // Flush pending network key updates + Keys.SendPending(); } diff --git a/Source/Engine/Networking/NetworkMessage.h b/Source/Engine/Networking/NetworkMessage.h index 83bbc251c..139b78e32 100644 --- a/Source/Engine/Networking/NetworkMessage.h +++ b/Source/Engine/Networking/NetworkMessage.h @@ -67,7 +67,7 @@ public: /// /// The bytes that will be written. /// The amount of bytes to write from the bytes pointer. - FORCE_INLINE void WriteBytes(uint8* bytes, const int numBytes) + FORCE_INLINE void WriteBytes(const uint8* bytes, const int32 numBytes) { ASSERT(Position + numBytes <= BufferSize); Platform::MemoryCopy(Buffer + Position, bytes, numBytes); @@ -106,7 +106,7 @@ public: template FORCE_INLINE void WriteStructure(const T& data) { - WriteBytes((uint8*)&data, sizeof(data)); + WriteBytes((const uint8*)&data, sizeof(data)); } template @@ -116,7 +116,7 @@ public: } #define DECL_READWRITE(type, name) \ - FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast(&value), sizeof(type)); } \ + FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast(&value), sizeof(type)); } \ FORCE_INLINE type Read##name() { type value = 0; ReadBytes(reinterpret_cast(&value), sizeof(type)); return value; } DECL_READWRITE(int8, Int8) DECL_READWRITE(uint8, UInt8) @@ -207,22 +207,37 @@ public: /// /// Writes data of type String into the message. UTF-16 encoded. /// - FORCE_INLINE void WriteString(const String& value) + FORCE_INLINE void WriteString(const StringView& value) { WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible - WriteBytes((uint8*)value.Get(), value.Length() * sizeof(Char)); + WriteBytes((const uint8*)value.Get(), value.Length() * sizeof(Char)); } /// - /// Reads and returns data of type String from the message. UTF-16 encoded. + /// Writes data of type String into the message. /// - FORCE_INLINE String ReadString() + FORCE_INLINE void WriteStringAnsi(const StringAnsiView& value) { - uint16 length = ReadUInt16(); - String value; - value.Resize(length); - ReadBytes((uint8*)value.Get(), length * sizeof(Char)); - return value; + WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible + WriteBytes((const uint8*)value.Get(), value.Length()); + } + + /// + /// Reads and returns data of type String from the message. UTF-16 encoded. Data valid within message lifetime. + /// + FORCE_INLINE StringView ReadString() + { + const uint16 length = ReadUInt16(); + return StringView(length ? (const Char*)SkipBytes(length * 2) : nullptr, length); + } + + /// + /// Reads and returns data of type String from the message. ANSI encoded. Data valid within message lifetime. + /// + FORCE_INLINE StringAnsiView ReadStringAnsi() + { + const uint16 length = ReadUInt16(); + return StringAnsiView(length ? (const char*)SkipBytes(length) : nullptr, length); } /// @@ -230,7 +245,7 @@ public: /// FORCE_INLINE void WriteGuid(const Guid& value) { - WriteBytes((uint8*)&value, sizeof(Guid)); + WriteBytes((const uint8*)&value, sizeof(Guid)); } /// @@ -243,6 +258,30 @@ public: return value; } + /// + /// Writes identifier into the stream that is networked-synced (by a server). If both peers acknowledge a specific id then the data transfer is optimized to 32 bits. + /// + /// Network-synced identifier. + void WriteNetworkId(const Guid& id); + + /// + /// Reads identifier from the stream that is networked-synced (by a server). If both peers acknowledge a specific id then the data transfer is optimized to 32 bits. + /// + /// Network-synced identifier. + void ReadNetworkId(Guid& id); + + /// + /// Writes name into the stream that is networked-synced (by a server). If both peers acknowledge a specific name then the data transfer is optimized to 32 bits. + /// + /// Network-synced name. + void WriteNetworkName(const StringAnsiView& name); + + /// + /// Reads name from the stream that is networked-synced (by a server). If both peers acknowledge a specific name then the data transfer is optimized to 32 bits. + /// + /// Network-synced name. + void ReadNetworkName(StringAnsiView& name); + public: /// /// Returns true if the message is valid for reading or writing. diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 232980f9f..a0fa11514 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -52,11 +52,13 @@ PACK_STRUCT(struct NetworkMessageObjectReplicate { NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicate; uint32 OwnerFrame; - Guid ObjectId; // TODO: introduce networked-ids to synchronize unique ids as ushort (less data over network) - Guid ParentId; - char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) + }); + +PACK_STRUCT(struct NetworkMessageObjectReplicatePayload + { uint16 DataSize; uint16 PartsCount; + uint16 PartSize; }); PACK_STRUCT(struct NetworkMessageObjectReplicatePart @@ -67,7 +69,6 @@ PACK_STRUCT(struct NetworkMessageObjectReplicatePart uint16 PartsCount; uint16 PartStart; uint16 PartSize; - Guid ObjectId; // TODO: introduce networked-ids to synchronize unique ids as ushort (less data over network) }); PACK_STRUCT(struct NetworkMessageObjectSpawn @@ -75,7 +76,6 @@ PACK_STRUCT(struct NetworkMessageObjectSpawn NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawn; uint32 OwnerClientId; uint32 OwnerSpawnId; // Unique for peer who spawned it and matches OwnerSpawnId inside following part messages - Guid PrefabId; uint16 ItemsCount; // Total items count uint8 UseParts : 1; // True if spawn message is header-only and all items come in the separate parts }); @@ -87,35 +87,29 @@ PACK_STRUCT(struct NetworkMessageObjectSpawnPart uint32 OwnerSpawnId; }); +// TODO: optimize spawn item to use Network Keys rather than fixed-size data PACK_STRUCT(struct NetworkMessageObjectSpawnItem { Guid ObjectId; Guid ParentId; Guid PrefabObjectID; - char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) + char ObjectTypeName[128]; }); PACK_STRUCT(struct NetworkMessageObjectDespawn { NetworkMessageIDs ID = NetworkMessageIDs::ObjectDespawn; - Guid ObjectId; }); PACK_STRUCT(struct NetworkMessageObjectRole { NetworkMessageIDs ID = NetworkMessageIDs::ObjectRole; - Guid ObjectId; uint32 OwnerClientId; }); PACK_STRUCT(struct NetworkMessageObjectRpc { NetworkMessageIDs ID = NetworkMessageIDs::ObjectRpc; - Guid ObjectId; - Guid ParentId; - char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) - char RpcTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) - char RpcName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) uint16 ArgsSize; }); @@ -193,6 +187,7 @@ struct SpawnItem struct SpawnItemParts { NetworkMessageObjectSpawn MsgData; + Guid PrefabId; Array Items; }; @@ -333,7 +328,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId) return it != Objects.End() ? &it->Item : nullptr; } -NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char objectTypeName[128]) +NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const StringAnsiView& objectTypeName) { // Lookup object NetworkReplicatedObject* obj = ResolveObject(objectId); @@ -342,7 +337,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char // Try to find the object within the same parent (eg. spawned locally on both client and server) IdsRemappingTable.TryGet(parentId, parentId); - const ScriptingTypeHandle objectType = Scripting::FindScriptingType(StringAnsiView(objectTypeName)); + const ScriptingTypeHandle objectType = Scripting::FindScriptingType(objectTypeName); if (!objectType) return nullptr; for (auto& e : Objects) @@ -467,12 +462,6 @@ FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item, const BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, item.OwnerClientId, clientsMask); } -FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name) -{ - Platform::MemoryCopy(buffer, name.Get(), name.Length()); - buffer[name.Length()] = 0; -} - void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg) { ScriptingObject* obj = e->Object.Get(); @@ -493,7 +482,9 @@ void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg) auto* objScene = ScriptingObject::Cast(obj); if (objScene && objScene->HasPrefabLink()) msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID(); - GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname); + const StringAnsiView objectTypeName = obj->GetType().Fullname; + Platform::MemoryCopy(msgDataItem.ObjectTypeName, objectTypeName.Get(), objectTypeName.Length()); + msgDataItem.ObjectTypeName[objectTypeName.Length()] = 0; msg.WriteStructure(msgDataItem); } @@ -505,13 +496,14 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array NetworkMessage msg = peer->BeginSendMessage(); NetworkMessageObjectSpawn msgData; msgData.ItemsCount = group.Items.Count(); + Guid prefabId; { // The first object is a root of the group (eg. prefab instance root actor) SpawnItem* e = group.Items[0]; ScriptingObject* obj = e->Object.Get(); msgData.OwnerClientId = e->OwnerClientId; auto* objScene = ScriptingObject::Cast(obj); - msgData.PrefabId = objScene && objScene->HasPrefabLink() ? objScene->GetPrefabID() : Guid::Empty; + prefabId = objScene && objScene->HasPrefabLink() ? objScene->GetPrefabID() : Guid::Empty; // Setup clients that should receive this spawn message auto it = Objects.Find(obj->GetID()); @@ -523,6 +515,7 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array msgData.OwnerSpawnId = ++SpawnId; msgData.UseParts = msg.BufferSize - msg.Position < group.Items.Count() * sizeof(NetworkMessageObjectSpawnItem); msg.WriteStructure(msgData); + msg.WriteNetworkId(prefabId); if (msgData.UseParts) { if (isClient) @@ -570,11 +563,11 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkClient* excludedClient = nullptr) { NetworkMessageObjectRole msgData; - msgData.ObjectId = item.ObjectId; msgData.OwnerClientId = item.OwnerClientId; auto peer = NetworkManager::Peer; NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + msg.WriteNetworkId(item.ObjectId); if (NetworkManager::IsClient()) { NetworkManager::Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); @@ -695,14 +688,13 @@ FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject Hierarchy->DirtyObject(obj); } -template -ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& msgData, uint16 partStart, uint16 partSize, uint32 senderClientId) +ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, uint32 ownerFrame, uint16 partsCount, uint16 dataSize, const Guid& objectId, uint16 partStart, uint16 partSize, uint32 senderClientId) { // Reuse or add part item ReplicateItem* replicateItem = nullptr; for (auto& e : ReplicationParts) { - if (e.OwnerFrame == msgData.OwnerFrame && e.Data.Count() == msgData.DataSize && e.ObjectId == msgData.ObjectId) + if (e.OwnerFrame == ownerFrame && e.Data.Count() == dataSize && e.ObjectId == objectId) { // Reuse replicateItem = &e; @@ -713,11 +705,11 @@ ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& ms { // Add replicateItem = &ReplicationParts.AddOne(); - replicateItem->ObjectId = msgData.ObjectId; - replicateItem->PartsLeft = msgData.PartsCount; - replicateItem->OwnerFrame = msgData.OwnerFrame; + replicateItem->ObjectId = objectId; + replicateItem->PartsLeft = partsCount; + replicateItem->OwnerFrame = ownerFrame; replicateItem->OwnerClientId = senderClientId; - replicateItem->Data.Resize(msgData.DataSize); + replicateItem->Data.Resize(dataSize); } // Copy part data @@ -775,7 +767,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b DirtyObjectImpl(item, obj); } -void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMessageObjectSpawnItem* msgDataItems) +void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& prefabId, const NetworkMessageObjectSpawnItem* msgDataItems) { ScopeLock lock(ObjectsLock); @@ -814,11 +806,11 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe // Recreate object locally (spawn only root) Actor* prefabInstance = nullptr; Array objects; - if (msgData.PrefabId.IsValid()) + if (prefabId.IsValid()) { const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId); Actor* parentActor = parent && parent->Object && parent->Object->Is() ? parent->Object.As() : nullptr; - if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId) + if (parentActor && parentActor->GetPrefabID() == prefabId) { // Reuse parent object as prefab instance prefabInstance = parentActor; @@ -828,7 +820,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe // Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it) for (Actor* child : parentActor->Children) { - if (child->GetPrefabID() == msgData.PrefabId) + if (child->GetPrefabID() == prefabId) { if (Objects.Contains(child->GetID())) { @@ -851,16 +843,16 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe if (!prefabInstance) { // Spawn prefab - auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer); + auto prefab = (Prefab*)LoadAsset(prefabId, Prefab::TypeInitializer); if (!prefab) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString()); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", prefabId.ToString()); return; } prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr); if (!prefabInstance) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString()); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", prefabId.ToString()); return; } } @@ -873,7 +865,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID); if (!obj) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), prefabId.ToString()); Delete(prefabInstance); return; } @@ -1667,14 +1659,15 @@ void NetworkInternal::NetworkReplicatorUpdate() // Send despawn message NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object ID={}", e.Id.ToString()); NetworkMessageObjectDespawn msgData; - msgData.ObjectId = e.Id; + Guid objectId = e.Id; if (isClient) { // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); + IdsRemappingTable.KeyOf(objectId, &objectId); } NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + msg.WriteNetworkId(objectId); BuildCachedTargets(NetworkManager::Clients, e.Targets); if (isClient) peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); @@ -1877,18 +1870,23 @@ void NetworkInternal::NetworkReplicatorUpdate() ASSERT(size <= MAX_uint16); NetworkMessageObjectReplicate msgData; msgData.OwnerFrame = NetworkManager::Frame; - msgData.ObjectId = item.ObjectId; - msgData.ParentId = item.ParentId; + Guid objectId = item.ObjectId, parentId = item.ParentId; if (isClient) { // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); - IdsRemappingTable.KeyOf(msgData.ParentId, &msgData.ParentId); + IdsRemappingTable.KeyOf(objectId, &objectId); + IdsRemappingTable.KeyOf(parentId, &parentId); } - GetNetworkName(msgData.ObjectTypeName, obj->GetType().Fullname); - msgData.DataSize = size; - const uint32 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate); - const uint32 partMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicatePart); + NetworkMessage msg = peer->BeginSendMessage(); + msg.WriteStructure(msgData); + msg.WriteNetworkId(objectId); + msg.WriteNetworkId(parentId); + msg.WriteNetworkName(obj->GetType().Fullname); + NetworkMessageObjectReplicatePayload msgDataPayload; + msgDataPayload.DataSize = size; + const uint32 networkKeyIdWorstCaseSize = sizeof(uint32) + sizeof(Guid); + const uint32 msgMaxData = peer->Config.MessageSize - msg.Position - sizeof(NetworkMessageObjectReplicatePayload); + const uint32 partMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicatePart) - networkKeyIdWorstCaseSize; uint32 partsCount = 1; uint32 dataStart = 0; uint32 msgDataSize = size; @@ -1904,9 +1902,9 @@ void NetworkInternal::NetworkReplicatorUpdate() else dataStart += size; ASSERT(partsCount <= MAX_uint8); - msgData.PartsCount = partsCount; - NetworkMessage msg = peer->BeginSendMessage(); - msg.WriteStructure(msgData); + msgDataPayload.PartsCount = partsCount; + msgDataPayload.PartSize = msgDataSize; + msg.WriteStructure(msgDataPayload); msg.WriteBytes(stream->GetBuffer(), msgDataSize); uint32 dataSize = msgDataSize, messageSize = msg.Length; if (isClient) @@ -1919,13 +1917,13 @@ void NetworkInternal::NetworkReplicatorUpdate() { NetworkMessageObjectReplicatePart msgDataPart; msgDataPart.OwnerFrame = msgData.OwnerFrame; - msgDataPart.ObjectId = msgData.ObjectId; - msgDataPart.DataSize = msgData.DataSize; - msgDataPart.PartsCount = msgData.PartsCount; + msgDataPart.DataSize = msgDataPayload.DataSize; + msgDataPart.PartsCount = msgDataPayload.PartsCount; msgDataPart.PartStart = dataStart; msgDataPart.PartSize = Math::Min(size - dataStart, partMaxData); msg = peer->BeginSendMessage(); msg.WriteStructure(msgDataPart); + msg.WriteNetworkId(objectId); msg.WriteBytes(stream->GetBuffer() + msgDataPart.PartStart, msgDataPart.PartSize); messageSize += msg.Length; dataSize += msgDataPart.PartSize; @@ -1974,20 +1972,22 @@ void NetworkInternal::NetworkReplicatorUpdate() // Send RPC message //NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}::{} object ID={}", e.Name.First.ToString(), String(e.Name.Second), item.ToString()); NetworkMessageObjectRpc msgData; - msgData.ObjectId = item.ObjectId; - msgData.ParentId = item.ParentId; + Guid msgObjectId = item.ObjectId; + Guid msgParentId = item.ParentId; if (isClient) { // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); - IdsRemappingTable.KeyOf(msgData.ParentId, &msgData.ParentId); + IdsRemappingTable.KeyOf(msgObjectId, &msgObjectId); + IdsRemappingTable.KeyOf(msgParentId, &msgParentId); } - GetNetworkName(msgData.ObjectTypeName, obj->GetType().Fullname); - GetNetworkName(msgData.RpcTypeName, e.Name.First.GetType().Fullname); - GetNetworkName(msgData.RpcName, e.Name.Second); msgData.ArgsSize = (uint16)e.ArgsData.Length(); NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + msg.WriteNetworkId(msgObjectId); + msg.WriteNetworkId(msgParentId); + msg.WriteNetworkName(obj->GetType().Fullname); + msg.WriteNetworkName(e.Name.First.GetType().Fullname); + msg.WriteNetworkName(e.Name.Second); msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length()); uint32 dataSize = e.ArgsData.Length(), messageSize = msg.Length, receivers = 0; NetworkChannelType channel = (NetworkChannelType)e.Info.Channel; @@ -2032,11 +2032,18 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo { PROFILE_CPU(); NetworkMessageObjectReplicate msgData; + NetworkMessageObjectReplicatePayload msgDataPayload; + Guid objectId, parentId; + StringAnsiView objectTypeName; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); + event.Message.ReadNetworkId(parentId); + event.Message.ReadNetworkName(objectTypeName); + event.Message.ReadStructure(msgDataPayload); ScopeLock lock(ObjectsLock); - if (DespawnedObjects.Contains(msgData.ObjectId)) + if (DespawnedObjects.Contains(objectId)) return; // Skip replicating not-existing objects - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); + NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName); if (!e) return; auto& item = *e; @@ -2046,16 +2053,15 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo return; const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; - if (msgData.PartsCount == 1) + if (msgDataPayload.PartsCount == 1) { // Replicate - InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgData.DataSize, senderClientId); + InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgDataPayload.DataSize, senderClientId); } else { // Add to replication from multiple parts - const uint16 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate); - ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData, 0, msgMaxData, senderClientId); + ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData.OwnerFrame, msgDataPayload.PartsCount, msgDataPayload.DataSize, objectId, 0, msgDataPayload.PartSize, senderClientId); replicateItem->Object = e->Object; } } @@ -2064,20 +2070,24 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N { PROFILE_CPU(); NetworkMessageObjectReplicatePart msgData; + Guid objectId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); ScopeLock lock(ObjectsLock); - if (DespawnedObjects.Contains(msgData.ObjectId)) + if (DespawnedObjects.Contains(objectId)) return; // Skip replicating not-existing objects const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; - AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize, senderClientId); + AddObjectReplicateItem(event, msgData.OwnerFrame, msgData.PartsCount, msgData.DataSize, objectId, msgData.PartStart, msgData.PartSize, senderClientId); } void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) { PROFILE_CPU(); NetworkMessageObjectSpawn msgData; + Guid prefabId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(prefabId); if (msgData.ItemsCount == 0) return; if (msgData.UseParts) @@ -2085,6 +2095,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl // Allocate spawn message parts collecting auto& parts = SpawnParts.AddOne(); parts.MsgData = msgData; + parts.PrefabId = prefabId; parts.Items.Resize(msgData.ItemsCount); for (auto& item : parts.Items) item.ObjectId = Guid::Empty; // Mark as not yet received @@ -2092,7 +2103,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl else { const auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem)); - InvokeObjectSpawn(msgData, msgDataItems); + InvokeObjectSpawn(msgData, prefabId, msgDataItems); } } @@ -2130,7 +2141,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawnPart(NetworkEvent& event, Netwo if (!e.ObjectId.IsValid()) return; } - InvokeObjectSpawn(spawnParts.MsgData, spawnParts.Items.Get()); + InvokeObjectSpawn(spawnParts.MsgData, spawnParts.PrefabId, spawnParts.Items.Get()); SpawnParts.RemoveAt(spawnPartsIndex); } @@ -2138,9 +2149,11 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network { PROFILE_CPU(); NetworkMessageObjectDespawn msgData; + Guid objectId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); ScopeLock lock(ObjectsLock); - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId); + NetworkReplicatedObject* e = ResolveObject(objectId); if (e) { auto& item = *e; @@ -2153,10 +2166,10 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network return; // Remove object - NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object {}", msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object {}", objectId); if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative) Hierarchy->RemoveObject(obj); - DespawnedObjects.Add(msgData.ObjectId); + DespawnedObjects.Add(objectId); if (item.AsNetworkObject) item.AsNetworkObject->OnNetworkDespawn(); Objects.Remove(obj); @@ -2164,7 +2177,7 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network } else { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", objectId); } } @@ -2172,9 +2185,11 @@ void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkCli { PROFILE_CPU(); NetworkMessageObjectRole msgData; + Guid objectId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); ScopeLock lock(ObjectsLock); - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId); + NetworkReplicatedObject* e = ResolveObject(objectId); if (e) { auto& item = *e; @@ -2212,7 +2227,7 @@ void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkCli } else { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object role update {}", msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object role update {}", objectId); } } @@ -2220,21 +2235,28 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie { PROFILE_CPU(); NetworkMessageObjectRpc msgData; + Guid msgObjectId, msgParentId; + StringAnsiView objectTypeName, rpcTypeName, rpcName; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(msgObjectId); + event.Message.ReadNetworkId(msgParentId); + event.Message.ReadNetworkName(objectTypeName); + event.Message.ReadNetworkName(rpcTypeName); + event.Message.ReadNetworkName(rpcName); ScopeLock lock(ObjectsLock); // Find RPC info NetworkRpcName name; - name.First = Scripting::FindScriptingType(msgData.RpcTypeName); - name.Second = msgData.RpcName; + name.First = Scripting::FindScriptingType(rpcTypeName); + name.Second = rpcName; const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name); if (!info) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(rpcTypeName), String(rpcName), msgObjectId); return; } - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); + NetworkReplicatedObject* e = ResolveObject(msgObjectId, msgParentId, objectTypeName); if (e) { auto& item = *e; @@ -2245,12 +2267,12 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie // Validate RPC if (info->Server && NetworkManager::IsClient()) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke server RPC {}::{} on client", String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke server RPC {}::{} on client", String(rpcTypeName), String(rpcName)); return; } if (info->Client && NetworkManager::IsServer()) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke client RPC {}::{} on server", String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke client RPC {}::{} on server", String(rpcTypeName), String(rpcName)); return; } @@ -2266,6 +2288,6 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie } else if (info->Channel != static_cast(NetworkChannelType::Unreliable) && info->Channel != static_cast(NetworkChannelType::UnreliableOrdered)) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgObjectId, String(rpcTypeName), String(rpcName)); } } From 4ddf1a2cc87af8e2ae14b064188d2cc098ffce5b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Oct 2024 12:12:06 +0200 Subject: [PATCH 028/215] Another fix for network profiler to properly handle stats updating when recording is disabled #2815 --- Source/Editor/Windows/Profiler/Network.cs | 48 +++++++++++-------- .../Editor/Windows/Profiler/ProfilerMode.cs | 7 +++ .../Editor/Windows/Profiler/ProfilerWindow.cs | 6 +++ 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index c2418f590..6e99d6338 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -44,7 +44,8 @@ namespace FlaxEditor.Windows.Profiler private readonly Table _tableRep; private List _tableRowsCache; private SamplesBuffer _events; - private NetworkDriverStats _prevStats; + private NetworkDriverStats _frameStats; + private NetworkDriverStats _prevTotalStats; private List _stats; public Network() @@ -109,34 +110,41 @@ namespace FlaxEditor.Windows.Profiler _dataReceivedRateChart.Clear(); _events?.Clear(); _stats?.Clear(); - _prevStats = new NetworkDriverStats(); + _frameStats = _prevTotalStats = new NetworkDriverStats(); + } + + /// + public override void UpdateStats() + { + // Gather peer stats + var peers = NetworkPeer.Peers; + var totalStats = new NetworkDriverStats(); + totalStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT + foreach (var peer in peers) + { + var peerStats = peer.NetworkDriver.GetStats(); + totalStats.TotalDataSent += peerStats.TotalDataSent; + totalStats.TotalDataReceived += peerStats.TotalDataReceived; + } + var stats = totalStats; + stats.TotalDataSent = (uint)Mathf.Max((long)totalStats.TotalDataSent - (long)_prevTotalStats.TotalDataSent, 0); + stats.TotalDataReceived = (uint)Mathf.Max((long)totalStats.TotalDataReceived - (long)_prevTotalStats.TotalDataReceived, 0); + _frameStats = stats; + _prevTotalStats = totalStats; } /// public override void Update(ref SharedUpdateData sharedData) { - // Gather peer stats - var peers = NetworkPeer.Peers; - var thisStats = new NetworkDriverStats(); - thisStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT - foreach (var peer in peers) - { - var peerStats = peer.NetworkDriver.GetStats(); - thisStats.TotalDataSent += peerStats.TotalDataSent; - thisStats.TotalDataReceived += peerStats.TotalDataReceived; - } - var stats = thisStats; - stats.TotalDataSent = (uint)Mathf.Max((long)thisStats.TotalDataSent - (long)_prevStats.TotalDataSent, 0); - stats.TotalDataReceived = (uint)Mathf.Max((long)thisStats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0); - _dataSentChart.AddSample(stats.TotalDataSent); - _dataReceivedChart.AddSample(stats.TotalDataReceived); - _prevStats = thisStats; + // Add this-frame stats + _dataSentChart.AddSample(_frameStats.TotalDataSent); + _dataReceivedChart.AddSample(_frameStats.TotalDataReceived); if (_stats == null) _stats = new List(); - _stats.Add(stats); + _stats.Add(_frameStats); // Remove all stats older than 1 second - while (_stats.Count > 0 && thisStats.RTT - _stats[0].RTT >= 1.0f) + while (_stats.Count > 0 && _frameStats.RTT - _stats[0].RTT >= 1.0f) _stats.RemoveAt(0); // Calculate average data rates (from last second) diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs index 6aa9b8cf6..088edd558 100644 --- a/Source/Editor/Windows/Profiler/ProfilerMode.cs +++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs @@ -120,6 +120,13 @@ namespace FlaxEditor.Windows.Profiler { } + /// + /// Tick called before updating any modes display. Can be used to update per-frame statistics data. + /// + public virtual void UpdateStats() + { + } + /// /// Updates the mode view. Called after init and on selected frame changed. /// diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index 0b4ac39b4..6b7a41db2 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -230,6 +230,12 @@ namespace FlaxEditor.Windows.Profiler /// public override void OnUpdate() { + for (int i = 0; i < _tabs.ChildrenCount; i++) + { + if (_tabs.Children[i] is ProfilerMode mode) + mode.UpdateStats(); + } + if (LiveRecording) { FlaxEngine.Profiler.BeginEvent("ProfilerWindow.OnUpdate"); From b38af8fd002001ceeaaaacc9023a2948311c3e04 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Oct 2024 12:12:48 +0200 Subject: [PATCH 029/215] Format code and add debug cmd attributes --- Source/Engine/Networking/INetworkObject.h | 4 ++-- Source/Engine/Networking/INetworkSerializable.h | 4 ++-- Source/Engine/Networking/NetworkClient.h | 2 +- Source/Engine/Networking/NetworkManager.h | 2 +- Source/Engine/Networking/NetworkPeer.h | 2 +- .../Engine/Networking/NetworkReplicationHierarchy.h | 12 ++++++------ Source/Engine/Networking/NetworkReplicator.h | 4 ++-- Source/Engine/Networking/NetworkStream.h | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Networking/INetworkObject.h b/Source/Engine/Networking/INetworkObject.h index 1eed87703..e53348e34 100644 --- a/Source/Engine/Networking/INetworkObject.h +++ b/Source/Engine/Networking/INetworkObject.h @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #pragma once @@ -8,7 +8,7 @@ /// /// Interface for objects for network replication to receive additional events. /// -API_INTERFACE(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API INetworkObject +API_INTERFACE(Namespace="FlaxEngine.Networking") class FLAXENGINE_API INetworkObject { DECLARE_SCRIPTING_TYPE_MINIMAL(INetworkObject); public: diff --git a/Source/Engine/Networking/INetworkSerializable.h b/Source/Engine/Networking/INetworkSerializable.h index c976b42c9..7c27db63b 100644 --- a/Source/Engine/Networking/INetworkSerializable.h +++ b/Source/Engine/Networking/INetworkSerializable.h @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #pragma once @@ -10,7 +10,7 @@ class NetworkStream; /// /// Interface for values and objects that can be serialized/deserialized for network replication. /// -API_INTERFACE(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API INetworkSerializable +API_INTERFACE(Namespace="FlaxEngine.Networking") class FLAXENGINE_API INetworkSerializable { DECLARE_SCRIPTING_TYPE_MINIMAL(INetworkSerializable); public: diff --git a/Source/Engine/Networking/NetworkClient.h b/Source/Engine/Networking/NetworkClient.h index 8b47fa248..a6b9e2bf9 100644 --- a/Source/Engine/Networking/NetworkClient.h +++ b/Source/Engine/Networking/NetworkClient.h @@ -10,7 +10,7 @@ /// /// High-level network client object (local or connected to the server). /// -API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkClient final : public ScriptingObject +API_CLASS(sealed, NoSpawn, Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkClient final : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(NetworkClient); friend class NetworkManager; diff --git a/Source/Engine/Networking/NetworkManager.h b/Source/Engine/Networking/NetworkManager.h index f76f53d59..192d67655 100644 --- a/Source/Engine/Networking/NetworkManager.h +++ b/Source/Engine/Networking/NetworkManager.h @@ -45,7 +45,7 @@ API_STRUCT(Namespace="FlaxEngine.Networking", NoDefault) struct NetworkClientCon /// /// High-level networking manager for multiplayer games. /// -API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkManager +API_CLASS(static, Namespace="FlaxEngine.Networking", Attributes="DebugCommand") class FLAXENGINE_API NetworkManager { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkManager); public: diff --git a/Source/Engine/Networking/NetworkPeer.h b/Source/Engine/Networking/NetworkPeer.h index 26593c902..e72811698 100644 --- a/Source/Engine/Networking/NetworkPeer.h +++ b/Source/Engine/Networking/NetworkPeer.h @@ -11,7 +11,7 @@ /// /// Low-level network peer class. Provides server-client communication functions, message processing and sending. /// -API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public ScriptingObject +API_CLASS(sealed, NoSpawn, Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public ScriptingObject { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkPeer, ScriptingObject); diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.h b/Source/Engine/Networking/NetworkReplicationHierarchy.h index b58602254..0704c0c6c 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.h +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.h @@ -14,7 +14,7 @@ class Actor; /// /// Network replication hierarchy object data. /// -API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API NetworkReplicationHierarchyObject +API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkReplicationHierarchyObject { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicationObjectInfo); @@ -59,7 +59,7 @@ inline uint32 GetHash(const NetworkReplicationHierarchyObject& key) /// /// Bit mask for NetworkClient list (eg. to selectively send object replication). /// -API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API NetworkClientsMask +API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkClientsMask { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkClientsMask); @@ -109,7 +109,7 @@ API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API /// /// Network replication hierarchy output data to send. /// -API_CLASS(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationHierarchyUpdateResult : public ScriptingObject +API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationHierarchyUpdateResult : public ScriptingObject { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationHierarchyUpdateResult, ScriptingObject); friend class NetworkInternal; @@ -178,7 +178,7 @@ public: /// /// Base class for the network objects replication hierarchy nodes. Contains a list of objects. /// -API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationNode : public ScriptingObject +API_CLASS(Abstract, Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationNode : public ScriptingObject { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationNode, ScriptingObject); @@ -240,7 +240,7 @@ inline uint32 GetHash(const Int3& key) /// /// Network replication hierarchy node with 3D grid spatialization. Organizes static objects into chunks to improve performance in large worlds. /// -API_CLASS(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationGridNode : public NetworkReplicationNode +API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationGridNode : public NetworkReplicationNode { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationGridNode, NetworkReplicationNode); ~NetworkReplicationGridNode(); @@ -273,7 +273,7 @@ public: /// Defines the network objects replication hierarchy (tree structure) that controls chunking and configuration of the game objects replication. /// Contains only 'owned' objects. It's used by the networking system only on a main thread. /// -API_CLASS(Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationHierarchy : public NetworkReplicationNode +API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicationHierarchy : public NetworkReplicationNode { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(NetworkReplicationHierarchy, NetworkReplicationNode); }; diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 0b9da2c28..a67e1dd29 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -28,7 +28,7 @@ API_ENUM(Namespace="FlaxEngine.Networking") enum class NetworkObjectRole : byte /// /// High-level networking replication system for game objects. /// -API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicator +API_CLASS(static, Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicator { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicator); friend class NetworkReplicatorInternal; @@ -39,7 +39,7 @@ public: /// /// Enables verbose logging of the networking runtime. Can be used to debug problems of missing RPC invoke or object replication issues. /// - API_FIELD() static bool EnableLog; + API_FIELD(Attributes="DebugCommand") static bool EnableLog; #endif /// diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index 73cb2eff8..37b8c6f17 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -11,7 +11,7 @@ class INetworkSerializable; /// /// Objects and values serialization stream for sending data over network. Uses memory buffer for both read and write operations. /// -API_CLASS(sealed, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkStream final : public ScriptingObject, public ReadStream, public WriteStream +API_CLASS(sealed, Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkStream final : public ScriptingObject, public ReadStream, public WriteStream { DECLARE_SCRIPTING_TYPE(NetworkStream); private: From a6f1dbbf32671fd3f4a896c4f2be87d88fd5b827 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Oct 2024 22:16:08 +0200 Subject: [PATCH 030/215] Fix `Quaternion` error tolerance to be more reasonable due to math optimization enabled in compiler --- Source/Engine/Core/Math/Quaternion.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 1dbcda32d..cadc6730b 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -19,7 +19,7 @@ API_STRUCT() struct FLAXENGINE_API Quaternion /// /// Equality tolerance factor used when comparing quaternions via dot operation. /// - API_FIELD() static constexpr Real Tolerance = 0.9999999f; + API_FIELD() static constexpr Real Tolerance = 0.999999f; public: union @@ -358,7 +358,7 @@ public: /// true if the specified isn't equal to this instance; otherwise, false. FORCE_INLINE bool operator!=(const Quaternion& other) const { - return !(*this == other); + return Dot(*this, other) < Tolerance; } public: From 7e4d7743a4888cc7d68fdefcf8f22234559531c6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Oct 2024 22:32:29 +0200 Subject: [PATCH 031/215] Add `Quaternion` quantization for network stream Reduced send rate by 56% in average. Identity quat is just 8-bits (flags) Added tests with full-sphere rotation to ensure this works correctly. --- Source/Engine/Networking/NetworkStream.cpp | 98 ++++++++++++++++++++++ Source/Engine/Networking/NetworkStream.h | 2 + Source/Engine/Tests/TestNetworking.cpp | 70 ++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 Source/Engine/Tests/TestNetworking.cpp diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index a5de34232..8c197602c 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -2,6 +2,94 @@ #include "NetworkStream.h" #include "INetworkSerializable.h" +#include "Engine/Core/Math/Quaternion.h" + +// Quaternion quantized for optimized network data size. +struct NetworkQuaternion +{ + enum Flag : uint8 + { + None = 0, + HasX = 1, + HasY = 2, + HasZ = 4, + NegativeX = 8, + NegativeY = 16, + NegativeZ = 32, + NegativeW = 64, + }; + + FORCE_INLINE static void Read(NetworkStream* stream, Quaternion& data) + { + uint8 flags; + stream->Read(flags); + if (flags == None) + { + // Early out on default value + data = Quaternion::Identity; + return; + } + + Quaternion raw = Quaternion::Identity; +#define READ_COMPONENT(comp, hasFlag, negativeFlag) \ + if (flags & hasFlag) \ + { \ + uint16 packed; \ + stream->Read(packed); \ + const float norm = (float)packed / (float)MAX_uint16; \ + raw.comp = norm; \ + if (flags & negativeFlag) \ + raw.comp = -raw.comp; \ + } + READ_COMPONENT(X, HasX, NegativeX); + READ_COMPONENT(Y, HasY, NegativeY); + READ_COMPONENT(Z, HasZ, NegativeZ); +#define READ_COMPONENT + + // Calculate W + raw.W = Math::Sqrt(Math::Max(1.0f - raw.X * raw.X - raw.Y * raw.Y - raw.Z * raw.Z, 0.0f)); + if (flags & NegativeW) + raw.W = -raw.W; + + raw.Normalize(); + data = raw; + } + + FORCE_INLINE static void Write(NetworkStream* stream, const Quaternion& data) + { + // Assumes rotation is normalized so W can be recalculated + Quaternion raw = data; + raw.Normalize(); + + // Compose flags that describe the data + uint8 flags = HasX | HasY | HasZ; +#define QUANTIZE_COMPONENT(comp, hasFlag, negativeFlag) \ + if (Math::IsZero(raw.comp)) \ + flags &= ~hasFlag; \ + else if (raw.comp < 0.0f) \ + flags |= negativeFlag + QUANTIZE_COMPONENT(X, HasX, NegativeX); + QUANTIZE_COMPONENT(Y, HasY, NegativeY); + QUANTIZE_COMPONENT(Z, HasZ, NegativeZ); + if (raw.W < 0.0f) + flags |= NegativeW; +#undef QUANTIZE_COMPONENT + + // Write data + stream->Write(flags); +#define WRITE_COMPONENT(comp, hasFlag) \ + if (flags & hasFlag) \ + { \ + const float norm = Math::Abs(raw.comp); \ + const uint16 packed = (uint16)(norm * MAX_uint16); \ + stream->Write(packed); \ + } + WRITE_COMPONENT(X, HasX); + WRITE_COMPONENT(Y, HasY); + WRITE_COMPONENT(Z, HasZ); +#undef WRITE_COMPONENT + } +}; NetworkStream::NetworkStream(const SpawnParams& params) : ScriptingObject(params) @@ -58,6 +146,11 @@ void NetworkStream::Read(INetworkSerializable* obj) obj->Deserialize(this); } +void NetworkStream::Read(Quaternion& data) +{ + NetworkQuaternion::Read(this, data); +} + void NetworkStream::Write(INetworkSerializable& obj) { obj.Serialize(this); @@ -68,6 +161,11 @@ void NetworkStream::Write(INetworkSerializable* obj) obj->Serialize(this); } +void NetworkStream::Write(const Quaternion& data) +{ + NetworkQuaternion::Write(this, data); +} + void NetworkStream::Flush() { // Nothing to do diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index 37b8c6f17..a79a6ec6b 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -72,10 +72,12 @@ public: using ReadStream::Read; void Read(INetworkSerializable& obj); void Read(INetworkSerializable* obj); + void Read(Quaternion& data); using WriteStream::Write; void Write(INetworkSerializable& obj); void Write(INetworkSerializable* obj); + void Write(const Quaternion& data); public: // [Stream] diff --git a/Source/Engine/Tests/TestNetworking.cpp b/Source/Engine/Tests/TestNetworking.cpp new file mode 100644 index 000000000..c249d7fc8 --- /dev/null +++ b/Source/Engine/Tests/TestNetworking.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "Engine/Core/Formatting.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Networking/NetworkStream.h" +#include + +TEST_CASE("Networking") +{ + SECTION("Test Network Stream") + { + auto writeStream = New(); + auto readStream = New(); + + // Quaternions + { +#define TEST_RAW 0 + writeStream->Initialize(); + constexpr int32 QuatRes = 64; + constexpr float ResToDeg = 360.0f / (float)QuatRes; + for (int32 x = 0; x <= QuatRes; x++) + { + const float qx = (float)x * ResToDeg; + for (int32 y = 0; y <= QuatRes; y++) + { + const float qy = (float)y * ResToDeg; + for (int32 z = 0; z <= QuatRes; z++) + { + const float qz = (float)z * ResToDeg; + auto quat = Quaternion::Euler(qx, qy, qz); + quat.Normalize(); +#if TEST_RAW + writeStream->WriteBytes(&quat, sizeof(quat)); +#else + writeStream->Write(quat); +#endif + } + } + } + int dataSizeInkB = writeStream->GetPosition() / 1024; // 4291 -> 1869 + readStream->Initialize(writeStream->GetBuffer(), writeStream->GetPosition()); + for (int32 x = 0; x <= QuatRes; x++) + { + const float qx = (float)x * ResToDeg; + for (int32 y = 0; y <= QuatRes; y++) + { + const float qy = (float)y * ResToDeg; + for (int32 z = 0; z <= QuatRes; z++) + { + const float qz = (float)z * ResToDeg; + auto expected = Quaternion::Euler(qx, qy, qz); + expected.Normalize(); + Quaternion quat; +#if TEST_RAW + readStream->ReadBytes(&quat, sizeof(quat)); +#else + readStream->Read(quat); +#endif + const bool equal = Quaternion::Dot(expected, quat) > 0.9999f; + CHECK(equal); + } + } + } +#undef TEST_RAW + } + + Delete(readStream); + Delete(writeStream); + } +} From b957733150c516b83634c69b96f4769de358a926 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Oct 2024 23:35:41 +0200 Subject: [PATCH 032/215] Fixes --- Source/Engine/Networking/NetworkManager.cpp | 3 ++- Source/Engine/Platform/Android/AndroidFileSystem.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index f2de18fef..265f239b9 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -10,12 +10,13 @@ #include "FlaxEngine.Gen.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Scripting.h" -#define NETWORK_PROTOCOL_VERSION 3 +#define NETWORK_PROTOCOL_VERSION 4 float NetworkManager::NetworkFPS = 60.0f; NetworkPeer* NetworkManager::Peer = nullptr; diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp index 88114c899..1cea6715e 100644 --- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp +++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp @@ -80,7 +80,7 @@ namespace // Determinate a full path of an entry char full_path[256]; ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, pathStr); + strcpy(full_path, path); strcat(full_path, "/"); strcat(full_path, entry->d_name); From c202a639cf8d8d6b8d0afecc53689dea02d8751e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Oct 2024 00:01:10 +0200 Subject: [PATCH 033/215] Fix linux --- Source/Engine/Platform/Linux/LinuxFileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index cb4fb15cd..69afa23a4 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -227,7 +227,7 @@ bool DeleteUnixPathTree(const char* path) // Determinate a full path of an entry char full_path[256]; ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, pathStr); + strcpy(full_path, path); strcat(full_path, "/"); strcat(full_path, entry->d_name); From ac832a0e8182c2e720a27ad5e14cb97c7b2d740d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Oct 2024 11:24:10 +0200 Subject: [PATCH 034/215] Optimize network transform replication --- .../Components/NetworkTransform.cpp | 8 ++---- Source/Engine/Networking/NetworkStream.cpp | 28 +++++++++++++++++++ Source/Engine/Networking/NetworkStream.h | 2 ++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Networking/Components/NetworkTransform.cpp b/Source/Engine/Networking/Components/NetworkTransform.cpp index 38b3a560e..1f15ac3f7 100644 --- a/Source/Engine/Networking/Components/NetworkTransform.cpp +++ b/Source/Engine/Networking/Components/NetworkTransform.cpp @@ -157,6 +157,7 @@ void NetworkTransform::Serialize(NetworkStream* stream) // Encode data Data data; + Platform::MemoryClear(&data, sizeof(data)); data.LocalSpace = LocalSpace; data.HasSequenceIndex = Mode == ReplicationModes::Prediction; data.Components = Components; @@ -195,8 +196,7 @@ void NetworkTransform::Serialize(NetworkStream* stream) } if (EnumHasAllFlags(data.Components, ReplicationComponents::Rotation)) { - const Float3 rotation = transform.Orientation.GetEuler(); - stream->Write(rotation); + stream->Write(transform.Orientation); } else if (EnumHasAnyFlags(data.Components, ReplicationComponents::Rotation)) { @@ -260,9 +260,7 @@ void NetworkTransform::Deserialize(NetworkStream* stream) } if (EnumHasAllFlags(data.Components, ReplicationComponents::Rotation)) { - Float3 rotation; - stream->Read(rotation); - transform.Orientation = Quaternion::Euler(rotation); + stream->Read(transform.Orientation); } else if (EnumHasAnyFlags(data.Components, ReplicationComponents::Rotation)) { diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index 8c197602c..e35405ae8 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -3,6 +3,7 @@ #include "NetworkStream.h" #include "INetworkSerializable.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/Transform.h" // Quaternion quantized for optimized network data size. struct NetworkQuaternion @@ -151,6 +152,20 @@ void NetworkStream::Read(Quaternion& data) NetworkQuaternion::Read(this, data); } +void NetworkStream::Read(Transform& data) +{ + struct NonQuantized + { + Vector3 Pos; + Float3 Scale; + }; + NonQuantized nonQuantized; + ReadBytes(&nonQuantized, sizeof(nonQuantized)); + NetworkQuaternion::Read(this, data.Orientation); + data.Translation = nonQuantized.Pos; + data.Scale = nonQuantized.Scale; +} + void NetworkStream::Write(INetworkSerializable& obj) { obj.Serialize(this); @@ -166,6 +181,19 @@ void NetworkStream::Write(const Quaternion& data) NetworkQuaternion::Write(this, data); } +void NetworkStream::Write(const Transform& data) +{ + struct NonQuantized + { + Vector3 Pos; + Float3 Scale; + }; + // TODO: quantize transform (at least scale) + NonQuantized nonQuantized = { data.Translation, data.Scale }; + WriteBytes(&nonQuantized, sizeof(nonQuantized)); + NetworkQuaternion::Write(this, data.Orientation); +} + void NetworkStream::Flush() { // Nothing to do diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index a79a6ec6b..c13ad37d8 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -73,11 +73,13 @@ public: void Read(INetworkSerializable& obj); void Read(INetworkSerializable* obj); void Read(Quaternion& data); + void Read(Transform& data); using WriteStream::Write; void Write(INetworkSerializable& obj); void Write(INetworkSerializable* obj); void Write(const Quaternion& data); + void Write(const Transform& data); public: // [Stream] From 60ed23105dff72dec2ac8415161edc48516aefa9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Oct 2024 12:33:51 +0200 Subject: [PATCH 035/215] Fix typo --- Source/Engine/Networking/NetworkReplicator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index a0fa11514..b2ab231e9 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1395,7 +1395,7 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli // Ensure local client owns that object actually if (localRole != NetworkObjectRole::OwnedAuthoritative) { - LOG(Error, "Cannot change overship of object (Id={}) to the local client (Id={}) if the local role is not set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + LOG(Error, "Cannot change ownership of object (Id={}) to the local client (Id={}) if the local role is not set to OwnedAuthoritative.", obj->GetID(), ownerClientId); return; } } @@ -1404,7 +1404,7 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli // Ensure local client doesn't own that object since it's owned by other client if (localRole == NetworkObjectRole::OwnedAuthoritative) { - LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); return; } } @@ -1433,7 +1433,7 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli #if !BUILD_RELEASE if (localRole == NetworkObjectRole::OwnedAuthoritative) { - LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); return; } #endif @@ -1451,7 +1451,7 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli #if !BUILD_RELEASE if (localRole == NetworkObjectRole::OwnedAuthoritative) { - LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); return; } #endif From c94052513e6d2bcb62dc64512063cd7a8a6c9310 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Oct 2024 19:47:09 +0200 Subject: [PATCH 036/215] Add object replication data cache and send via `Reliable` channel to reduce data transfer --- Source/Engine/Networking/NetworkInternal.h | 9 +++ Source/Engine/Networking/NetworkManager.cpp | 18 +++++- .../Engine/Networking/NetworkReplicator.cpp | 60 +++++++++++++++---- Source/Engine/Tests/TestNetworking.cpp | 1 - 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 23738cfc8..4b3dc03ad 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -7,6 +7,15 @@ #include "Engine/Core/Collections/Dictionary.h" #endif +// Internal version number of networking implementation. Updated once engine changes serialization or connection rules. +#define NETWORK_PROTOCOL_VERSION 4 + +// Enables encoding object ids and typenames via uint32 keys rather than full data send. +#define USE_NETWORK_KEYS 1 + +// Cached replication messages if contents didn't change +#define USE_NETWORK_REPLICATOR_CACHE 1 + enum class NetworkMessageIDs : uint8 { None = 0, diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 265f239b9..4d3212e14 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -16,8 +16,6 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Scripting.h" -#define NETWORK_PROTOCOL_VERSION 4 - float NetworkManager::NetworkFPS = 60.0f; NetworkPeer* NetworkManager::Peer = nullptr; NetworkManagerMode NetworkManager::Mode = NetworkManagerMode::Offline; @@ -140,6 +138,7 @@ FORCE_INLINE bool IsNetworkKeyValid(uint32 index) void NetworkMessage::WriteNetworkId(const Guid& id) { +#if USE_NETWORK_KEYS ScopeLock lock(Keys.Lock); uint32 index = MAX_uint32; bool hasIndex = Keys.LookupId.TryGet(id, index); @@ -158,10 +157,14 @@ void NetworkMessage::WriteNetworkId(const Guid& id) Keys.PendingIds.Add(id, NetworkKey(id)); } } +#else + WriteBytes((const uint8*)&id, sizeof(Guid)); +#endif } void NetworkMessage::ReadNetworkId(Guid& id) { +#if USE_NETWORK_KEYS ScopeLock lock(Keys.Lock); uint32 index = ReadUInt32(); if (index != MAX_uint32) @@ -193,10 +196,14 @@ void NetworkMessage::ReadNetworkId(Guid& id) Keys.PendingIds.Add(id, NetworkKey(id)); } } +#else + ReadBytes((uint8*)&id, sizeof(Guid)); +#endif } void NetworkMessage::WriteNetworkName(const StringAnsiView& name) { +#if USE_NETWORK_KEYS ScopeLock lock(Keys.Lock); uint32 index = MAX_uint32; bool hasIndex = Keys.LookupName.TryGet(name, index); @@ -216,10 +223,14 @@ void NetworkMessage::WriteNetworkName(const StringAnsiView& name) Keys.PendingNames.Add(newName, NetworkKey(newName)); } } +#else + WriteStringAnsi(name); +#endif } void NetworkMessage::ReadNetworkName(StringAnsiView& name) { +#if USE_NETWORK_KEYS ScopeLock lock(Keys.Lock); uint32 index = ReadUInt32(); if (index != MAX_uint32) @@ -252,6 +263,9 @@ void NetworkMessage::ReadNetworkName(StringAnsiView& name) Keys.PendingNames.Add(newName, NetworkKey(newName)); } } +#else + name = ReadStringAnsi(); +#endif } void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index b2ab231e9..19c72f760 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -126,12 +126,29 @@ struct NetworkReplicatedObject DataContainer TargetClientIds; INetworkObject* AsNetworkObject; + struct + { + NetworkClientsMask Mask; + BytesContainer Data; + + void Clear() + { + Mask = NetworkClientsMask(); + Data.Release(); + } + } RepCache; + NetworkReplicatedObject() { Spawned = 0; Synced = 0; } + void Dirty() + { + RepCache.Mask = NetworkClientsMask(); + } + bool operator==(const NetworkReplicatedObject& other) const { return Object == other.Object; @@ -684,6 +701,7 @@ void FindObjectsForSpawn(SpawnGroup& group, ChunkedArray& spawnI FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj) { + item.Dirty(); if (Hierarchy) Hierarchy->DirtyObject(obj); } @@ -787,6 +805,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& pre { // Server always knows the best so update ownership of the existing object item.OwnerClientId = msgData.OwnerClientId; + item.RepCache.Clear(); if (item.Role == NetworkObjectRole::OwnedAuthoritative) { if (Hierarchy) @@ -1442,6 +1461,7 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli item.OwnerClientId = ownerClientId; item.LastOwnerFrame = 1; item.Role = localRole; + item.RepCache.Clear(); SendObjectRoleMessage(item); } } @@ -1449,11 +1469,11 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli { // Allow to change local role of the object (except ownership) #if !BUILD_RELEASE - if (localRole == NetworkObjectRole::OwnedAuthoritative) - { - LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); - return; - } + if (localRole == NetworkObjectRole::OwnedAuthoritative) + { + LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + return; + } #endif if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative) Hierarchy->RemoveObject(obj); @@ -1864,10 +1884,28 @@ void NetworkInternal::NetworkReplicatorUpdate() //NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString()); continue; } + const uint32 size = stream->GetPosition(); + if (size > MAX_uint16) + { + LOG(Error, "Too much data for object {} replication ({} bytes provided while limit is {}).", item.ToString(), size, MAX_uint16); + continue; + } + +#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 == e.TargetClients && + Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0) + { + continue; + } + item.RepCache.Mask = e.TargetClients; + 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; // Send object to clients - const uint32 size = stream->GetPosition(); - ASSERT(size <= MAX_uint16); NetworkMessageObjectReplicate msgData; msgData.OwnerFrame = NetworkManager::Frame; Guid objectId = item.ObjectId, parentId = item.ParentId; @@ -1908,9 +1946,9 @@ void NetworkInternal::NetworkReplicatorUpdate() msg.WriteBytes(stream->GetBuffer(), msgDataSize); uint32 dataSize = msgDataSize, messageSize = msg.Length; if (isClient) - peer->EndSendMessage(NetworkChannelType::Unreliable, msg); + peer->EndSendMessage(repChannel, msg); else - peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets); + peer->EndSendMessage(repChannel, msg, CachedTargets); // Send all other parts for (uint32 partIndex = 1; partIndex < partsCount; partIndex++) @@ -1929,9 +1967,9 @@ void NetworkInternal::NetworkReplicatorUpdate() dataSize += msgDataPart.PartSize; dataStart += msgDataPart.PartSize; if (isClient) - peer->EndSendMessage(NetworkChannelType::Unreliable, msg); + peer->EndSendMessage(repChannel, msg); else - peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets); + peer->EndSendMessage(repChannel, msg, CachedTargets); } ASSERT_LOW_LAYER(dataStart == size); diff --git a/Source/Engine/Tests/TestNetworking.cpp b/Source/Engine/Tests/TestNetworking.cpp index c249d7fc8..38caff95f 100644 --- a/Source/Engine/Tests/TestNetworking.cpp +++ b/Source/Engine/Tests/TestNetworking.cpp @@ -37,7 +37,6 @@ TEST_CASE("Networking") } } } - int dataSizeInkB = writeStream->GetPosition() / 1024; // 4291 -> 1869 readStream->Initialize(writeStream->GetBuffer(), writeStream->GetPosition()); for (int32 x = 0; x <= QuatRes; x++) { From 91d86552cde0806b6f9b921b6ef8fa7c5e85465f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Oct 2024 23:43:07 +0200 Subject: [PATCH 037/215] Minor cleanup --- Source/Engine/Debug/DebugCommands.cpp | 17 +++++++++-------- Source/Engine/Debug/DebugCommands.h | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index e4af0268e..e9f5b9b93 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -99,10 +99,11 @@ namespace bool Inited = false; Array Commands; - void OnBinaryModuleLoaded(BinaryModule* module) + void FindDebugCommands(BinaryModule* module) { if (module == GetBinaryModuleCorlib()) return; + PROFILE_CPU(); #if USE_CSHARP if (auto* managedModule = dynamic_cast(module)) @@ -200,9 +201,9 @@ namespace const auto& modules = BinaryModule::GetModules(); for (BinaryModule* module : modules) { - OnBinaryModuleLoaded(module); + FindDebugCommands(module); } - Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded); + Scripting::BinaryModuleLoaded.Bind(&FindDebugCommands); Scripting::ScriptsReloading.Bind(&OnScriptsReloading); } } @@ -219,7 +220,7 @@ public: { // Cleanup ScopeLock lock(Locker); - Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded); + Scripting::BinaryModuleLoaded.Unbind(&FindDebugCommands); Scripting::ScriptsReloading.Unbind(&OnScriptsReloading); Commands.Clear(); Inited = true; @@ -252,12 +253,12 @@ void DebugCommands::Execute(StringView command) InitCommands(); // Find command to run - for (const CommandData& command : Commands) + for (const CommandData& cmd : Commands) { - if (name.Length() == command.Name.Length() && - StringUtils::CompareIgnoreCase(name.Get(), command.Name.Get(), name.Length()) == 0) + if (name.Length() == cmd.Name.Length() && + StringUtils::CompareIgnoreCase(name.Get(), cmd.Name.Get(), name.Length()) == 0) { - command.Invoke(args); + cmd.Invoke(args); return; } } diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h index cedf39f57..e30ec048e 100644 --- a/Source/Engine/Debug/DebugCommands.h +++ b/Source/Engine/Debug/DebugCommands.h @@ -13,7 +13,7 @@ API_CLASS(static) class FLAXENGINE_API DebugCommands public: /// - /// Executees the command. + /// Executes the command. /// /// The command line (optionally with arguments). API_FUNCTION() static void Execute(StringView command); From 5328ea891dd1fa41d98535c0626aa862b639e811 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Oct 2024 23:23:54 +0200 Subject: [PATCH 038/215] Add command line input to Output Log in Editor --- Source/Editor/Windows/OutputLogWindow.cs | 84 +++++++++++++++++++++++- Source/Engine/Debug/DebugCommands.cpp | 33 +++++++++- Source/Engine/Debug/DebugCommands.h | 10 ++- 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index f8c4ea9b5..36c6a5c6c 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -125,6 +125,78 @@ namespace FlaxEditor.Windows } } + /// + /// Command line input textbox control which can execute debug commands. + /// + private class CommandLineBox : TextBox + { + public CommandLineBox(float x, float y, float width) + : base(false, x, y, width) + { + WatermarkText = ">"; + } + + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.Return: + { + // Run command + DebugCommands.Execute(Text); + SetText(string.Empty); + return true; + } + case KeyboardKeys.Tab: + { + // Auto-complete + DebugCommands.Search(Text, out var matches, true); + if (matches.Length == 0) + { + // Nothing found + } + else if (matches.Length == 1) + { + // Exact match + SetText(matches[0]); + SetSelection(Text.Length); + } + else + { + // Find the most common part + Array.Sort(matches); + int minLength = Text.Length; + int maxLength = matches[0].Length; + int sharedLength = minLength + 1; + bool allMatch = true; + for (; allMatch && sharedLength < maxLength; sharedLength++) + { + var shared = matches[0].Substring(0, sharedLength); + for (int i = 1; i < matches.Length; i++) + { + if (!matches[i].StartsWith(shared, StringComparison.OrdinalIgnoreCase)) + { + sharedLength -= 2; + allMatch = false; + break; + } + } + } + if (sharedLength > minLength) + { + // Use the largest shared part of all matches + SetText(matches[0].Substring(0, sharedLength)); + SetSelection(sharedLength); + } + } + return true; + } + } + return base.OnKeyDown(key); + } + } + private InterfaceOptions.TimestampsFormats _timestampsFormats; private bool _showLogType; @@ -147,6 +219,7 @@ namespace FlaxEditor.Windows private HScrollBar _hScroll; private VScrollBar _vScroll; private OutputTextBox _output; + private CommandLineBox _commandLineBox; private ContextMenu _contextMenu; /// @@ -173,13 +246,13 @@ namespace FlaxEditor.Windows Parent = this, }; _searchBox.TextChanged += Refresh; - _hScroll = new HScrollBar(this, Height - _scrollSize, Width - _scrollSize, _scrollSize) + _hScroll = new HScrollBar(this, Height - _scrollSize - TextBox.DefaultHeight - 2, Width - _scrollSize, _scrollSize) { ThumbThickness = 10, Maximum = 0, }; _hScroll.ValueChanged += OnHScrollValueChanged; - _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 2, _scrollSize) + _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 4 - TextBox.DefaultHeight, _scrollSize) { ThumbThickness = 10, Maximum = 0, @@ -197,6 +270,10 @@ namespace FlaxEditor.Windows }; _output.TargetViewOffsetChanged += OnOutputTargetViewOffsetChanged; _output.TextChanged += OnOutputTextChanged; + _commandLineBox = new CommandLineBox(2, Height - 2 - TextBox.DefaultHeight, Width - 4) + { + Parent = this, + }; // Setup context menu _contextMenu = new ContextMenu(); @@ -422,6 +499,8 @@ namespace FlaxEditor.Windows { _searchBox.Width = Width - _viewDropdown.Right - 4; _output.Size = new Float2(_vScroll.X - 2, _hScroll.Y - 4 - _viewDropdown.Bottom); + _commandLineBox.Width = Width - 4; + _commandLineBox.Y = Height - 2 - _commandLineBox.Height; } } @@ -664,6 +743,7 @@ namespace FlaxEditor.Windows _hScroll = null; _vScroll = null; _output = null; + _commandLineBox = null; _contextMenu = null; base.OnDestroy(); diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index e9f5b9b93..19c569b8a 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -266,6 +266,37 @@ void DebugCommands::Execute(StringView command) LOG(Error, "Unknown command '{}'", name); } +void DebugCommands::Search(StringView searchText, Array& matches, bool startsWith) +{ + if (searchText.IsEmpty()) + return; + + ScopeLock lock(Locker); + if (!Inited) + InitCommands(); + + if (startsWith) + { + for (auto& command : Commands) + { + if (command.Name.StartsWith(searchText, StringSearchCase::IgnoreCase)) + { + matches.Add(command.Name); + } + } + } + else + { + for (auto& command : Commands) + { + if (command.Name.Contains(searchText.Get(), StringSearchCase::IgnoreCase)) + { + matches.Add(command.Name); + } + } + } +} + bool DebugCommands::Iterate(const StringView& searchText, int32& index) { ScopeLock lock(Locker); @@ -286,7 +317,7 @@ bool DebugCommands::Iterate(const StringView& searchText, int32& index) return false; } -String DebugCommands::GetCommandName(int32 index) +StringView DebugCommands::GetCommandName(int32 index) { ScopeLock lock(Locker); CHECK_RETURN(Commands.IsValidIndex(index), String::Empty); diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h index e30ec048e..f89cba371 100644 --- a/Source/Engine/Debug/DebugCommands.h +++ b/Source/Engine/Debug/DebugCommands.h @@ -18,7 +18,15 @@ public: /// The command line (optionally with arguments). API_FUNCTION() static void Execute(StringView command); + /// + /// Searches the list of commands to return candidates that match the given query text. + /// + /// The query text. + /// The output list of commands that match a given query (unsorted). + /// True if filter commands that start with a specific search text, otherwise will return commands that contain a specific query. + API_FUNCTION() static void Search(StringView searchText, API_PARAM(Out) Array& matches, bool startsWith = false); + public: static bool Iterate(const StringView& searchText, int32& index); - static String GetCommandName(int32 index); + static StringView GetCommandName(int32 index); }; From a2693fa3543f1efb5c030a8c82851bdbab6a15c8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Oct 2024 23:24:26 +0200 Subject: [PATCH 039/215] Ignore non-public debug commands from marked classes --- Source/Engine/Debug/DebugCommands.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 19c569b8a..8ea17f177 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -53,6 +53,11 @@ struct CommandData } // Parse arguments + if (args == StringView(TEXT("?"), 1)) + { + LOG(Warning, "TODO: debug commands help/docs printing"); // TODO: debug commands help/docs printing (use CodeDocsModule that parses XML docs) + return; + } Array params; params.Resize(sigParams.Count()); Array argsSeparated; @@ -140,6 +145,8 @@ namespace continue; if (!useClass && !method->HasAttribute(attribute)) continue; + if (useClass && method->GetVisibility() != MVisibility::Public) + continue; auto& commandData = Commands.AddOne(); BUILD_NAME(commandData, method->GetName()); @@ -155,6 +162,8 @@ namespace continue; if (!useClass && !field->HasAttribute(attribute)) continue; + if (useClass && field->GetVisibility() != MVisibility::Public) + continue; auto& commandData = Commands.AddOne(); BUILD_NAME(commandData, field->GetName()); @@ -170,6 +179,8 @@ namespace continue; if (!useClass && !property->HasAttribute(attribute)) continue; + if (useClass && property->GetVisibility() != MVisibility::Public) + continue; auto& commandData = Commands.AddOne(); BUILD_NAME(commandData, property->GetName()); From 449e988a59fcb31ed6d8990b72bc9112629e30b4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Oct 2024 23:25:38 +0200 Subject: [PATCH 040/215] Add workaround for debug command text memory bug due to managed handles gc --- Source/Engine/Debug/DebugCommands.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 8ea17f177..4702971d2 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -242,6 +242,10 @@ DebugCommandsService DebugCommandsServiceInstance; void DebugCommands::Execute(StringView command) { + // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) + String commandCopy = command; + command = commandCopy; + // Preprocess command text while (command.HasChars() && StringUtils::IsWhitespace(command[0])) command = StringView(command.Get() + 1, command.Length() - 1); @@ -281,6 +285,9 @@ void DebugCommands::Search(StringView searchText, Array& matches, bo { if (searchText.IsEmpty()) return; + // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) + String searchTextCopy = searchText; + searchText = searchTextCopy; ScopeLock lock(Locker); if (!Inited) From b681a03bfbebfaeaec8d6817d07d26e2712b6195 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Oct 2024 13:03:40 +0200 Subject: [PATCH 041/215] Fix output log window to properly handle copy and other input events --- Source/Editor/Windows/OutputLogWindow.cs | 24 +++++++++++++++++----- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 7 ++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 36c6a5c6c..86d0f060d 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -80,12 +80,9 @@ namespace FlaxEditor.Windows /// public TextBlockStyle ErrorStyle; - /// - public override bool OnKeyDown(KeyboardKeys key) + public OutputTextBox() { - if (Window.InputActions.Process(Editor.Instance, this, key)) - return true; - return base.OnKeyDown(key); + _consumeAllKeyDownEvents = false; } /// @@ -504,6 +501,23 @@ namespace FlaxEditor.Windows } } + /// + public override bool OnKeyDown(KeyboardKeys key) + { + var input = Editor.Options.Options.Input; + if (input.Search.Process(this, key)) + { + if (!_searchBox.ContainsFocus) + { + _searchBox.Focus(); + _searchBox.SelectAll(); + } + return true; + } + + return base.OnKeyDown(key); + } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 55f810211..021986420 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -127,6 +127,11 @@ namespace FlaxEngine.GUI /// protected bool _changeCursor = true; + /// + /// True if always return true as default for key events, otherwise won't consume them. + /// + protected bool _consumeAllKeyDownEvents = true; + /// /// Event fired when text gets changed /// @@ -1542,7 +1547,7 @@ namespace FlaxEngine.GUI return false; } - return true; + return _consumeAllKeyDownEvents; } } } From 2127961069c38b5ac52bd31e3d8bf16e875272ae Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Oct 2024 13:05:26 +0200 Subject: [PATCH 042/215] Add printing debug command upon execute --- Source/Engine/Debug/DebugCommands.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 4702971d2..a9ae862f2 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -69,6 +69,7 @@ struct CommandData } // Call command + LOG(Info, "> {}{}{}", Name, args.Length() != 0 ? TEXT(" ") : TEXT(""), args); Variant result; if (Method) { From 16235c21c0cb1e6a2924dccadb288c14b2109747 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Oct 2024 21:34:28 +0200 Subject: [PATCH 043/215] Add optional direction option to context menu --- Source/Editor/GUI/ContextMenu/ContextMenu.cs | 4 +-- .../Editor/GUI/ContextMenu/ContextMenuBase.cs | 25 +++++++++++++++++-- .../Editor/Surface/ContextMenu/VisjectCM.cs | 2 +- Source/Editor/Windows/Search/ContentFinder.cs | 4 +-- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index 80a2d7494..cb0eb09c1 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -355,14 +355,14 @@ namespace FlaxEditor.GUI.ContextMenu } /// - public override void Show(Control parent, Float2 location) + public override void Show(Control parent, Float2 location, ContextMenuDirection? direction = null) { // Remove last separator to make context menu look better int lastIndex = _panel.Children.Count - 1; if (lastIndex >= 0 && _panel.Children[lastIndex] is ContextMenuSeparator separator) separator.Dispose(); - base.Show(parent, location); + base.Show(parent, location, direction); } /// diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 511cda2e5..121b3bf31 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -134,7 +134,8 @@ namespace FlaxEditor.GUI.ContextMenu /// /// Parent control to attach to it. /// Popup menu origin location in parent control coordinates. - public virtual void Show(Control parent, Float2 location) + /// The custom popup direction. Null to use automatic direction. + public virtual void Show(Control parent, Float2 location, ContextMenuDirection? direction = null) { Assert.IsNotNull(parent); @@ -166,7 +167,7 @@ namespace FlaxEditor.GUI.ContextMenu var monitorBounds = Platform.GetMonitorBounds(locationSS); var rightBottomLocationSS = locationSS + dpiSize; bool isUp = false, isLeft = false; - if (UseAutomaticDirectionFix) + if (UseAutomaticDirectionFix && direction == null) { var parentMenu = parent as ContextMenu; if (monitorBounds.Bottom < rightBottomLocationSS.Y) @@ -193,6 +194,26 @@ namespace FlaxEditor.GUI.ContextMenu locationSS.X -= dpiSize.X; } } + else if (direction.HasValue) + { + switch (direction.Value) + { + case ContextMenuDirection.RightUp: + isUp = true; + break; + case ContextMenuDirection.LeftDown: + isLeft = true; + break; + case ContextMenuDirection.LeftUp: + isLeft = true; + isUp = true; + break; + } + if (isLeft) + locationSS.X -= dpiSize.X; + if (isUp) + locationSS.Y -= dpiSize.Y; + } // Update direction flag if (isUp) diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index f31f1245a..21aa9ca7f 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -759,7 +759,7 @@ namespace FlaxEditor.Surface.ContextMenu } /// - public override void Show(Control parent, Float2 location) + public override void Show(Control parent, Float2 location, ContextMenuDirection? direction = null) { Show(parent, location, null); } diff --git a/Source/Editor/Windows/Search/ContentFinder.cs b/Source/Editor/Windows/Search/ContentFinder.cs index 06b5852dd..c6522255b 100644 --- a/Source/Editor/Windows/Search/ContentFinder.cs +++ b/Source/Editor/Windows/Search/ContentFinder.cs @@ -155,9 +155,9 @@ namespace FlaxEditor.Windows.Search } /// - public override void Show(Control parent, Float2 location) + public override void Show(Control parent, Float2 location, ContextMenuDirection? direction = null) { - base.Show(parent, location); + base.Show(parent, location, direction); // Setup _resultPanel.ScrollViewTo(Float2.Zero); From 0b1263a9e27af5bb51c9ac338a645f598516e7de Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Oct 2024 21:54:38 +0200 Subject: [PATCH 044/215] Disable `VK_EXT_tooling_info` check for debugger on Linux --- Source/Engine/Graphics/Graphics.cpp | 2 +- Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 886728d95..3cd600955 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -200,7 +200,7 @@ bool GraphicsService::Init() #endif ) { -#if !USE_EDITOR && BUILD_RELEASE +#if !USE_EDITOR && BUILD_RELEASE && !PLATFORM_LINUX // IsDebugToolAttached seams to be enabled on many Linux machines via VK_EXT_tooling_info // Block graphics debugging to protect contents Platform::Fatal(TEXT("Graphics debugger attached.")); #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp index 3a1a34e1c..63d6cd27a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp @@ -483,10 +483,12 @@ void GPUDeviceVulkan::GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array Date: Tue, 22 Oct 2024 21:55:25 +0200 Subject: [PATCH 045/215] Add `Alignment` option to Vertical and Horizonal Panels #2599 --- .../Engine/UI/GUI/Panels/HorizontalPanel.cs | 18 +++++++++++++++ .../Engine/UI/GUI/Panels/PanelWithMargins.cs | 22 +++++++++++++++++++ Source/Engine/UI/GUI/Panels/VerticalPanel.cs | 18 +++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs index a25a98c86..1c8e646e3 100644 --- a/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/HorizontalPanel.cs @@ -80,6 +80,24 @@ namespace FlaxEngine.GUI size.Y = maxHeight; Size = size; } + else if (_alignment != TextAlignment.Near && hasAnyLeft) + { + // Apply layout alignment + var offset = Width - left - _margin.Right; + if (_alignment == TextAlignment.Center) + offset *= 0.5f; + for (int i = 0; i < _children.Count; i++) + { + Control c = _children[i]; + if (c.Visible) + { + if (Mathf.IsZero(c.AnchorMin.X) && Mathf.IsZero(c.AnchorMax.X)) + { + c.X += offset; + } + } + } + } } } } diff --git a/Source/Engine/UI/GUI/Panels/PanelWithMargins.cs b/Source/Engine/UI/GUI/Panels/PanelWithMargins.cs index 071f0ccf2..18bd1a52d 100644 --- a/Source/Engine/UI/GUI/Panels/PanelWithMargins.cs +++ b/Source/Engine/UI/GUI/Panels/PanelWithMargins.cs @@ -30,6 +30,11 @@ namespace FlaxEngine.GUI /// protected Float2 _offset; + /// + /// The child controls alignment within layout area. + /// + protected TextAlignment _alignment = TextAlignment.Near; + /// /// Gets or sets the left margin. /// @@ -172,6 +177,23 @@ namespace FlaxEngine.GUI } } + /// + /// Gets or sets the child controls alignment within layout area. + /// + [EditorOrder(50), VisibleIf(nameof(AutoSize), true)] + public TextAlignment Alignment + { + get => _alignment; + set + { + if (_alignment != value) + { + _alignment = value; + PerformLayout(); + } + } + } + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs index 9ee2a2297..aae95bd43 100644 --- a/Source/Engine/UI/GUI/Panels/VerticalPanel.cs +++ b/Source/Engine/UI/GUI/Panels/VerticalPanel.cs @@ -80,6 +80,24 @@ namespace FlaxEngine.GUI size.X = maxWidth; Size = size; } + else if (_alignment != TextAlignment.Near && hasAnyTop) + { + // Apply layout alignment + var offset = Height - top - _margin.Bottom; + if (_alignment == TextAlignment.Center) + offset *= 0.5f; + for (int i = 0; i < _children.Count; i++) + { + Control c = _children[i]; + if (c.Visible) + { + if (Mathf.IsZero(c.AnchorMin.Y) && Mathf.IsZero(c.AnchorMax.Y)) + { + c.Y += offset; + } + } + } + } } } } From 5904c0eea57abcd5831a06488dc32b8d2e102835 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Oct 2024 21:59:06 +0200 Subject: [PATCH 046/215] Add debug command history to Output Log in Editor --- Source/Editor/GUI/ItemsListContextMenu.cs | 30 ++++--- Source/Editor/Windows/OutputLogWindow.cs | 98 ++++++++++++++++++++++- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 23f493c65..8eeccf288 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -248,26 +248,30 @@ namespace FlaxEditor.GUI /// /// The control width. /// The control height. - public ItemsListContextMenu(float width = 320, float height = 220) + /// Enables search field. + public ItemsListContextMenu(float width = 320, float height = 220, bool withSearch = true) { // Context menu dimensions Size = new Float2(width, height); - // Search box - _searchBox = new SearchBox(false, 1, 1) + if (withSearch) { - Parent = this, - Width = Width - 3, - }; - _searchBox.TextChanged += OnSearchFilterChanged; - _searchBox.ClearSearchButton.Clicked += () => PerformLayout(); + // Search box + _searchBox = new SearchBox(false, 1, 1) + { + Parent = this, + Width = Width - 3, + }; + _searchBox.TextChanged += OnSearchFilterChanged; + _searchBox.ClearSearchButton.Clicked += () => PerformLayout(); + } // Panel with scrollbar _scrollPanel = new Panel(ScrollBars.Vertical) { Parent = this, AnchorPreset = AnchorPresets.StretchAll, - Bounds = new Rectangle(0, _searchBox.Bottom + 1, Width, Height - _searchBox.Bottom - 2), + Bounds = withSearch ? new Rectangle(0, _searchBox.Bottom + 1, Width, Height - _searchBox.Bottom - 2) : new Rectangle(Float2.Zero, Size), }; // Items list panel @@ -446,7 +450,7 @@ namespace FlaxEditor.GUI } } - _searchBox.Clear(); + _searchBox?.Clear(); UnlockChildrenRecursive(); PerformLayout(true); } @@ -510,7 +514,7 @@ namespace FlaxEditor.GUI if (RootWindow.FocusedControl == null) { // Focus search box if nothing is focused - _searchBox.Focus(); + _searchBox?.Focus(); return true; } @@ -536,7 +540,7 @@ namespace FlaxEditor.GUI var focusedIndex = items.IndexOf(focusedItem); if (focusedIndex == 0) { - _searchBox.Focus(); + _searchBox?.Focus(); } else if (focusedIndex > 0) { @@ -556,7 +560,7 @@ namespace FlaxEditor.GUI break; } - if (_waitingForInput) + if (_waitingForInput && _searchBox != null) { _waitingForInput = false; _searchBox.Focus(); diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 86d0f060d..361f2e4b1 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Xml; +using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Options; @@ -127,10 +129,13 @@ namespace FlaxEditor.Windows /// private class CommandLineBox : TextBox { - public CommandLineBox(float x, float y, float width) + private OutputLogWindow _window; + + public CommandLineBox(float x, float y, float width, OutputLogWindow window) : base(false, x, y, width) { WatermarkText = ">"; + _window = window; } /// @@ -141,8 +146,22 @@ namespace FlaxEditor.Windows case KeyboardKeys.Return: { // Run command - DebugCommands.Execute(Text); + var command = Text.Trim(); + if (command.Length == 0) + return true; + DebugCommands.Execute(command); SetText(string.Empty); + + // Update history buffer + if (_window._commandHistory == null) + _window._commandHistory = new List(); + else if (_window._commandHistory.Count != 0 && _window._commandHistory.Last() == command) + _window._commandHistory.RemoveAt(_window._commandHistory.Count - 1); + _window._commandHistory.Add(command); + if (_window._commandHistory.Count > CommandHistoryLimit) + _window._commandHistory.RemoveAt(0); + _window.SaveHistory(); + return true; } case KeyboardKeys.Tab: @@ -189,6 +208,48 @@ namespace FlaxEditor.Windows } return true; } + case KeyboardKeys.ArrowUp: + { + if (TextLength == 0) + { + if (_window._commandHistory != null && _window._commandHistory.Count != 0) + { + // Show command history popup + var cm = new ItemsListContextMenu(180, 220, false); + ItemsListContextMenu.Item lastItem = null; + var count = _window._commandHistory.Count; + for (int i = 0; i < count; i++) + { + var command = _window._commandHistory[i]; + cm.AddItem(lastItem = new ItemsListContextMenu.Item + { + Name = command, + }); + } + cm.ItemClicked += item => + { + SetText(item.Name); + SetSelection(Text.Length); + }; + var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1); + if (cm.Height > totalHeight) + cm.Height = totalHeight; // Limit popup height if history is small + cm.Show(this, Float2.Zero, ContextMenuDirection.RightUp); + lastItem.Focus(); + cm.ScrollViewTo(lastItem); + } + } + else + { + // TODO: focus similar commands (via popup) + } + return true; + } + case KeyboardKeys.ArrowDown: + { + // Ignore + return true; + } } return base.OnKeyDown(key); } @@ -210,6 +271,9 @@ namespace FlaxEditor.Windows private List _textBlocks = new List(); private DateTime _startupTime; private Regex _compileRegex = new Regex("(?^(?:[a-zA-Z]\\:|\\\\\\\\[ \\-\\.\\w\\.]+\\\\[ \\-\\.\\w.$]+)\\\\(?:[ \\-\\.\\w]+\\\\)*\\w([ \\w.])+)\\((?\\d{1,}),\\d{1,},\\d{1,},\\d{1,}\\): (?error|warning) (?.*)", RegexOptions.Compiled); + private List _commandHistory; + private const string CommandHistoryKey = "CommandHistory"; + private const int CommandHistoryLimit = 30; private Button _viewDropdown; private TextBox _searchBox; @@ -267,7 +331,7 @@ namespace FlaxEditor.Windows }; _output.TargetViewOffsetChanged += OnOutputTargetViewOffsetChanged; _output.TextChanged += OnOutputTextChanged; - _commandLineBox = new CommandLineBox(2, Height - 2 - TextBox.DefaultHeight, Width - 4) + _commandLineBox = new CommandLineBox(2, Height - 2 - TextBox.DefaultHeight, Width - 4, this) { Parent = this, }; @@ -394,6 +458,14 @@ namespace FlaxEditor.Windows FocusOrShow(); } + private void SaveHistory() + { + if (_commandHistory == null || _commandHistory.Count == 0) + Editor.ProjectCache.RemoveCustomData(CommandHistoryKey); + else + Editor.ProjectCache.SetCustomData(CommandHistoryKey, FlaxEngine.Json.JsonSerializer.Serialize(_commandHistory)); + } + /// /// Refreshes the log output. /// @@ -710,6 +782,25 @@ namespace FlaxEditor.Windows public override void OnInit() { _startupTime = Time.StartupTime; + + // Load debug commands history + if (Editor.ProjectCache.TryGetCustomData(CommandHistoryKey, out string history)) + { + try + { + _commandHistory = (List)FlaxEngine.Json.JsonSerializer.Deserialize(history, typeof(List)); + for (int i = _commandHistory.Count - 1; i >= 0; i--) + { + if (string.IsNullOrEmpty(_commandHistory[i])) + _commandHistory.RemoveAt(i); + } + } + catch + { + // Ignore errors + _commandHistory = null; + } + } } /// @@ -750,6 +841,7 @@ namespace FlaxEditor.Windows _outLogTypes = null; _outLogTimes = null; _compileRegex = null; + _commandHistory = null; // Unlink controls _viewDropdown = null; From 87d35f0314c132820c0a508018ba542230b769d1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 19:26:11 +0200 Subject: [PATCH 047/215] Add debug commands search popup to Output Log --- .../Editor/GUI/ContextMenu/ContextMenuBase.cs | 32 ++- Source/Editor/GUI/ItemsListContextMenu.cs | 71 +++++- Source/Editor/Windows/OutputLogWindow.cs | 211 +++++++++++++++--- 3 files changed, 265 insertions(+), 49 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index 121b3bf31..f18bf0a1f 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -98,6 +98,16 @@ namespace FlaxEditor.GUI.ContextMenu /// public List ExternalPopups = new List(); + /// + /// Optional flag that can disable popup visibility based on window focus and use external control via Hide. + /// + public bool UseVisibilityControl = true; + + /// + /// Optional flag that can disable popup input capturing. Useful for transparent or visual-only popups. + /// + public bool UseInput = true; + /// /// Initializes a new instance of the class. /// @@ -230,8 +240,8 @@ namespace FlaxEditor.GUI.ContextMenu desc.HasBorder = false; desc.SupportsTransparency = false; desc.ShowInTaskbar = false; - desc.ActivateWhenFirstShown = true; - desc.AllowInput = true; + desc.ActivateWhenFirstShown = UseInput; + desc.AllowInput = UseInput; desc.AllowMinimize = false; desc.AllowMaximize = false; desc.AllowDragAndDrop = false; @@ -240,8 +250,11 @@ namespace FlaxEditor.GUI.ContextMenu desc.HasSizingFrame = false; OnWindowCreating(ref desc); _window = Platform.CreateWindow(ref desc); - _window.GotFocus += OnWindowGotFocus; - _window.LostFocus += OnWindowLostFocus; + if (UseVisibilityControl) + { + _window.GotFocus += OnWindowGotFocus; + _window.LostFocus += OnWindowLostFocus; + } // Attach to the window _parentCM = parent as ContextMenuBase; @@ -253,9 +266,12 @@ namespace FlaxEditor.GUI.ContextMenu return; _window.Show(); PerformLayout(); - _previouslyFocused = parentWin.FocusedControl; - Focus(); - OnShow(); + if (UseVisibilityControl) + { + _previouslyFocused = parentWin.FocusedControl; + Focus(); + OnShow(); + } } /// @@ -508,7 +524,7 @@ namespace FlaxEditor.GUI.ContextMenu base.Update(deltaTime); // Let root context menu to check if none of the popup windows - if (_parentCM == null && !IsForeground) + if (_parentCM == null && UseVisibilityControl && !IsForeground) { Hide(); } diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 8eeccf288..e23bb27f7 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -56,6 +56,11 @@ namespace FlaxEditor.GUI /// public event Action Clicked; + /// + /// Occurs when items gets focused. + /// + public event Action Focused; + /// /// The tint color of the text. /// @@ -141,6 +146,10 @@ namespace FlaxEditor.GUI protected virtual void GetTextRect(out Rectangle rect) { rect = new Rectangle(2, 0, Width - 4, Height); + + // Indent for drop panel items is handled by drop panel margin + if (Parent is not DropPanel) + rect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0); } /// @@ -155,10 +164,6 @@ namespace FlaxEditor.GUI if (IsMouseOver || IsFocused) Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted); - // Indent for drop panel items is handled by drop panel margin - if (Parent is not DropPanel) - textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0); - // Draw all highlights if (_highlights != null) { @@ -207,6 +212,14 @@ namespace FlaxEditor.GUI base.OnMouseLeave(); } + /// + public override void OnGotFocus() + { + base.OnGotFocus(); + + Focused?.Invoke(this); + } + /// public override int Compare(Control other) { @@ -227,6 +240,7 @@ namespace FlaxEditor.GUI private readonly Panel _scrollPanel; private List _categoryPanels; private bool _waitingForInput; + private string _customSearch; /// /// Event fired when any item in this popup menu gets clicked. @@ -290,12 +304,13 @@ namespace FlaxEditor.GUI LockChildrenRecursive(); + var searchText = _searchBox?.Text ?? _customSearch; var items = ItemsPanel.Children; for (int i = 0; i < items.Count; i++) { if (items[i] is Item item) { - item.UpdateFilter(_searchBox.Text); + item.UpdateFilter(searchText); item.UpdateScore(); } } @@ -309,13 +324,13 @@ namespace FlaxEditor.GUI { if (category.Children[j] is Item item2) { - item2.UpdateFilter(_searchBox.Text); + item2.UpdateFilter(searchText); item2.UpdateScore(); anyVisible |= item2.Visible; } } category.Visible = anyVisible; - if (string.IsNullOrEmpty(_searchBox.Text)) + if (string.IsNullOrEmpty(searchText)) category.Close(false); else category.Open(false); @@ -326,8 +341,8 @@ namespace FlaxEditor.GUI UnlockChildrenRecursive(); PerformLayout(true); - _searchBox.Focus(); - TextChanged?.Invoke(_searchBox.Text); + _searchBox?.Focus(); + TextChanged?.Invoke(searchText); } /// @@ -359,6 +374,14 @@ namespace FlaxEditor.GUI } } + /// + /// Removes all added items. + /// + public void ClearItems() + { + ItemsPanel.DisposeChildren(); + } + /// /// Sorts the items list (by item name by default). /// @@ -372,6 +395,34 @@ namespace FlaxEditor.GUI } } + /// + /// Focuses and scroll to the given item to be selected. + /// + /// The item to select. + public void SelectItem(Item item) + { + item.Focus(); + ScrollViewTo(item); + } + + /// + /// Applies custom search text query on the items list. Works even if search field is disabled + /// + /// The custom search text. Null to clear search. + public void Search(string text) + { + if (_searchBox != null) + { + _searchBox.SetText(text); + } + else + { + _customSearch = text; + if (VisibleInHierarchy) + OnSearchFilterChanged(); + } + } + /// /// Adds the item to the view and registers for the click event. /// @@ -453,6 +504,8 @@ namespace FlaxEditor.GUI _searchBox?.Clear(); UnlockChildrenRecursive(); PerformLayout(true); + if (_customSearch != null) + OnSearchFilterChanged(); } private List GetVisibleItems() diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 361f2e4b1..882730fed 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -129,7 +129,57 @@ namespace FlaxEditor.Windows /// private class CommandLineBox : TextBox { + private sealed class Item : ItemsListContextMenu.Item + { + public CommandLineBox Owner; + + public Item() + { + } + + protected override void GetTextRect(out Rectangle rect) + { + rect = new Rectangle(2, 0, Width - 4, Height); + } + + public override bool OnCharInput(char c) + { + if (Owner != null && (!Owner._searchPopup?.Visible ?? true)) + { + // Redirect input into search textbox while typing and using command history + Owner.Set(Owner.Text + c); + return true; + } + return false; + } + + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.Delete: + case KeyboardKeys.Backspace: + if (Owner != null && (!Owner._searchPopup?.Visible ?? true)) + { + // Redirect input into search textbox while typing and using command history + Owner.OnKeyDown(key); + return true; + } + break; + } + return base.OnKeyDown(key); + } + + public override void OnDestroy() + { + Owner = null; + base.OnDestroy(); + } + } + private OutputLogWindow _window; + private ItemsListContextMenu _searchPopup; + private bool _isSettingText; public CommandLineBox(float x, float y, float width, OutputLogWindow window) : base(false, x, y, width) @@ -138,6 +188,100 @@ namespace FlaxEditor.Windows _window = window; } + private void Set(string command) + { + _isSettingText = true; + SetText(command); + SetSelection(command.Length); + _isSettingText = false; + } + + private void ShowPopup(ref ItemsListContextMenu cm, IEnumerable commands, string searchText = null) + { + if (cm == null) + cm = new ItemsListContextMenu(180, 220, false); + else + cm.ClearItems(); + + // Add items + ItemsListContextMenu.Item lastItem = null; + foreach (var command in commands) + { + cm.AddItem(lastItem = new Item + { + Name = command, + Owner = this, + }); + lastItem.Focused += item => + { + // Set command + Set(item.Name); + }; + } + cm.ItemClicked += item => + { + // Execute command + OnKeyDown(KeyboardKeys.Return); + }; + + // Setup popup + var count = commands.Count(); + var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1); + cm.Height = 220; + if (cm.Height > totalHeight) + cm.Height = totalHeight; // Limit popup height if list is small + if (searchText != null) + { + cm.SortItems(); + cm.Search(searchText); + cm.UseVisibilityControl = false; + cm.UseInput = false; + } + + // Show popup + cm.Show(this, Float2.Zero, ContextMenuDirection.RightUp); + cm.ScrollViewTo(lastItem); + if (searchText != null) + { + RootWindow.Window.LostFocus += OnRootWindowLostFocus; + } + else + { + lastItem.Focus(); + } + } + + private void OnRootWindowLostFocus() + { + // Prevent popup from staying active when editor window looses focus + _searchPopup?.Hide(); + if (RootWindow?.Window != null) + RootWindow.Window.LostFocus -= OnRootWindowLostFocus; + } + + /// + protected override void OnTextChanged() + { + base.OnTextChanged(); + + // Skip when editing text from code + if (_isSettingText) + return; + + // Show commands search popup based on current text input + var text = Text.Trim(); + if (text.Length != 0) + { + DebugCommands.Search(text, out var matches); + if (matches.Length != 0) + { + ShowPopup(ref _searchPopup, matches, text); + return; + } + } + _searchPopup?.Hide(); + } + /// public override bool OnKeyDown(KeyboardKeys key) { @@ -146,6 +290,7 @@ namespace FlaxEditor.Windows case KeyboardKeys.Return: { // Run command + _searchPopup?.Hide(); var command = Text.Trim(); if (command.Length == 0) return true; @@ -175,8 +320,7 @@ namespace FlaxEditor.Windows else if (matches.Length == 1) { // Exact match - SetText(matches[0]); - SetSelection(Text.Length); + Set(matches[0]); } else { @@ -202,57 +346,60 @@ namespace FlaxEditor.Windows if (sharedLength > minLength) { // Use the largest shared part of all matches - SetText(matches[0].Substring(0, sharedLength)); - SetSelection(sharedLength); + Set(matches[0].Substring(0, sharedLength)); } } return true; } case KeyboardKeys.ArrowUp: { - if (TextLength == 0) + if (_searchPopup != null && _searchPopup.Visible) + { + // Route navigation to active popup + var focusedItem = _searchPopup.RootWindow.FocusedControl as Item; + if (focusedItem == null) + _searchPopup.SelectItem((Item)_searchPopup.ItemsPanel.Children.Last()); + else + _searchPopup.OnKeyDown(key); + } + else if (TextLength == 0) { if (_window._commandHistory != null && _window._commandHistory.Count != 0) { // Show command history popup - var cm = new ItemsListContextMenu(180, 220, false); - ItemsListContextMenu.Item lastItem = null; - var count = _window._commandHistory.Count; - for (int i = 0; i < count; i++) - { - var command = _window._commandHistory[i]; - cm.AddItem(lastItem = new ItemsListContextMenu.Item - { - Name = command, - }); - } - cm.ItemClicked += item => - { - SetText(item.Name); - SetSelection(Text.Length); - }; - var totalHeight = count * lastItem.Height + cm.ItemsPanel.Margin.Height + cm.ItemsPanel.Spacing * (count - 1); - if (cm.Height > totalHeight) - cm.Height = totalHeight; // Limit popup height if history is small - cm.Show(this, Float2.Zero, ContextMenuDirection.RightUp); - lastItem.Focus(); - cm.ScrollViewTo(lastItem); + _searchPopup?.Hide(); + ItemsListContextMenu cm = null; + ShowPopup(ref cm, _window._commandHistory); } } - else - { - // TODO: focus similar commands (via popup) - } return true; } case KeyboardKeys.ArrowDown: { - // Ignore + if (_searchPopup != null && _searchPopup.Visible) + { + // Route navigation to active popup + var focusedItem = _searchPopup.RootWindow.FocusedControl as Item; + if (focusedItem == null) + _searchPopup.SelectItem((Item)_searchPopup.ItemsPanel.Children.First()); + else + _searchPopup.OnKeyDown(key); + } return true; } } + return base.OnKeyDown(key); } + + /// + public override void OnDestroy() + { + _searchPopup?.Dispose(); + _searchPopup = null; + + base.OnDestroy(); + } } private InterfaceOptions.TimestampsFormats _timestampsFormats; From 9a654f729fa997f6812f0817902c9981252ad07b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 19:27:53 +0200 Subject: [PATCH 048/215] Fix context menu to resize instead of flash when showing while already visible --- .../Editor/GUI/ContextMenu/ContextMenuBase.cs | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index f18bf0a1f..40bf28c05 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -148,18 +148,22 @@ namespace FlaxEditor.GUI.ContextMenu public virtual void Show(Control parent, Float2 location, ContextMenuDirection? direction = null) { Assert.IsNotNull(parent); - - // Ensure to be closed - Hide(); + bool isAlreadyVisible = Visible && _window; + if (!isAlreadyVisible) + Hide(); // Peek parent control window var parentWin = parent.RootWindow; if (parentWin == null) + { + Hide(); return; + } // Check if show menu inside the other menu - then link as a child to prevent closing the calling menu window on lost focus if (_parentCM == null && parentWin.ChildrenCount == 1 && parentWin.Children[0] is ContextMenuBase parentCM) { + Hide(); parentCM.ShowChild(this, parentCM.PointFromScreen(parent.PointToScreen(location)), false); return; } @@ -231,40 +235,47 @@ namespace FlaxEditor.GUI.ContextMenu else _direction = isLeft ? ContextMenuDirection.LeftDown : ContextMenuDirection.RightDown; - // Create window - var desc = CreateWindowSettings.Default; - desc.Position = locationSS; - desc.StartPosition = WindowStartPosition.Manual; - desc.Size = dpiSize; - desc.Fullscreen = false; - desc.HasBorder = false; - desc.SupportsTransparency = false; - desc.ShowInTaskbar = false; - desc.ActivateWhenFirstShown = UseInput; - desc.AllowInput = UseInput; - desc.AllowMinimize = false; - desc.AllowMaximize = false; - desc.AllowDragAndDrop = false; - desc.IsTopmost = true; - desc.IsRegularWindow = false; - desc.HasSizingFrame = false; - OnWindowCreating(ref desc); - _window = Platform.CreateWindow(ref desc); - if (UseVisibilityControl) + if (isAlreadyVisible) { - _window.GotFocus += OnWindowGotFocus; - _window.LostFocus += OnWindowLostFocus; + _window.ClientBounds = new Rectangle(locationSS, dpiSize); } + else + { + // Create window + var desc = CreateWindowSettings.Default; + desc.Position = locationSS; + desc.StartPosition = WindowStartPosition.Manual; + desc.Size = dpiSize; + desc.Fullscreen = false; + desc.HasBorder = false; + desc.SupportsTransparency = false; + desc.ShowInTaskbar = false; + desc.ActivateWhenFirstShown = UseInput; + desc.AllowInput = UseInput; + desc.AllowMinimize = false; + desc.AllowMaximize = false; + desc.AllowDragAndDrop = false; + desc.IsTopmost = true; + desc.IsRegularWindow = false; + desc.HasSizingFrame = false; + OnWindowCreating(ref desc); + _window = Platform.CreateWindow(ref desc); + if (UseVisibilityControl) + { + _window.GotFocus += OnWindowGotFocus; + _window.LostFocus += OnWindowLostFocus; + } - // Attach to the window - _parentCM = parent as ContextMenuBase; - Parent = _window.GUI; + // Attach to the window + _parentCM = parent as ContextMenuBase; + Parent = _window.GUI; - // Show - Visible = true; - if (_window == null) - return; - _window.Show(); + // Show + Visible = true; + if (_window == null) + return; + _window.Show(); + } PerformLayout(); if (UseVisibilityControl) { From 5d7eba05ea454f89e3aa1409643173d8b5ee2ec5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 19:33:38 +0200 Subject: [PATCH 049/215] Add `GetFields` and `GetMethods` to binary module api --- Source/Engine/Scripting/BinaryModule.cpp | 31 ++++++++++++++++++++++-- Source/Engine/Scripting/BinaryModule.h | 20 +++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index ac0d6da2d..2f3483a07 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1212,6 +1212,16 @@ bool ManagedBinaryModule::IsLoaded() const #endif } +void ManagedBinaryModule::GetMethods(const ScriptingTypeHandle& typeHandle, Array& methods) +{ + const ScriptingType& type = typeHandle.GetType(); + if (type.ManagedClass) + { + const auto& mMethods = type.ManagedClass->GetMethods(); + methods.Add((void* const*)mMethods.Get(), mMethods.Count()); + } +} + void* ManagedBinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, int32 numParams) { const ScriptingType& type = typeHandle.GetType(); @@ -1379,6 +1389,23 @@ void ManagedBinaryModule::GetMethodSignature(void* method, ScriptingTypeMethodSi #else #define ManagedBinaryModuleFieldIsPropertyBit (uintptr)(1ul << 31) #endif +#define GetManagedBinaryModulePropertyHandle(ptr) ((uintptr)ptr & ~ManagedBinaryModuleFieldIsPropertyBit) +#define SetManagedBinaryModulePropertyHandle(ptr) (void*)((uintptr)ptr | ManagedBinaryModuleFieldIsPropertyBit) + +void ManagedBinaryModule::GetFields(const ScriptingTypeHandle& typeHandle, Array& fields) +{ + const ScriptingType& type = typeHandle.GetType(); + if (type.ManagedClass) + { + const auto& mFields = type.ManagedClass->GetFields(); + const auto& mProperties = type.ManagedClass->GetProperties(); + fields.EnsureCapacity(fields.Count() + mFields.Count() + mProperties.Count()); + for (MField* field : mFields) + fields.Add(field); + for (MProperty* property : mProperties) + fields.Add(SetManagedBinaryModulePropertyHandle(property)); + } +} void* ManagedBinaryModule::FindField(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name) { @@ -1388,7 +1415,7 @@ void* ManagedBinaryModule::FindField(const ScriptingTypeHandle& typeHandle, cons { result = type.ManagedClass->GetProperty(name.Get()); if (result) - result = (void*)((uintptr)result | ManagedBinaryModuleFieldIsPropertyBit); + result = SetManagedBinaryModulePropertyHandle(result); } return result; } @@ -1398,7 +1425,7 @@ void ManagedBinaryModule::GetFieldSignature(void* field, ScriptingTypeFieldSigna #if USE_CSHARP if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit) { - const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit); + const auto mProperty = (MProperty*)GetManagedBinaryModulePropertyHandle(field); fieldSignature.Name = mProperty->GetName(); fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mProperty->GetType())); fieldSignature.IsStatic = mProperty->IsStatic(); diff --git a/Source/Engine/Scripting/BinaryModule.h b/Source/Engine/Scripting/BinaryModule.h index 6d9baf5ce..e014c44d1 100644 --- a/Source/Engine/Scripting/BinaryModule.h +++ b/Source/Engine/Scripting/BinaryModule.h @@ -115,6 +115,15 @@ public: return TypeNameToTypeIndex.TryGet(typeName, typeIndex); } + /// + /// Gets handles of all method in a given scripting type. + /// + /// The type to find methods inside it. + /// The output list of method pointers. + virtual void GetMethods(const ScriptingTypeHandle& typeHandle, Array& methods) + { + } + /// /// Tries to find a method in a given scripting type by the method name and parameters count. /// @@ -158,6 +167,15 @@ public: { } + /// + /// Gets handles of all fields in a given scripting type. + /// + /// The type to find fields inside it. + /// The output list of field pointers. + virtual void GetFields(const ScriptingTypeHandle& typeHandle, Array& fields) + { + } + /// /// Tries to find a field in a given scripting type by the field name. /// @@ -318,10 +336,12 @@ public: // [BinaryModule] const StringAnsi& GetName() const override; bool IsLoaded() const override; + void GetMethods(const ScriptingTypeHandle& typeHandle, Array& methods) override; void* FindMethod(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, int32 numParams = 0) override; void* FindMethod(const ScriptingTypeHandle& typeHandle, const ScriptingTypeMethodSignature& signature) override; bool InvokeMethod(void* method, const Variant& instance, Span paramValues, Variant& result) override; void GetMethodSignature(void* method, ScriptingTypeMethodSignature& signature) override; + void GetFields(const ScriptingTypeHandle& typeHandle, Array& fields) override; void* FindField(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name) override; void GetFieldSignature(void* field, ScriptingTypeFieldSignature& fieldSignature) override; bool GetFieldValue(void* field, const Variant& instance, Variant& result) override; From e324d3276910377f092d59fbb3bc0f5ec67f62f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 19:34:42 +0200 Subject: [PATCH 050/215] Add async init and tint coloring based on type to debug commands --- Source/Editor/Windows/OutputLogWindow.cs | 14 +++++ Source/Engine/Debug/DebugCommands.cpp | 80 +++++++++++++++++++++--- Source/Engine/Debug/DebugCommands.h | 28 +++++++++ 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 882730fed..f22befe9c 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -212,6 +212,11 @@ namespace FlaxEditor.Windows Name = command, Owner = this, }); + var flags = DebugCommands.GetCommandFlags(command); + if (flags.HasFlag(DebugCommands.CommandFlags.Exec)) + lastItem.TintColor = new Color(0.85f, 0.85f, 1.0f, 1.0f); + else if (flags.HasFlag(DebugCommands.CommandFlags.Read) && !flags.HasFlag(DebugCommands.CommandFlags.Write)) + lastItem.TintColor = new Color(0.85f, 0.85f, 0.85f, 1.0f); lastItem.Focused += item => { // Set command @@ -259,6 +264,15 @@ namespace FlaxEditor.Windows RootWindow.Window.LostFocus -= OnRootWindowLostFocus; } + /// + public override void OnGotFocus() + { + // Precache debug commands to reduce time-to-interactive + DebugCommands.InitAsync(); + + base.OnGotFocus(); + } + /// protected override void OnTextChanged() { diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index a9ae862f2..b041e779f 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Engine/EngineService.h" #include "Engine/Threading/Threading.h" +#include "Engine/Threading/Task.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/Scripting.h" @@ -103,6 +104,7 @@ namespace { CriticalSection Locker; bool Inited = false; + Task* AsyncTask = nullptr; Array Commands; void FindDebugCommands(BinaryModule* module) @@ -208,15 +210,45 @@ namespace void InitCommands() { + ASSERT_LOW_LAYER(!Inited); PROFILE_CPU(); - Inited = true; + + // Cache existing modules const auto& modules = BinaryModule::GetModules(); for (BinaryModule* module : modules) { FindDebugCommands(module); } + + // Link for modules load/reload actions Scripting::BinaryModuleLoaded.Bind(&FindDebugCommands); Scripting::ScriptsReloading.Bind(&OnScriptsReloading); + + // Mark as done + Locker.Lock(); + Inited = true; + AsyncTask = nullptr; + Locker.Unlock(); + } + + void EnsureInited() + { + // Check current state + Locker.Lock(); + bool inited = Inited; + Locker.Unlock(); + if (inited) + return; + + // Wait for any async task + if (AsyncTask) + AsyncTask->Wait(); + + // Do sync init if still not inited + Locker.Lock(); + if (!Inited) + InitCommands(); + Locker.Unlock(); } } @@ -231,6 +263,8 @@ public: void Dispose() override { // Cleanup + if (AsyncTask) + AsyncTask->Wait(); ScopeLock lock(Locker); Scripting::BinaryModuleLoaded.Unbind(&FindDebugCommands); Scripting::ScriptsReloading.Unbind(&OnScriptsReloading); @@ -264,9 +298,8 @@ void DebugCommands::Execute(StringView command) } // Ensure that commands cache has been created + EnsureInited(); ScopeLock lock(Locker); - if (!Inited) - InitCommands(); // Find command to run for (const CommandData& cmd : Commands) @@ -290,9 +323,8 @@ void DebugCommands::Search(StringView searchText, Array& matches, bo String searchTextCopy = searchText; searchText = searchTextCopy; + EnsureInited(); ScopeLock lock(Locker); - if (!Inited) - InitCommands(); if (startsWith) { @@ -316,13 +348,45 @@ void DebugCommands::Search(StringView searchText, Array& matches, bo } } -bool DebugCommands::Iterate(const StringView& searchText, int32& index) +void DebugCommands::InitAsync() { ScopeLock lock(Locker); + if (Inited) + return; + AsyncTask = Task::StartNew(InitCommands); +} + +DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command) +{ + CommandFlags result = CommandFlags::None; + // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush) + String commandCopy = command; + command = commandCopy; + EnsureInited(); + for (auto& e : Commands) + { + if (e.Name == command) + { + if (e.Method) + result |= CommandFlags::Exec; + else if (e.Field) + result |= CommandFlags::ReadWrite; + if (e.MethodGet) + result |= CommandFlags::Read; + if (e.MethodSet) + result |= CommandFlags::Write; + break; + } + } + return result; +} + +bool DebugCommands::Iterate(const StringView& searchText, int32& index) +{ + EnsureInited(); if (index >= 0) { - if (!Inited) - InitCommands(); + ScopeLock lock(Locker); while (index < Commands.Count()) { auto& command = Commands.Get()[index]; diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h index f89cba371..e82963792 100644 --- a/Source/Engine/Debug/DebugCommands.h +++ b/Source/Engine/Debug/DebugCommands.h @@ -11,6 +11,21 @@ API_CLASS(static) class FLAXENGINE_API DebugCommands { DECLARE_SCRIPTING_TYPE_MINIMAL(DebugCommands); + // Types of debug command flags. + API_ENUM(Attributes="Flags") enum class CommandFlags + { + // Incorrect or missing command. + None = 0, + // Executable method. + Exec = 1, + // Can get value. + Read = 2, + // Can set value. + Write = 4, + // Can get and set value. + ReadWrite = Read | Write, + }; + public: /// /// Executes the command. @@ -26,7 +41,20 @@ public: /// True if filter commands that start with a specific search text, otherwise will return commands that contain a specific query. API_FUNCTION() static void Search(StringView searchText, API_PARAM(Out) Array& matches, bool startsWith = false); + /// + /// Starts asynchronous debug commands caching. Cna be used to minimize time-to-interactive when using console interface or when using scripted actions. + /// + API_FUNCTION() static void InitAsync(); + + /// + /// Returns flags of the command. + /// + /// The full name of the command. + API_FUNCTION() static CommandFlags GetCommandFlags(StringView command); + public: static bool Iterate(const StringView& searchText, int32& index); static StringView GetCommandName(int32 index); }; + +DECLARE_ENUM_OPERATORS(DebugCommands::CommandFlags); From 302fc2feb1a7f441bf818e0f44d908666777e933 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 19:35:24 +0200 Subject: [PATCH 051/215] Add various engine systems to debug commands --- Source/Engine/Audio/Audio.h | 2 +- Source/Engine/Audio/AudioDevice.h | 8 ++++++-- Source/Engine/Engine/Globals.h | 2 +- Source/Engine/Engine/Screen.h | 2 +- Source/Engine/Engine/Time.h | 2 +- Source/Engine/Physics/Physics.h | 2 +- Source/Engine/Profiler/ProfilerGPU.h | 4 ++-- Source/Engine/Profiler/ProfilingTools.h | 2 +- Source/Engine/Streaming/Streaming.h | 2 +- Source/Engine/Utilities/Screenshot.h | 2 +- 10 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Audio/Audio.h b/Source/Engine/Audio/Audio.h index 5a82a6da8..05ec4c9d2 100644 --- a/Source/Engine/Audio/Audio.h +++ b/Source/Engine/Audio/Audio.h @@ -11,7 +11,7 @@ /// /// The audio service used for music and sound effects playback. /// -API_CLASS(Static) class FLAXENGINE_API Audio +API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Audio { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Audio); friend class AudioStreamingHandler; diff --git a/Source/Engine/Audio/AudioDevice.h b/Source/Engine/Audio/AudioDevice.h index 1a816f627..1d05e41cf 100644 --- a/Source/Engine/Audio/AudioDevice.h +++ b/Source/Engine/Audio/AudioDevice.h @@ -9,7 +9,7 @@ /// API_CLASS(NoSpawn) class AudioDevice : public ScriptingObject { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(AudioDevice); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(AudioDevice); explicit AudioDevice() : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) @@ -31,7 +31,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AudioDevice); } public: - /// /// The device name. /// @@ -41,4 +40,9 @@ public: /// The internal device name used by the audio backend. /// StringAnsi InternalName; + + String ToString() const override + { + return Name; + } }; diff --git a/Source/Engine/Engine/Globals.h b/Source/Engine/Engine/Globals.h index 5e58d5039..f204aff29 100644 --- a/Source/Engine/Engine/Globals.h +++ b/Source/Engine/Engine/Globals.h @@ -8,7 +8,7 @@ /// /// Global engine variables container. /// -API_CLASS(Static) class FLAXENGINE_API Globals +API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Globals { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 7322fa368..be8fef0fb 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -10,7 +10,7 @@ /// /// Helper class to access display information. /// -API_CLASS(Static) class FLAXENGINE_API Screen +API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Screen { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index ff81fb93e..e61b0f913 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -10,7 +10,7 @@ /// /// Game ticking and timing system. /// -API_CLASS(Static) class FLAXENGINE_API Time +API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Time { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time); friend class Engine; diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index efd11b184..a2faab2f0 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -41,7 +41,7 @@ public: /// /// Gets the current gravity force. /// - API_PROPERTY() static Vector3 GetGravity(); + API_PROPERTY(Attributes="DebugCommand") static Vector3 GetGravity(); /// /// Sets the current gravity force. diff --git a/Source/Engine/Profiler/ProfilerGPU.h b/Source/Engine/Profiler/ProfilerGPU.h index c316abb15..7652a03ad 100644 --- a/Source/Engine/Profiler/ProfilerGPU.h +++ b/Source/Engine/Profiler/ProfilerGPU.h @@ -132,12 +132,12 @@ public: /// /// True if GPU profiling is enabled, otherwise false to disable events collecting and GPU timer queries usage. Can be changed during rendering. /// - API_FIELD() static bool Enabled; + API_FIELD(Attributes="DebugCommand") static bool Enabled; /// /// True if GPU events are enabled (see GPUContext::EventBegin), otherwise false. Cannot be changed during rendering. /// - API_FIELD() static bool EventsEnabled; + API_FIELD(Attributes="DebugCommand") static bool EventsEnabled; /// /// The current frame buffer to collect events. diff --git a/Source/Engine/Profiler/ProfilingTools.h b/Source/Engine/Profiler/ProfilingTools.h index 7969cca55..43242baf2 100644 --- a/Source/Engine/Profiler/ProfilingTools.h +++ b/Source/Engine/Profiler/ProfilingTools.h @@ -127,7 +127,7 @@ public: /// /// Controls the engine profiler (CPU, GPU, etc.) usage. /// - API_PROPERTY() static bool GetEnabled(); + API_PROPERTY(Attributes="DebugCommand") static bool GetEnabled(); /// /// Controls the engine profiler (CPU, GPU, etc.) usage. diff --git a/Source/Engine/Streaming/Streaming.h b/Source/Engine/Streaming/Streaming.h index d492d74e9..7a83f7e8e 100644 --- a/Source/Engine/Streaming/Streaming.h +++ b/Source/Engine/Streaming/Streaming.h @@ -33,7 +33,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Streaming); /// /// Gets streaming statistics. /// - API_PROPERTY() static StreamingStats GetStats(); + API_PROPERTY(Attributes="DebugCommand") static StreamingStats GetStats(); /// /// Requests the streaming update for all the loaded resources. Use it to refresh content streaming after changing configuration. diff --git a/Source/Engine/Utilities/Screenshot.h b/Source/Engine/Utilities/Screenshot.h index 82882f707..976865062 100644 --- a/Source/Engine/Utilities/Screenshot.h +++ b/Source/Engine/Utilities/Screenshot.h @@ -38,5 +38,5 @@ API_CLASS(Static) class FLAXENGINE_API Screenshot /// Remember that downloading data from the GPU may take a while so screenshot may be taken one or more frames later due to latency. /// /// The custom file location. Use null or empty to use default one. - API_FUNCTION() static void Capture(const StringView& path = StringView::Empty); + API_FUNCTION(Attributes="DebugCommand") static void Capture(const StringView& path = StringView::Empty); }; From 86e21a53ff95a3a6cda50faca59e1680f561bea6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 22:31:07 +0200 Subject: [PATCH 052/215] Add improved value printing of debug command result --- Source/Engine/Debug/DebugCommands.cpp | 73 ++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index b041e779f..2c9827b66 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -2,6 +2,7 @@ #include "DebugCommands.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Engine/EngineService.h" #include "Engine/Threading/Threading.h" @@ -31,11 +32,13 @@ struct CommandData // Get command signature Array> sigParams; + VariantType sigValue; if (Method) { ScriptingTypeMethodSignature sig; Module->GetMethodSignature(Method, sig); sigParams = MoveTemp(sig.Params); + sigValue = MoveTemp(sig.ReturnType); } else if (Field) { @@ -44,6 +47,7 @@ struct CommandData auto& p = sigParams.AddOne(); p.IsOut = false; p.Type = sig.ValueType; + sigValue = MoveTemp(sig.ValueType); } else if (MethodSet && args.HasChars()) { @@ -52,6 +56,12 @@ struct CommandData sigParams = MoveTemp(sig.Params); sigParams.Resize(1); } + else if (MethodGet && args.IsEmpty()) + { + ScriptingTypeMethodSignature sig; + Module->GetMethodSignature(MethodGet, sig); + sigValue = MoveTemp(sig.ReturnType); + } // Parse arguments if (args == StringView(TEXT("?"), 1)) @@ -91,11 +101,72 @@ struct CommandData { Module->InvokeMethod(MethodSet, Variant::Null, ToSpan(params), result); } + else if (args.HasChars()) + { + LOG(Warning, "Property {} doesn't have a setter (read-only)", Name); + } + else if (args.IsEmpty()) + { + LOG(Warning, "Property {} doesn't have a getter (write-only)", Name); + } // Print result if (result != Variant()) { - LOG_STR(Info, result.ToString()); + String str = result.ToString(); + if (result.Type.Type == VariantType::Array) + { + // Prettify array printing + auto& resultArray = result.AsArray(); + StringBuilder sb; + sb.Append('['); + for (int32 i = 0; i < resultArray.Count(); i++) + { + if (i > 0) + sb.Append(',').Append(' '); + sb.Append(resultArray[i].ToString()); + if (i > 30) // Limit on too large values + { + sb.Append(TEXT("...")); + break; + } + } + sb.Append(']'); + str = sb.ToString(); + } + else if (result.Type.Type == VariantType::Structure) + { + // Prettify structure printing + ScriptingTypeHandle resultType = Scripting::FindScriptingType(result.Type.GetTypeName()); + if (resultType) + { + Array fields; + resultType.Module->GetFields(resultType, fields); + StringBuilder sb; + sb.Append('{'); + Variant fieldValue; + ScriptingTypeFieldSignature fieldSig; + bool first = true; + for (void* field : fields) + { + if (!resultType.Module->GetFieldValue(field, result, fieldValue)) + { + resultType.Module->GetFieldSignature(field, fieldSig); + if (!first) + sb.Append(','); + first = false; + sb.Append(' ').Append(String(fieldSig.Name)).Append(':').Append(' ').Append(fieldValue.ToString()); + } + } + sb.Append(' ').Append('}'); + str = sb.ToString(); + } + } + LOG_STR(Info, str); + } + else if (args.IsEmpty() && sigValue.Type != VariantType::Void && sigValue.Type != VariantType::Null) + { + LOG_STR(Info, TEXT("null")); } } }; From 8338ec60769e0f2b5a13859bb98fe20593f97fbe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 22:32:02 +0200 Subject: [PATCH 053/215] Fix regression --- Source/Editor/Options/InterfaceOptions.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index ca053dcab..30cb64aa8 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -223,11 +223,15 @@ namespace FlaxEditor.Options /// [DefaultValue(TextAlignment.Center)] [EditorDisplay("Interface"), EditorOrder(321)] - public TextAlignment TooltipTextAlignment { get => _tooltipTextAlignment; + public TextAlignment TooltipTextAlignment + { + get => _tooltipTextAlignment; set { _tooltipTextAlignment = value; - Style.Current.SharedTooltip.HorizontalTextAlignment = value; + var tooltip = Style.Current?.SharedTooltip; + if (tooltip != null) + tooltip.HorizontalTextAlignment = value; } } From 62ece0d92e1798f97e1b7618f6f04d495e16132d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 23:38:25 +0200 Subject: [PATCH 054/215] Fix crash when using attributes cache after hot-reload in editor --- .../Engine/Scripting/ManagedCLR/MAssembly.h | 1 + Source/Engine/Scripting/ManagedCLR/MEvent.h | 1 + Source/Engine/Scripting/ManagedCLR/MField.h | 1 + Source/Engine/Scripting/ManagedCLR/MMethod.h | 1 + .../Engine/Scripting/ManagedCLR/MProperty.h | 1 + Source/Engine/Scripting/Runtime/DotNet.cpp | 39 +++++++++++++++++++ 6 files changed, 44 insertions(+) diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index 169cfd7e3..8e9b21509 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -14,6 +14,7 @@ /// class FLAXENGINE_API MAssembly { + friend MCore; friend MDomain; friend Scripting; diff --git a/Source/Engine/Scripting/ManagedCLR/MEvent.h b/Source/Engine/Scripting/ManagedCLR/MEvent.h index 5692f3dc6..5fe4da1a1 100644 --- a/Source/Engine/Scripting/ManagedCLR/MEvent.h +++ b/Source/Engine/Scripting/ManagedCLR/MEvent.h @@ -10,6 +10,7 @@ class FLAXENGINE_API MEvent { friend MClass; + friend MCore; protected: #if USE_MONO diff --git a/Source/Engine/Scripting/ManagedCLR/MField.h b/Source/Engine/Scripting/ManagedCLR/MField.h index 5475f4535..7c0d89287 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.h +++ b/Source/Engine/Scripting/ManagedCLR/MField.h @@ -11,6 +11,7 @@ class FLAXENGINE_API MField { friend MClass; + friend MCore; protected: #if USE_MONO diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index f2b127871..4731e0723 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -16,6 +16,7 @@ class FLAXENGINE_API MMethod friend MClass; friend MProperty; friend MEvent; + friend MCore; protected: #if USE_MONO diff --git a/Source/Engine/Scripting/ManagedCLR/MProperty.h b/Source/Engine/Scripting/ManagedCLR/MProperty.h index dbbed8ab3..975d62b8c 100644 --- a/Source/Engine/Scripting/ManagedCLR/MProperty.h +++ b/Source/Engine/Scripting/ManagedCLR/MProperty.h @@ -12,6 +12,7 @@ class FLAXENGINE_API MProperty { friend MClass; + friend MCore; protected: #if USE_MONO diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 4a71e9c2f..38df5c16f 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -336,7 +336,46 @@ void MCore::ReloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) for (auto e : CachedClassHandles) + { + e.Value->_hasCachedAttributes = false; e.Value->_attributes.Clear(); + } + for (auto e : CachedAssemblyHandles) + { + MAssembly* a = e.Value; + if (!a->IsLoaded() || !a->_hasCachedClasses) + continue; + for (auto q : a->GetClasses()) + { + MClass* c = q.Value; + c->_hasCachedAttributes = false; + c->_attributes.Clear(); + if (c->_hasCachedMethods) + { + for (MMethod* m : c->GetMethods()) + { + m->_hasCachedAttributes = false; + m->_attributes.Clear(); + } + } + if (c->_hasCachedFields) + { + for (MField* f : c->GetFields()) + { + f->_hasCachedAttributes = false; + f->_attributes.Clear(); + } + } + if (c->_hasCachedProperties) + { + for (MProperty* p : c->GetProperties()) + { + p->_hasCachedAttributes = false; + p->_attributes.Clear(); + } + } + } + } static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext")); CallStaticMethod(ReloadScriptingAssemblyLoadContextPtr); From 2d371fd05f76d054af56429a343532843439d8fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 24 Oct 2024 23:38:36 +0200 Subject: [PATCH 055/215] Fix `DebugCommands::InitAsync` when called every frame --- Source/Engine/Debug/DebugCommands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 2c9827b66..549612fd8 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -422,7 +422,7 @@ void DebugCommands::Search(StringView searchText, Array& matches, bo void DebugCommands::InitAsync() { ScopeLock lock(Locker); - if (Inited) + if (Inited || AsyncTask) return; AsyncTask = Task::StartNew(InitCommands); } From dcd7b4b6c315c89588ee68c72b540649b78d7c97 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Oct 2024 13:21:40 +0200 Subject: [PATCH 056/215] Fix crash when boxing native non-POD structure into managed data --- Source/Engine/Engine/NativeInterop.cs | 52 +++++++++++++++---- .../Bindings/BindingsGenerator.CSharp.cs | 1 - 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 3591df299..35fe70221 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -55,6 +55,9 @@ namespace FlaxEngine.Interop private static Dictionary assemblyOwnedNativeLibraries = new(); internal static AssemblyLoadContext scriptingAssemblyLoadContext; + private delegate TInternal ToNativeDelegate(T value); + private delegate T ToManagedDelegate(TInternal value); + [System.Diagnostics.DebuggerStepThrough] private static IntPtr NativeLibraryImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) { @@ -490,6 +493,7 @@ namespace FlaxEngine.Interop internal delegate void MarshalFieldTypedDelegate(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); internal delegate void* GetBasePointer(ref T fieldOwner); + internal static Delegate toManagedDelegate; internal static FieldInfo[] marshallableFields; internal static int[] marshallableFieldOffsets; internal static MarshalFieldTypedDelegate[] toManagedFieldMarshallers; @@ -612,16 +616,32 @@ namespace FlaxEngine.Interop MethodInfo toManagedMethod; if (type.IsValueType) { - string methodName; - if (type == typeof(IntPtr)) - methodName = nameof(MarshalHelperValueType.ToManagedPointer); - else if (type == typeof(ManagedHandle)) - methodName = nameof(MarshalHelperValueType.ToManagedHandle); - else if (marshallableFields != null) - methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); + // Non-POD structures use internal layout (eg. SpriteHandleManaged in C++ with SpriteHandleMarshaller.SpriteHandleInternal in C#) so convert C++ data into C# data + var attr = type.GetCustomAttribute(); + toManagedMethod = attr?.NativeType.GetMethod("ToManaged", BindingFlags.Static | BindingFlags.NonPublic); + if (toManagedMethod != null) + { + // TODO: optimize via delegate call rather than method invoke + var internalType = toManagedMethod.GetParameters()[0].ParameterType; + var types = new Type[] { type, internalType }; + toManagedDelegate = toManagedMethod.CreateDelegate(typeof(ToManagedDelegate<,>).MakeGenericType(types)); + //toManagedDelegate = toManagedMethod.CreateDelegate();//.CreateDelegate(typeof(ToManagedDelegate<,>).MakeGenericType(type, toManagedMethod.GetParameters()[0].ParameterType)); + string methodName = nameof(MarshalInternalHelper.ToManagedMarshaller); + toManagedMethod = typeof(MarshalInternalHelper<,>).MakeGenericType(types).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } else - methodName = nameof(MarshalHelperValueType.ToManaged); - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + { + string methodName; + if (type == typeof(IntPtr)) + methodName = nameof(MarshalHelperValueType.ToManagedPointer); + else if (type == typeof(ManagedHandle)) + methodName = nameof(MarshalHelperValueType.ToManagedHandle); + else if (marshallableFields != null) + methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); + else + methodName = nameof(MarshalHelperValueType.ToManaged); + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } } else if (type.IsArray) { @@ -1065,6 +1085,17 @@ namespace FlaxEngine.Interop } } + internal static class MarshalInternalHelper where T : struct + where TInternal : struct + { + internal static void ToManagedMarshaller(ref T managedValue, IntPtr nativePtr, bool byRef) + { + ToManagedDelegate toManaged = Unsafe.As>(MarshalHelper.toManagedDelegate); + TInternal intern = Unsafe.Read(nativePtr.ToPointer()); + managedValue = toManaged(Unsafe.Read(nativePtr.ToPointer())); + } + } + internal static class MarshalHelperValueType where T : struct { internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) @@ -1504,8 +1535,6 @@ namespace FlaxEngine.Interop private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; private static uint pinnedAllocationsPointer = 0; - private delegate TInternal ToNativeDelegate(T value); - private delegate IntPtr UnboxerDelegate(object value, object converter); private static ConcurrentDictionary unboxers = new(1, 3); @@ -1683,6 +1712,7 @@ namespace FlaxEngine.Interop internal static class TypeHelpers { public static readonly int MarshalSize; + static TypeHelpers() { Type type = typeof(T); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 9642c8b02..551417ebf 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1855,7 +1855,6 @@ namespace Flax.Build.Bindings {{structureInfo.Name}}Internal unmanaged; public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed; public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; } - //public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) { {{marshallerFullName}}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; } public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged; public {{structureInfo.Name}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; } public void Free() => NativeToManaged.Free(unmanaged); From 46c1cc9a49f1c5e884b2ebf42acdf90bacd93b2e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Oct 2024 13:25:15 +0200 Subject: [PATCH 057/215] Add nested variant printing in debug command output --- Source/Engine/Debug/DebugCommands.cpp | 108 ++++++++++++++------------ 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 549612fd8..1dabec081 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -26,6 +26,61 @@ struct CommandData void* MethodSet = nullptr; void* Field = nullptr; + static void PrettyPrint(StringBuilder& sb, const Variant& value) + { + if (value.Type.Type == VariantType::Array) + { + // Prettify array printing + auto& resultArray = value.AsArray(); + sb.Append('['); + for (int32 i = 0; i < resultArray.Count(); i++) + { + if (i > 0) + sb.Append(',').Append(' '); + PrettyPrint(sb, resultArray[i]); + if (i > 30) // Limit on too large values + { + sb.Append(TEXT("...")); + break; + } + } + sb.Append(']'); + } + else if (value.Type.Type == VariantType::Structure) + { + // Prettify structure printing + ScriptingTypeHandle resultType = Scripting::FindScriptingType(value.Type.GetTypeName()); + if (resultType) + { + Array fields; + resultType.Module->GetFields(resultType, fields); + sb.Append('{'); + Variant fieldValue; + ScriptingTypeFieldSignature fieldSig; + bool first = true; + for (void* field : fields) + { + resultType.Module->GetFieldSignature(field, fieldSig); + if (fieldSig.IsStatic) + continue; + if (!resultType.Module->GetFieldValue(field, value, fieldValue)) + { + if (!first) + sb.Append(','); + first = false; + sb.Append(' ').Append(String(fieldSig.Name)).Append(':').Append(' '); + PrettyPrint(sb, fieldValue); + } + } + sb.Append(' ').Append('}'); + } + } + else + { + sb.Append(value.ToString()); + } + } + void Invoke(StringView args) const { PROFILE_CPU(); @@ -113,56 +168,9 @@ struct CommandData // Print result if (result != Variant()) { - String str = result.ToString(); - if (result.Type.Type == VariantType::Array) - { - // Prettify array printing - auto& resultArray = result.AsArray(); - StringBuilder sb; - sb.Append('['); - for (int32 i = 0; i < resultArray.Count(); i++) - { - if (i > 0) - sb.Append(',').Append(' '); - sb.Append(resultArray[i].ToString()); - if (i > 30) // Limit on too large values - { - sb.Append(TEXT("...")); - break; - } - } - sb.Append(']'); - str = sb.ToString(); - } - else if (result.Type.Type == VariantType::Structure) - { - // Prettify structure printing - ScriptingTypeHandle resultType = Scripting::FindScriptingType(result.Type.GetTypeName()); - if (resultType) - { - Array fields; - resultType.Module->GetFields(resultType, fields); - StringBuilder sb; - sb.Append('{'); - Variant fieldValue; - ScriptingTypeFieldSignature fieldSig; - bool first = true; - for (void* field : fields) - { - if (!resultType.Module->GetFieldValue(field, result, fieldValue)) - { - resultType.Module->GetFieldSignature(field, fieldSig); - if (!first) - sb.Append(','); - first = false; - sb.Append(' ').Append(String(fieldSig.Name)).Append(':').Append(' ').Append(fieldValue.ToString()); - } - } - sb.Append(' ').Append('}'); - str = sb.ToString(); - } - } - LOG_STR(Info, str); + StringBuilder sb; + PrettyPrint(sb, result); + LOG_STR(Info, sb.ToStringView()); } else if (args.IsEmpty() && sigValue.Type != VariantType::Void && sigValue.Type != VariantType::Null) { From fa9ce1d346e1add2060b237c883d41471e98abcb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 25 Oct 2024 15:59:38 +0200 Subject: [PATCH 058/215] Code formatting #2969 --- Source/Engine/Core/Types/Nullable.h | 66 +++++------------------------ 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 43fe5342a..4bebfbf85 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -19,7 +19,7 @@ private: union { - T _value; + T _value; Dummy _dummy; }; bool _hasValue; @@ -32,15 +32,12 @@ public: : _dummy() , _hasValue(false) { - // Value is not initialized. } ~Nullable() { if (_hasValue) - { _value.~T(); - } } /// @@ -82,9 +79,7 @@ public: Nullable(Nullable&& other) noexcept { if (other._hasValue) - { new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) - } _hasValue = other._hasValue; other.Reset(); @@ -94,12 +89,10 @@ public: /// Reassigns the wrapped value by copying. /// template::Value>::Type> - auto operator=(const T& value) -> Nullable& + Nullable& operator=(const T& value) { if (_hasValue) - { _value.~T(); - } new (&_value) T(value); // Placement new (copy constructor) _hasValue = true; @@ -110,12 +103,10 @@ public: /// /// Reassigns the wrapped value by moving. /// - auto operator=(T&& value) noexcept -> Nullable& + Nullable& operator=(T&& value) noexcept { if (_hasValue) - { _value.~T(); - } new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; @@ -127,17 +118,13 @@ public: /// Reassigns the wrapped value by copying another . /// template::Value>::Type> - auto operator=(const Nullable& other) -> Nullable& + Nullable& operator=(const Nullable& other) { if (_hasValue) - { _value.~T(); - } if (other._hasValue) - { new (&_value) T(other._value); // Placement new (copy constructor) - } _hasValue = other._hasValue; // Set the flag AFTER the value is copied. @@ -147,17 +134,13 @@ public: /// /// Reassigns the wrapped value by moving another . /// - auto operator=(Nullable&& other) noexcept -> Nullable& + Nullable& operator=(Nullable&& other) noexcept { if (this == &other) - { return *this; - } if (_hasValue) - { _value.~T(); - } if (other._hasValue) { @@ -228,10 +211,7 @@ public: FORCE_INLINE void SetValue(const T& value) { if (_hasValue) - { _value.~T(); - } - new (&_value) T(value); // Placement new (copy constructor) _hasValue = true; // Set the flag AFTER the value is copied. } @@ -243,10 +223,7 @@ public: FORCE_INLINE void SetValue(T&& value) noexcept { if (_hasValue) - { _value.~T(); - } - new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; // Set the flag AFTER the value is moved. } @@ -259,10 +236,7 @@ public: FORCE_INLINE bool TrySet(const T& value) { if (_hasValue) - { return false; - } - new (&_value) T(value); // Placement new (copy constructor) _hasValue = true; // Set the flag AFTER the value is copied. return true; @@ -275,10 +249,7 @@ public: FORCE_INLINE bool TrySet(T&& value) noexcept { if (_hasValue) - { return false; - } - new (&_value) T(MoveTemp(value)); // Placement new (move constructor) _hasValue = true; // Set the flag AFTER the value is moved. return true; @@ -290,10 +261,7 @@ public: FORCE_INLINE void Reset() { if (!_hasValue) - { return; - } - _hasValue = false; // Reset the flag BEFORE the value is (potentially) destructed. _value.~T(); } @@ -316,12 +284,7 @@ public: /// true if both values are equal. FORCE_INLINE bool operator==(const Nullable& other) const { - if (other._hasValue != _hasValue) - { - return false; - } - - return _value == other._value; + return other._hasValue == _hasValue && _value == other._value; } /// @@ -343,7 +306,6 @@ public: return _hasValue; } - /// /// Matches the wrapped value with a handler for the value or a handler for the null value. /// @@ -354,13 +316,8 @@ public: FORCE_INLINE auto Match(ValueVisitor valueHandler, NullVisitor nullHandler) const { if (_hasValue) - { return valueHandler(_value); - } - else - { - return nullHandler(); - } + return nullHandler(); } }; @@ -409,11 +366,10 @@ public: _value = value ? Value::True : Value::False; } - /// /// Reassigns the wrapped value by implicitly casting a boolean value. /// - auto operator=(const bool value) noexcept -> Nullable& + Nullable& operator=(const bool value) noexcept { _value = value ? Value::True : Value::False; return *this; @@ -422,13 +378,12 @@ public: /// /// Reassigns the wrapped value by copying another nullable boolean. /// - auto operator=(const Nullable& value) -> Nullable& = default; + Nullable& operator=(const Nullable& value) = default; /// /// Reassigns the wrapped value by moving another nullable boolean. /// - auto operator=(Nullable&& value) -> Nullable& = default; - + Nullable& operator=(Nullable&& value) = default; /// /// Checks if wrapped bool has a valid value. @@ -495,7 +450,6 @@ public: _value = Value::Null; } - /// /// Checks if the current object has a valid value and it's set to true. If the value is false or not valid, the method returns false. /// From 082d97248d60ec4873111bbcf76db77e5e7f23f5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 27 Oct 2024 15:22:57 +0100 Subject: [PATCH 059/215] Add unit test for `DebugCommands` --- Source/Engine/Level/DirectionalLight.cs | 2 + Source/Engine/Tests/TestDebugCommands.cpp | 45 +++++++++++++++++++++++ Source/Engine/Tests/TestScripting.h | 21 +++++++++++ 3 files changed, 68 insertions(+) create mode 100644 Source/Engine/Tests/TestDebugCommands.cpp diff --git a/Source/Engine/Level/DirectionalLight.cs b/Source/Engine/Level/DirectionalLight.cs index 201d35dc1..032d0dce6 100644 --- a/Source/Engine/Level/DirectionalLight.cs +++ b/Source/Engine/Level/DirectionalLight.cs @@ -1,3 +1,5 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + namespace FlaxEngine { public partial class DirectionalLight diff --git a/Source/Engine/Tests/TestDebugCommands.cpp b/Source/Engine/Tests/TestDebugCommands.cpp new file mode 100644 index 000000000..d8b8c2e08 --- /dev/null +++ b/Source/Engine/Tests/TestDebugCommands.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "TestScripting.h" +#include "Engine/Debug/DebugCommands.h" +#include + +namespace +{ + bool PassExec = false; +} + +bool TestDebugCommand1::Var = false; +float TestDebugCommand2::Var = 0.0f; + +TestDebugCommand2::TestDebugCommand2(const SpawnParams& params) + : ScriptingObject(params) +{ +} + +void TestDebugCommand2::Exec() +{ + PassExec = true; +} + +TEST_CASE("DebugCommands") +{ + SECTION("Test Commands") + { + // Test async cache flow + DebugCommands::InitAsync(); + Platform::Sleep(1); + + // Test commands invoking + CHECK(TestDebugCommand1::Var == false); + DebugCommands::Execute(TEXT("TestDebugCommand1.Var true")); + CHECK(TestDebugCommand1::Var == true); + CHECK(TestDebugCommand2::Var == 0.0f); + DebugCommands::Execute(TEXT("TestDebugCommand2.Var 1.5")); + DebugCommands::Execute(TEXT("TestDebugCommand2.Var")); + CHECK(TestDebugCommand2::Var == 1.5f); + CHECK(!PassExec); + DebugCommands::Execute(TEXT("TestDebugCommand2.Exec")); + CHECK(PassExec); + } +} diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index dc05750e1..b86938b85 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -177,3 +177,24 @@ public: return str.Length(); } }; + +// Test debug commands via static class. +API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API TestDebugCommand1 +{ + DECLARE_SCRIPTING_TYPE_NO_SPAWN(TestDebugCommand1); + + // Static variable to test. + API_FIELD() static bool Var; +}; + +// Test debug commands inside a class. +API_CLASS() class FLAXENGINE_API TestDebugCommand2 : public ScriptingObject +{ + DECLARE_SCRIPTING_TYPE(TestDebugCommand2); + + // Static variable to test. + API_FIELD(Attributes="DebugCommand") static float Var; + + // Static method to test. + API_FUNCTION(Attributes="DebugCommand") static void Exec(); +}; From f88eeeb31300f63b05047b67a0a97a6d6c8fdf1b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 28 Oct 2024 14:32:19 +0100 Subject: [PATCH 060/215] Fix missing casting from scalar Variant types to Enum --- Source/Engine/Core/Types/Variant.cpp | 35 ++++++++++++++++++++++++++++ Source/Engine/Core/Types/Variant.h | 1 + 2 files changed, 36 insertions(+) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 95e815b81..b410d828e 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -3174,6 +3174,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3196,6 +3197,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3218,6 +3220,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3240,6 +3243,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3262,6 +3266,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3284,6 +3289,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3306,6 +3312,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3328,6 +3335,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3350,6 +3358,7 @@ bool Variant::CanCast(const Variant& v, const VariantType& to) case VariantType::Double2: case VariantType::Double3: case VariantType::Double4: + case VariantType::Enum: return true; default: return false; @@ -3482,6 +3491,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsBool ? 1.0 : 0.0)); case VariantType::Double4: return Variant(Double4(v.AsBool ? 1.0 : 0.0)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3518,6 +3529,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsInt16)); case VariantType::Double4: return Variant(Double4((double)v.AsInt16)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3548,6 +3561,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Float4((float)v.AsInt)); case VariantType::Color: return Variant(Color((float)v.AsInt)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3584,6 +3599,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsUint16)); case VariantType::Double4: return Variant(Double4((double)v.AsUint16)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3620,6 +3637,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsUint)); case VariantType::Double4: return Variant(Double4((double)v.AsUint)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3656,6 +3675,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsInt64)); case VariantType::Double4: return Variant(Double4((double)v.AsInt64)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3692,6 +3713,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3((double)v.AsInt)); case VariantType::Double4: return Variant(Double4((double)v.AsInt)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3728,6 +3751,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsFloat)); case VariantType::Double4: return Variant(Double4(v.AsFloat)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -3764,6 +3789,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsDouble)); case VariantType::Double4: return Variant(Double4(v.AsDouble)); + case VariantType::Enum: + return Enum(to, (int64)v.AsBool); default: ; } break; @@ -4131,6 +4158,14 @@ void Variant::FreeStructure() Allocator::Free(AsBlob.Data); } +Variant Variant::Enum(const VariantType& type, const uint64 value) +{ + Variant v; + v.SetType(type); + v.AsUint64 = value; + return MoveTemp(v); +} + uint32 GetHash(const Variant& key) { switch (key.Type.Type) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index cd05fc038..1cf2fecf7 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -421,6 +421,7 @@ private: void AllocStructure(); void CopyStructure(void* src); void FreeStructure(); + static Variant Enum(const VariantType& type, const uint64 value); }; namespace Math From 41a0ccb218ccd4e1f2e5315d1b5970392d01ac43 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 28 Oct 2024 20:32:17 +0100 Subject: [PATCH 061/215] Fix Visject Surface search to check nested surfaces (such as State Machine graphs) --- .../Archetypes/Animation.StateMachine.cs | 21 +++++---- Source/Editor/Surface/SurfaceComment.cs | 2 +- .../Editor/Surface/VisjectSurface.Context.cs | 31 ++++++++++++- .../Windows/Search/ContentSearchWindow.cs | 44 ++++++++++++------- 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 7de90f743..e7568f439 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -225,8 +225,7 @@ namespace FlaxEditor.Surface.Archetypes /// public override void OnDestroy() { - if (Surface != null) - Surface.RemoveContext(this); + Surface?.RemoveContext(this); _maxTransitionsPerUpdate = null; _reinitializeOnBecomingRelevant = null; @@ -717,9 +716,12 @@ namespace FlaxEditor.Surface.Archetypes LoadTransitions(); - // Register for surface mouse events to handle transition arrows interactions - Surface.CustomMouseUp += OnSurfaceMouseUp; - Surface.CustomMouseDoubleClick += OnSurfaceMouseDoubleClick; + if (Surface != null) + { + // Register for surface mouse events to handle transition arrows interactions + Surface.CustomMouseUp += OnSurfaceMouseUp; + Surface.CustomMouseDoubleClick += OnSurfaceMouseDoubleClick; + } } private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled) @@ -1398,7 +1400,8 @@ namespace FlaxEditor.Surface.Archetypes if (context.FindNode(9, 21) == null) { var wasEnabled = true; - if (Surface.Undo != null) + var undo = Surface?.Undo; + if (undo != null) { wasEnabled = Surface.Undo.Enabled; Surface.Undo.Enabled = false; @@ -1406,7 +1409,7 @@ namespace FlaxEditor.Surface.Archetypes context.SpawnNode(9, 21, new Float2(100.0f)); - if (Surface.Undo != null) + if (undo != null) { Surface.Undo.Enabled = wasEnabled; } @@ -1492,7 +1495,7 @@ namespace FlaxEditor.Surface.Archetypes /// public override void OnDestroy() { - Surface.RemoveContext(this); + Surface?.RemoveContext(this); base.OnDestroy(); } @@ -1886,7 +1889,7 @@ namespace FlaxEditor.Surface.Archetypes if (context.FindNode(9, 22) == null) { var wasEnabled = true; - var undo = SourceState.Surface.Undo; + var undo = SourceState.Surface?.Undo; if (undo != null) { wasEnabled = undo.Enabled; diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs index d072ee28e..94e7c21ba 100644 --- a/Source/Editor/Surface/SurfaceComment.cs +++ b/Source/Editor/Surface/SurfaceComment.cs @@ -87,7 +87,7 @@ namespace FlaxEditor.Surface Title = TitleValue; Color = ColorValue; var size = SizeValue; - if (Surface.GridSnappingEnabled) + if (Surface != null && Surface.GridSnappingEnabled) size = Surface.SnapToGrid(size, true); Size = size; diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index 1197b387b..3fe29adaa 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Surface.Undo; -using FlaxEngine; namespace FlaxEditor.Surface { @@ -57,6 +57,28 @@ namespace FlaxEditor.Surface return null; } + /// + /// Opens the surface context with the given owning nodes IDs path. + /// + /// The node ids path. + /// Found context or null if cannot. + public VisjectSurfaceContext OpenContext(Span nodePath) + { + OpenContext(RootContext.Context); + if (nodePath != null && nodePath.Length != 0) + { + for (int i = 0; i < nodePath.Length; i++) + { + var node = Context.FindNode(nodePath[i]); + if (node is ISurfaceContext context) + OpenContext(context); + else + return null; + } + } + return Context; + } + /// /// Creates the Visject surface context for the given surface data source context. /// @@ -101,7 +123,12 @@ namespace FlaxEditor.Surface if (_root == null) _root = surfaceContext; else if (ContextStack.Contains(surfaceContext)) - throw new ArgumentException("Context has been already added to the stack."); + { + // Go up until the given context + while (ContextStack.First() != surfaceContext) + CloseContext(); + return; + } // Change stack ContextStack.Push(surfaceContext); diff --git a/Source/Editor/Windows/Search/ContentSearchWindow.cs b/Source/Editor/Windows/Search/ContentSearchWindow.cs index 0e3d1efdc..dd506a849 100644 --- a/Source/Editor/Windows/Search/ContentSearchWindow.cs +++ b/Source/Editor/Windows/Search/ContentSearchWindow.cs @@ -13,7 +13,6 @@ using FlaxEditor.GUI.Docking; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tree; using FlaxEditor.Options; -using FlaxEditor.Scripting; using FlaxEditor.Surface; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; @@ -496,6 +495,7 @@ namespace FlaxEngine.Windows.Search // Iterate over all assets var tempFolder = StringUtils.NormalizePath(Path.GetDirectoryName(Globals.TemporaryFolder)); + var nodePath = new List(); for (var i = 0; i < assets.Length && !_token.IsCancellationRequested; i++) { var id = assets[i]; @@ -512,20 +512,21 @@ namespace FlaxEngine.Windows.Search continue; // Search asset contents + nodePath.Clear(); if (asset is VisualScript visualScript) - SearchAsyncInnerVisject(asset, visualScript.LoadSurface()); + SearchAsyncInnerVisject(asset, visualScript.LoadSurface(), nodePath); else if (asset is Material material) - SearchAsyncInnerVisject(asset, material.LoadSurface(false)); + SearchAsyncInnerVisject(asset, material.LoadSurface(false), nodePath); else if (asset is MaterialFunction materialFunction) - SearchAsyncInnerVisject(asset, materialFunction.LoadSurface()); + SearchAsyncInnerVisject(asset, materialFunction.LoadSurface(), nodePath); else if (asset is AnimationGraph animationGraph) - SearchAsyncInnerVisject(asset, animationGraph.LoadSurface()); + SearchAsyncInnerVisject(asset, animationGraph.LoadSurface(), nodePath); else if (asset is AnimationGraphFunction animationGraphFunction) - SearchAsyncInnerVisject(asset, animationGraphFunction.LoadSurface()); + SearchAsyncInnerVisject(asset, animationGraphFunction.LoadSurface(), nodePath); else if (asset is ParticleEmitter particleEmitter) - SearchAsyncInnerVisject(asset, particleEmitter.LoadSurface(false)); + SearchAsyncInnerVisject(asset, particleEmitter.LoadSurface(false), nodePath); else if (asset is ParticleEmitterFunction particleEmitterFunction) - SearchAsyncInnerVisject(asset, particleEmitterFunction.LoadSurface()); + SearchAsyncInnerVisject(asset, particleEmitterFunction.LoadSurface(), nodePath); // Don't eat whole performance Thread.Sleep(15); @@ -551,7 +552,7 @@ namespace FlaxEngine.Windows.Search }; } - private void SearchAsyncInnerVisject(Asset asset, byte[] surfaceData) + private void SearchAsyncInnerVisject(Asset asset, byte[] surfaceData, List nodePath) { // Load Visject surface from data if (surfaceData == null || surfaceData.Length == 0) @@ -566,7 +567,6 @@ namespace FlaxEngine.Windows.Search if (_visjectSurfaceStyle == null) _visjectSurfaceStyle = SurfaceStyle.CreateDefault(Editor); SearchResultTreeNode assetTreeNode = null; - // TODO: support nested surfaces (eg. in Anim Graph) // Search parameters foreach (var parameter in _visjectSurfaceContext.Parameters) @@ -592,7 +592,8 @@ namespace FlaxEngine.Windows.Search // Search nodes var newTreeNodes = new List(); - foreach (var node in _visjectSurfaceContext.Nodes) + var nodes = _visjectSurfaceContext.Nodes.ToArray(); + foreach (var node in nodes) { newTreeNodes.Clear(); if (node.Values != null) @@ -602,12 +603,18 @@ namespace FlaxEngine.Windows.Search SearchVisjectMatch(value, (matchedValue, matchedText) => { var valueTreeNode = AddVisjectSearchResult(matchedValue, matchedText, node.Archetype.ConnectionsHints); - valueTreeNode.Tag = node.ID; + valueTreeNode.Tag = new VisjectNodeTag { NodeId = node.ID, NodePath = nodePath.ToArray() }; valueTreeNode.Navigate = OnNavigateVisjectNode; newTreeNodes.Add(valueTreeNode); }); } } + if (node is ISurfaceContext context) + { + nodePath.Add(node.ID); + SearchAsyncInnerVisject(asset, context.SurfaceData, nodePath); + nodePath.RemoveAt(nodePath.Count - 1); + } var nodeSearchText = node.ContentSearchText; if (newTreeNodes.Count != 0 || (nodeSearchText != null && IsSearchMatch(ref nodeSearchText))) @@ -617,7 +624,7 @@ namespace FlaxEngine.Windows.Search { Text = node.Title, TooltipText = node.TooltipText, - Tag = node.ID, + Tag = new VisjectNodeTag { NodeId = node.ID, NodePath = nodePath.ToArray() }, Navigate = OnNavigateVisjectNode, Parent = assetTreeNode, }; @@ -723,9 +730,15 @@ namespace FlaxEngine.Windows.Search Editor.ContentEditing.Open(contentItem); } + private struct VisjectNodeTag + { + public uint NodeId; + public uint[] NodePath; + } + private void OnNavigateVisjectNode(SearchResultTreeNode treeNode) { - var nodeId = (uint)treeNode.Tag; + var tag = (VisjectNodeTag)treeNode.Tag; var assetId = Guid.Empty; var assetTreeNode = treeNode.Parent; while (!(assetTreeNode.Tag is Guid)) @@ -734,7 +747,8 @@ namespace FlaxEngine.Windows.Search var contentItem = Editor.ContentDatabase.FindAsset(assetId); if (Editor.ContentEditing.Open(contentItem) is IVisjectSurfaceWindow window) { - var node = window.VisjectSurface.FindNode(nodeId); + var context = window.VisjectSurface.OpenContext(tag.NodePath) ?? window.VisjectSurface.Context; + var node = context.FindNode(tag.NodeId); if (node != null) { // Focus this node From 072f7c7e45757f011ebd6936e4b2e01d9833abfe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Oct 2024 20:36:01 +0100 Subject: [PATCH 062/215] Fix C++ `Vector3::Angle` to return value in degrees just like C# API (instead of radians) --- Source/Engine/Core/Math/Vector3.cpp | 4 ++-- Source/Engine/Core/Math/Vector3.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 5a26f2309..d9e00f0c0 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -321,7 +321,7 @@ float Float3::Angle(const Float3& from, const Float3& to) const float dot = Math::Clamp(Dot(Normalize(from), Normalize(to)), -1.0f, 1.0f); if (Math::Abs(dot) > 1.0f - ZeroTolerance) return dot > 0.0f ? 0.0f : PI; - return Math::Acos(dot); + return Math::Acos(dot) * RadiansToDegrees; } template<> @@ -649,7 +649,7 @@ double Double3::Angle(const Double3& from, const Double3& to) const double dot = Math::Clamp(Dot(Normalize(from), Normalize(to)), -1.0, 1.0); if (Math::Abs(dot) > 1.0 - ZeroTolerance) return dot > 0.0 ? 0.0 : PI; - return Math::Acos(dot); + return Math::Acos(dot) * RadiansToDegrees; } template<> diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 67f8e7d44..5a571c47f 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -812,11 +812,11 @@ public: static FLAXENGINE_API T TriangleArea(const Vector3Base& v0, const Vector3Base& v1, const Vector3Base& v2); /// - /// Calculates the angle (in radians) between from and to. This is always the smallest value. + /// Calculates the angle (in degrees) between from and to. This is always the smallest value. /// /// The first vector. /// The second vector. - /// The angle (in radians). + /// The angle (in degrees). static FLAXENGINE_API T Angle(const Vector3Base& from, const Vector3Base& to); /// From 0c645e8b0c570d5b47f86c7709a2a89c2281f688 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Oct 2024 21:59:21 +0100 Subject: [PATCH 063/215] Fix root animation in blend spaces when one of the animations doesn't use root motion but animated root node channel --- .../Engine/Animations/Graph/AnimGroup.Animation.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 2103bb8cd..0eb7c952c 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -356,9 +356,10 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* if (_rootMotionMode == RootMotionExtraction::Enable && nodeToChannel != -1) { // Get the root bone transformation - Transform rootBefore = refPose; + Transform rootBefore = refPose, rootNow = refPose; const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel]; rootChannel.Evaluate(animPrevPos, &rootBefore, false); + rootChannel.Evaluate(animPos, &rootNow, false); // Check if animation looped if (animPos < animPrevPos) @@ -375,18 +376,18 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // (end - before + now - begin) // It sums the motion since the last update to anim end and since the start to now if (motionPosition) - srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation) * motionPositionMask; + srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNow.Translation - rootBegin.Translation) * motionPositionMask; if (motionRotation) - srcNode.Orientation = (rootBefore.Orientation.Conjugated() * rootEnd.Orientation) * (rootBegin.Orientation.Conjugated() * rootNode.Orientation); + srcNode.Orientation = (rootBefore.Orientation.Conjugated() * rootEnd.Orientation) * (rootBegin.Orientation.Conjugated() * rootNow.Orientation); } else { // Simple motion delta // (now - before) if (motionPosition) - srcNode.Translation = (rootNode.Translation - rootBefore.Translation) * motionPositionMask; + srcNode.Translation = (rootNow.Translation - rootBefore.Translation) * motionPositionMask; if (motionRotation) - srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; + srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNow.Orientation; } // Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale) From 2288684950e7059ed4c2f95b7b71df89211b705b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Oct 2024 23:08:50 +0100 Subject: [PATCH 064/215] Fix pasting or duplicating Multi Blend nodes --- .../Archetypes/Animation.MultiBlend.cs | 11 ++++++++++ Source/Editor/Surface/SurfaceNode.cs | 9 +++++++++ .../Surface/VisjectSurface.CopyPaste.cs | 20 +++++++++++++++---- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index f76faa794..71601ed23 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -885,6 +885,17 @@ namespace FlaxEditor.Surface.Archetypes _selectedAnimation.SelectedIndex = 0; } + /// + public override void SetValuesPaste(object[] values) + { + // Fix Guids pasted as string + // TODO: let copy/paste system in Visject handle value types to be strongly typed + for (int i = 5; i < values.Length; i += 2) + values[i] = Guid.Parse((string)values[i]); + + base.SetValuesPaste(values); + } + /// public override void OnValuesChanged() { diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index c5c011db0..da64fe613 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -1001,6 +1001,15 @@ namespace FlaxEditor.Surface _isDuringValuesEditing = value; } + /// + /// Sets teh node values from the given pasted source. Can be overriden to perform validation or custom values processing. + /// + /// The input values array. + public virtual void SetValuesPaste(object[] values) + { + Values = values; + } + /// /// Called when node values set gets changed. /// diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index 94d41a624..71004aede 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -286,13 +286,14 @@ namespace FlaxEditor.Surface // Initialize if (nodeData.Values != null && node.Values.Length > 0) { - if (node.Values != null && node.Values.Length == nodeData.Values.Length) + var nodeValues = (object[])node.Values?.Clone(); + if (nodeValues != null && nodeValues.Length == nodeData.Values.Length) { // Copy and fix values (Json deserializes may output them in a different format) - for (int l = 0; l < node.Values.Length; l++) + for (int l = 0; l < nodeData.Values.Length; l++) { var src = nodeData.Values[l].Value; - var dst = node.Values[l]; + var dst = nodeValues[l]; try { @@ -364,13 +365,24 @@ namespace FlaxEditor.Surface Editor.LogWarning(ex); } - node.Values[l] = src; + nodeValues[l] = src; + } + } + else if (node.Archetype.Flags.HasFlag(NodeFlags.VariableValuesSize)) + { + // Copy values + nodeValues = new object[nodeData.Values.Length]; + for (int l = 0; l < nodeData.Values.Length; l++) + { + nodeValues[l] = nodeData.Values[l].Value; } } else { Editor.LogWarning("Invalid node custom values."); } + if (nodeValues != null) + node.SetValuesPaste(nodeValues); } Context.OnControlLoaded(node, SurfaceNodeActions.Paste); From b1a54d2967d8bcfbf89331cf3ba99abc4989b9e6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Oct 2024 23:34:10 +0100 Subject: [PATCH 065/215] Missing change in 072f7c7e45757f011ebd6936e4b2e01d9833abfe --- Source/Engine/Core/Math/Vector3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index d9e00f0c0..791137f8c 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -320,7 +320,7 @@ float Float3::Angle(const Float3& from, const Float3& to) { const float dot = Math::Clamp(Dot(Normalize(from), Normalize(to)), -1.0f, 1.0f); if (Math::Abs(dot) > 1.0f - ZeroTolerance) - return dot > 0.0f ? 0.0f : PI; + return dot > 0.0f ? 0.0f : 180.0f; return Math::Acos(dot) * RadiansToDegrees; } @@ -648,7 +648,7 @@ double Double3::Angle(const Double3& from, const Double3& to) { const double dot = Math::Clamp(Dot(Normalize(from), Normalize(to)), -1.0, 1.0); if (Math::Abs(dot) > 1.0 - ZeroTolerance) - return dot > 0.0 ? 0.0 : PI; + return dot > 0.0f ? 0.0f : 180.0f; return Math::Acos(dot) * RadiansToDegrees; } From c1bd42ff7eb71824683bb8ba63f0c9173281bb4d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Oct 2024 23:55:42 +0100 Subject: [PATCH 066/215] Add `Vector3.SignedAngle` utility method --- Source/Engine/Core/Math/Vector3.cpp | 24 ++++++++++++++++++++++++ Source/Engine/Core/Math/Vector3.cs | 17 ++++++++++++++++- Source/Engine/Core/Math/Vector3.h | 11 ++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp index 791137f8c..4e30f6f6c 100644 --- a/Source/Engine/Core/Math/Vector3.cpp +++ b/Source/Engine/Core/Math/Vector3.cpp @@ -324,6 +324,15 @@ float Float3::Angle(const Float3& from, const Float3& to) return Math::Acos(dot) * RadiansToDegrees; } +template<> +float Float3::SignedAngle(const Float3& from, const Float3& to, const Float3& axis) +{ + const float angle = Angle(from, to); + const Float3 cross = Cross(from, to); + const float sign = Math::Sign(axis.X * cross.X + axis.Y * cross.Y + axis.Z * cross.Z); + return angle * sign; +} + template<> Float3 Float3::SnapToGrid(const Float3& pos, const Float3& gridSize) { @@ -652,6 +661,15 @@ double Double3::Angle(const Double3& from, const Double3& to) return Math::Acos(dot) * RadiansToDegrees; } +template<> +double Double3::SignedAngle(const Double3& from, const Double3& to, const Double3& axis) +{ + const double angle = Angle(from, to); + const Double3 cross = Cross(from, to); + const double sign = Math::Sign(axis.X * cross.X + axis.Y * cross.Y + axis.Z * cross.Z); + return angle * sign; +} + template<> Double3 Double3::SnapToGrid(const Double3& pos, const Double3& gridSize) { @@ -881,6 +899,12 @@ int32 Int3::Angle(const Int3& from, const Int3& to) return 0; } +template<> +int32 Int3::SignedAngle(const Int3& from, const Int3& to, const Int3& axis) +{ + return 0; +} + template<> Int3 Int3::SnapToGrid(const Int3& pos, const Int3& gridSize) { diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 5505c5053..81ac0a427 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -1345,7 +1345,7 @@ namespace FlaxEngine } /// - /// Calculates the angle (in degrees) between and . This is always the smallest value. + /// Calculates the angle (in degrees) between and vectors. This is always the smallest value. /// /// The first vector. /// The second vector. @@ -1358,6 +1358,21 @@ namespace FlaxEngine return (Real)Math.Acos(dot) * Mathr.RadiansToDegrees; } + /// + /// Calculates the signed angle (in degrees) between and vectors. This is always the smallest value. The sign of the result depends on: the order of input vectors, and the direction of the vector. + /// + /// The first vector. + /// The second vector. + /// The axis around which the vectors are rotated. + /// The angle (in degrees). + public static Real SignedAngle(Vector3 from, Vector3 to, Vector3 axis) + { + Real angle = Angle(from, to); + Vector3 cross = Cross(from, to); + Real sign = Mathr.Sign(axis.X * cross.X + axis.Y * cross.Y + axis.Z * cross.Z); + return angle * sign; + } + /// /// Projects a 3D vector from object space into screen space. /// diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 5a571c47f..1f2e051f7 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -812,13 +812,22 @@ public: static FLAXENGINE_API T TriangleArea(const Vector3Base& v0, const Vector3Base& v1, const Vector3Base& v2); /// - /// Calculates the angle (in degrees) between from and to. This is always the smallest value. + /// Calculates the angle (in degrees) between from and to vectors. This is always the smallest value. /// /// The first vector. /// The second vector. /// The angle (in degrees). static FLAXENGINE_API T Angle(const Vector3Base& from, const Vector3Base& to); + /// + /// Calculates the signed angle (in degrees) between from and to vectors. This is always the smallest value. The sign of the result depends on: the order of input vectors, and the direction of the axis vector. + /// + /// The first vector. + /// The second vector. + /// The axis around which the vectors are rotated. + /// The angle (in degrees). + static FLAXENGINE_API T SignedAngle(const Vector3Base& from, const Vector3Base& to, const Vector3Base& axis); + /// /// Snaps the input position onto the grid. /// From 4de9e9d918368d34bf112244bacd719862754d30 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 30 Oct 2024 00:01:54 +0100 Subject: [PATCH 067/215] Add gravity to `CharacterController::AddMovement` for proper movement when using Root Motion --- Source/Engine/Physics/Colliders/CharacterController.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 4a4452fc2..d4627cc5f 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -261,7 +261,13 @@ void CharacterController::UpdateBounds() void CharacterController::AddMovement(const Vector3& translation, const Quaternion& rotation) { - Move(translation); + Vector3 displacement = translation; + + // Apply gravity + const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); + displacement += GetPhysicsScene()->GetGravity() * deltaTime; + + Move(displacement); if (!rotation.IsIdentity()) { From 6e598ae9f6a5419e7c3ab2d17e274cbdc39644fa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 30 Oct 2024 17:24:47 +0100 Subject: [PATCH 068/215] Refactor Multi Blend animations sampling length to fix blend spaces with anims of different length --- .../Animations/Graph/AnimGraph.Base.cpp | 3 +- Source/Engine/Animations/Graph/AnimGraph.h | 21 +- .../Animations/Graph/AnimGroup.Animation.cpp | 301 +++++++++++++----- 3 files changed, 227 insertions(+), 98 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 204026724..b23d55b9e 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -87,8 +87,7 @@ void AnimationBucketInit(AnimGraphInstanceData::Bucket& bucket) void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket) { - bucket.MultiBlend.TimePosition = 0.0f; - bucket.MultiBlend.LastUpdateFrame = 0; + Platform::MemoryClear(&bucket.MultiBlend, sizeof(bucket.MultiBlend)); } void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index ff8d9b977..deca7bb5f 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -245,7 +245,10 @@ public: struct MultiBlendBucket { - float TimePosition; + constexpr static int32 MaxCount = 3; // Up to 3 anims to be used at once in 2D blend space (triangle) + float TimePositions[MaxCount]; + ANIM_GRAPH_MULTI_BLEND_INDEX Animations[MaxCount]; + byte Count; uint64 LastUpdateFrame; }; @@ -431,22 +434,22 @@ typedef VisjectGraphBox AnimGraphBox; class AnimGraphNode : public VisjectGraphNode { public: - struct MultiBlend1DData + struct MultiBlendData { // Amount of blend points. ANIM_GRAPH_MULTI_BLEND_INDEX Count; // The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. float Length; + }; + + struct MultiBlend1DData : MultiBlendData + { // The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_INVALID which is invalid. ANIM_GRAPH_MULTI_BLEND_INDEX* IndicesSorted; }; - struct MultiBlend2DData + struct MultiBlend2DData : MultiBlendData { - // Amount of blend points. - ANIM_GRAPH_MULTI_BLEND_INDEX Count; - // The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. - float Length; // Amount of triangles. int32 TrianglesCount; // Cached triangles vertices (3 bytes per triangle). Contains list of indices for triangles to use for blending. @@ -862,8 +865,10 @@ private: void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed); void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override, BitArray>* usedNodes = nullptr); Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed); + Variant SampleAnimation(AnimGraphNode* node, bool loop, float startTimePos, struct AnimSampleData& sample); Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha); - Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC); + Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, float alpha); + Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, AnimSampleData& c, float alphaA, float alphaB, float alphaC); Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode); Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 0eb7c952c..a0c90b854 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -10,6 +10,83 @@ #include "Engine/Animations/InverseKinematics.h" #include "Engine/Level/Actors/AnimatedModel.h" +struct AnimSampleData +{ + Animation* Anim; + float TimePos; + float PrevTimePos; + float Length; + float Speed; + ANIM_GRAPH_MULTI_BLEND_INDEX MultiBlendIndex; // Index of the animation in the multi-blend node data array + + AnimSampleData(Animation* anim, float speed = 1.0f, ANIM_GRAPH_MULTI_BLEND_INDEX multiBlendIndex = 0) + : Anim(anim) + , TimePos(0.0f) + , PrevTimePos(0.0f) + , Length(anim->GetLength()) + , Speed(speed) + , MultiBlendIndex(multiBlendIndex) + { + } +}; + +struct MultiBlendAnimData +{ + float TimePosition; + ANIM_GRAPH_MULTI_BLEND_INDEX Animation; + + typedef Array> List; + + static void BeforeSample(const AnimGraphContext& context, const AnimGraphInstanceData::MultiBlendBucket& bucket, const List& prevList, AnimSampleData& sample, float speed = 1.0f) + { + // Find time position in the previous frame + sample.PrevTimePos = 0.0f; + for (const auto& e : prevList) + { + if (e.Animation == sample.MultiBlendIndex) + { + sample.PrevTimePos = e.TimePosition; + break; + } + } + + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) + { + // If speed is negative and it's the first node update then start playing from end + sample.PrevTimePos = sample.Length; + } + + // Calculate new time position + sample.TimePos = sample.PrevTimePos + context.DeltaTime * speed; + } + + static void AfterSample(List& newList, const AnimSampleData& sample) + { + CHECK(newList.Count() < AnimGraphInstanceData::MultiBlendBucket::MaxCount); + + // Save animation position for the next frame + newList.Add({ sample.TimePos, sample.MultiBlendIndex }); + } + + static void GetList(const AnimGraphInstanceData::MultiBlendBucket& multiBlend, List& list) + { + list.Resize(multiBlend.Count); + for (int32 i = 0; i < multiBlend.Count; i++) + list[i] = { multiBlend.TimePositions[i], multiBlend.Animations[i] }; + } + + static void SetList(AnimGraphInstanceData::MultiBlendBucket& multiBlend, const List& list) + { + multiBlend.Count = list.Count(); + for (int32 i = 0; i < multiBlend.Count; i++) + { + auto& e = list[i]; + multiBlend.TimePositions[i] = e.TimePosition; + multiBlend.Animations[i] = e.Animation; + } + } +}; + namespace { FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight) @@ -201,7 +278,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed) // Convert into animation local time (track length may be bigger so fill the gaps with animation clip and include playback speed) // Also, scale the animation to fit the total animation node length without cut in a middle const auto animLength = anim->GetLength(); - const int32 cyclesCount = Math::FloorToInt(length / animLength); + const int32 cyclesCount = Math::Max(Math::FloorToInt(length / animLength), 1); const float cycleLength = animLength * (float)cyclesCount; const float adjustRateScale = length / cycleLength; auto animPos = pos * speed * adjustRateScale; @@ -463,6 +540,11 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float return nodes; } +Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& sample) +{ + return SampleAnimation(node, loop, sample.Length, startTimePos, sample.PrevTimePos, sample.TimePos, sample.Anim, sample.Speed); +} + Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha) { // Skip if any animation is not ready to use @@ -485,26 +567,53 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l return nodes; } -Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC) +Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, float alpha) { // Skip if any animation is not ready to use - if (animA == nullptr || !animA->IsLoaded() || - animB == nullptr || !animB->IsLoaded() || - animC == nullptr || !animC->IsLoaded()) + if (a.Anim == nullptr || !a.Anim->IsLoaded() || + b.Anim == nullptr || !b.Anim->IsLoaded()) return Value::Null; - float pos, prevPos; - GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos); + // Get actual animation position (includes looping and start offset) + float posA, prevPosA, posB, prevPosB; + GetAnimSamplePos(loop, a.Length, startTimePos, a.PrevTimePos, a.TimePos, posA, prevPosA); + GetAnimSamplePos(loop, b.Length, startTimePos, b.PrevTimePos, b.TimePos, posB, prevPosB); // Sample the animations with blending const auto nodes = node->GetNodes(this); InitNodes(nodes); - nodes->Position = pos; - nodes->Length = length; + nodes->Position = (a.TimePos + b.TimePos) / 2.0f; + nodes->Length = Math::Max(a.Length, b.Length); + ProcessAnimation(nodes, node, loop, a.Length, posA, prevPosA, a.Anim, a.Speed, 1.0f - alpha, ProcessAnimationMode::Override); + ProcessAnimation(nodes, node, loop, b.Length, posB, prevPosB, b.Anim, b.Speed, alpha, ProcessAnimationMode::BlendAdditive); + NormalizeRotations(nodes, _rootMotionMode); + + return nodes; +} + +Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, AnimSampleData& c, float alphaA, float alphaB, float alphaC) +{ + // Skip if any animation is not ready to use + if (a.Anim == nullptr || !a.Anim->IsLoaded() || + b.Anim == nullptr || !b.Anim->IsLoaded() || + c.Anim == nullptr || !c.Anim->IsLoaded()) + return Value::Null; + + // Get actual animation position (includes looping and start offset) + float posA, prevPosA, posB, prevPosB, posC, prevPosC; + GetAnimSamplePos(loop, a.Length, startTimePos, a.PrevTimePos, a.TimePos, posA, prevPosA); + GetAnimSamplePos(loop, b.Length, startTimePos, b.PrevTimePos, b.TimePos, posB, prevPosB); + GetAnimSamplePos(loop, c.Length, startTimePos, c.PrevTimePos, c.TimePos, posC, prevPosC); + + // Sample the animations with blending + const auto nodes = node->GetNodes(this); + InitNodes(nodes); + nodes->Position = (a.TimePos + b.TimePos + c.TimePos) / 3.0f; + nodes->Length = Math::Max(a.Length, b.Length, c.Length); ASSERT(Math::Abs(alphaA + alphaB + alphaC - 1.0f) <= ANIM_GRAPH_BLEND_THRESHOLD); // Assumes weights are normalized - ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, alphaA, ProcessAnimationMode::Override); - ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alphaB, ProcessAnimationMode::BlendAdditive); - ProcessAnimation(nodes, node, loop, length, pos, prevPos, animC, speedC, alphaC, ProcessAnimationMode::BlendAdditive); + ProcessAnimation(nodes, node, loop, a.Length, posA, prevPosA, a.Anim, a.Speed, alphaA, ProcessAnimationMode::Override); + ProcessAnimation(nodes, node, loop, b.Length, posB, prevPosB, b.Anim, b.Speed, alphaB, ProcessAnimationMode::BlendAdditive); + ProcessAnimation(nodes, node, loop, c.Length, posC, prevPosC, c.Anim, c.Speed, alphaC, ProcessAnimationMode::BlendAdditive); NormalizeRotations(nodes, _rootMotionMode); return nodes; @@ -666,7 +775,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const void ComputeMultiBlendLength(float& length, AnimGraphNode* node) { - ANIM_GRAPH_PROFILE_EVENT("Setup Mutli Blend Length"); + ANIM_GRAPH_PROFILE_EVENT("Setup Multi Blend Length"); // TODO: lock graph or graph asset here? make it thread safe @@ -1246,6 +1355,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Multi Blend 1D case 12: { + ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D"); ASSERT(box->ID == 0); value = Value::Null; @@ -1264,11 +1374,10 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); const auto startTimePos = (float)tryGetValue(node->GetBox(3), node->Values[3]); + const auto syncLength = false; // TODO: make it configurable via node settings? (change node->Values[2] to contain flags) auto& data = node->Data.MultiBlend1D; - - // Check if not valid animation binded if (data.Count == 0) - break; + break; // Skip if no valid animations added // Get axis X float x = (float)tryGetValue(node->GetBox(4), Value::Zero); @@ -1287,42 +1396,36 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (data.Length <= ZeroTolerance) break; - // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) - { - // If speed is negative and it's the first node update then start playing from end - bucket.TimePosition = data.Length; - } - float newTimePos = bucket.TimePosition + context.DeltaTime * speed; - - ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D"); + MultiBlendAnimData::List prevList, newList; + MultiBlendAnimData::GetList(bucket, prevList); // Find 2 animations to blend (line) for (int32 i = 0; i < data.Count - 1; i++) { - const auto a = data.IndicesSorted[i]; - const auto b = data.IndicesSorted[i + 1]; - - // Get A animation data - const auto aAnim = node->Assets[a].As(); - auto aData = node->Values[4 + a * 2].AsFloat4(); + const auto aIndex = data.IndicesSorted[i]; + const auto bIndex = data.IndicesSorted[i + 1]; + const auto aData = node->Values[4 + aIndex * 2].AsFloat4(); + AnimSampleData a(node->Assets[aIndex].As(), aData.W, aIndex); // Check single A case or the last valid animation - if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || b == ANIM_GRAPH_MULTI_BLEND_INVALID) + if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || bIndex == ANIM_GRAPH_MULTI_BLEND_INVALID) { - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed); + value = SampleAnimation(node, loop, startTimePos, a); + MultiBlendAnimData::AfterSample(newList, a); break; } // Get B animation data - ASSERT(b != ANIM_GRAPH_MULTI_BLEND_INVALID); - const auto bAnim = node->Assets[b].As(); - auto bData = node->Values[4 + b * 2].AsFloat4(); + auto bData = node->Values[4 + bIndex * 2].AsFloat4(); + AnimSampleData b(node->Assets[bIndex].As(), bData.W, bIndex); // Check single B edge case if (Math::NearEqual(bData.X, x, ANIM_GRAPH_BLEND_THRESHOLD)) { - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, bData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed); + value = SampleAnimation(node, loop, startTimePos, b); + MultiBlendAnimData::AfterSample(newList, b); break; } @@ -1330,11 +1433,15 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const float alpha = (x - aData.X) / (bData.X - aData.X); if (alpha > 1.0f) continue; - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed); + value = SampleAnimationsWithBlend(node, loop, startTimePos, a, b, alpha); + MultiBlendAnimData::AfterSample(newList, a); + MultiBlendAnimData::AfterSample(newList, b); break; } - bucket.TimePosition = newTimePos; + MultiBlendAnimData::SetList(bucket, newList); bucket.LastUpdateFrame = context.CurrentFrameIndex; break; @@ -1342,6 +1449,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Multi Blend 2D case 13: { + ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D"); ASSERT(box->ID == 0); value = Value::Null; @@ -1360,11 +1468,10 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); const auto startTimePos = (float)tryGetValue(node->GetBox(3), node->Values[3]); + const auto syncLength = false; // TODO: make it configurable via node settings? (change node->Values[2] to contain flags) auto& data = node->Data.MultiBlend2D; - - // Check if not valid animation binded if (data.TrianglesCount == 0) - break; + break; // Skip if no valid animations added // Get axis X float x = (float)tryGetValue(node->GetBox(4), Value::Zero); @@ -1388,15 +1495,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (data.Length <= ZeroTolerance) break; - // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) - { - // If speed is negative and it's the first node update then start playing from end - bucket.TimePosition = data.Length; - } - float newTimePos = bucket.TimePosition + context.DeltaTime * speed; - - ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D"); + MultiBlendAnimData::List prevList, newList; + MultiBlendAnimData::GetList(bucket, prevList); // Find 3 animations to blend (triangle) Float2 p(x, y); @@ -1406,23 +1506,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu byte bestAnims[2]; for (int32 i = 0, t = 0; i < data.TrianglesCount; i++) { - // Get A animation data - const auto a = data.Triangles[t++]; - const auto aAnim = node->Assets[a].As(); - const auto aData = node->Values[4 + a * 2].AsFloat4(); - - // Get B animation data - const auto b = data.Triangles[t++]; - const auto bAnim = node->Assets[b].As(); - const auto bData = node->Values[4 + b * 2].AsFloat4(); - - // Get C animation data - const auto c = data.Triangles[t++]; - const auto cAnim = node->Assets[c].As(); - const auto cData = node->Values[4 + c * 2].AsFloat4(); + // Get animations data at vertices + const auto aIndex = data.Triangles[t++]; + const auto bIndex = data.Triangles[t++]; + const auto cIndex = data.Triangles[t++]; + const auto aData = node->Values[4 + aIndex * 2].AsFloat4(); + const auto bData = node->Values[4 + bIndex * 2].AsFloat4(); + const auto cData = node->Values[4 + cIndex * 2].AsFloat4(); + AnimSampleData a(node->Assets[aIndex].As(), aData.W, aIndex); + AnimSampleData b(node->Assets[bIndex].As(), bData.W, bIndex); + AnimSampleData c(node->Assets[cIndex].As(), cData.W, cIndex); + if (syncLength) + a.Length = b.Length = c.Length = data.Length; // Get triangle coords - byte anims[3] = { a, b, c }; + byte anims[3] = { aIndex, bIndex, cIndex }; Float2 points[3] = { Float2(aData.X, aData.Y), Float2(bData.X, bData.Y), @@ -1435,19 +1533,25 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (Float2::DistanceSquared(p, points[0]) < ANIM_GRAPH_BLEND_THRESHOLD2) { // Use only vertex A - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed); + value = SampleAnimation(node, loop, startTimePos, a); + MultiBlendAnimData::AfterSample(newList, a); break; } if (Float2::DistanceSquared(p, points[1]) < ANIM_GRAPH_BLEND_THRESHOLD2) { // Use only vertex B - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, bData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed); + value = SampleAnimation(node, loop, startTimePos, b); + MultiBlendAnimData::AfterSample(newList, b); break; } if (Float2::DistanceSquared(p, points[2]) < ANIM_GRAPH_BLEND_THRESHOLD2) { // Use only vertex C - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, cData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, c, speed); + value = SampleAnimation(node, loop, startTimePos, c); + MultiBlendAnimData::AfterSample(newList, c); break; } @@ -1468,7 +1572,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (xAxis && yAxis) { // Single animation - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed); + value = SampleAnimation(node, loop, startTimePos, a); + MultiBlendAnimData::AfterSample(newList, a); } else if (xAxis || yAxis) { @@ -1485,31 +1591,36 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu struct BlendData { float AlphaX, AlphaY; - Animation* AnimA, *AnimB; - const Float4* AnimAd, *AnimBd; + AnimSampleData *SampleA, *SampleB; }; BlendData blendData; if (v1.Y >= v0.Y) { if (p.Y < v0.Y && v1.Y >= v0.Y) - blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData }; + blendData = { p.Y, v0.Y, &a, &b }; else - blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData }; + blendData = { p.Y - v0.Y, v1.Y - v0.Y, &b, &c }; } else { if (p.Y < v1.Y) - blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData }; + blendData = { p.Y, v1.Y, &a, &c }; else - blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData }; + blendData = { p.Y - v1.Y, v0.Y - v1.Y, &c, &b }; } const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY; - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, *blendData.SampleA, speed); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, *blendData.SampleB, speed); + value = SampleAnimationsWithBlend(node, loop, startTimePos, *blendData.SampleA, *blendData.SampleB, alpha); + MultiBlendAnimData::AfterSample(newList, *blendData.SampleA); + MultiBlendAnimData::AfterSample(newList, *blendData.SampleB); } else { // Use only vertex A for invalid triangle - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed); + value = SampleAnimation(node, loop, startTimePos, a); + MultiBlendAnimData::AfterSample(newList, a); } break; } @@ -1518,7 +1629,13 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const float u = 1.0f - v - w; // Blend A and B and C - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, cAnim, aData.W, bData.W, cData.W, u, v, w); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, c, speed); + value = SampleAnimationsWithBlend(node, loop, startTimePos, a, b, c, u, v, w); + MultiBlendAnimData::AfterSample(newList, a); + MultiBlendAnimData::AfterSample(newList, b); + MultiBlendAnimData::AfterSample(newList, c); break; } @@ -1538,7 +1655,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const float d = Float2::Distance(s[0], s[1]); bestWeight = d < ANIM_GRAPH_BLEND_THRESHOLD ? 0 : Float2::Distance(s[0], closest) / d; - + bestAnims[0] = anims[j]; bestAnims[1] = anims[(j + 1) % 3]; } @@ -1548,23 +1665,31 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Check if use the closest sample if ((void*)value == nullptr && hasBest) { - const auto aAnim = node->Assets[bestAnims[0]].As(); - const auto aData = node->Values[4 + bestAnims[0] * 2].AsFloat4(); + const auto best0Index = bestAnims[0]; + const auto best1Index = bestAnims[1]; + const auto best0Data = node->Values[4 + best0Index * 2].AsFloat4(); + const auto best1Data = node->Values[4 + best1Index * 2].AsFloat4(); + AnimSampleData best0(node->Assets[best0Index].As(), best0Data.W, best0Index); + AnimSampleData best1(node->Assets[best1Index].As(), best1Data.W, best1Index); + if (syncLength) + best0.Length = best1.Length = data.Length; // Check if use only one sample + MultiBlendAnimData::BeforeSample(context, bucket, prevList, best0, speed); if (bestWeight < ANIM_GRAPH_BLEND_THRESHOLD) { - value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); + value = SampleAnimation(node, loop, startTimePos, best0); } else { - const auto bAnim = node->Assets[bestAnims[1]].As(); - const auto bData = node->Values[4 + bestAnims[1] * 2].AsFloat4(); - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, bestWeight); + MultiBlendAnimData::BeforeSample(context, bucket, prevList, best1, speed); + value = SampleAnimationsWithBlend(node, loop, startTimePos, best0, best1, bestWeight); + MultiBlendAnimData::AfterSample(newList, best1); } + MultiBlendAnimData::AfterSample(newList, best0); } - bucket.TimePosition = newTimePos; + MultiBlendAnimData::SetList(bucket, newList); bucket.LastUpdateFrame = context.CurrentFrameIndex; break; @@ -2331,7 +2456,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } break; } - + if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition) { // Blend out From 35c44d60a45bdb41eb932a0692f92c15929b8520 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 30 Oct 2024 17:25:17 +0100 Subject: [PATCH 069/215] Add `AutoGravity` option to `CharacterController` for logic from 4de9e9d918368d34bf112244bacd719862754d30 --- .../Physics/Colliders/CharacterController.cpp | 15 +++++++++++---- .../Physics/Colliders/CharacterController.h | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index d4627cc5f..b554776cd 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -131,6 +131,11 @@ void CharacterController::SetMinMoveDistance(float value) _minMoveDistance = Math::Max(value, 0.0f); } +void CharacterController::SetAutoGravity(bool value) +{ + _autoGravity = value; +} + Vector3 CharacterController::GetVelocity() const { return _controller ? PhysicsBackend::GetRigidDynamicActorLinearVelocity(PhysicsBackend::GetControllerRigidDynamicActor(_controller)) : Vector3::Zero; @@ -262,10 +267,12 @@ void CharacterController::UpdateBounds() void CharacterController::AddMovement(const Vector3& translation, const Quaternion& rotation) { Vector3 displacement = translation; - - // Apply gravity - const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); - displacement += GetPhysicsScene()->GetGravity() * deltaTime; + if (_autoGravity) + { + // Apply gravity + const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); + displacement += GetPhysicsScene()->GetGravity() * deltaTime; + } Move(displacement); diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 540adc5c9..0f22440dd 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -65,6 +65,7 @@ private: float _height; float _minMoveDistance; bool _isUpdatingTransform; + bool _autoGravity = false; Vector3 _upDirection; Vector3 _gravityDisplacement; NonWalkableModes _nonWalkableMode; @@ -148,6 +149,20 @@ public: /// API_PROPERTY() void SetMinMoveDistance(float value); + /// + /// Gets the automatic gravity force applying mode. Can be toggled off if gameplay controls character movement velocity including gravity, or toggled on if gravity should be applied together with root motion from animation movement. + /// + API_PROPERTY(Attributes="EditorOrder(250), DefaultValue(false), EditorDisplay(\"Character Controller\")") + bool GetAutoGravity() const + { + return _autoGravity; + } + + /// + /// Sets the automatic gravity force applying mode. Can be toggled off if gameplay controls character movement velocity including gravity, or toggled on if gravity should be applied together with root motion from animation movement. + /// + API_PROPERTY() void SetAutoGravity(bool value); + public: /// /// Gets the linear velocity of the Character Controller. This allows tracking how fast the character is actually moving, for instance when it is stuck at a wall this value will be the near zero vector. From 3f50625cc3058b3b79ba21f4a8355bd59c943676 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 30 Oct 2024 17:26:06 +0100 Subject: [PATCH 070/215] Fix proper handling of `Scripting.InvokeOnUpdate` if called within that callback --- Source/Engine/Scripting/Scripting.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 66a34faf0..8faf52c2b 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -316,7 +316,8 @@ namespace FlaxEngine lock (UpdateActions) { - for (int i = 0; i < UpdateActions.Count; i++) + int count = UpdateActions.Count; + for (int i = 0; i < count; i++) { try { @@ -327,7 +328,18 @@ namespace FlaxEngine Debug.LogException(ex); } } - UpdateActions.Clear(); + int newlyAdded = UpdateActions.Count - count; + if (newlyAdded == 0) + UpdateActions.Clear(); + else + { + // Someone added another action within current callback + var tmp = new List(); + for (int i = newlyAdded; i < UpdateActions.Count; i++) + tmp.Add(UpdateActions[i]); + UpdateActions.Clear(); + UpdateActions.AddRange(tmp); + } } MainThreadTaskScheduler.Execute(); From e9dcb8a8bbd2a5136a68aa5c2f74c160cb5bd8a2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 6 Nov 2024 09:29:26 +0100 Subject: [PATCH 071/215] Add option to build editor without profiler --- Source/Editor/Editor.Build.cs | 2 ++ Source/Editor/Utilities/Utils.cs | 4 +++- Source/Editor/Windows/GameWindow.cs | 4 +++- Source/Editor/Windows/Profiler/Assets.cs | 2 ++ Source/Editor/Windows/Profiler/CPU.cs | 2 ++ Source/Editor/Windows/Profiler/GPU.cs | 2 ++ Source/Editor/Windows/Profiler/Memory.cs | 2 ++ Source/Editor/Windows/Profiler/MemoryGPU.cs | 2 ++ Source/Editor/Windows/Profiler/Network.cs | 2 ++ Source/Editor/Windows/Profiler/Overall.cs | 2 ++ Source/Editor/Windows/Profiler/Physics.cs | 2 ++ Source/Editor/Windows/Profiler/ProfilerMode.cs | 2 ++ Source/Editor/Windows/Profiler/ProfilerWindow.cs | 6 ++++++ Source/Editor/Windows/Profiler/SamplesBuffer.cs | 4 ++++ Source/Editor/Windows/Profiler/SingleChart.cs | 4 ++++ 15 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index e76cb5dfe..d757c13a3 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -41,6 +41,8 @@ public class Editor : EditorModule options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter"); options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile"); + if (Profiler.Use(options)) + options.ScriptingAPI.Defines.Add("USE_PROFILER"); // Enable optimizations for Editor, disable this for debugging the editor if (options.Configuration == TargetConfiguration.Development) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 1487f1a59..147bd71b7 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1452,6 +1452,7 @@ namespace FlaxEditor.Utilities inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF); inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot); inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow()); +#if USE_PROFILER inputActions.Add(options => options.ProfilerStartStop, () => { bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; @@ -1461,8 +1462,9 @@ namespace FlaxEditor.Utilities inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); - Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); + Editor.Instance.UI.AddStatusMessage("Profiling results cleared."); }); +#endif inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes()); inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes()); inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution()); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 56e4c4380..edcff9b98 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -249,6 +249,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); InputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); InputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame); +#if USE_PROFILER InputActions.Add(options => options.ProfilerStartStop, () => { bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; @@ -258,8 +259,9 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); - Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); + Editor.Instance.UI.AddStatusMessage("Profiling results cleared."); }); +#endif InputActions.Add(options => options.Save, () => { if (Editor.IsPlayMode) diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs index 3b408587a..bfd14a8ac 100644 --- a/Source/Editor/Windows/Profiler/Assets.cs +++ b/Source/Editor/Windows/Profiler/Assets.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System; using System.Collections.Generic; using System.Text; @@ -301,3 +302,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index ab25532d4..4dd1b8eac 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System; using System.Collections.Generic; using FlaxEditor.GUI; @@ -545,3 +546,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index b1203787e..58fbc043c 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System.Collections.Generic; using FlaxEditor.GUI; using FlaxEngine; @@ -390,3 +391,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index 8ab279fb8..5b026fa20 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System; using FlaxEngine.GUI; @@ -91,3 +92,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index 84853b275..205af8f58 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System; using System.Collections.Generic; using System.Linq; @@ -340,3 +341,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index 6e99d6338..68c94186b 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System; using System.Collections.Generic; using FlaxEditor.GUI; @@ -341,3 +342,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/Overall.cs b/Source/Editor/Windows/Profiler/Overall.cs index e2db8f584..e70c01a54 100644 --- a/Source/Editor/Windows/Profiler/Overall.cs +++ b/Source/Editor/Windows/Profiler/Overall.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using FlaxEngine; using FlaxEngine.GUI; @@ -114,3 +115,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/Physics.cs b/Source/Editor/Windows/Profiler/Physics.cs index ec80a6633..3e5ec6605 100644 --- a/Source/Editor/Windows/Profiler/Physics.cs +++ b/Source/Editor/Windows/Profiler/Physics.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using FlaxEngine; using FlaxEngine.GUI; @@ -111,3 +112,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs index 088edd558..297d7045d 100644 --- a/Source/Editor/Windows/Profiler/ProfilerMode.cs +++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#if USE_PROFILER using System; using System.Collections.Generic; using FlaxEditor.GUI; @@ -169,3 +170,4 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index 6b7a41db2..4a85c246e 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.Windows.Profiler /// public sealed class ProfilerWindow : EditorWindow { +#if USE_PROFILER private readonly ToolStripButton _liveRecordingButton; private readonly ToolStripButton _clearButton; private readonly ToolStripButton _prevFrameButton; @@ -77,6 +78,7 @@ namespace FlaxEditor.Windows.Profiler } } } +#endif /// /// Initializes a new instance of the class. @@ -87,6 +89,7 @@ namespace FlaxEditor.Windows.Profiler { Title = "Profiler"; +#if USE_PROFILER var toolstrip = new ToolStrip { Parent = this, @@ -121,8 +124,10 @@ namespace FlaxEditor.Windows.Profiler FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); InputActions.Bindings.RemoveAll(x => x.Callback == this.FocusOrShow); InputActions.Add(options => options.ProfilerWindow, Hide); +#endif } +#if USE_PROFILER private void OnLiveRecordingChanged() { _liveRecordingButton.Icon = LiveRecording ? Editor.Icons.Stop64 : Editor.Icons.Play64; @@ -298,5 +303,6 @@ namespace FlaxEditor.Windows.Profiler return false; } +#endif } } diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs index 3a5d51f5e..5a707aa1e 100644 --- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs +++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs @@ -36,7 +36,11 @@ namespace FlaxEditor.Windows.Profiler /// Initializes a new instance of the class. /// /// The maximum buffer capacity. +#if USE_PROFILER public SamplesBuffer(int capacity = ProfilerMode.MaxSamples) +#else + public SamplesBuffer(int capacity = 600) +#endif { _data = new T[capacity]; _count = 0; diff --git a/Source/Editor/Windows/Profiler/SingleChart.cs b/Source/Editor/Windows/Profiler/SingleChart.cs index cd24ac635..259af1c69 100644 --- a/Source/Editor/Windows/Profiler/SingleChart.cs +++ b/Source/Editor/Windows/Profiler/SingleChart.cs @@ -63,7 +63,11 @@ namespace FlaxEditor.Windows.Profiler /// Initializes a new instance of the class. /// /// The maximum samples to collect. +#if USE_PROFILER public SingleChart(int maxSamples = ProfilerMode.MaxSamples) +#else + public SingleChart(int maxSamples = 600) +#endif : base(0, 0, 100, DefaultHeight) { _samples = new SamplesBuffer(maxSamples); From 3de3c1f2c6d516bca311ddad5c40f8325f09d447 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 6 Nov 2024 15:38:27 +0100 Subject: [PATCH 072/215] Add debug drawing tangent frame of the selected skeleton node --- Source/Editor/Windows/Assets/SkinnedModelWindow.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 671512584..066d49b4c 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -1060,8 +1060,13 @@ namespace FlaxEditor.Windows.Assets // Draw selected skeleton nodes foreach (var node in proxy.NodesTree.Selection) { - proxy.Window._preview.PreviewActor.GetNodeTransformation(node.Text, out var nodeTransformation, true); - DebugDraw.DrawWireSphere(new BoundingSphere(nodeTransformation.TranslationVector, 4.0f), Color.Red, 0.0f, false); + proxy.Window._preview.PreviewActor.GetNodeTransformation(node.Text, out var t, true); + DebugDraw.DrawWireSphere(new BoundingSphere(t.TranslationVector, 4.0f), Color.Red, 0.0f, false); + float tangentFrameSize = 0.05f * Utilities.Units.Meters2Units; + Vector3 arrowsOrigin = t.TranslationVector + 0.001f * Utilities.Units.Meters2Units; + DebugDraw.DrawLine(arrowsOrigin, arrowsOrigin + t.Forward * tangentFrameSize, CustomEditors.Editors.ActorTransformEditor.AxisColorX, 0.0f, false); + DebugDraw.DrawLine(arrowsOrigin, arrowsOrigin + t.Up * tangentFrameSize, CustomEditors.Editors.ActorTransformEditor.AxisColorY, 0.0f, false); + DebugDraw.DrawLine(arrowsOrigin, arrowsOrigin + t.Right * tangentFrameSize, CustomEditors.Editors.ActorTransformEditor.AxisColorZ, 0.0f, false); } } } From 377eb78020ee57b542ca23454ab9eb76c58c6476 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Nov 2024 23:22:08 +0100 Subject: [PATCH 073/215] Fix output log command history popup to return focus back to input field on arrow left --- Source/Editor/Windows/OutputLogWindow.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 6468a2b42..ed5d1acdc 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -166,6 +166,17 @@ namespace FlaxEditor.Windows return true; } break; + case KeyboardKeys.ArrowLeft: + if (Owner != null && (!Owner._searchPopup?.Visible ?? true)) + { + // Focus back the input field as user want to modify command from history + Owner._searchPopup?.Hide(); + Owner.RootWindow.Focus(); + Owner.Focus(); + Owner.OnKeyDown(key); + return true; + } + break; } return base.OnKeyDown(key); } From 826d37c513d6097c0b4117d78459765e6d2b34ae Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Nov 2024 23:40:25 +0100 Subject: [PATCH 074/215] Add option to copy/paste skeleton retargetting between models --- .../Windows/Assets/SkinnedModelWindow.cs | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 066d49b4c..67697eb67 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -832,6 +832,8 @@ namespace FlaxEditor.Windows.Assets // New setup { var setupGroup = layout.Group("New setup"); + setupGroup.Panel.Tag = null; + setupGroup.Panel.MouseButtonRightClicked += OnPanelHeaderRightClicked; infoLabel = setupGroup.Label("Select model or animation asset to add new retarget source", TextAlignment.Center).Label; infoLabel.Wrapping = TextWrapping.WrapWords; infoLabel.AutoHeight = true; @@ -853,6 +855,8 @@ namespace FlaxEditor.Windows.Assets if (sourceAsset == null) continue; var setupGroup = layout.Group(Path.GetFileNameWithoutExtension(sourceAsset.Path)); + setupGroup.Panel.Tag = sourceAsset; + setupGroup.Panel.MouseButtonRightClicked += OnPanelHeaderRightClicked; var settingsButton = setupGroup.AddSettingsButton(); settingsButton.Tag = sourceAsset; settingsButton.Clicked += OnShowSetupSettings; @@ -931,6 +935,11 @@ namespace FlaxEditor.Windows.Assets } } + private void OnPanelHeaderRightClicked(DropPanel panel, Float2 location) + { + OnShowSetupSettings((Asset)panel.Tag, panel, location); + } + private void OnSelectedNodeChanged(ComboBox comboBox) { var proxy = (RetargetPropertiesProxy)Values[0]; @@ -946,12 +955,21 @@ namespace FlaxEditor.Windows.Assets { if (button == MouseButton.Left) { - var sourceAsset = (Asset)settingsButton.Tag; - var menu = new ContextMenu { Tag = sourceAsset }; + OnShowSetupSettings((Asset)settingsButton.Tag, settingsButton, new Float2(0, settingsButton.Height)); + } + } + + private void OnShowSetupSettings(Asset sourceAsset, Control targetControl, Float2 targetLocation) + { + var menu = new ContextMenu { Tag = sourceAsset }; + if (sourceAsset != null) + { menu.AddButton("Clear", OnClearSetup); menu.AddButton("Remove", OnRemoveSetup).Icon = Editor.Instance.Icons.Cross12; - menu.Show(settingsButton, new Float2(0, settingsButton.Height)); + menu.AddButton("Copy", OnCopySetup); } + menu.AddButton("Paste", OnPasteSetup); + menu.Show(targetControl, targetLocation); } private void OnClearSetup(ContextMenuButton button) @@ -975,6 +993,53 @@ namespace FlaxEditor.Windows.Assets RebuildLayout(); } + private struct RetargetSetupData + { + public Asset SourceAsset; + public SetupProxy Proxy; + } + + private void OnCopySetup(ContextMenuButton button) + { + var proxy = (RetargetPropertiesProxy)Values[0]; + var sourceAsset = (Asset)button.ParentContextMenu.Tag; + var setup = proxy.Setups[sourceAsset]; + var str = FlaxEngine.Json.JsonSerializer.Serialize(new RetargetSetupData + { + SourceAsset = sourceAsset, + Proxy = setup, + }); + Clipboard.Text = str; + } + + private void OnPasteSetup(ContextMenuButton button) + { + var proxy = (RetargetPropertiesProxy)Values[0]; + var sourceAsset = (Asset)button.ParentContextMenu.Tag; + var str = Clipboard.Text; + var data = FlaxEngine.Json.JsonSerializer.Deserialize(str); + if (sourceAsset == null) + sourceAsset = data.SourceAsset; + if (proxy.Setups.TryGetValue(sourceAsset, out var setup)) + { + // Copy mappings for existing nodes in that mapping + foreach (var e in setup.NodesMapping.Keys.ToArray()) + { + data.Proxy.NodesMapping.TryGetValue(e, out string name); + setup.NodesMapping[e] = name; + } + } + else + { + // Add a new mapping + proxy.Setups.Add(sourceAsset, setup = new SetupProxy()); + setup.Skeleton = data.Proxy.Skeleton; + setup.NodesMapping = data.Proxy.NodesMapping; + } + proxy.Window.MarkAsEdited(); + RebuildLayout(); + } + private bool CheckSourceAssetValid(ContentItem item) { var proxy = (RetargetPropertiesProxy)Values[0]; From f8f4edfa765a5ac1e193976acb1d1b426e1ddf05 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Nov 2024 23:42:05 +0100 Subject: [PATCH 075/215] Fix variious issues --- Source/Editor/Modules/UIModule.cs | 2 ++ Source/Editor/Windows/GameCookerWindow.cs | 2 +- Source/Engine/Core/Math/Quaternion.h | 4 ++-- Source/Engine/Core/Math/Transform.h | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index f6d140e0e..b3f9ce05a 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -447,6 +447,8 @@ namespace FlaxEditor.Modules private void StateMachineOnStateChanged() { + if (Editor.StateMachine.CurrentState is States.ClosingState) + return; UpdateToolstrip(); UpdateStatusBar(); } diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index cbe211eca..e6b9fac2e 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -648,7 +648,7 @@ namespace FlaxEditor.Windows /// True if can build, otherwise false. public bool CanBuild(PlatformType platformType) { - if (_buildTabProxy.PerPlatformOptions.TryGetValue(platformType, out var platform)) + if (_buildTabProxy != null && _buildTabProxy.PerPlatformOptions.TryGetValue(platformType, out var platform)) return platform.IsAvailable && platform.IsSupported; return false; } diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index cadc6730b..0b181f862 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -393,8 +393,8 @@ public: /// The inverse of the specified quaternion. static Quaternion Invert(const Quaternion& value) { - Quaternion result; - Invert(value, result); + Quaternion result = value; + result.Invert(); return result; } diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index 8e9e07e0a..378e44ac0 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -155,7 +155,7 @@ public: /// Subtracts transformation from this transform. /// /// The other transformation. - /// The different of two transformations. + /// The difference of two transformations. Transform Subtract(const Transform& other) const; /// From 74993dcf9e1120f56fd251f9816e1d6bf48ca410 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Nov 2024 12:29:05 +0100 Subject: [PATCH 076/215] Fix animation retargetting to properly handle different skeleton nodes orientation --- Source/Engine/Animations/Graph/AnimGraph.cpp | 24 ++--- .../Animations/Graph/AnimGroup.Animation.cpp | 92 ++++++++++++++++--- Source/Engine/Content/Assets/SkinnedModel.cpp | 15 ++- Source/Engine/Content/Assets/SkinnedModel.h | 3 +- 4 files changed, 98 insertions(+), 36 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 18013b169..a4424e922 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -7,7 +7,7 @@ #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Scripting/Scripting.h" -extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i); +extern void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes); ThreadLocal AnimGraphExecutor::Context; @@ -338,31 +338,25 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) if (_graph.BaseModel != data.NodesSkeleton) { ANIM_GRAPH_PROFILE_EVENT("Retarget"); - - // Init nodes for the target skeleton auto& targetSkeleton = data.NodesSkeleton->Skeleton; retargetNodes = *animResult; retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count()); Transform* targetNodes = retargetNodes.Nodes.Get(); - for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++) - targetNodes[i] = targetSkeleton.Nodes[i].LocalTransform; - // Use skeleton mapping + // Attempt to retarget output pose for the target skeleton const SkinnedModel::SkeletonMapping mapping = data.NodesSkeleton->GetSkeletonMapping(_graph.BaseModel); if (mapping.NodesMapping.IsValid()) { + // Use skeleton mapping const auto& sourceSkeleton = _graph.BaseModel->Skeleton; Transform* sourceNodes = animResult->Nodes.Get(); + RetargetSkeletonPose(sourceSkeleton, targetSkeleton, mapping, sourceNodes, targetNodes); + } + else + { + // Use T-pose as a fallback for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++) - { - const int32 nodeToNode = mapping.NodesMapping[i]; - if (nodeToNode != -1) - { - Transform node = sourceNodes[nodeToNode]; - RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i); - targetNodes[i] = node; - } - } + targetNodes[i] = targetSkeleton.Nodes[i].LocalTransform; } animResult = &retargetNodes; diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index a0c90b854..bee1f4db4 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -109,24 +109,86 @@ namespace nodes->RootMotion.Orientation.Normalize(); } } + + Matrix ComputeWorldMatrixRecursive(const SkeletonData& skeleton, int32 index, Matrix localMatrix) + { + const auto& node = skeleton.Nodes[index]; + index = node.ParentIndex; + while (index != -1) + { + const auto& parent = skeleton.Nodes[index]; + localMatrix *= parent.LocalTransform.GetWorld(); + index = parent.ParentIndex; + } + return localMatrix; + } + + Matrix ComputeInverseParentMatrixRecursive(const SkeletonData& skeleton, int32 index) + { + Matrix inverseParentMatrix = Matrix::Identity; + const auto& node = skeleton.Nodes[index]; + if (node.ParentIndex != -1) + { + inverseParentMatrix = ComputeWorldMatrixRecursive(skeleton, index, inverseParentMatrix); + inverseParentMatrix = Matrix::Invert(inverseParentMatrix); + } + return inverseParentMatrix; + } } -void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, Transform& node, int32 i) +void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 targetIndex) { - const int32 nodeToNode = mapping.NodesMapping[i]; - if (nodeToNode == -1) + // sourceSkeleton - skeleton of Anim Graph (Base Locomotion pack) + // targetSkeleton - visual mesh skeleton (City Characters pack) + // target - anim graph input/output transformation of that node + const auto& targetNode = targetSkeleton.Nodes[targetIndex]; + const int32 sourceIndex = sourceMapping.NodesMapping[targetIndex]; + if (sourceIndex == -1) + { + // Use T-pose + node = targetNode.LocalTransform; return; + } + const auto& sourceNode = sourceSkeleton.Nodes[sourceIndex]; - // Map source skeleton node to the target skeleton (use ref pose difference) - const auto& sourceNode = sourceSkeleton.Nodes[nodeToNode]; - const auto& targetNode = targetSkeleton.Nodes[i]; - Transform value = node; - const Transform sourceToTarget = targetNode.LocalTransform - sourceNode.LocalTransform; - value.Translation += sourceToTarget.Translation; - value.Scale *= sourceToTarget.Scale; - value.Orientation = sourceToTarget.Orientation * value.Orientation; // TODO: find out why this doesn't match referenced animation when played on that skeleton originally - value.Orientation.Normalize(); - node = value; + // [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/] + + // Calculate T-Pose of source node, target node and target parent node + Matrix bindMatrix = ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, sourceNode.LocalTransform.GetWorld()); + Matrix inverseBindMatrix = Matrix::Invert(bindMatrix); + Matrix targetMatrix = ComputeWorldMatrixRecursive(targetSkeleton, targetIndex, targetNode.LocalTransform.GetWorld()); + Matrix inverseParentMatrix = ComputeInverseParentMatrixRecursive(targetSkeleton, targetIndex); + + // Target node animation is world-space difference of the animated source node inside the target's parent node world-space + Matrix localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, node.GetWorld()); + localMatrix = targetMatrix * localMatrix * inverseParentMatrix; + + // Extract local node transformation + localMatrix.Decompose(node); +} + +void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes) +{ + // TODO: cache source and target skeletons world-space poses for faster retargeting (use some pooled memory) + ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == mapping.NodesMapping.Length()); + for (int32 targetIndex = 0; targetIndex < targetSkeleton.Nodes.Count(); targetIndex++) + { + auto& targetNode = targetSkeleton.Nodes.Get()[targetIndex]; + const int32 sourceIndex = mapping.NodesMapping.Get()[targetIndex]; + Transform node; + if (sourceIndex == -1) + { + // Use T-pose + node = targetNode.LocalTransform; + } + else + { + // Retarget + node = sourceNodes[sourceIndex]; + RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, targetIndex); + } + targetNodes[targetIndex] = node; + } } AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node) @@ -1867,8 +1929,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Check for transition interruption else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking) && - EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) && - bucket.ActiveTransition->RuleGraph) + EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) && + bucket.ActiveTransition->RuleGraph) { // Execute transition rule auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode(); diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index d70c9fb35..80131b03b 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -155,7 +155,7 @@ void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const GetChunkData(chunkIndex, data); } -SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source) +SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget) { SkeletonMapping mapping; mapping.TargetSkeleton = this; @@ -168,10 +168,6 @@ SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source) PROFILE_CPU(); // Initialize the mapping - const int32 nodesCount = Skeleton.Nodes.Count(); - mappingData.NodesMapping = Span((int32*)Allocator::Allocate(nodesCount * sizeof(int32)), nodesCount); - for (int32 i = 0; i < nodesCount; i++) - mappingData.NodesMapping[i] = -1; SkeletonRetarget* retarget = nullptr; const Guid sourceId = source->GetID(); for (auto& e : _skeletonRetargets) @@ -182,6 +178,15 @@ SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source) break; } } + if (!retarget && !autoRetarget) + { + // Skip automatic retarget + return mapping; + } + const int32 nodesCount = Skeleton.Nodes.Count(); + mappingData.NodesMapping = Span((int32*)Allocator::Allocate(nodesCount * sizeof(int32)), nodesCount); + for (int32 i = 0; i < nodesCount; i++) + mappingData.NodesMapping[i] = -1; if (const auto* sourceAnim = Cast(source)) { const auto& channels = sourceAnim->Data.Channels; diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index 1461844a1..cf778027d 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -177,8 +177,9 @@ public: /// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup. /// /// The source asset (animation or other skinned model) to get mapping to its skeleton. + /// Enables automatic skeleton retargeting based on nodes names. Can be disabled to query existing skeleton mapping or return null if not defined. /// The skeleton mapping for the source asset into this skeleton. - SkeletonMapping GetSkeletonMapping(Asset* source); + SkeletonMapping GetSkeletonMapping(Asset* source, bool autoRetarget = true); /// /// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance. From 914d82a087817756591142346a95cca9e51f4462 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Nov 2024 16:04:34 +0100 Subject: [PATCH 077/215] Fix Variant enum casting --- Source/Engine/Core/Types/Variant.cpp | 61 ++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index b410d828e..fc921df0d 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -33,6 +33,7 @@ #else #define MANAGED_GC_HANDLE AsUint #endif +#define AsEnum AsUint64 namespace { @@ -1155,8 +1156,9 @@ bool Variant::operator==(const Variant& other) const case VariantType::Int64: return AsInt64 == other.AsInt64; case VariantType::Uint64: - case VariantType::Enum: return AsUint64 == other.AsUint64; + case VariantType::Enum: + return AsEnum == other.AsEnum; case VariantType::Float: return Math::NearEqual(AsFloat, other.AsFloat); case VariantType::Double: @@ -1285,8 +1287,9 @@ bool Variant::operator<(const Variant& other) const case VariantType::Int64: return AsInt64 < other.AsInt64; case VariantType::Uint64: - case VariantType::Enum: return AsUint64 < other.AsUint64; + case VariantType::Enum: + return AsEnum < other.AsEnum; case VariantType::Float: return AsFloat < other.AsFloat; case VariantType::Double: @@ -3009,7 +3012,7 @@ Variant Variant::NewValue(const StringAnsiView& typeName) break; case ScriptingTypes::Enum: v.SetType(VariantType(VariantType::Enum, typeName)); - v.AsUint64 = 0; + v.AsEnum = 0; break; default: LOG(Error, "Unsupported scripting type '{}' for Variant", typeName.ToString()); @@ -3023,7 +3026,7 @@ Variant Variant::NewValue(const StringAnsiView& typeName) if (mclass->IsEnum()) { v.SetType(VariantType(VariantType::Enum, typeName)); - v.AsUint64 = 0; + v.AsEnum = 0; } else if (mclass->IsValueType()) { @@ -3097,8 +3100,10 @@ Variant Variant::Parse(const StringView& text, const VariantType& type) StringUtils::Parse(text.Get(), text.Length(), &result.AsInt64); break; case VariantType::Uint64: + StringUtils::Parse(text.Get(), text.Length(), &result.AsInt64); + break; case VariantType::Enum: - if (!StringUtils::Parse(text.Get(), text.Length(), &result.AsUint64)) + if (!StringUtils::Parse(text.Get(), text.Length(), &result.AsEnum)) { } else if (type.TypeName) @@ -3113,7 +3118,7 @@ Variant Variant::Parse(const StringView& text, const VariantType& type) { if (textAnsiView == items[i].Name) { - result.AsUint64 = items[i].Value; + result.AsEnum = items[i].Value; break; } } @@ -3491,8 +3496,8 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) return Variant(Double3(v.AsBool ? 1.0 : 0.0)); case VariantType::Double4: return Variant(Double4(v.AsBool ? 1.0 : 0.0)); - case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + case VariantType::Enum: + return Enum(to, v.AsBool ? 1 : 0); default: ; } break; @@ -3530,7 +3535,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4((double)v.AsInt16)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsInt16); default: ; } break; @@ -3562,7 +3567,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Color: return Variant(Color((float)v.AsInt)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsInt); default: ; } break; @@ -3600,7 +3605,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4((double)v.AsUint16)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsUint16); default: ; } break; @@ -3638,7 +3643,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4((double)v.AsUint)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsUint); default: ; } break; @@ -3676,7 +3681,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4((double)v.AsInt64)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsInt64); default: ; } break; @@ -3714,7 +3719,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4((double)v.AsInt)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsInt); default: ; } break; @@ -3752,7 +3757,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4(v.AsFloat)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsFloat); default: ; } break; @@ -3790,7 +3795,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Double4: return Variant(Double4(v.AsDouble)); case VariantType::Enum: - return Enum(to, (int64)v.AsBool); + return Enum(to, (int64)v.AsDouble); default: ; } break; @@ -3938,6 +3943,25 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) default: ; } break; + case VariantType::Enum: + switch (to.Type) + { + case VariantType::Bool: + return Variant(v.AsEnum != 0); + case VariantType::Int: + return Variant((int32)v.AsEnum); + case VariantType::Uint: + return Variant((uint32)v.AsEnum); + case VariantType::Int64: + return Variant((int64)v.AsEnum); + case VariantType::Uint64: + return Variant((uint64)v.AsEnum); + case VariantType::Float: + return Variant((float)v.AsEnum); + case VariantType::Double: + return Variant((double)v.AsEnum); + } + break; default: ; } LOG(Error, "Cannot cast Variant from {0} to {1}", v.Type, to); @@ -4162,7 +4186,7 @@ Variant Variant::Enum(const VariantType& type, const uint64 value) { Variant v; v.SetType(type); - v.AsUint64 = value; + v.AsEnum = value; return MoveTemp(v); } @@ -4183,8 +4207,9 @@ uint32 GetHash(const Variant& key) case VariantType::Int64: return GetHash(key.AsInt64); case VariantType::Uint64: - case VariantType::Enum: return GetHash(key.AsUint64); + case VariantType::Enum: + return GetHash(key.AsEnum); case VariantType::Float: return GetHash(key.AsFloat); case VariantType::Double: From 344595e171ac72e74630328d301cbd8b1197d72b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 23 Nov 2024 20:31:17 +0100 Subject: [PATCH 078/215] Don't mark surface as edited if none of the nodes were pasted --- Source/Editor/Surface/VisjectSurface.CopyPaste.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs index 71004aede..0d5057613 100644 --- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs +++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs @@ -457,7 +457,8 @@ namespace FlaxEditor.Surface // Select those nodes Select(nodes.Values); - MarkAsEdited(); + if (nodes.Count > 0) + MarkAsEdited(); } catch (Exception ex) { From a7061a75241b57d07cb33d6bfd03b83180139189 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Nov 2024 22:13:21 +0100 Subject: [PATCH 079/215] Fix raw scripting object pointers auto-serialization --- Source/Engine/Serialization/SerializationFwd.h | 15 +++++++++++++++ .../Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/Source/Engine/Serialization/SerializationFwd.h b/Source/Engine/Serialization/SerializationFwd.h index de129d5d8..7917fc52e 100644 --- a/Source/Engine/Serialization/SerializationFwd.h +++ b/Source/Engine/Serialization/SerializationFwd.h @@ -76,3 +76,18 @@ class ISerializeModifier; if (e != stream.MemberEnd() && e->value.IsBool()) \ member = e->value.GetBool() ? 1 : 0; \ } + +// Explicit auto-cast for object pointer + +#define SERIALIZE_OBJ(name) \ + if (Serialization::ShouldSerialize((const ScriptingObject*&)name, other ? &other->name : nullptr)) \ + { \ + stream.JKEY(#name); \ + Serialization::Serialize(stream, (const ScriptingObject*&)name, other ? &other->name : nullptr); \ + } +#define DESERIALIZE_OBJ(name) \ + { \ + const auto e = SERIALIZE_FIND_MEMBER(stream, #name); \ + if (e != stream.MemberEnd()) \ + Serialization::Deserialize(e->value, (ScriptingObject*&)name, modifier); \ + } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 138291177..86bd3dcd4 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1799,6 +1799,12 @@ namespace Flax.Build.Bindings { if (memberType.IsBitField) return "_BIT"; + if (memberType.IsPtr) + { + var t = FindApiTypeInfo(buildData, memberType, caller); + if (t.IsScriptingObject) + return "_OBJ"; + } return string.Empty; } From 0e131731199a2d8ab420ef47e0c88992146dbcc8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Nov 2024 22:13:46 +0100 Subject: [PATCH 080/215] Fix actor reference select in prefab editor --- .../Editors/FlaxObjectRefEditor.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index fd367d21f..8abe54e78 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -328,9 +328,9 @@ namespace FlaxEditor.CustomEditors.Editors { // Select object if (_value is Actor actor) - Editor.Instance.SceneEditing.Select(actor); + Select(actor); else if (_value is Script script && script.Actor) - Editor.Instance.SceneEditing.Select(script.Actor); + Select(script.Actor); else if (_value is Asset asset) Editor.Instance.Windows.ContentWin.Select(asset); } @@ -348,6 +348,28 @@ namespace FlaxEditor.CustomEditors.Editors ShowDropDownMenu(); } + private void Select(Actor actor) + { + var node = SceneGraphFactory.FindNode(actor.ID) as ActorNode; + if (node == null) + return; + + var c = Parent; + while (c != null) + { + if (c is Windows.Assets.PrefabWindow prefabWindow) + { + // Prefab editor + prefabWindow.Select(node); + return; + } + c = c.Parent; + } + + // Global selection + Editor.Instance.SceneEditing.Select(actor); + } + private void DoDrag() { // Do the drag drop operation if has selected element From ab78314a01aa58286558351b7671258162633715 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Nov 2024 22:39:02 +0100 Subject: [PATCH 081/215] Revert part of 9870d162e4c5e86706dbc3e91ffe5aa241079e39 to support prefab variants creation --- Source/Engine/Level/Prefabs/PrefabManager.cpp | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 99fd7d674..96040e8b6 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -336,8 +336,6 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat // Serialize to json data ASSERT(!IsCreatingPrefab); IsCreatingPrefab = true; - const Guid targetPrefabId = targetActor->GetPrefabID(); - const bool hasTargetPrefabId = targetPrefabId.IsValid(); rapidjson_flax::StringBuffer actorsDataBuffer; { CompactJsonWriter writerObj(actorsDataBuffer); @@ -346,27 +344,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); - - // Detect when creating prefab from object that is already part of prefab then serialize it as unlinked - const Guid prefabId = obj->GetPrefabID(); - const Guid prefabObjectId = obj->GetPrefabObjectID(); - bool isObjectFromPrefab = targetPrefabId == prefabId && prefabId.IsValid(); // Allow to use other nested prefabs properly (ignore only root object's prefab link) - if (isObjectFromPrefab) - { - //obj->BreakPrefabLink(); - obj->_prefabID = Guid::Empty; - obj->_prefabObjectID = Guid::Empty; - } - writer.SceneObject(obj); - - // Restore broken link - if (hasTargetPrefabId) - { - //obj->LinkPrefab(prefabId, prefabObjectId); - obj->_prefabID = prefabId; - obj->_prefabObjectID = prefabObjectId; - } } writer.EndArray(); } From cedf4b1eb5cb9f84e240b8440e2cfbcb47c96db9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Dec 2024 15:08:13 +0100 Subject: [PATCH 082/215] Remove `GPUShaderProgramsContainer` to simplify `GPUShader` --- Source/Engine/Graphics/Shaders/GPUShader.cpp | 82 +++++--------------- Source/Engine/Graphics/Shaders/GPUShader.h | 59 +------------- 2 files changed, 23 insertions(+), 118 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 577452a5c..3fcde42cf 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -7,53 +7,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Serialization/MemoryReadStream.h" -GPUShaderProgramsContainer::GPUShaderProgramsContainer() - : _shaders(64) -{ - // TODO: test different values for _shaders capacity, test performance impact (less hash collisions but more memory?) -} - -GPUShaderProgramsContainer::~GPUShaderProgramsContainer() -{ - // Remember to delete all programs - _shaders.ClearDelete(); -} - -void GPUShaderProgramsContainer::Add(GPUShaderProgram* shader, int32 permutationIndex) -{ - // Validate input - ASSERT(shader && Math::IsInRange(permutationIndex, 0, SHADER_PERMUTATIONS_MAX_COUNT - 1)); -#if ENABLE_ASSERTION - if ((Get(shader->GetName(), permutationIndex) != nullptr)) - { - CRASH; - } -#endif - - // Store shader - const int32 hash = CalculateHash(shader->GetName(), permutationIndex); - _shaders.Add(hash, shader); -} - -GPUShaderProgram* GPUShaderProgramsContainer::Get(const StringAnsiView& name, int32 permutationIndex) const -{ - // Validate input - ASSERT(name.Length() > 0 && Math::IsInRange(permutationIndex, 0, SHADER_PERMUTATIONS_MAX_COUNT - 1)); - - // Find shader - GPUShaderProgram* result = nullptr; - const int32 hash = CalculateHash(name, permutationIndex); - _shaders.TryGet(hash, result); - - return result; -} - -void GPUShaderProgramsContainer::Clear() -{ - _shaders.ClearDelete(); -} - -uint32 GPUShaderProgramsContainer::CalculateHash(const StringAnsiView& name, int32 permutationIndex) +static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 permutationIndex) { return GetHash(name) * 37 + permutationIndex; } @@ -73,7 +27,7 @@ bool GPUShader::Create(MemoryReadStream& stream) stream.ReadInt32(&version); if (version != GPU_SHADER_CACHE_VERSION) { - LOG(Warning, "Unsupported shader version {0}. The supported version is {1}.", version, GPU_SHADER_CACHE_VERSION); + LOG(Warning, "Unsupported shader version {0}. The current version is {1}.", version, GPU_SHADER_CACHE_VERSION); return true; } @@ -130,19 +84,21 @@ bool GPUShader::Create(MemoryReadStream& stream) if (shader == nullptr) { #if !GPU_ALLOW_TESSELLATION_SHADERS - if (type == ShaderStage::Hull || type == ShaderStage::Domain) - continue; + if (type == ShaderStage::Hull || type == ShaderStage::Domain) + continue; #endif #if !GPU_ALLOW_GEOMETRY_SHADERS - if (type == ShaderStage::Geometry) - continue; + if (type == ShaderStage::Geometry) + continue; #endif LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name); return true; } - // Add to collection - _shaders.Add(shader, permutationIndex); + // Add to the collection + const uint32 hash = HashPermutation(shader->GetName(), permutationIndex); + ASSERT_LOW_LAYER(!_shaders.ContainsKey(hash)); + _shaders.Add(hash, shader); } } @@ -183,17 +139,21 @@ bool GPUShader::Create(MemoryReadStream& stream) return false; } +bool GPUShader::HasShader(const StringAnsiView& name, int32 permutationIndex) const +{ + const uint32 hash = HashPermutation(name, permutationIndex); + return _shaders.ContainsKey(hash); +} + GPUShaderProgram* GPUShader::GetShader(ShaderStage stage, const StringAnsiView& name, int32 permutationIndex) const { - const auto shader = _shaders.Get(name, permutationIndex); - + GPUShaderProgram* shader = nullptr; + const uint32 hash = HashPermutation(name, permutationIndex); + _shaders.TryGet(hash, shader); #if BUILD_RELEASE - // Release build is more critical on that ASSERT(shader != nullptr && shader->GetStage() == stage); - #else - if (shader == nullptr) { LOG(Error, "Missing {0} shader \'{1}\'[{2}]. Object: {3}.", ::ToString(stage), String(name), permutationIndex, ToString()); @@ -202,9 +162,7 @@ GPUShaderProgram* GPUShader::GetShader(ShaderStage stage, const StringAnsiView& { LOG(Error, "Invalid shader stage \'{1}\'[{2}]. Expected: {0}. Actual: {4}. Object: {3}.", ::ToString(stage), String(name), permutationIndex, ToString(), ::ToString(shader->GetStage())); } - #endif - return shader; } @@ -224,5 +182,5 @@ void GPUShader::OnReleaseGPU() } } _memoryUsage = 0; - _shaders.Clear(); + _shaders.ClearDelete(); } diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index c48bf48b3..0ff511cc3 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -14,64 +14,15 @@ class GPUShaderProgram; /// #define GPU_SHADER_CACHE_VERSION 9 -/// -/// Represents collection of shader programs with permutations and custom names. -/// -class GPUShaderProgramsContainer -{ -private: - Dictionary _shaders; - -public: - /// - /// Initializes a new instance of the class. - /// - GPUShaderProgramsContainer(); - - /// - /// Finalizes an instance of the class. - /// - ~GPUShaderProgramsContainer(); - -public: - /// - /// Adds a new shader program to the collection. - /// - /// The shader to store. - /// The shader permutation index. - void Add(GPUShaderProgram* shader, int32 permutationIndex); - - /// - /// Gets a shader of given name and permutation index. - /// - /// The shader program name. - /// The shader permutation index. - /// Stored shader program or null if cannot find it. - GPUShaderProgram* Get(const StringAnsiView& name, int32 permutationIndex) const; - - /// - /// Clears collection (deletes all shaders). - /// - void Clear(); - -public: - /// - /// Calculates unique hash for given shader program name and its permutation index. - /// - /// The shader program name. - /// The shader program permutation index. - /// Calculated hash value. - static uint32 CalculateHash(const StringAnsiView& name, int32 permutationIndex); -}; - /// /// The GPU resource with shader programs that can run on the GPU and are able to perform rendering calculation using textures, vertices and other resources. /// API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUShader : public GPUResource { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUShader); + protected: - GPUShaderProgramsContainer _shaders; + Dictionary _shaders; GPUConstantBuffer* _constantBuffers[MAX_CONSTANT_BUFFER_SLOTS]; GPUShader(); @@ -173,17 +124,13 @@ public: return _constantBuffers[slot]; } -public: /// /// Determines whether the specified shader program is in the shader. /// /// The shader program name. /// The shader permutation index. /// true if the shader is valid; otherwise, false. - FORCE_INLINE bool HasShader(const StringAnsiView& name, int32 permutationIndex = 0) const - { - return _shaders.Get(name, permutationIndex) != nullptr; - } + bool HasShader(const StringAnsiView& name, int32 permutationIndex = 0) const; protected: GPUShaderProgram* GetShader(ShaderStage stage, const StringAnsiView& name, int32 permutationIndex) const; From fc4e6f4972d58be0f6ad79bbc6170aa2abe4794f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Dec 2024 09:20:01 +0100 Subject: [PATCH 083/215] Add `GPUVertexLayout` to graphics backends --- Source/Engine/Graphics/Config.h | 4 + Source/Engine/Graphics/GPUDevice.cpp | 3 + Source/Engine/Graphics/GPUDevice.h | 8 ++ Source/Engine/Graphics/Shaders/Config.h | 7 +- Source/Engine/Graphics/Shaders/GPUShader.cpp | 63 +++++++---- Source/Engine/Graphics/Shaders/GPUShader.h | 4 +- .../Graphics/Shaders/GPUShaderProgram.h | 32 +++--- .../Graphics/Shaders/GPUVertexLayout.cpp | 103 ++++++++++++++++++ .../Engine/Graphics/Shaders/GPUVertexLayout.h | 44 ++++++++ .../Engine/Graphics/Shaders/VertexElement.h | 86 +++++++++++++++ .../DirectX/DX11/GPUDeviceDX11.cpp | 30 +++++ .../DirectX/DX11/GPUDeviceDX11.h | 1 + .../DirectX/DX11/GPUShaderDX11.h | 10 +- .../DirectX/DX11/GPUVertexLayoutDX11.h | 22 ++++ .../DirectX/DX12/GPUDeviceDX12.cpp | 30 +++++ .../DirectX/DX12/GPUDeviceDX12.h | 1 + .../DirectX/DX12/GPUShaderDX12.h | 3 - .../DirectX/DX12/GPUShaderProgramDX12.h | 2 + .../DirectX/DX12/GPUVertexLayoutDX12.h | 22 ++++ .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 58 ++++++++++ .../GraphicsDevice/DirectX/RenderToolsDX.h | 3 + .../GraphicsDevice/Null/GPUDeviceNull.cpp | 6 + .../GraphicsDevice/Null/GPUDeviceNull.h | 1 + .../GraphicsDevice/Null/GPUVertexLayoutNull.h | 22 ++++ .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 50 +++++++++ .../GraphicsDevice/Vulkan/GPUDeviceVulkan.h | 1 + .../Vulkan/GPUVertexLayoutVulkan.h | 23 ++++ .../Parser/ShaderFunctionReader.CB.h | 5 +- .../ShadersCompilation/Parser/ShaderMeta.h | 25 +---- .../ShadersCompilation/ShaderCompiler.cpp | 24 +--- .../Bindings/BindingsGenerator.Parsing.cs | 5 + 31 files changed, 605 insertions(+), 93 deletions(-) create mode 100644 Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp create mode 100644 Source/Engine/Graphics/Shaders/GPUVertexLayout.h create mode 100644 Source/Engine/Graphics/Shaders/VertexElement.h create mode 100644 Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h create mode 100644 Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h create mode 100644 Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h create mode 100644 Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h diff --git a/Source/Engine/Graphics/Config.h b/Source/Engine/Graphics/Config.h index 2a9167045..f2f0cc34f 100644 --- a/Source/Engine/Graphics/Config.h +++ b/Source/Engine/Graphics/Config.h @@ -25,6 +25,9 @@ // Maximum amount of binded vertex buffers at the same time #define GPU_MAX_VB_BINDED 4 +// Maximum amount of vertex shader input elements in a layout +#define GPU_MAX_VS_ELEMENTS 16 + // Maximum amount of thread groups per dimension for compute dispatch #define GPU_MAX_CS_DISPATCH_THREAD_GROUPS 65535 @@ -33,6 +36,7 @@ // Enable/disable assertion for graphics layers #define GPU_ENABLE_ASSERTION 1 +#define GPU_ENABLE_ASSERTION_LOW_LAYERS (!BUILD_RELEASE) // Enable/disable dynamic textures quality streaming #define GPU_ENABLE_TEXTURES_STREAMING 1 diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 60298a513..2f42ac08b 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -479,6 +479,8 @@ void GPUDevice::DumpResourcesToLog() const LOG_STR(Info, output.ToStringView()); } +extern void ClearVertexLayoutCache(); + void GPUDevice::preDispose() { Locker.Lock(); @@ -494,6 +496,7 @@ void GPUDevice::preDispose() SAFE_DELETE_GPU_RESOURCE(_res->PS_Clear); SAFE_DELETE_GPU_RESOURCE(_res->PS_DecodeYUY2); SAFE_DELETE_GPU_RESOURCE(_res->FullscreenTriangleVB); + ClearVertexLayoutCache(); Locker.Unlock(); diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index f7ffe00a9..23636bfdf 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -23,6 +23,7 @@ class GPUBuffer; class GPUSampler; class GPUPipelineState; class GPUConstantBuffer; +class GPUVertexLayout; class GPUTasksContext; class GPUTasksExecutor; class GPUSwapChain; @@ -396,6 +397,13 @@ public: /// The sampler. API_FUNCTION() virtual GPUSampler* CreateSampler() = 0; + /// + /// Creates the vertex buffer layout. + /// + /// The vertex buffer layout. + API_FUNCTION() virtual GPUVertexLayout* CreateVertexLayout(const Array>& elements) = 0; + typedef Array> VertexElements; + /// /// Creates the native window swap chain. /// diff --git a/Source/Engine/Graphics/Shaders/Config.h b/Source/Engine/Graphics/Shaders/Config.h index 4b5c92744..29b20fedb 100644 --- a/Source/Engine/Graphics/Shaders/Config.h +++ b/Source/Engine/Graphics/Shaders/Config.h @@ -6,12 +6,16 @@ #include "Engine/Core/Enums.h" #include "Engine/Graphics/Enums.h" +// [Deprecated in v1.10] #define INPUT_LAYOUT_ELEMENT_ALIGN 0xffffffff +// [Deprecated in v1.10] #define INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA 0 +// [Deprecated in v1.10] #define INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA 1 /// /// Maximum amount of input elements for vertex shader programs +/// [Deprecated in v1.10] /// #define VERTEX_SHADER_MAX_INPUT_ELEMENTS 16 @@ -26,7 +30,8 @@ #define SHADER_PERMUTATIONS_MAX_PARAMS_COUNT 4 /// -/// Maximum allowed amount of constant buffers that can be binded to the pipeline (maximum slot index is MAX_CONSTANT_BUFFER_SLOTS-1) +/// Maximum allowed amount of constant buffers that can be binded to the pipeline (maximum slot index is MAX_CONSTANT_BUFFER_SLOTS-1) +/// [Deprecated in v1.10] /// #define MAX_CONSTANT_BUFFER_SLOTS 4 diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 3fcde42cf..b29d3236b 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Serialization/MemoryReadStream.h" static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 permutationIndex) @@ -12,6 +13,21 @@ static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 per return GetHash(name) * 37 + permutationIndex; } +void GPUShaderProgram::Init(const GPUShaderProgramInitializer& initializer) +{ + _name = initializer.Name; + _bindings = initializer.Bindings; + _flags = initializer.Flags; +#if !BUILD_RELEASE + _owner = initializer.Owner; +#endif +} + +GPUShaderProgramVS::~GPUShaderProgramVS() +{ + SAFE_DELETE(Layout); +} + GPUShader::GPUShader() : GPUResource(SpawnParams(Guid::New(), TypeInitializer)) { @@ -104,33 +120,32 @@ bool GPUShader::Create(MemoryReadStream& stream) // Constant Buffers const byte constantBuffersCount = stream.ReadByte(); - const byte maximumConstantBufferSlot = stream.ReadByte(); - if (constantBuffersCount > 0) + for (int32 i = 0; i < constantBuffersCount; i++) { - ASSERT(maximumConstantBufferSlot < MAX_CONSTANT_BUFFER_SLOTS); - - for (int32 i = 0; i < constantBuffersCount; i++) + // Load info + const byte slotIndex = stream.ReadByte(); + if (slotIndex >= GPU_MAX_CB_BINDED) { - // Load info - const byte slotIndex = stream.ReadByte(); - uint32 size; - stream.ReadUint32(&size); - - // Create CB -#if GPU_ENABLE_RESOURCE_NAMING - String name = String::Format(TEXT("{}.CB{}"), ToString(), i); -#else - String name; -#endif - ASSERT(_constantBuffers[slotIndex] == nullptr); - const auto cb = GPUDevice::Instance->CreateConstantBuffer(size, name); - if (cb == nullptr) - { - LOG(Warning, "Failed to create shader constant buffer."); - return true; - } - _constantBuffers[slotIndex] = cb; + LOG(Warning, "Failed to create shader constant buffer."); + return true; } + uint32 size; + stream.ReadUint32(&size); + + // Create CB +#if GPU_ENABLE_RESOURCE_NAMING + String cbName = String::Format(TEXT("{}.CB{}"), ToString(), i); +#else + String cbName; +#endif + ASSERT(_constantBuffers[slotIndex] == nullptr); + const auto cb = GPUDevice::Instance->CreateConstantBuffer(size, cbName); + if (cb == nullptr) + { + LOG(Warning, "Failed to create shader constant buffer."); + return true; + } + _constantBuffers[slotIndex] = cb; } // Don't read additional data diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 0ff511cc3..035619c2e 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -12,7 +12,7 @@ class GPUShaderProgram; /// /// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate). /// -#define GPU_SHADER_CACHE_VERSION 9 +#define GPU_SHADER_CACHE_VERSION 10 /// /// The GPU resource with shader programs that can run on the GPU and are able to perform rendering calculation using textures, vertices and other resources. @@ -23,7 +23,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUShader : public GPUResource protected: Dictionary _shaders; - GPUConstantBuffer* _constantBuffers[MAX_CONSTANT_BUFFER_SLOTS]; + GPUConstantBuffer* _constantBuffers[GPU_MAX_CB_BINDED]; GPUShader(); diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h index 9210ad938..9447e04a5 100644 --- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h +++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h @@ -7,6 +7,7 @@ #include "Config.h" class GPUShader; +class GPUVertexLayout; /// /// The shader program metadata container. Contains description about resources used by the shader. @@ -18,17 +19,17 @@ struct FLAXENGINE_API ShaderBindings uint32 UsedSRsMask; uint32 UsedUAsMask; - bool IsUsingCB(uint32 slotIndex) const + FORCE_INLINE bool IsUsingCB(uint32 slotIndex) const { return (UsedCBsMask & (1u << slotIndex)) != 0u; } - bool IsUsingSR(uint32 slotIndex) const + FORCE_INLINE bool IsUsingSR(uint32 slotIndex) const { return (UsedSRsMask & (1u << slotIndex)) != 0u; } - bool IsUsingUA(uint32 slotIndex) const + FORCE_INLINE bool IsUsingUA(uint32 slotIndex) const { return (UsedUAsMask & (1u << slotIndex)) != 0u; } @@ -57,15 +58,7 @@ protected: GPUShader* _owner; #endif - void Init(const GPUShaderProgramInitializer& initializer) - { - _name = initializer.Name; - _bindings = initializer.Bindings; - _flags = initializer.Flags; -#if !BUILD_RELEASE - _owner = initializer.Owner; -#endif - } + void Init(const GPUShaderProgramInitializer& initializer); public: /// @@ -122,8 +115,18 @@ public: /// class GPUShaderProgramVS : public GPUShaderProgram { +public: + ~GPUShaderProgramVS(); + + // Vertex elements input layout defined explicitly in the shader. + // It's optional as it's been deprecated in favor or layouts defined by vertex buffers to allow data customizations. + // Can be overriden by the vertex buffers provided upon draw call. + // [Deprecated in v1.10] + GPUVertexLayout* Layout = nullptr; + public: // Input element run-time data (see VertexShaderMeta::InputElement for compile-time data) + // [Deprecated in v1.10] PACK_STRUCT(struct InputElement { byte Type; // VertexShaderMeta::InputType @@ -135,14 +138,15 @@ public: uint32 InstanceDataStepRate; // 0 if per-vertex }); -public: /// /// Gets input layout description handle (platform dependent). + /// [Deprecated in v1.10] /// virtual void* GetInputLayout() const = 0; /// /// Gets input layout description size (in bytes). + /// [Deprecated in v1.10] /// virtual byte GetInputLayoutSize() const = 0; @@ -173,7 +177,7 @@ public: class GPUShaderProgramHS : public GPUShaderProgram { protected: - int32 _controlPointsCount; + int32 _controlPointsCount = 0; public: /// diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp new file mode 100644 index 000000000..d90613a31 --- /dev/null +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "GPUVertexLayout.h" +#if GPU_ENABLE_ASSERTION_LOW_LAYERS +#include "Engine/Core/Log.h" +#endif +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Graphics/GPUDevice.h" +#if GPU_ENABLE_RESOURCE_NAMING +#include "Engine/Scripting/Enums.h" +#endif + +// VertexElement has been designed to be POD and memory-comparable for faster hashing and comparision. +struct VertexElementRaw +{ + uint32 Words[2]; +}; + +static_assert(sizeof(VertexElement) == sizeof(VertexElementRaw), "Incorrect size of the VertexElement!"); + +namespace +{ + CriticalSection CacheLocker; + Dictionary LayoutCache; +} + +String VertexElement::ToString() const +{ +#if GPU_ENABLE_RESOURCE_NAMING + return String::Format(TEXT("{}, format {}, offset {}, per-instance {}, slot {}"), ScriptingEnum::ToString(Type), ScriptingEnum::ToString(Format), Offset, PerInstance, Slot); +#else + return TEXT("VertexElement"); +#endif +} + +bool VertexElement::operator==(const VertexElement& other) const +{ + auto thisRaw = (const VertexElementRaw*)this; + auto otherRaw = (const VertexElementRaw*)&other; + return thisRaw->Words[0] == otherRaw->Words[0] && thisRaw->Words[1] == otherRaw->Words[1]; +} + +uint32 GetHash(const VertexElement& key) +{ + auto keyRaw = (const VertexElementRaw*)&key; + uint32 hash = keyRaw->Words[0]; + CombineHash(hash, keyRaw->Words[1]); + return hash; +} + +GPUVertexLayout::GPUVertexLayout() + : GPUResource(SpawnParams(Guid::New(), TypeInitializer)) +{ +} + +GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) +{ + // Hash input layout + uint32 hash = 0; + for (const VertexElement& element : elements) + { + CombineHash(hash, GetHash(element)); + } + + // Lookup existing cache + CacheLocker.Lock(); + GPUVertexLayout* result; + if (!LayoutCache.TryGet(hash, result)) + { + result = GPUDevice::Instance->CreateVertexLayout(elements); + if (!result) + { +#if GPU_ENABLE_ASSERTION_LOW_LAYERS + for (auto& e : elements) + LOG(Error, " {}", e.ToString()); +#endif + LOG(Error, "Failed to create vertex layout"); + CacheLocker.Unlock(); + return nullptr; + } + LayoutCache.Add(hash, result); + } +#if GPU_ENABLE_ASSERTION_LOW_LAYERS + else if (result->GetElements() != elements) + { + for (auto& e : result->GetElements()) + LOG(Error, " (a) {}", e.ToString()); + for (auto& e : elements) + LOG(Error, " (b) {}", e.ToString()); + LOG(Fatal, "Vertex layout cache collision for hash {}", hash); + } +#endif + CacheLocker.Unlock(); + + return result; +} + +void ClearVertexLayoutCache() +{ + for (const auto& e : LayoutCache) + Delete(e.Value); + LayoutCache.Clear(); +} diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h new file mode 100644 index 000000000..a3f5158bc --- /dev/null +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "VertexElement.h" +#include "Engine/Graphics/GPUResource.h" +#include "Engine/Core/Collections/Array.h" + +/// +/// Defines input layout of vertex buffer data passed to the Vertex Shader. +/// +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUVertexLayout : public GPUResource +{ + DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUVertexLayout); + typedef Array> Elements; + +protected: + Elements _elements; + + GPUVertexLayout(); + +public: + /// + /// Gets the list of elements used by this layout. + /// + API_PROPERTY() FORCE_INLINE const Array>& GetElements() const + { + return _elements; + } + + /// + /// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list. + /// + /// The list of elements for the layout. + /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. + API_FUNCTION() static GPUVertexLayout* Get(const Array>& elements); + +public: + // [GPUResource] + GPUResourceType GetResourceType() const override + { + return GPUResourceType::Descriptor; + } +}; diff --git a/Source/Engine/Graphics/Shaders/VertexElement.h b/Source/Engine/Graphics/Shaders/VertexElement.h new file mode 100644 index 000000000..edf2d5df6 --- /dev/null +++ b/Source/Engine/Graphics/Shaders/VertexElement.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Graphics/PixelFormat.h" + +/// +/// Vertex buffer data element. Defines access to data passed to Vertex Shader. +/// +API_STRUCT(NoDefault) +PACK_BEGIN() struct FLAXENGINE_API VertexElement +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(VertexElement); + + /// + /// Types of vertex elements. + /// + API_ENUM() enum class Types : byte + { + // Undefined. + Unknown = 0, + // Vertex position. + Position = 1, + // Vertex color. + Color = 2, + // Vertex normal vector. + Normal = 3, + // Vertex tangent vector. + Tangent = 4, + // Skinned bone blend indices. + BlendIndices = 5, + // Skinned bone blend weights. + BlendWeight = 6, + // Primary texture coordinate (UV). + TexCoord0 = 7, + // Additional texture coordinate (UV1). + TexCoord1 = 8, + // Additional texture coordinate (UV2). + TexCoord2 = 9, + // Additional texture coordinate (UV3). + TexCoord3 = 10, + // Additional texture coordinate (UV4). + TexCoord4 = 11, + // Additional texture coordinate (UV5). + TexCoord5 = 12, + // Additional texture coordinate (UV6). + TexCoord6 = 13, + // Additional texture coordinate (UV7). + TexCoord7 = 14, + // General purpose attribute (at index 0). + Attribute0 = 15, + // General purpose attribute (at index 1). + Attribute1 = 16, + // General purpose attribute (at index 2). + Attribute2 = 17, + // General purpose attribute (at index 3). + Attribute3 = 18, + // Texture coordinate. + TexCoord = TexCoord0, + // General purpose attribute. + Attribute = Attribute0, + MAX + }; + + // Type of the vertex element data. + Types Type; + // Index of the input vertex buffer slot (as provided in GPUContext::BindVB). + byte Slot; + // Byte offset of this element relative to the start of a vertex buffer. Use value 0 to use auto-calculated offset based on previous elements in the layout (or for the first one). + byte Offset; + // Flag used to mark data using hardware-instancing (element will be repeated for every instance). Empty to step data per-vertex when reading input buffer stream (rather than per-instance step). + byte PerInstance; + // Format of the vertex element data. + PixelFormat Format; + + String ToString() const; + + bool operator==(const VertexElement& other) const; + + FORCE_INLINE bool operator!=(const VertexElement& other) const + { + return !operator==(other); + } +} PACK_END(); + +uint32 FLAXENGINE_API GetHash(const VertexElement& key); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index ae24d3879..4a9e4a6d2 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -10,11 +10,13 @@ #include "GPUTimerQueryDX11.h" #include "GPUBufferDX11.h" #include "GPUSamplerDX11.h" +#include "GPUVertexLayoutDX11.h" #include "GPUSwapChainDX11.h" #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" #include "Engine/Threading/Threading.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Engine/CommandLine.h" #if !USE_EDITOR && PLATFORM_WINDOWS @@ -146,6 +148,29 @@ static bool TryCreateDevice(IDXGIAdapter* adapter, D3D_FEATURE_LEVEL maxFeatureL return false; } +GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements) + : GPUResourceBase(device, StringView::Empty) + , InputElementsCount(elements.Count()) +{ + _elements = elements; + uint32 offsets[GPU_MAX_VB_BINDED] = {}; + for (int32 i = 0; i < _elements.Count(); i++) + { + const VertexElement& src = _elements.Get()[i]; + D3D11_INPUT_ELEMENT_DESC& dst = InputElements[i]; + uint32& offset = offsets[src.Slot]; + if (src.Offset != 0) + offset = src.Offset; + dst.SemanticName = RenderToolsDX::GetVertexInputSemantic(src.Type, dst.SemanticIndex); + dst.Format = RenderToolsDX::ToDxgiFormat(src.Format); + dst.InputSlot = src.Slot; + dst.AlignedByteOffset = offset; + dst.InputSlotClass = src.PerInstance ? D3D11_INPUT_PER_INSTANCE_DATA : D3D11_INPUT_PER_VERTEX_DATA; + dst.InstanceDataStepRate = src.PerInstance ? 1 : 0; + offset += PixelFormatExtensions::SizeInBytes(src.Format); + } +} + GPUDevice* GPUDeviceDX11::Create() { // Configuration @@ -807,6 +832,11 @@ GPUSampler* GPUDeviceDX11::CreateSampler() return New(this); } +GPUVertexLayout* GPUDeviceDX11::CreateVertexLayout(const VertexElements& elements) +{ + return New(this, elements); +} + GPUSwapChain* GPUDeviceDX11::CreateSwapChain(Window* window) { return New(this, window); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h index ffe1688f1..079d38967 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h @@ -128,6 +128,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h index 18aa4652d..3ccfa85c7 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h @@ -14,7 +14,6 @@ class GPUConstantBufferDX11 : public GPUResourceDX11 { private: - ID3D11Buffer* _resource; public: @@ -36,7 +35,6 @@ public: } public: - /// /// Gets the constant buffer object. /// @@ -47,7 +45,6 @@ public: } public: - // [GPUResourceDX11] ID3D11Resource* GetResource() override { @@ -55,7 +52,6 @@ public: } public: - // [GPUResourceDX11] void OnReleaseGPU() final override { @@ -69,11 +65,9 @@ public: class GPUShaderDX11 : public GPUResourceDX11 { private: - - Array> _cbs; + Array> _cbs; public: - /// /// Initializes a new instance of the class. /// @@ -85,13 +79,11 @@ public: } protected: - // [GPUShader] GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override; void OnReleaseGPU() override; public: - // [GPUResourceDX11] ID3D11Resource* GetResource() final override { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h new file mode 100644 index 000000000..119ee2964 --- /dev/null +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#if GRAPHICS_API_DIRECTX11 + +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" +#include "GPUDeviceDX11.h" + +/// +/// Vertex layout object for DirectX 11 backend. +/// +class GPUVertexLayoutDX11 : public GPUResourceBase +{ +public: + GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements); + + uint32 InputElementsCount; + D3D11_INPUT_ELEMENT_DESC InputElements[GPU_MAX_VS_ELEMENTS]; +}; + +#endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 0b251e5a6..eb252be54 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -10,10 +10,12 @@ #include "GPUTimerQueryDX12.h" #include "GPUBufferDX12.h" #include "GPUSamplerDX12.h" +#include "GPUVertexLayoutDX12.h" #include "GPUSwapChainDX12.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Log.h" @@ -34,6 +36,29 @@ static bool CheckDX12Support(IDXGIAdapter* adapter) return false; } +GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements) + : GPUResourceDX12(device, StringView::Empty) + , InputElementsCount(elements.Count()) +{ + _elements = elements; + uint32 offsets[GPU_MAX_VB_BINDED] = {}; + for (int32 i = 0; i < _elements.Count(); i++) + { + const VertexElement& src = _elements.Get()[i]; + D3D12_INPUT_ELEMENT_DESC& dst = InputElements[i]; + uint32& offset = offsets[src.Slot]; + if (src.Offset != 0) + offset = src.Offset; + dst.SemanticName = RenderToolsDX::GetVertexInputSemantic(src.Type, dst.SemanticIndex); + dst.Format = RenderToolsDX::ToDxgiFormat(src.Format); + dst.InputSlot = src.Slot; + dst.AlignedByteOffset = offset; + dst.InputSlotClass = src.PerInstance ? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA : D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA; + dst.InstanceDataStepRate = src.PerInstance ? 1 : 0; + offset += PixelFormatExtensions::SizeInBytes(src.Format); + } +} + GPUDevice* GPUDeviceDX12::Create() { #if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE @@ -843,6 +868,11 @@ GPUSampler* GPUDeviceDX12::CreateSampler() return New(this); } +GPUVertexLayout* GPUDeviceDX12::CreateVertexLayout(const VertexElements& elements) +{ + return New(this, elements); +} + GPUSwapChain* GPUDeviceDX12::CreateSwapChain(Window* window) { return New(this, window); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h index 2e6b4c274..82f59a8ee 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h @@ -196,6 +196,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h index 905b73633..48bf99f41 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h @@ -22,7 +22,6 @@ public: } public: - /// /// Last uploaded data address. /// @@ -35,7 +34,6 @@ public: class GPUShaderDX12 : public GPUResourceDX12 { public: - /// /// Initializes a new instance of the class. /// @@ -47,7 +45,6 @@ public: } protected: - // [GPUShader] GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h index 12f2f2656..5195394c1 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h @@ -35,6 +35,7 @@ public: { return (void*)_data.Get(); } + uint32 GetBufferSize() const override { return _data.Count(); @@ -65,6 +66,7 @@ public: { return (void*)_inputLayout; } + byte GetInputLayoutSize() const override { return _inputLayoutSize; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h new file mode 100644 index 000000000..225c3367c --- /dev/null +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#if GRAPHICS_API_DIRECTX12 + +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" +#include "GPUDeviceDX12.h" + +/// +/// Vertex layout object for DirectX 12 backend. +/// +class GPUVertexLayoutDX12 : public GPUResourceDX12 +{ +public: + GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements); + + uint32 InputElementsCount; + D3D12_INPUT_ELEMENT_DESC InputElements[GPU_MAX_VS_ELEMENTS]; +}; + +#endif diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index b82126702..f9d36a748 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -273,4 +273,62 @@ String RenderToolsDX::GetD3DErrorString(HRESULT errorCode) return sb.ToString(); } +LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& semanticIndex) +{ + static_assert((int32)VertexElement::Types::MAX == 16, "Update code below."); + semanticIndex = 0; + switch (type) + { + case VertexElement::Types::Position: + return "POSITION"; + case VertexElement::Types::Color: + return "COLOR"; + case VertexElement::Types::Normal: + return "NORMAL"; + case VertexElement::Types::Tangent: + return "TANGENT"; + case VertexElement::Types::BlendIndices: + return "BLENDINDICES"; + case VertexElement::Types::BlendWeight: + return "BLENDWEIGHT"; + case VertexElement::Types::TexCoord0: + return "TEXCOORD"; + case VertexElement::Types::TexCoord1: + semanticIndex = 1; + return "TEXCOORD"; + case VertexElement::Types::TexCoord2: + semanticIndex = 2; + return "TEXCOORD"; + case VertexElement::Types::TexCoord3: + semanticIndex = 3; + return "TEXCOORD"; + case VertexElement::Types::TexCoord4: + semanticIndex = 4; + return "TEXCOORD"; + case VertexElement::Types::TexCoord5: + semanticIndex = 5; + return "TEXCOORD"; + case VertexElement::Types::TexCoord6: + semanticIndex = 6; + return "TEXCOORD"; + case VertexElement::Types::TexCoord7: + semanticIndex = 7; + return "TEXCOORD"; + case VertexElement::Types::Attribute0: + return "ATTRIBUTE"; + case VertexElement::Types::Attribute1: + semanticIndex = 1; + return "ATTRIBUTE"; + case VertexElement::Types::Attribute2: + semanticIndex = 2; + return "ATTRIBUTE"; + case VertexElement::Types::Attribute3: + semanticIndex = 3; + return "ATTRIBUTE"; + default: + LOG(Fatal, "Invalid vertex shader element semantic type"); + return ""; + } +} + #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h index 5cfb5b4eb..b34f2794d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h @@ -7,6 +7,7 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Graphics//RenderTools.h" #include "Engine/Graphics/Enums.h" +#include "Engine/Graphics/Shaders/VertexElement.h" #include "IncludeDirectXHeaders.h" #include "Engine/Core/Log.h" @@ -112,6 +113,8 @@ namespace RenderToolsDX const String& errorString = GetD3DErrorString(result); LOG(Error, "DirectX error: {0} at {1}:{2}", errorString, String(file), line); } + + LPCSTR GetVertexInputSemantic(VertexElement::Types type, UINT& semanticIndex); }; #if GPU_ENABLE_ASSERTION diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp index 2716e103e..d1fe23dd8 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp @@ -11,6 +11,7 @@ #include "GPUTimerQueryNull.h" #include "GPUBufferNull.h" #include "GPUSamplerNull.h" +#include "GPUVertexLayoutNull.h" #include "GPUSwapChainNull.h" #include "Engine/Core/Log.h" #include "Engine/Graphics/Async/GPUTasksManager.h" @@ -172,6 +173,11 @@ GPUSampler* GPUDeviceNull::CreateSampler() return New(); } +GPUVertexLayout* GPUDeviceNull::CreateVertexLayout(const VertexElements& elements) +{ + return New(elements); +} + GPUSwapChain* GPUDeviceNull::CreateSwapChain(Window* window) { return New(window); diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h index 50389d840..80de34db3 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h @@ -47,6 +47,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h b/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h new file mode 100644 index 000000000..c1b7e96a7 --- /dev/null +++ b/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#if GRAPHICS_API_NULL + +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" + +/// +/// Vertex layout for Null backend. +/// +class GPUVertexLayoutNull : public GPUVertexLayout +{ +public: + GPUVertexLayoutNull(const Elements& elements) + : GPUVertexLayout() + { + _elements = elements; + } +}; + +#endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 5e11e1a86..694730ab3 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -14,6 +14,7 @@ #include "GPUTimerQueryVulkan.h" #include "GPUBufferVulkan.h" #include "GPUSamplerVulkan.h" +#include "GPUVertexLayoutVulkan.h" #include "GPUSwapChainVulkan.h" #include "RenderToolsVulkan.h" #include "QueueVulkan.h" @@ -448,6 +449,50 @@ uint32 GetHash(const FramebufferVulkan::Key& key) return hash; } +GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements) + : GPUResourceVulkan(device, StringView::Empty) +{ + _elements = elements; + uint32 offsets[GPU_MAX_VB_BINDED] = {}; + for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) + { + VkVertexInputBindingDescription& binding = Bindings[i]; + binding.binding = i; + binding.stride = 0; + binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + } + uint32 bindingsCount = 0; + for (int32 i = 0; i < _elements.Count(); i++) + { + const VertexElement& src = _elements.Get()[i]; + uint32& offset = offsets[src.Slot]; + if (src.Offset != 0) + offset = src.Offset; + const int32 size = PixelFormatExtensions::SizeInBytes(src.Format); + + ASSERT_LOW_LAYER(src.Slot < GPU_MAX_VB_BINDED); + VkVertexInputBindingDescription& binding = Bindings[src.Slot]; + binding.binding = src.Slot; + binding.stride = Math::Max(binding.stride, (uint32_t)(offset + size)); + binding.inputRate = src.PerInstance ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription& attribute = Attributes[i]; + attribute.location = i; + attribute.binding = src.Slot; + attribute.format = RenderToolsVulkan::ToVulkanFormat(src.Format); + attribute.offset = offset; + + bindingsCount = Math::Max(bindingsCount, (uint32)src.Slot + 1); + offset += size; + } + + RenderToolsVulkan::ZeroStruct(CreateInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); + CreateInfo.vertexBindingDescriptionCount = bindingsCount; + CreateInfo.pVertexBindingDescriptions = Bindings; + CreateInfo.vertexAttributeDescriptionCount = _elements.Count(); + CreateInfo.pVertexAttributeDescriptions = Attributes; +} + FramebufferVulkan::FramebufferVulkan(GPUDeviceVulkan* device, const Key& key, const VkExtent2D& extent, uint32 layers) : Device(device) , Handle(VK_NULL_HANDLE) @@ -2072,6 +2117,11 @@ GPUSampler* GPUDeviceVulkan::CreateSampler() return New(this); } +GPUVertexLayout* GPUDeviceVulkan::CreateVertexLayout(const VertexElements& elements) +{ + return New(this, elements); +} + GPUSwapChain* GPUDeviceVulkan::CreateSwapChain(Window* window) { return New(this, window); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index d31149d20..7fa84152a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -610,6 +610,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h new file mode 100644 index 000000000..b2d0e4b75 --- /dev/null +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h @@ -0,0 +1,23 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#if GRAPHICS_API_VULKAN + +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" +#include "GPUDeviceVulkan.h" + +/// +/// Vertex layout object for Vulkan backend. +/// +class GPUVertexLayoutVulkan : public GPUResourceVulkan +{ +public: + GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements); + + VkPipelineVertexInputStateCreateInfo CreateInfo; + VkVertexInputBindingDescription Bindings[GPU_MAX_VB_BINDED]; + VkVertexInputAttributeDescription Attributes[GPU_MAX_VS_ELEMENTS]; +}; + +#endif diff --git a/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.CB.h b/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.CB.h index 0724b85bb..7f8c6a3a8 100644 --- a/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.CB.h +++ b/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.CB.h @@ -3,6 +3,7 @@ #pragma once #include "ShaderFunctionReader.h" +#include "Engine/Graphics/Config.h" #if COMPILE_WITH_SHADER_COMPILER @@ -115,9 +116,9 @@ namespace ShaderProcessing for (int32 i = 0; i < _cache.Count(); i++) { auto& f = _cache[i]; - if (f.Slot >= MAX_CONSTANT_BUFFER_SLOTS) + if (f.Slot >= GPU_MAX_CB_BINDED) { - parser->OnError(String::Format(TEXT("Constant buffer {0} is using invalid slot {1}. Maximum supported slot is {2}."), String(f.Name), f.Slot, MAX_CONSTANT_BUFFER_SLOTS - 1)); + parser->OnError(String::Format(TEXT("Constant buffer {0} is using invalid slot {1}. Maximum supported slot is {2}."), String(f.Name), f.Slot, GPU_MAX_CB_BINDED - 1)); return; } } diff --git a/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h b/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h index 5ccab5e66..a8c45d0eb 100644 --- a/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h +++ b/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h @@ -30,14 +30,12 @@ struct ShaderPermutation class ShaderFunctionMeta { public: - /// /// Virtual destructor /// virtual ~ShaderFunctionMeta() = default; public: - /// /// Function name /// @@ -59,7 +57,6 @@ public: Array Permutations; public: - /// /// Checks if definition name has been added to the given permutation /// @@ -117,7 +114,6 @@ public: } public: - /// /// Gets shader function meta stage type. /// @@ -130,9 +126,9 @@ public: class VertexShaderMeta : public ShaderFunctionMeta { public: - /// /// Input element type + /// [Deprecated in v1.10] /// enum class InputType : byte { @@ -150,6 +146,7 @@ public: /// /// Input element + /// [Deprecated in v1.10] /// struct InputElement { @@ -195,14 +192,13 @@ public: }; public: - /// /// Input layout description + /// [Deprecated in v1.10] /// Array InputLayout; public: - // [ShaderFunctionMeta] ShaderStage GetStage() const override { @@ -216,14 +212,12 @@ public: class HullShaderMeta : public ShaderFunctionMeta { public: - /// /// The input control points count (valid range: 1-32). /// int32 ControlPointsCount; public: - // [ShaderFunctionMeta] ShaderStage GetStage() const override { @@ -237,7 +231,6 @@ public: class DomainShaderMeta : public ShaderFunctionMeta { public: - // [ShaderFunctionMeta] ShaderStage GetStage() const override { @@ -251,7 +244,6 @@ public: class GeometryShaderMeta : public ShaderFunctionMeta { public: - // [ShaderFunctionMeta] ShaderStage GetStage() const override { @@ -265,7 +257,6 @@ public: class PixelShaderMeta : public ShaderFunctionMeta { public: - // [ShaderFunctionMeta] ShaderStage GetStage() const override { @@ -279,7 +270,6 @@ public: class ComputeShaderMeta : public ShaderFunctionMeta { public: - // [ShaderFunctionMeta] ShaderStage GetStage() const override { @@ -309,7 +299,6 @@ struct ConstantBufferMeta class ShaderMeta { public: - /// /// Vertex Shaders /// @@ -346,18 +335,16 @@ public: Array CB; public: - /// - /// Gets amount of shaders attached (not counting permutations) + /// Gets amount of shaders attached (not counting permutations). /// - /// Amount of all shader programs - uint32 GetShadersCount() const + int32 GetShadersCount() const { return VS.Count() + HS.Count() + DS.Count() + GS.Count() + PS.Count() + CS.Count(); } /// - /// Gets all shader functions (all types) + /// Gets all shader functions (all types). /// /// Output collections of functions void GetShaders(Array& functions) const diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 72e3aacf8..bf7456b9e 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -6,7 +6,6 @@ #include "ShadersCompilation.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Dictionary.h" -#include "Engine/Engine/Globals.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderTools.h" @@ -14,7 +13,6 @@ #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryWriteStream.h" -#include "Engine/Utilities/StringConverter.h" namespace IncludedFiles { @@ -68,24 +66,12 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) // [Output] Constant Buffers { - const int32 cbsCount = _constantBuffers.Count(); - ASSERT(cbsCount == meta->CB.Count()); - - // Find maximum used slot index - byte maxCbSlot = 0; - for (int32 i = 0; i < cbsCount; i++) + ASSERT(_constantBuffers.Count() == meta->CB.Count()); + output->WriteByte((byte)_constantBuffers.Count()); + for (const ShaderResourceBuffer& cb : _constantBuffers) { - maxCbSlot = Math::Max(maxCbSlot, _constantBuffers[i].Slot); - } - - output->WriteByte(static_cast(cbsCount)); - output->WriteByte(maxCbSlot); - // TODO: do we still need to serialize max cb slot? - - for (int32 i = 0; i < cbsCount; i++) - { - output->WriteByte(_constantBuffers[i].Slot); - output->WriteUint32(_constantBuffers[i].Size); + output->WriteByte(cb.Slot); + output->WriteUint32(cb.Size); } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index a6ffe9a98..98675bf6f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1254,6 +1254,11 @@ namespace Flax.Build.Bindings // Read 'struct' keyword var token = context.Tokenizer.NextToken(); + if (token.Value == "PACK_BEGIN") + { + context.Tokenizer.SkipUntil(TokenType.RightParent); + token = context.Tokenizer.NextToken(); + } if (token.Value != "struct") throw new ParseException(ref context, $"Invalid {ApiTokens.Struct} usage (expected 'struct' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); From 666efb76755edc2e321758db1b743e1571f88e16 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Dec 2024 14:56:20 +0100 Subject: [PATCH 084/215] Fix incorrect dummy GPU Buffer format when binding missing resource to Vulkan descriptor --- .../Vulkan/GPUContextVulkan.cpp | 8 +- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 23 ++-- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.h | 4 +- Source/Engine/GraphicsDevice/Vulkan/Types.h | 9 +- .../Vulkan/ShaderCompilerVulkan.cpp | 115 +++++++++++++++++- 5 files changed, 139 insertions(+), 20 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index a8b34f781..106ffadf8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -504,7 +504,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des auto handle = handles[slot]; if (!handle) { - const auto dummy = _device->HelperResources.GetDummyBuffer(); + const auto dummy = _device->HelperResources.GetDummyBuffer(descriptor.ResourceFormat); handle = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); } VkBufferView bufferView; @@ -528,7 +528,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des auto handle = handles[slot]; if (!handle) { - const auto dummy = _device->HelperResources.GetDummyBuffer(); + const auto dummy = _device->HelperResources.GetDummyBuffer(descriptor.ResourceFormat); handle = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); } VkBuffer buffer; @@ -542,7 +542,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des auto handle = handles[slot]; if (!handle) { - const auto dummy = _device->HelperResources.GetDummyBuffer(); + const auto dummy = _device->HelperResources.GetDummyBuffer(descriptor.ResourceFormat); handle = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); } VkBufferView bufferView; @@ -561,7 +561,7 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des handle->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); else { - const auto dummy = _device->HelperResources.GetDummyBuffer(); + const auto dummy = _device->HelperResources.GetDummyBuffer(PixelFormat::R32_SInt); buffer = dummy->GetHandle(); range = dummy->GetSize(); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 694730ab3..319dd653f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -794,7 +794,6 @@ bool BufferedQueryPoolVulkan::HasRoom() const HelperResourcesVulkan::HelperResourcesVulkan(GPUDeviceVulkan* device) : _device(device) - , _dummyBuffer(nullptr) , _dummyVB(nullptr) { Platform::MemoryClear(_dummyTextures, sizeof(_dummyTextures)); @@ -921,15 +920,20 @@ GPUTextureVulkan* HelperResourcesVulkan::GetDummyTexture(SpirvShaderResourceType return texture; } -GPUBufferVulkan* HelperResourcesVulkan::GetDummyBuffer() +GPUBufferVulkan* HelperResourcesVulkan::GetDummyBuffer(PixelFormat format) { - if (!_dummyBuffer) + if (_dummyBuffers.IsEmpty()) { - _dummyBuffer = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyBuffer")); - _dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32) * 256, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt)); + _dummyBuffers.Resize((int32)PixelFormat::MAX); + Platform::MemoryClear((void*)_dummyBuffers.Get(), (int32)PixelFormat::MAX * sizeof(void*)); } - - return _dummyBuffer; + auto& dummyBuffer = _dummyBuffers[(int32)format]; + if (!dummyBuffer) + { + dummyBuffer = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyBuffer")); + dummyBuffer->Init(GPUBufferDescription::Buffer(PixelFormatExtensions::SizeInBytes(format) * 256, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, format)); + } + return dummyBuffer; } GPUBufferVulkan* HelperResourcesVulkan::GetDummyVertexBuffer() @@ -939,15 +943,16 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyVertexBuffer() _dummyVB = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyVertexBuffer")); _dummyVB->Init(GPUBufferDescription::Vertex(sizeof(Color32), 1, &Color32::Transparent)); } - return _dummyVB; } void HelperResourcesVulkan::Dispose() { SAFE_DELETE_GPU_RESOURCES(_dummyTextures); - SAFE_DELETE_GPU_RESOURCE(_dummyBuffer); SAFE_DELETE_GPU_RESOURCE(_dummyVB); + for (GPUBuffer* buffer : _dummyBuffers) + Delete(buffer); + _dummyBuffers.Clear(); for (int32 i = 0; i < ARRAY_COUNT(_staticSamplers); i++) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 7fa84152a..e5e9c7de8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -308,7 +308,7 @@ class HelperResourcesVulkan private: GPUDeviceVulkan* _device; GPUTextureVulkan* _dummyTextures[6]; - GPUBufferVulkan* _dummyBuffer; + Array _dummyBuffers; GPUBufferVulkan* _dummyVB; VkSampler _staticSamplers[GPU_STATIC_SAMPLERS_COUNT]; @@ -318,7 +318,7 @@ public: public: VkSampler* GetStaticSamplers(); GPUTextureVulkan* GetDummyTexture(SpirvShaderResourceType type); - GPUBufferVulkan* GetDummyBuffer(); + GPUBufferVulkan* GetDummyBuffer(PixelFormat format); GPUBufferVulkan* GetDummyVertexBuffer(); void Dispose(); }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/Types.h b/Source/Engine/GraphicsDevice/Vulkan/Types.h index 2daf72619..31acb0832 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Types.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Types.h @@ -4,6 +4,8 @@ #if COMPILE_WITH_VK_SHADER_COMPILER || GRAPHICS_API_VULKAN +#include "Engine/Graphics/PixelFormat.h" + #if GRAPHICS_API_VULKAN #include "Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h" #else @@ -95,6 +97,11 @@ struct SpirvShaderDescriptorInfo /// SpirvShaderResourceType ResourceType; + /// + /// The resource format. + /// + PixelFormat ResourceFormat; + /// /// The amount of slots used by the descriptor (eg. array of textures size). /// @@ -127,8 +134,6 @@ struct SpirvShaderHeader /// The shader descriptors usage information. /// SpirvShaderDescriptorInfo DescriptorInfo; - - // .. rest is just a actual data array }; #endif diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp index c14e15ee2..5f509ef1e 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp @@ -116,9 +116,9 @@ const TBuiltInResource DefaultTBuiltInResource = /* .MinProgramTexelOffset = */ -8, /* .MaxProgramTexelOffset = */ 7, /* .MaxClipDistances = */ 8, - /* .MaxComputeWorkGroupCountX = */ 65535, - /* .MaxComputeWorkGroupCountY = */ 65535, - /* .MaxComputeWorkGroupCountZ = */ 65535, + /* .MaxComputeWorkGroupCountX = */ GPU_MAX_CS_DISPATCH_THREAD_GROUPS, + /* .MaxComputeWorkGroupCountY = */ GPU_MAX_CS_DISPATCH_THREAD_GROUPS, + /* .MaxComputeWorkGroupCountZ = */ GPU_MAX_CS_DISPATCH_THREAD_GROUPS, /* .MaxComputeWorkGroupSizeX = */ 1024, /* .MaxComputeWorkGroupSizeY = */ 1024, /* .MaxComputeWorkGroupSizeZ = */ 64, @@ -212,6 +212,7 @@ struct Descriptor SpirvShaderResourceBindingType BindingType; VkDescriptorType DescriptorType; SpirvShaderResourceType ResourceType; + PixelFormat ResourceFormat; std::string Name; }; @@ -233,6 +234,112 @@ SpirvShaderResourceType GetTextureType(const glslang::TSampler& sampler) } } +PixelFormat GetResourceFormat(const glslang::TSampler& sampler) +{ + switch (sampler.type) + { + case glslang::EbtVoid: + return PixelFormat::Unknown; + case glslang::EbtFloat: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R32_Float; + case 2: + return PixelFormat::R32G32_Float; + case 3: + return PixelFormat::R32G32B32_Float; + case 4: + return PixelFormat::R32G32B32A32_Float; + } + break; + case glslang::EbtFloat16: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R16_Float; + case 2: + return PixelFormat::R16G16_Float; + case 4: + return PixelFormat::R16G16B16A16_Float; + } + break; + case glslang::EbtUint: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R32_UInt; + case 2: + return PixelFormat::R32G32_UInt; + case 3: + return PixelFormat::R32G32B32_UInt; + case 4: + return PixelFormat::R32G32B32A32_UInt; + } + break; + case glslang::EbtInt: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R32_SInt; + case 2: + return PixelFormat::R32G32_SInt; + case 3: + return PixelFormat::R32G32B32_SInt; + case 4: + return PixelFormat::R32G32B32A32_SInt; + } + break; + case glslang::EbtUint8: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R8_UInt; + case 2: + return PixelFormat::R8G8_UInt; + case 4: + return PixelFormat::R8G8B8A8_UInt; + } + break; + case glslang::EbtInt8: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R8_SInt; + case 2: + return PixelFormat::R8G8_SInt; + case 4: + return PixelFormat::R8G8B8A8_SInt; + } + break; + case glslang::EbtUint16: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R16_UInt; + case 2: + return PixelFormat::R16G16_UInt; + case 4: + return PixelFormat::R16G16B16A16_UInt; + } + break; + case glslang::EbtInt16: + switch (sampler.vectorSize) + { + case 1: + return PixelFormat::R16_SInt; + case 2: + return PixelFormat::R16G16_SInt; + case 4: + return PixelFormat::R16G16B16A16_SInt; + } + break; + default: + break; + } + return PixelFormat::Unknown; +} + bool IsUavType(const glslang::TType& type) { if (type.getQualifier().isReadOnly()) @@ -371,6 +478,7 @@ public: descriptor.BindingType = resourceBindingType; descriptor.DescriptorType = descriptorType; descriptor.ResourceType = resourceType; + descriptor.ResourceFormat = GetResourceFormat(type.getSampler()); descriptor.Name = name; descriptor.Count = type.isSizedArray() ? type.getCumulativeArraySize() : 1; @@ -694,6 +802,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat d.BindingType = descriptor.BindingType; d.DescriptorType = descriptor.DescriptorType; d.ResourceType = descriptor.ResourceType; + d.ResourceFormat = descriptor.ResourceFormat; d.Count = descriptor.Count; switch (descriptor.BindingType) From b3f37ca041a361353426e51d6dc41c887ebe4486 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 15 Dec 2024 22:10:45 +0100 Subject: [PATCH 085/215] Refactor Vertex Shader input vertex layout to use `GPUVertexLayout` defined on Vertex Buffer rather than Vertex Shader #3044 #2667 --- .../Editor/MaterialTemplates/Particle.shader | 2 - .../Editor/MaterialTemplates/Terrain.shader | 2 - Flax.flaxproj | 4 +- Source/Editor/Gizmo/GridGizmo.cs | 3 +- Source/Engine/Debug/DebugDraw.cpp | 11 +- Source/Engine/Debug/DebugDraw.h | 3 + Source/Engine/Graphics/DynamicBuffer.cpp | 8 +- Source/Engine/Graphics/DynamicBuffer.h | 10 +- Source/Engine/Graphics/GPUBuffer.cpp | 49 +++++++- Source/Engine/Graphics/GPUBuffer.h | 8 ++ .../Engine/Graphics/GPUBufferDescription.cs | 110 +++++++++++++++- Source/Engine/Graphics/GPUBufferDescription.h | 36 +++++- Source/Engine/Graphics/GPUContext.h | 4 +- Source/Engine/Graphics/GPUDevice.cpp | 8 +- Source/Engine/Graphics/Models/Mesh.cpp | 31 ++++- Source/Engine/Graphics/Models/ModelData.h | 3 + Source/Engine/Graphics/Models/SkinnedMesh.cpp | 15 ++- Source/Engine/Graphics/Models/Types.h | 33 ++++- Source/Engine/Graphics/Shaders/GPUShader.cpp | 38 ++++-- Source/Engine/Graphics/Shaders/GPUShader.h | 5 +- .../Graphics/Shaders/GPUShaderProgram.h | 32 ++--- .../Graphics/Shaders/GPUVertexLayout.cpp | 62 +++++++++ .../Engine/Graphics/Shaders/GPUVertexLayout.h | 9 ++ .../Engine/Graphics/Shaders/VertexElement.h | 10 +- .../DirectX/DX11/GPUContextDX11.cpp | 32 ++++- .../DirectX/DX11/GPUContextDX11.h | 6 +- .../DirectX/DX11/GPUShaderDX11.cpp | 106 +++++----------- .../DirectX/DX11/GPUShaderDX11.h | 2 +- .../DirectX/DX11/GPUShaderProgramDX11.h | 83 ++---------- .../DirectX/DX12/GPUContextDX12.cpp | 13 +- .../DirectX/DX12/GPUContextDX12.h | 4 +- .../DirectX/DX12/GPUPipelineStateDX12.cpp | 22 +++- .../DirectX/DX12/GPUPipelineStateDX12.h | 12 +- .../DirectX/DX12/GPUShaderDX12.cpp | 82 ++---------- .../DirectX/DX12/GPUShaderDX12.h | 2 +- .../DirectX/DX12/GPUShaderProgramDX12.h | 58 +++------ .../GraphicsDevice/DirectX/DX12/Types.h | 2 - .../GraphicsDevice/Null/GPUContextNull.h | 2 +- .../GraphicsDevice/Null/GPUShaderNull.h | 2 +- .../Vulkan/GPUContextVulkan.cpp | 17 ++- .../GraphicsDevice/Vulkan/GPUContextVulkan.h | 4 +- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 21 ++-- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.h | 2 +- .../Vulkan/GPUPipelineStateVulkan.cpp | 26 ++-- .../Vulkan/GPUPipelineStateVulkan.h | 13 +- .../Vulkan/GPUShaderProgramVulkan.h | 78 +----------- .../GraphicsDevice/Vulkan/GPUShaderVulkan.cpp | 64 +--------- .../GraphicsDevice/Vulkan/GPUShaderVulkan.h | 2 +- Source/Engine/Level/Actors/StaticModel.cpp | 2 +- Source/Engine/Particles/Particles.cpp | 14 ++- Source/Engine/Render2D/Render2D.cpp | 9 ++ .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 10 +- Source/Engine/Renderer/RenderList.cpp | 3 +- Source/Engine/Renderer/VolumetricFogPass.cpp | 6 +- .../Parser/ShaderFunctionReader.VS.h | 9 +- .../ShadersCompilation/ShaderCompiler.cpp | 118 +++++++++++++++--- Source/Engine/Terrain/TerrainManager.cpp | 13 +- Source/Engine/Terrain/TerrainPatch.cpp | 2 +- Source/Engine/UI/TextRender.cpp | 6 +- Source/Shaders/Common.hlsl | 2 +- Source/Shaders/DebugDraw.shader | 2 - Source/Shaders/Editor/Grid.shader | 1 - Source/Shaders/GI/GlobalSurfaceAtlas.shader | 3 - Source/Shaders/GUI.shader | 5 - Source/Shaders/Quad.shader | 8 -- Source/Shaders/VolumetricFog.shader | 1 - 66 files changed, 786 insertions(+), 579 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 5cdbc40a5..5cf8b9263 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -315,8 +315,6 @@ float3 GetParticlePosition(uint ParticleIndex) // Vertex Shader function for Sprite Rendering META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) VertexOutput VS_Sprite(SpriteInput input, uint particleIndex : SV_InstanceID) { VertexOutput output; diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index d2996d7a3..9b654e8f0 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -319,8 +319,6 @@ struct TerrainVertexInput // Vertex Shader function for terrain rendering META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R8G8B8A8_UNORM, 0, ALIGN, PER_VERTEX, 0, true) VertexOutput VS(TerrainVertexInput input) { VertexOutput output; diff --git a/Flax.flaxproj b/Flax.flaxproj index 15b925c0d..629bf4087 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,9 +2,9 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 9, + "Minor": 10, "Revision": 0, - "Build": 6606 + "Build": 6701 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs index b5fcf9a31..293e9e84f 100644 --- a/Source/Editor/Gizmo/GridGizmo.cs +++ b/Source/Editor/Gizmo/GridGizmo.cs @@ -69,7 +69,8 @@ namespace FlaxEditor.Gizmo if (_vertexBuffer == null) { _vertexBuffer = new GPUBuffer(); - var desc = GPUBufferDescription.Vertex(sizeof(Float3), 4); + var layout = GPUVertexLayout.Get([new VertexElement(VertexElement.Types.Position, 0, 0, false, PixelFormat.R32G32B32_Float)]); + var desc = GPUBufferDescription.Vertex(layout, sizeof(Float3), 4); _vertexBuffer.Init(ref desc); } if (_indexBuffer == null) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 447e6cabc..9f78a0b25 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -19,6 +19,7 @@ #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Animations/AnimationUtils.h" #include "Engine/Profiler/Profiler.h" @@ -689,7 +690,7 @@ void DebugDrawService::Update() // Vertex buffer if (DebugDrawVB == nullptr) - DebugDrawVB = New((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB")); + DebugDrawVB = New((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB"), Vertex::GetLayout()); } void DebugDrawService::Dispose() @@ -710,6 +711,14 @@ void DebugDrawService::Dispose() DebugDrawShader = nullptr; } +GPUVertexLayout* DebugDraw::Vertex::GetLayout() +{ + return GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float }, + { VertexElement::Types::Color, 0, 12, 0, PixelFormat::R8G8B8A8_UNorm }, + }); +} + #if USE_EDITOR void* DebugDraw::AllocateContext() diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 6cfceca6d..f70fcc267 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -17,6 +17,7 @@ struct RenderContext; class GPUTextureView; class GPUContext; class GPUBuffer; +class GPUVertexLayout; class RenderTask; class SceneRenderTask; class Actor; @@ -35,6 +36,8 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw PACK_STRUCT(struct Vertex { Float3 Position; Color32 Color; + + static GPUVertexLayout* GetLayout(); }); #if USE_EDITOR diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index 8414d0a57..67192a821 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -67,9 +67,15 @@ void DynamicBuffer::Dispose() Data.Resize(0); } +void DynamicVertexBuffer::SetLayout(GPUVertexLayout* layout) +{ + _layout = layout; + SAFE_DELETE_GPU_RESOURCE(_buffer); +} + void DynamicVertexBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) { - desc = GPUBufferDescription::Vertex(_stride, numElements, GPUResourceUsage::Dynamic); + desc = GPUBufferDescription::Vertex(_layout, _stride, numElements, GPUResourceUsage::Dynamic); } void DynamicIndexBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index e00ffacbd..6cc72d26b 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -127,6 +127,9 @@ protected: /// class FLAXENGINE_API DynamicVertexBuffer : public DynamicBuffer { +private: + GPUVertexLayout* _layout; + public: /// /// Init @@ -134,11 +137,16 @@ public: /// Initial capacity of the buffer (in bytes) /// Stride in bytes /// Buffer name - DynamicVertexBuffer(uint32 initialCapacity, uint32 stride, const String& name = String::Empty) + /// The vertex buffer layout. + DynamicVertexBuffer(uint32 initialCapacity, uint32 stride, const String& name = String::Empty, GPUVertexLayout* layout = nullptr) : DynamicBuffer(initialCapacity, stride, name) + , _layout(layout) { } + // Sets the vertex buffer layout. + void SetLayout(GPUVertexLayout* layout); + protected: // [DynamicBuffer] void InitDesc(GPUBufferDescription& desc, int32 numElements) override; diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 6a0221b14..b0d081d4c 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -27,6 +27,7 @@ GPUBufferDescription GPUBufferDescription::Buffer(uint32 size, GPUBufferFlags fl desc.Format = format; desc.InitData = initData; desc.Usage = usage; + desc.VertexLayout = nullptr; return desc; } @@ -48,6 +49,32 @@ GPUBufferDescription GPUBufferDescription::Typed(const void* data, int32 count, return Buffer(count * stride, bufferFlags, viewFormat, data, stride, usage); } +GPUBufferDescription GPUBufferDescription::Vertex(GPUVertexLayout* layout, uint32 elementStride, uint32 elementsCount, const void* data) +{ + GPUBufferDescription desc; + desc.Size = elementsCount * elementStride; + desc.Stride = elementStride; + desc.Flags = GPUBufferFlags::VertexBuffer; + desc.Format = PixelFormat::Unknown; + desc.InitData = data; + desc.Usage = GPUResourceUsage::Default; + desc.VertexLayout = layout; + return desc; +} + +GPUBufferDescription GPUBufferDescription::Vertex(GPUVertexLayout* layout, uint32 elementStride, uint32 elementsCount, GPUResourceUsage usage) +{ + GPUBufferDescription desc; + desc.Size = elementsCount * elementStride; + desc.Stride = elementStride; + desc.Flags = GPUBufferFlags::VertexBuffer; + desc.Format = PixelFormat::Unknown; + desc.InitData = nullptr; + desc.Usage = GPUResourceUsage::Default; + desc.VertexLayout = layout; + return desc; +} + void GPUBufferDescription::Clear() { Platform::MemoryClear(this, sizeof(GPUBufferDescription)); @@ -78,7 +105,8 @@ bool GPUBufferDescription::Equals(const GPUBufferDescription& other) const && Flags == other.Flags && Format == other.Format && Usage == other.Usage - && InitData == other.InitData; + && InitData == other.InitData + && VertexLayout == other.VertexLayout; } String GPUBufferDescription::ToString() const @@ -98,6 +126,7 @@ uint32 GetHash(const GPUBufferDescription& key) hashCode = (hashCode * 397) ^ (uint32)key.Flags; hashCode = (hashCode * 397) ^ (uint32)key.Format; hashCode = (hashCode * 397) ^ (uint32)key.Usage; + hashCode = (hashCode * 397) ^ GetHash(key.VertexLayout); return hashCode; } @@ -129,11 +158,16 @@ bool GPUBuffer::Init(const GPUBufferDescription& desc) && Math::IsInRange(desc.Stride, 0, 1024)); // Validate description +#if !BUILD_RELEASE +#define GET_NAME() GetName() +#else +#define GET_NAME() TEXT("") +#endif if (EnumHasAnyFlags(desc.Flags, GPUBufferFlags::Structured)) { if (desc.Stride <= 0) { - LOG(Warning, "Cannot create buffer. Element size cannot be less or equal 0 for structured buffer."); + LOG(Warning, "Cannot create buffer '{}'. Element size cannot be less or equal 0 for structured buffer.", GET_NAME()); return true; } } @@ -141,10 +175,19 @@ bool GPUBuffer::Init(const GPUBufferDescription& desc) { if (desc.Format != PixelFormat::R32_Typeless) { - LOG(Warning, "Cannot create buffer. Raw buffers must use format R32_Typeless."); + LOG(Warning, "Cannot create buffer '{}'. Raw buffers must use format R32_Typeless.", GET_NAME()); return true; } } + if (EnumHasAnyFlags(desc.Flags, GPUBufferFlags::VertexBuffer)) + { + if (desc.VertexLayout == nullptr) + { + // [Deprecated in v1.10] Change this into an error as VertexLayout becomes a requirement when layout is no longer set in a vertex shader + LOG(Warning, "Missing Vertex Layout in buffer '{}'. Vertex Buffers should provide layout information about contained vertex elements.", GET_NAME()); + } + } +#undef GET_NAME // Release previous data ReleaseGPU(); diff --git a/Source/Engine/Graphics/GPUBuffer.h b/Source/Engine/Graphics/GPUBuffer.h index d40b7744d..d8eb70a0f 100644 --- a/Source/Engine/Graphics/GPUBuffer.h +++ b/Source/Engine/Graphics/GPUBuffer.h @@ -85,6 +85,14 @@ public: return _desc.Flags; } + /// + /// Gets vertex elements layout used by vertex buffers only. + /// + API_PROPERTY() FORCE_INLINE GPUVertexLayout* GetVertexLayout() const + { + return _desc.VertexLayout; + } + /// /// Checks if buffer is a staging buffer (supports CPU readback). /// diff --git a/Source/Engine/Graphics/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs index 6bc0051a8..ee64a86c5 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.cs +++ b/Source/Engine/Graphics/GPUBufferDescription.cs @@ -48,6 +48,7 @@ namespace FlaxEngine desc.Format = format; desc.InitData = initData; desc.Usage = usage; + desc.VertexLayout = null; return desc; } @@ -70,6 +71,7 @@ namespace FlaxEngine desc.Format = format; desc.InitData = initData; desc.Usage = usage; + desc.VertexLayout = null; return desc; } @@ -117,11 +119,55 @@ namespace FlaxEngine /// /// Creates vertex buffer description. /// + /// The vertex buffer layout. /// The element stride. /// The elements count. /// The data. /// The buffer description. - public static GPUBufferDescription Vertex(int elementStride, int elementsCount, IntPtr data) + public static GPUBufferDescription Vertex(GPUVertexLayout layout, int elementStride, int elementsCount, IntPtr data) + { + GPUBufferDescription desc; + desc.Size = (uint)(elementsCount * elementStride); + desc.Stride = (uint)elementStride; + desc.Flags = GPUBufferFlags.VertexBuffer; + desc.Format = PixelFormat.Unknown; + desc.InitData = data; + desc.Usage = GPUResourceUsage.Default; + desc.VertexLayout = layout; + return desc; + } + + /// + /// Creates vertex buffer description. + /// + /// The vertex buffer layout. + /// The element stride. + /// The elements count. + /// The usage mode. + /// The buffer description. + public static GPUBufferDescription Vertex(GPUVertexLayout layout, int elementStride, int elementsCount, GPUResourceUsage usage = GPUResourceUsage.Default) + { + GPUBufferDescription desc; + desc.Size = (uint)(elementsCount * elementStride); + desc.Stride = (uint)elementStride; + desc.Flags = GPUBufferFlags.VertexBuffer; + desc.Format = PixelFormat.Unknown; + desc.InitData = new IntPtr(); + desc.Usage = usage; + desc.VertexLayout = layout; + return desc; + } + + /// + /// Creates vertex buffer description. + /// + /// The element stride. + /// The elements count. + /// The data. + /// The vertex buffer layout. + /// The buffer description. + [Obsolete("Use Vertex with vertex layout parameter instead")] + public static GPUBufferDescription Vertex(int elementStride, int elementsCount, IntPtr data, GPUVertexLayout layout = null) { return Buffer(elementsCount * elementStride, GPUBufferFlags.VertexBuffer, PixelFormat.Unknown, data, elementStride, GPUResourceUsage.Default); } @@ -132,18 +178,22 @@ namespace FlaxEngine /// The element stride. /// The elements count. /// The usage mode. + /// The vertex buffer layout. /// The buffer description. - public static GPUBufferDescription Vertex(int elementStride, int elementsCount, GPUResourceUsage usage = GPUResourceUsage.Default) + [Obsolete("Use Vertex with vertex layout parameter instead")] + public static GPUBufferDescription Vertex(int elementStride, int elementsCount, GPUResourceUsage usage = GPUResourceUsage.Default, GPUVertexLayout layout = null) { return Buffer(elementsCount * elementStride, GPUBufferFlags.VertexBuffer, PixelFormat.Unknown, new IntPtr(), elementStride, usage); } /// /// Creates vertex buffer description. + /// [Deprecated in v1.10] /// /// The size (in bytes). /// The usage mode. /// The buffer description. + [Obsolete("Use Vertex with separate vertex stride and count instead")] public static GPUBufferDescription Vertex(int size, GPUResourceUsage usage = GPUResourceUsage.Default) { return Buffer(size, GPUBufferFlags.VertexBuffer, PixelFormat.Unknown, new IntPtr(), 0, usage); @@ -190,7 +240,6 @@ namespace FlaxEngine var bufferFlags = GPUBufferFlags.Structured | GPUBufferFlags.ShaderResource; if (isUnorderedAccess) bufferFlags |= GPUBufferFlags.UnorderedAccess; - return Buffer(elementCount * elementSize, bufferFlags, PixelFormat.Unknown, new IntPtr(), elementSize); } @@ -304,7 +353,8 @@ namespace FlaxEngine Flags == other.Flags && Format == other.Format && InitData.Equals(other.InitData) && - Usage == other.Usage; + Usage == other.Usage && + VertexLayout == other.VertexLayout; } /// @@ -324,6 +374,58 @@ namespace FlaxEngine hashCode = (hashCode * 397) ^ (int)Format; hashCode = (hashCode * 397) ^ InitData.GetHashCode(); hashCode = (hashCode * 397) ^ (int)Usage; + if (VertexLayout != null) + hashCode = (hashCode * 397) ^ VertexLayout.GetHashCode(); + return hashCode; + } + } + } + + partial struct VertexElement : IEquatable + { + /// + /// Creates the vertex element description. + /// + /// Element type. + /// Vertex buffer bind slot. + /// Data byte offset. + /// True if element data is instanced. + /// Data format. + public VertexElement(Types type, byte slot, byte offset, bool perInstance, PixelFormat format) + { + Type = type; + Slot = slot; + Offset = offset; + PerInstance = (byte)(perInstance ? 1 : 0); + Format = format; + } + + /// + public bool Equals(VertexElement other) + { + return Type == other.Type && + Slot == other.Slot && + Offset == other.Offset && + PerInstance == other.PerInstance && + Format == other.Format; + } + + /// + public override bool Equals(object obj) + { + return obj is VertexElement other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = (int)Type; + hashCode = (hashCode * 397) ^ Slot; + hashCode = (hashCode * 397) ^ Offset; + hashCode = (hashCode * 397) ^ PerInstance; + hashCode = (hashCode * 397) ^ (int)Format; return hashCode; } } diff --git a/Source/Engine/Graphics/GPUBufferDescription.h b/Source/Engine/Graphics/GPUBufferDescription.h index d4049ea5b..73f459da2 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.h +++ b/Source/Engine/Graphics/GPUBufferDescription.h @@ -5,6 +5,8 @@ #include "Enums.h" #include "PixelFormat.h" +class GPUVertexLayout; + /// /// The GPU buffer usage flags. /// @@ -110,6 +112,11 @@ API_STRUCT() struct FLAXENGINE_API GPUBufferDescription /// API_FIELD() GPUResourceUsage Usage; + /// + /// The vertex elements layout used by vertex buffers only. + /// + API_FIELD() GPUVertexLayout* VertexLayout; + public: /// /// Gets the number elements in the buffer. @@ -178,34 +185,57 @@ public: /// /// Creates vertex buffer description. /// + /// The vertex buffer layout. /// The element stride. /// The elements count. /// The data. /// The buffer description. - static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, const void* data) + static GPUBufferDescription Vertex(GPUVertexLayout* layout, uint32 elementStride, uint32 elementsCount, const void* data); + + /// + /// Creates vertex buffer description. + /// + /// The vertex buffer layout. + /// The element stride. + /// The elements count. + /// The usage mode. + /// The buffer description. + static GPUBufferDescription Vertex(GPUVertexLayout* layout, uint32 elementStride, uint32 elementsCount, GPUResourceUsage usage = GPUResourceUsage::Default); + + /// + /// Creates vertex buffer description. + /// [Deprecated in v1.10] + /// + /// The element stride. + /// The elements count. + /// The data. + /// The buffer description. + DEPRECATED("Use Vertex with vertex layout parameter instead") static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, const void* data) { return Buffer(elementsCount * elementStride, GPUBufferFlags::VertexBuffer, PixelFormat::Unknown, data, elementStride, GPUResourceUsage::Default); } /// /// Creates vertex buffer description. + /// [Deprecated in v1.10] /// /// The element stride. /// The elements count. /// The usage mode. /// The buffer description. - static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, GPUResourceUsage usage = GPUResourceUsage::Default) + DEPRECATED("Use Vertex with vertex layout parameter instead") static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, GPUResourceUsage usage = GPUResourceUsage::Default) { return Buffer(elementsCount * elementStride, GPUBufferFlags::VertexBuffer, PixelFormat::Unknown, nullptr, elementStride, usage); } /// /// Creates vertex buffer description. + /// [Deprecated in v1.10] /// /// The size (in bytes). /// The usage mode. /// The buffer description. - static GPUBufferDescription Vertex(int32 size, GPUResourceUsage usage = GPUResourceUsage::Default) + DEPRECATED("Use Vertex with separate vertex stride and count instead") static GPUBufferDescription Vertex(int32 size, GPUResourceUsage usage = GPUResourceUsage::Default) { return Buffer(size, GPUBufferFlags::VertexBuffer, PixelFormat::Unknown, nullptr, 0, usage); } diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 52ff5ef5d..2f326c205 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -20,6 +20,7 @@ class GPUResource; class GPUResourceView; class GPUTextureView; class GPUBufferView; +class GPUVertexLayout; // Gets the GPU texture view. Checks if pointer is not null and texture has one or more mip levels loaded. #define GET_TEXTURE_VIEW_SAFE(t) (t && t->ResidentMipLevels() > 0 ? t->View() : nullptr) @@ -404,7 +405,8 @@ public: /// /// The array of vertex buffers to use. /// The optional array of byte offsets from the vertex buffers begins. Can be used to offset the vertex data when reusing the same buffer allocation for multiple geometry objects. - API_FUNCTION() virtual void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr) = 0; + /// The optional vertex layout to use when passing data from vertex buffers to the vertex shader. If null, layout will be automatically extracted from vertex buffers combined. + API_FUNCTION() virtual void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr, GPUVertexLayout* vertexLayout = nullptr) = 0; /// /// Binds the index buffer to the pipeline. diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 2f42ac08b..ad87261af 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -26,6 +26,8 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/Enums.h" +#include "Shaders/GPUVertexLayout.h" + GPUResourcePropertyBase::~GPUResourcePropertyBase() { const auto e = _resource; @@ -386,7 +388,11 @@ bool GPUDevice::LoadContent() }; // @formatter:on _res->FullscreenTriangleVB = CreateBuffer(TEXT("QuadVB")); - if (_res->FullscreenTriangleVB->Init(GPUBufferDescription::Vertex(sizeof(float) * 4, 3, vb))) + auto layout = GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32_Float }, + { VertexElement::Types::TexCoord, 0, 8, 0, PixelFormat::R32G32_Float }, + }); + if (_res->FullscreenTriangleVB->Init(GPUBufferDescription::Vertex(layout, 16, 3, vb))) return true; } diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 77c2089e1..b0ab1356b 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -11,6 +11,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/ManagedCLR/MCore.h" @@ -21,6 +22,30 @@ #include "Engine/Renderer/GBufferPass.h" #endif +GPUVertexLayout* VB0ElementType18::GetLayout() +{ + return GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float }, + }); +} + +GPUVertexLayout* VB1ElementType18::GetLayout() +{ + return GPUVertexLayout::Get({ + { VertexElement::Types::TexCoord, 1, 0, 0, PixelFormat::R16G16_Float }, + { VertexElement::Types::Normal, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm }, + { VertexElement::Types::Tangent, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm }, + { VertexElement::Types::TexCoord1, 1, 0, 0, PixelFormat::R16G16_Float }, + }); +} + +GPUVertexLayout* VB2ElementType18::GetLayout() +{ + return GPUVertexLayout::Get({ + { VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }, + }); +} + namespace { template @@ -233,15 +258,15 @@ bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* #define MESH_BUFFER_NAME(postfix) String::Empty #endif vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0")); - if (vertexBuffer0->Init(GPUBufferDescription::Vertex(sizeof(VB0ElementType), vertices, vb0))) + if (vertexBuffer0->Init(GPUBufferDescription::Vertex(VB0ElementType::GetLayout(), sizeof(VB0ElementType), vertices, vb0))) goto ERROR_LOAD_END; vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1")); - if (vertexBuffer1->Init(GPUBufferDescription::Vertex(sizeof(VB1ElementType), vertices, vb1))) + if (vertexBuffer1->Init(GPUBufferDescription::Vertex(VB1ElementType::GetLayout(), sizeof(VB1ElementType), vertices, vb1))) goto ERROR_LOAD_END; if (vb2) { vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2")); - if (vertexBuffer2->Init(GPUBufferDescription::Vertex(sizeof(VB2ElementType), vertices, vb2))) + if (vertexBuffer2->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(VB2ElementType), vertices, vb2))) goto ERROR_LOAD_END; } indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB")); diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 7278611e6..87b0abe63 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -151,6 +151,7 @@ public: /// /// Init from model vertices array + /// [Deprecated on 28.04.2023, expires on 01.01.2024] /// /// Array of vertices /// Amount of vertices @@ -158,6 +159,7 @@ public: /// /// Init from model vertices array + /// [Deprecated on 28.04.2023, expires on 01.01.2024] /// /// Array of vertices /// Amount of vertices @@ -182,6 +184,7 @@ public: /// /// Init from model vertices array + /// [Deprecated on 28.04.2023, expires on 01.01.2024] /// /// Array of data for vertex buffer 0 /// Array of data for vertex buffer 1 diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 207ff8991..1d9ab1df0 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -10,6 +10,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -18,6 +19,18 @@ #include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" +GPUVertexLayout* VB0SkinnedElementType2::GetLayout() +{ + return GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float }, + { VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float }, + { VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }, + { VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }, + { VertexElement::Types::BlendIndices, 0, 0, 0, PixelFormat::R8G8B8A8_UInt }, + { VertexElement::Types::BlendWeight, 0, 0, 0, PixelFormat::R16G16B16A16_Float }, + }); +} + void SkeletonData::Swap(SkeletonData& other) { Nodes.Swap(other.Nodes); @@ -120,7 +133,7 @@ bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const #else vertexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty); #endif - if (vertexBuffer->Init(GPUBufferDescription::Vertex(sizeof(VB0SkinnedElementType), vertices, vb0))) + if (vertexBuffer->Init(GPUBufferDescription::Vertex(VB0SkinnedElementType::GetLayout(), sizeof(VB0SkinnedElementType), vertices, vb0))) goto ERROR_LOAD_END; // Create index buffer diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index 0d30e3b4a..1f57cfa2c 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -21,6 +21,7 @@ class Model; class SkinnedModel; class MeshDeformation; class GPUContext; +class GPUVertexLayout; struct RenderView; /// @@ -69,6 +70,7 @@ enum class MeshBufferType }; // Vertex structure for all models (versioned) +// [Deprecated on 28.04.2023, expires on 01.01.2024] PACK_STRUCT(struct ModelVertex15 { Float3 Position; @@ -77,6 +79,7 @@ PACK_STRUCT(struct ModelVertex15 Float1010102 Tangent; }); +// [Deprecated on 28.04.2023, expires on 01.01.2024] PACK_STRUCT(struct ModelVertex18 { Float3 Position; @@ -86,6 +89,7 @@ PACK_STRUCT(struct ModelVertex18 Half2 LightmapUVs; }); +// [Deprecated in v1.10] PACK_STRUCT(struct ModelVertex19 { Float3 Position; @@ -96,9 +100,10 @@ PACK_STRUCT(struct ModelVertex19 Color32 Color; }); +// [Deprecated in v1.10] typedef ModelVertex19 ModelVertex; -// +// [Deprecated in v1.10] struct RawModelVertex { Float3 Position; @@ -111,11 +116,12 @@ struct RawModelVertex }; // For vertex data we use three buffers: one with positions, one with other attributes, and one with colors +// [Deprecated on 28.04.2023, expires on 01.01.2024] PACK_STRUCT(struct VB0ElementType15 { Float3 Position; }); - +// [Deprecated on 28.04.2023, expires on 01.01.2024] PACK_STRUCT(struct VB1ElementType15 { Half2 TexCoord; @@ -123,30 +129,42 @@ PACK_STRUCT(struct VB1ElementType15 Float1010102 Tangent; }); +// [Deprecated in v1.10] PACK_STRUCT(struct VB0ElementType18 { Float3 Position; + + static GPUVertexLayout* GetLayout(); }); +// [Deprecated in v1.10] PACK_STRUCT(struct VB1ElementType18 { Half2 TexCoord; Float1010102 Normal; Float1010102 Tangent; Half2 LightmapUVs; + + static GPUVertexLayout* GetLayout(); }); +// [Deprecated in v1.10] PACK_STRUCT(struct VB2ElementType18 { Color32 Color; + + static GPUVertexLayout* GetLayout(); }); +// [Deprecated in v1.10] typedef VB0ElementType18 VB0ElementType; +// [Deprecated in v1.10] typedef VB1ElementType18 VB1ElementType; +// [Deprecated in v1.10] typedef VB2ElementType18 VB2ElementType; -// // Vertex structure for all skinned models (versioned) +// [Deprecated in v1.10] PACK_STRUCT(struct SkinnedModelVertex1 { Float3 Position; @@ -157,9 +175,10 @@ PACK_STRUCT(struct SkinnedModelVertex1 Color32 BlendWeights; }); +// [Deprecated in v1.10] typedef SkinnedModelVertex1 SkinnedModelVertex; -// +// [Deprecated in v1.10] struct RawSkinnedModelVertex { Float3 Position; @@ -171,6 +190,7 @@ struct RawSkinnedModelVertex Float4 BlendWeights; }; +// [Deprecated on 28.04.2023, expires on 01.01.2024] PACK_STRUCT(struct VB0SkinnedElementType1 { Float3 Position; @@ -181,6 +201,7 @@ PACK_STRUCT(struct VB0SkinnedElementType1 Color32 BlendWeights; }); +// [Deprecated in v1.10] PACK_STRUCT(struct VB0SkinnedElementType2 { Float3 Position; @@ -189,7 +210,9 @@ PACK_STRUCT(struct VB0SkinnedElementType2 Float1010102 Tangent; Color32 BlendIndices; Half4 BlendWeights; + + static GPUVertexLayout* GetLayout(); }); +// [Deprecated in v1.10] typedef VB0SkinnedElementType2 VB0SkinnedElementType; -// diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index b29d3236b..33f54582f 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -4,6 +4,7 @@ #include "GPUConstantBuffer.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Serialization/MemoryReadStream.h" @@ -23,11 +24,6 @@ void GPUShaderProgram::Init(const GPUShaderProgramInitializer& initializer) #endif } -GPUShaderProgramVS::~GPUShaderProgramVS() -{ - SAFE_DELETE(Layout); -} - GPUShader::GPUShader() : GPUResource(SpawnParams(Guid::New(), TypeInitializer)) { @@ -77,15 +73,15 @@ bool GPUShader::Create(MemoryReadStream& stream) for (int32 permutationIndex = 0; permutationIndex < permutationsCount; permutationIndex++) { - // Load cache - uint32 cacheSize; - stream.ReadUint32(&cacheSize); - if (cacheSize > stream.GetLength() - stream.GetPosition()) + // Load bytecode + uint32 bytecodeSize; + stream.ReadUint32(&bytecodeSize); + if (bytecodeSize > stream.GetLength() - stream.GetPosition()) { - LOG(Warning, "Invalid shader cache size."); + LOG(Warning, "Invalid shader bytecode size."); return true; } - byte* cache = stream.Move(cacheSize); + byte* bytecode = stream.Move(bytecodeSize); // Read bindings stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings)); @@ -96,7 +92,7 @@ bool GPUShader::Create(MemoryReadStream& stream) LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name); continue; } - GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream); + GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, Span(bytecode, bytecodeSize), stream); if (shader == nullptr) { #if !GPU_ALLOW_TESSELLATION_SHADERS @@ -181,6 +177,24 @@ GPUShaderProgram* GPUShader::GetShader(ShaderStage stage, const StringAnsiView& return shader; } +GPUVertexLayout* GPUShader::ReadVertexLayout(MemoryReadStream& stream) +{ + // [Deprecated in v1.10] + byte inputLayoutSize; + stream.ReadByte(&inputLayoutSize); + if (inputLayoutSize == 0) + return nullptr; + void* elementsData = stream.Move(sizeof(VertexElement) * inputLayoutSize); + if (inputLayoutSize > GPU_MAX_VS_ELEMENTS) + { + LOG(Error, "Incorrect input layout size."); + return nullptr; + } + GPUVertexLayout::Elements elements; + elements.Set((VertexElement*)elementsData, inputLayoutSize); + return GPUVertexLayout::Get(elements); +} + GPUResourceType GPUShader::GetResourceType() const { return GPUResourceType::Shader; diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 035619c2e..6879c3d5b 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -12,7 +12,7 @@ class GPUShaderProgram; /// /// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate). /// -#define GPU_SHADER_CACHE_VERSION 10 +#define GPU_SHADER_CACHE_VERSION 11 /// /// The GPU resource with shader programs that can run on the GPU and are able to perform rendering calculation using textures, vertices and other resources. @@ -134,7 +134,8 @@ public: protected: GPUShaderProgram* GetShader(ShaderStage stage, const StringAnsiView& name, int32 permutationIndex) const; - virtual GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) = 0; + virtual GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) = 0; + static GPUVertexLayout* ReadVertexLayout(MemoryReadStream& stream); public: // [GPUResource] diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h index 9447e04a5..aa8368ea0 100644 --- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h +++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h @@ -115,20 +115,11 @@ public: /// class GPUShaderProgramVS : public GPUShaderProgram { -public: - ~GPUShaderProgramVS(); - - // Vertex elements input layout defined explicitly in the shader. - // It's optional as it's been deprecated in favor or layouts defined by vertex buffers to allow data customizations. - // Can be overriden by the vertex buffers provided upon draw call. - // [Deprecated in v1.10] - GPUVertexLayout* Layout = nullptr; - public: // Input element run-time data (see VertexShaderMeta::InputElement for compile-time data) - // [Deprecated in v1.10] + // [Deprecated in v1.10] Use VertexElement instead. PACK_STRUCT(struct InputElement - { + { byte Type; // VertexShaderMeta::InputType byte Index; byte Format; // PixelFormat @@ -136,19 +127,14 @@ public: uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA uint32 InstanceDataStepRate; // 0 if per-vertex - }); + }); - /// - /// Gets input layout description handle (platform dependent). - /// [Deprecated in v1.10] - /// - virtual void* GetInputLayout() const = 0; - - /// - /// Gets input layout description size (in bytes). - /// [Deprecated in v1.10] - /// - virtual byte GetInputLayoutSize() const = 0; + // Vertex elements input layout defined explicitly in the shader. + // It's optional as it's been deprecated in favor or layouts defined by vertex buffers to allow data customizations. + // Can be overriden by the vertex buffers provided upon draw call. + // (don't release it as it's managed by GPUVertexLayout::Get) + // [Deprecated in v1.10] + GPUVertexLayout* Layout = nullptr; public: // [GPUShaderProgram] diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index d90613a31..6a4b8e677 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -5,7 +5,9 @@ #include "Engine/Core/Log.h" #endif #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/GPUBuffer.h" #if GPU_ENABLE_RESOURCE_NAMING #include "Engine/Scripting/Enums.h" #endif @@ -18,10 +20,29 @@ struct VertexElementRaw static_assert(sizeof(VertexElement) == sizeof(VertexElementRaw), "Incorrect size of the VertexElement!"); +struct VertexBufferLayouts +{ + GPUVertexLayout* Layouts[GPU_MAX_VB_BINDED]; + + bool operator==(const VertexBufferLayouts& other) const + { + return Platform::MemoryCompare(&Layouts, &other.Layouts, sizeof(Layouts)) == 0; + } +}; + +uint32 GetHash(const VertexBufferLayouts& key) +{ + uint32 hash = GetHash(key.Layouts[0]); + for (int32 i = 1; i < GPU_MAX_VB_BINDED; i++) + CombineHash(hash, GetHash(key.Layouts[i])); + return hash; +} + namespace { CriticalSection CacheLocker; Dictionary LayoutCache; + Dictionary VertexBufferCache; } String VertexElement::ToString() const @@ -95,9 +116,50 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) return result; } +GPUVertexLayout* GPUVertexLayout::Get(const Span& vertexBuffers) +{ + if (vertexBuffers.Length() == 0) + return nullptr; + if (vertexBuffers.Length() == 1) + return vertexBuffers.Get()[0] ? vertexBuffers.Get()[0]->GetVertexLayout() : nullptr; + + // Build hash key for set of buffers (in case there is layout sharing by different sets of buffers) + VertexBufferLayouts layouts; + for (int32 i = 0; i < vertexBuffers.Length(); i++) + layouts.Layouts[i] = vertexBuffers.Get()[i] ? vertexBuffers.Get()[i]->GetVertexLayout() : nullptr; + for (int32 i = vertexBuffers.Length(); i < GPU_MAX_VB_BINDED; i++) + layouts.Layouts[i] = nullptr; + + // Lookup existing cache + CacheLocker.Lock(); + GPUVertexLayout* result; + if (!VertexBufferCache.TryGet(layouts, result)) + { + Elements elements; + bool anyValid = false; + for (int32 slot = 0; slot < vertexBuffers.Length(); slot++) + { + if (layouts.Layouts[slot]) + { + anyValid = true; + int32 start = elements.Count(); + elements.Add(layouts.Layouts[slot]->GetElements()); + for (int32 j = start; j < elements.Count() ;j++) + elements.Get()[j].Slot = (byte)slot; + } + } + result = anyValid ? Get(elements) : nullptr; + VertexBufferCache.Add(layouts, result); + } + CacheLocker.Unlock(); + + return result; +} + void ClearVertexLayoutCache() { for (const auto& e : LayoutCache) Delete(e.Value); LayoutCache.Clear(); + VertexBufferCache.Clear(); } diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index a3f5158bc..8d60573b8 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -6,6 +6,8 @@ #include "Engine/Graphics/GPUResource.h" #include "Engine/Core/Collections/Array.h" +class GPUBuffer; + /// /// Defines input layout of vertex buffer data passed to the Vertex Shader. /// @@ -35,6 +37,13 @@ public: /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. API_FUNCTION() static GPUVertexLayout* Get(const Array>& elements); + /// + /// Gets the vertex layout for a given list of vertex buffers (sequence of binding slots based on layouts set on those buffers). Uses internal cache to skip creating layout if it's already exists for a given list. + /// + /// The list of vertex buffers for the layout. + /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. + API_FUNCTION() static GPUVertexLayout* Get(const Span& vertexBuffers); + public: // [GPUResource] GPUResourceType GetResourceType() const override diff --git a/Source/Engine/Graphics/Shaders/VertexElement.h b/Source/Engine/Graphics/Shaders/VertexElement.h index edf2d5df6..06bf0dbb5 100644 --- a/Source/Engine/Graphics/Shaders/VertexElement.h +++ b/Source/Engine/Graphics/Shaders/VertexElement.h @@ -63,15 +63,15 @@ PACK_BEGIN() struct FLAXENGINE_API VertexElement }; // Type of the vertex element data. - Types Type; + API_FIELD() Types Type; // Index of the input vertex buffer slot (as provided in GPUContext::BindVB). - byte Slot; + API_FIELD() byte Slot; // Byte offset of this element relative to the start of a vertex buffer. Use value 0 to use auto-calculated offset based on previous elements in the layout (or for the first one). - byte Offset; + API_FIELD() byte Offset; // Flag used to mark data using hardware-instancing (element will be repeated for every instance). Empty to step data per-vertex when reading input buffer stream (rather than per-instance step). - byte PerInstance; + API_FIELD() byte PerInstance; // Format of the vertex element data. - PixelFormat Format; + API_FIELD() PixelFormat Format; String ToString() const; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 0ff1392ec..f045f768a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -9,6 +9,7 @@ #include "GPUTextureDX11.h" #include "GPUBufferDX11.h" #include "GPUSamplerDX11.h" +#include "GPUVertexLayoutDX11.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" #include "Engine/Core/Math/Viewport.h" #include "Engine/Core/Math/Rectangle.h" @@ -82,9 +83,11 @@ void GPUContextDX11::FrameBegin() _omDirtyFlag = false; _uaDirtyFlag = false; _cbDirtyFlag = false; + _iaInputLayoutDirtyFlag = false; _srMaskDirtyGraphics = 0; _srMaskDirtyCompute = 0; _rtCount = 0; + _vertexLayout = nullptr; _currentState = nullptr; _rtDepth = nullptr; Platform::MemoryClear(_rtHandles, sizeof(_rtHandles)); @@ -380,7 +383,7 @@ void GPUContextDX11::BindUA(int32 slot, GPUResourceView* view) } } -void GPUContextDX11::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets) +void GPUContextDX11::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets, GPUVertexLayout* vertexLayout) { ASSERT(vertexBuffers.Length() >= 0 && vertexBuffers.Length() <= GPU_MAX_VB_BINDED); @@ -402,6 +405,13 @@ void GPUContextDX11::BindVB(const Span& vertexBuffers, const uint32* { _context->IASetVertexBuffers(0, vertexBuffers.Length(), _vbHandles, _vbStrides, _vbOffsets); } + if (!vertexLayout) + vertexLayout = GPUVertexLayout::Get(vertexBuffers); + if (_vertexLayout != vertexLayout) + { + _vertexLayout = (GPUVertexLayoutDX11*)vertexLayout; + _iaInputLayoutDirtyFlag = true; + } } void GPUContextDX11::BindIB(GPUBuffer* indexBuffer) @@ -610,7 +620,7 @@ void GPUContextDX11::SetState(GPUPipelineState* state) #endif CurrentVS = vs; _context->VSSetShader(vs ? vs->GetBufferHandleDX11() : nullptr, nullptr, 0); - _context->IASetInputLayout(vs ? vs->GetInputLayoutDX11() : nullptr); + _iaInputLayoutDirtyFlag = true; } #if GPU_ALLOW_TESSELLATION_SHADERS if (CurrentHS != hs) @@ -720,6 +730,7 @@ void GPUContextDX11::FlushState() flushCBs(); flushSRVs(); flushUAVs(); + flushIA(); flushOM(); } @@ -932,11 +943,28 @@ void GPUContextDX11::flushOM() } } +void GPUContextDX11::flushIA() +{ + if (_iaInputLayoutDirtyFlag) + { + _iaInputLayoutDirtyFlag = false; + ID3D11InputLayout* inputLayout = CurrentVS ? CurrentVS->GetInputLayout(_vertexLayout) : nullptr; +#if GPU_ENABLE_ASSERTION_LOW_LAYERS + if (!inputLayout && CurrentVS && !_vertexLayout && _vbHandles[0]) + { + LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); + } +#endif + _context->IASetInputLayout(inputLayout); + } +} + void GPUContextDX11::onDrawCall() { flushCBs(); flushSRVs(); flushUAVs(); + flushIA(); flushOM(); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h index 08225ca41..2d2b5b986 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h @@ -10,6 +10,7 @@ #if GRAPHICS_API_DIRECTX11 class GPUBufferDX11; +class GPUVertexLayoutDX11; /// /// GPU Context for DirectX 11 backend. @@ -49,6 +50,8 @@ private: ID3D11Buffer* _vbHandles[GPU_MAX_VB_BINDED]; UINT _vbStrides[GPU_MAX_VB_BINDED]; UINT _vbOffsets[GPU_MAX_VB_BINDED]; + GPUVertexLayoutDX11* _vertexLayout; + bool _iaInputLayoutDirtyFlag; // Pipeline State GPUPipelineStateDX11* _currentState; @@ -100,6 +103,7 @@ private: void flushUAVs(); void flushCBs(); void flushOM(); + void flushIA(); void onDrawCall(); public: @@ -130,7 +134,7 @@ public: void BindCB(int32 slot, GPUConstantBuffer* cb) override; void BindSR(int32 slot, GPUResourceView* view) override; void BindUA(int32 slot, GPUResourceView* view) override; - void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr) override; + void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr, GPUVertexLayout* vertexLayout = nullptr) override; void BindIB(GPUBuffer* indexBuffer) override; void BindSampler(int32 slot, GPUSampler* sampler) override; void UpdateCB(GPUConstantBuffer* cb, const void* data) override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index 70018233d..faf50a927 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -4,10 +4,33 @@ #include "GPUShaderDX11.h" #include "GPUShaderProgramDX11.h" +#include "GPUVertexLayoutDX11.h" #include "Engine/Serialization/MemoryReadStream.h" #include "../RenderToolsDX.h" -GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) +GPUShaderProgramVSDX11::~GPUShaderProgramVSDX11() +{ + for (const auto& e : _cache) + e.Value->Release(); +} + +ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout) +{ + if (!vertexLayout) + vertexLayout = (GPUVertexLayoutDX11*)Layout; + ID3D11InputLayout* inputLayout = nullptr; + if (!_cache.TryGet(vertexLayout, inputLayout)) + { + if (vertexLayout && vertexLayout->InputElementsCount) + { + VALIDATE_DIRECTX_CALL(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(vertexLayout->InputElements, vertexLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); + } + _cache.Add(vertexLayout, inputLayout); + } + return inputLayout; +} + +GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) { GPUShaderProgram* shader = nullptr; HRESULT result; @@ -16,80 +39,15 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const case ShaderStage::Vertex: { // Load Input Layout - byte inputLayoutSize; - stream.ReadByte(&inputLayoutSize); - ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); - D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - for (int32 a = 0; a < inputLayoutSize; a++) - { - // Read description - GPUShaderProgramVS::InputElement inputElement; - stream.Read(inputElement); - - // Get semantic name - const char* semanticName = nullptr; - // TODO: maybe use enum+mapping ? - switch (inputElement.Type) - { - case 1: - semanticName = "POSITION"; - break; - case 2: - semanticName = "COLOR"; - break; - case 3: - semanticName = "TEXCOORD"; - break; - case 4: - semanticName = "NORMAL"; - break; - case 5: - semanticName = "TANGENT"; - break; - case 6: - semanticName = "BITANGENT"; - break; - case 7: - semanticName = "ATTRIBUTE"; - break; - case 8: - semanticName = "BLENDINDICES"; - break; - case 9: - semanticName = "BLENDWEIGHT"; - break; - default: - LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type); - break; - } - - // Set data - inputLayoutDesc[a] = - { - semanticName, - static_cast(inputElement.Index), - static_cast(inputElement.Format), - static_cast(inputElement.InputSlot), - static_cast(inputElement.AlignedByteOffset), - static_cast(inputElement.InputSlotClass), - static_cast(inputElement.InstanceDataStepRate) - }; - } - - ID3D11InputLayout* inputLayout = nullptr; - if (inputLayoutSize > 0) - { - // Create input layout - VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateInputLayout(inputLayoutDesc, inputLayoutSize, cacheBytes, cacheSize, &inputLayout)); - } + GPUVertexLayout* vertexLayout = ReadVertexLayout(stream); // Create shader ID3D11VertexShader* buffer = nullptr; - result = _device->GetDevice()->CreateVertexShader(cacheBytes, cacheSize, nullptr, &buffer); + result = _device->GetDevice()->CreateVertexShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object - shader = New(initializer, buffer, inputLayout, inputLayoutSize); + shader = New(initializer, buffer, vertexLayout, bytecode); break; } #if GPU_ALLOW_TESSELLATION_SHADERS @@ -101,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const // Create shader ID3D11HullShader* buffer = nullptr; - result = _device->GetDevice()->CreateHullShader(cacheBytes, cacheSize, nullptr, &buffer); + result = _device->GetDevice()->CreateHullShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object @@ -112,7 +70,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11DomainShader* buffer = nullptr; - result = _device->GetDevice()->CreateDomainShader(cacheBytes, cacheSize, nullptr, &buffer); + result = _device->GetDevice()->CreateDomainShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object @@ -132,7 +90,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11GeometryShader* buffer = nullptr; - result = _device->GetDevice()->CreateGeometryShader(cacheBytes, cacheSize, nullptr, &buffer); + result = _device->GetDevice()->CreateGeometryShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object @@ -144,7 +102,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11PixelShader* buffer = nullptr; - result = _device->GetDevice()->CreatePixelShader(cacheBytes, cacheSize, nullptr, &buffer); + result = _device->GetDevice()->CreatePixelShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object @@ -155,7 +113,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { // Create shader ID3D11ComputeShader* buffer = nullptr; - result = _device->GetDevice()->CreateComputeShader(cacheBytes, cacheSize, nullptr, &buffer); + result = _device->GetDevice()->CreateComputeShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); // Create object diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h index 3ccfa85c7..7070644e4 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.h @@ -80,7 +80,7 @@ public: protected: // [GPUShader] - GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override; + GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) override; void OnReleaseGPU() override; public: diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h index 519afcbfc..d43e60b2d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Graphics/Shaders/GPUShaderProgram.h" +#include "Engine/Core/Types/DataContainer.h" #include "../IncludeDirectXHeaders.h" #if GRAPHICS_API_DIRECTX11 @@ -17,11 +18,6 @@ protected: BufferType* _buffer; public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. GPUShaderProgramDX11(const GPUShaderProgramInitializer& initializer, BufferType* buffer) : _buffer(buffer) { @@ -31,18 +27,11 @@ public: #endif } - /// - /// Finalizes an instance of the class. - /// ~GPUShaderProgramDX11() { DX_SAFE_RELEASE_CHECK(_buffer, 0); } -public: - /// - /// Gets DirectX 11 buffer handle. - /// FORCE_INLINE BufferType* GetBufferHandleDX11() const { return _buffer; @@ -66,51 +55,21 @@ public: class GPUShaderProgramVSDX11 : public GPUShaderProgramDX11 { private: - byte _inputLayoutSize; - ID3D11InputLayout* _inputLayout; + Dictionary _cache; public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. - /// The input layout. - /// Size of the input layout. - GPUShaderProgramVSDX11(const GPUShaderProgramInitializer& initializer, ID3D11VertexShader* buffer, ID3D11InputLayout* inputLayout, byte inputLayoutSize) + GPUShaderProgramVSDX11(const GPUShaderProgramInitializer& initializer, ID3D11VertexShader* buffer, GPUVertexLayout* vertexLayout, Span bytecode) : GPUShaderProgramDX11(initializer, buffer) - , _inputLayoutSize(inputLayoutSize) - , _inputLayout(inputLayout) { + Layout = vertexLayout; + Bytecode.Copy(bytecode); } - /// - /// Finalizes an instance of the class. - /// - ~GPUShaderProgramVSDX11() - { - DX_SAFE_RELEASE_CHECK(_inputLayout, 0); - } + ~GPUShaderProgramVSDX11(); -public: - /// - /// Gets the DirectX 11 input layout handle - /// - FORCE_INLINE ID3D11InputLayout* GetInputLayoutDX11() const - { - return _inputLayout; - } + BytesContainer Bytecode; -public: - // [GPUShaderProgramDX11] - void* GetInputLayout() const override - { - return (void*)_inputLayout; - } - byte GetInputLayoutSize() const override - { - return _inputLayoutSize; - } + ID3D11InputLayout* GetInputLayout(class GPUVertexLayoutDX11* vertexLayout); }; #if GPU_ALLOW_TESSELLATION_SHADERS @@ -120,12 +79,6 @@ public: class GPUShaderProgramHSDX11 : public GPUShaderProgramDX11 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. - /// The control points used by the hull shader for processing. GPUShaderProgramHSDX11(const GPUShaderProgramInitializer& initializer, ID3D11HullShader* buffer, int32 controlPointsCount) : GPUShaderProgramDX11(initializer, buffer) { @@ -139,11 +92,6 @@ public: class GPUShaderProgramDSDX11 : public GPUShaderProgramDX11 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. GPUShaderProgramDSDX11(const GPUShaderProgramInitializer& initializer, ID3D11DomainShader* buffer) : GPUShaderProgramDX11(initializer, buffer) { @@ -158,11 +106,6 @@ public: class GPUShaderProgramGSDX11 : public GPUShaderProgramDX11 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. GPUShaderProgramGSDX11(const GPUShaderProgramInitializer& initializer, ID3D11GeometryShader* buffer) : GPUShaderProgramDX11(initializer, buffer) { @@ -176,11 +119,6 @@ public: class GPUShaderProgramPSDX11 : public GPUShaderProgramDX11 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. GPUShaderProgramPSDX11(const GPUShaderProgramInitializer& initializer, ID3D11PixelShader* buffer) : GPUShaderProgramDX11(initializer, buffer) { @@ -193,11 +131,6 @@ public: class GPUShaderProgramCSDX11 : public GPUShaderProgramDX11 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader buffer object. GPUShaderProgramCSDX11(const GPUShaderProgramInitializer& initializer, ID3D11ComputeShader* buffer) : GPUShaderProgramDX11(initializer, buffer) { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 8c31a358a..6f9be059f 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -26,6 +26,7 @@ #include "GPUTextureDX12.h" #include "GPUBufferDX12.h" #include "GPUSamplerDX12.h" +#include "GPUVertexLayoutDX12.h" #include "CommandQueueDX12.h" #include "DescriptorHeapDX12.h" #include "Engine/Graphics/RenderTask.h" @@ -244,6 +245,7 @@ void GPUContextDX12::Reset() Platform::MemoryClear(_srHandles, sizeof(_srHandles)); Platform::MemoryClear(_uaHandles, sizeof(_uaHandles)); Platform::MemoryClear(_vbHandles, sizeof(_vbHandles)); + _vertexLayout = nullptr; _ibHandle = nullptr; Platform::MemoryClear(&_cbHandles, sizeof(_cbHandles)); Platform::MemoryClear(&_samplers, sizeof(_samplers)); @@ -560,7 +562,13 @@ void GPUContextDX12::flushPS() // Change state ASSERT(_currentState->IsValid()); - _commandList->SetPipelineState(_currentState->GetState(_rtDepth, _rtCount, _rtHandles)); +#if GPU_ENABLE_ASSERTION_LOW_LAYERS + if (!_vertexLayout && _vbHandles[0] && !_currentState->VertexLayout) + { + LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); + } +#endif + _commandList->SetPipelineState(_currentState->GetState(_rtDepth, _rtCount, _rtHandles, _vertexLayout)); if (_primitiveTopology != _currentState->PrimitiveTopology) { _primitiveTopology = _currentState->PrimitiveTopology; @@ -946,7 +954,7 @@ void GPUContextDX12::BindUA(int32 slot, GPUResourceView* view) *view->LastRenderTime = _lastRenderTime; } -void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets) +void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets, GPUVertexLayout* vertexLayout) { ASSERT(vertexBuffers.Length() >= 0 && vertexBuffers.Length() <= GPU_MAX_VB_BINDED); @@ -982,6 +990,7 @@ void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* #endif _commandList->IASetVertexBuffers(0, vertexBuffers.Length(), views); } + _vertexLayout = (GPUVertexLayoutDX12*)(vertexLayout ? vertexLayout : GPUVertexLayout::Get(vertexBuffers)); } void GPUContextDX12::BindIB(GPUBuffer* indexBuffer) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h index 72b6e4547..f97be6da9 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h @@ -15,6 +15,7 @@ class GPUBufferDX12; class GPUSamplerDX12; class GPUConstantBufferDX12; class GPUTextureViewDX12; +class GPUVertexLayoutDX12; /// /// Size of the resource barriers buffer size (will be flushed on overflow) @@ -61,6 +62,7 @@ private: GPUTextureViewDX12* _rtHandles[GPU_MAX_RT_BINDED]; IShaderResourceDX12* _srHandles[GPU_MAX_SR_BINDED]; IShaderResourceDX12* _uaHandles[GPU_MAX_UA_BINDED]; + GPUVertexLayoutDX12* _vertexLayout; GPUBufferDX12* _ibHandle; GPUBufferDX12* _vbHandles[GPU_MAX_VB_BINDED]; D3D12_INDEX_BUFFER_VIEW _ibView; @@ -176,7 +178,7 @@ public: void BindCB(int32 slot, GPUConstantBuffer* cb) override; void BindSR(int32 slot, GPUResourceView* view) override; void BindUA(int32 slot, GPUResourceView* view) override; - void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr) override; + void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr, GPUVertexLayout* vertexLayout = nullptr) override; void BindIB(GPUBuffer* indexBuffer) override; void BindSampler(int32 slot, GPUSampler* sampler) override; void UpdateCB(GPUConstantBuffer* cb, const void* data) override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index ead13f149..ecfce4eca 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -4,6 +4,7 @@ #include "GPUPipelineStateDX12.h" #include "GPUShaderProgramDX12.h" +#include "GPUVertexLayoutDX12.h" #include "GPUTextureDX12.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" @@ -45,14 +46,16 @@ bool GPUPipelineStateDX12::IsValid() const return !!_memoryUsage; } -ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, int32 rtCount, GPUTextureViewDX12** rtHandles) +ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, int32 rtCount, GPUTextureViewDX12** rtHandles, GPUVertexLayoutDX12* vertexLayout) { - // Validate ASSERT(depth || rtCount); + if (!vertexLayout) + vertexLayout = VertexLayout; // Prepare key GPUPipelineStateKeyDX12 key; key.RTsCount = rtCount; + key.VertexLayout = vertexLayout; key.DepthFormat = depth ? depth->GetFormat() : PixelFormat::Unknown; key.MSAA = depth ? depth->GetMSAA() : (rtCount ? rtHandles[0]->GetMSAA() : MSAALevel::None); for (int32 i = 0; i < rtCount; i++) @@ -72,7 +75,6 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i #endif return state; } - PROFILE_CPU_NAMED("Create Pipeline State"); // Update description to match the pipeline @@ -83,6 +85,8 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i _desc.SampleDesc.Quality = key.MSAA == MSAALevel::None ? 0 : GPUDeviceDX12::GetMaxMSAAQuality((int32)key.MSAA); _desc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK; _desc.DSVFormat = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindDepthStencilFormat(key.DepthFormat)); + _desc.InputLayout.pInputElementDescs = vertexLayout ? vertexLayout->InputElements : nullptr; + _desc.InputLayout.NumElements = vertexLayout ? vertexLayout->InputElementsCount : 0; // Create object const HRESULT result = _device->GetDevice()->CreateGraphicsPipelineState(&_desc, IID_PPV_ARGS(&state)); @@ -96,6 +100,7 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i name.Add(*DebugDesc.VS->GetName(), DebugDesc.VS->GetName().Length()); name.Add('+'); } +#if GPU_ALLOW_TESSELLATION_SHADERS if (DebugDesc.HS) { name.Add(*DebugDesc.HS->GetName(), DebugDesc.HS->GetName().Length()); @@ -106,11 +111,14 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i name.Add(*DebugDesc.DS->GetName(), DebugDesc.DS->GetName().Length()); name.Add('+'); } +#endif +#if GPU_ALLOW_GEOMETRY_SHADERS if (DebugDesc.GS) { name.Add(*DebugDesc.GS->GetName(), DebugDesc.GS->GetName().Length()); name.Add('+'); } +#endif if (DebugDesc.PS) { name.Add(*DebugDesc.PS->GetName(), DebugDesc.PS->GetName().Length()); @@ -148,7 +156,6 @@ bool GPUPipelineStateDX12::Init(const Description& desc) // Shaders Platform::MemoryClear(&Header, sizeof(Header)); - psDesc.InputLayout = { static_cast(desc.VS->GetInputLayout()), desc.VS->GetInputLayoutSize() }; #define INIT_SHADER_STAGE(stage, type) \ if (desc.stage) \ { \ @@ -172,14 +179,17 @@ bool GPUPipelineStateDX12::Init(const Description& desc) #endif INIT_SHADER_STAGE(VS, GPUShaderProgramVSDX12); INIT_SHADER_STAGE(PS, GPUShaderProgramPSDX12); - const static D3D12_PRIMITIVE_TOPOLOGY_TYPE primTypes1[] = + + // Input Assembly + VertexLayout = desc.VS ? (GPUVertexLayoutDX12*)desc.VS->Layout : nullptr; + const D3D12_PRIMITIVE_TOPOLOGY_TYPE primTypes1[] = { D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED, D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, }; - const static D3D_PRIMITIVE_TOPOLOGY primTypes2[] = + const D3D_PRIMITIVE_TOPOLOGY primTypes2[] = { D3D_PRIMITIVE_TOPOLOGY_UNDEFINED, D3D_PRIMITIVE_TOPOLOGY_POINTLIST, diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h index bcb114806..aaaae48d2 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h @@ -11,11 +11,13 @@ #include "../IncludeDirectXHeaders.h" class GPUTextureViewDX12; +class GPUVertexLayoutDX12; struct GPUPipelineStateKeyDX12 { int32 RTsCount; MSAALevel MSAA; + GPUVertexLayout* VertexLayout; PixelFormat DepthFormat; PixelFormat RTVsFormats[GPU_MAX_RT_BINDED]; @@ -29,6 +31,7 @@ struct GPUPipelineStateKeyDX12 uint32 hash = (int32)key.MSAA * 11; CombineHash(hash, (uint32)key.DepthFormat * 93473262); CombineHash(hash, key.RTsCount * 136); + CombineHash(hash, GetHash(key.VertexLayout)); CombineHash(hash, (uint32)key.RTVsFormats[0]); CombineHash(hash, (uint32)key.RTVsFormats[1]); CombineHash(hash, (uint32)key.RTVsFormats[2]); @@ -46,18 +49,16 @@ struct GPUPipelineStateKeyDX12 class GPUPipelineStateDX12 : public GPUResourceDX12 { private: - Dictionary _states; D3D12_GRAPHICS_PIPELINE_STATE_DESC _desc; public: - GPUPipelineStateDX12(GPUDeviceDX12* device); public: - D3D_PRIMITIVE_TOPOLOGY PrimitiveTopology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; DxShaderHeader Header; + GPUVertexLayoutDX12* VertexLayout; /// /// Gets DirectX 12 graphics pipeline state object for the given rendering state. Uses depth buffer and render targets formats and multi-sample levels to setup a proper PSO. Uses caching. @@ -65,17 +66,16 @@ public: /// The depth buffer (can be null). /// The render targets count (can be 0). /// The render target handles array. + /// The vertex buffers layout. /// DirectX 12 graphics pipeline state object - ID3D12PipelineState* GetState(GPUTextureViewDX12* depth, int32 rtCount, GPUTextureViewDX12** rtHandles); + ID3D12PipelineState* GetState(GPUTextureViewDX12* depth, int32 rtCount, GPUTextureViewDX12** rtHandles, GPUVertexLayoutDX12* vertexLayout); public: - // [GPUPipelineState] bool IsValid() const override; bool Init(const Description& desc) override; protected: - // [GPUResourceDX12] void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp index 638e1b409..c4aea2172 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp @@ -8,81 +8,19 @@ #include "Types.h" #include "../RenderToolsDX.h" -GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) +GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) { // Extract the DX shader header from the cache - DxShaderHeader* header = (DxShaderHeader*)cacheBytes; - cacheBytes += sizeof(DxShaderHeader); - cacheSize -= sizeof(DxShaderHeader); + DxShaderHeader* header = (DxShaderHeader*)bytecode.Get(); + bytecode = Span(bytecode.Get() + sizeof(DxShaderHeader), bytecode.Length() - sizeof(DxShaderHeader)); GPUShaderProgram* shader = nullptr; switch (type) { case ShaderStage::Vertex: { - // Load Input Layout (it may be empty) - byte inputLayoutSize; - stream.ReadByte(&inputLayoutSize); - ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); - D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - for (int32 a = 0; a < inputLayoutSize; a++) - { - // Read description - GPUShaderProgramVS::InputElement inputElement; - stream.Read(inputElement); - - // Get semantic name - const char* semanticName = nullptr; - // TODO: maybe use enum+mapping ? - switch (inputElement.Type) - { - case 1: - semanticName = "POSITION"; - break; - case 2: - semanticName = "COLOR"; - break; - case 3: - semanticName = "TEXCOORD"; - break; - case 4: - semanticName = "NORMAL"; - break; - case 5: - semanticName = "TANGENT"; - break; - case 6: - semanticName = "BITANGENT"; - break; - case 7: - semanticName = "ATTRIBUTE"; - break; - case 8: - semanticName = "BLENDINDICES"; - break; - case 9: - semanticName = "BLENDWEIGHT"; - break; - default: - LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type); - break; - } - - // Set data - inputLayout[a] = - { - semanticName, - static_cast(inputElement.Index), - static_cast(inputElement.Format), - static_cast(inputElement.InputSlot), - static_cast(inputElement.AlignedByteOffset), - static_cast(inputElement.InputSlotClass), - static_cast(inputElement.InstanceDataStepRate) - }; - } - - // Create object - shader = New(initializer, header, cacheBytes, cacheSize, inputLayout, inputLayoutSize); + GPUVertexLayout* vertexLayout = ReadVertexLayout(stream); + shader = New(initializer, header, bytecode, vertexLayout); break; } #if GPU_ALLOW_TESSELLATION_SHADERS @@ -90,12 +28,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const { int32 controlPointsCount; stream.ReadInt32(&controlPointsCount); - shader = New(initializer, header, cacheBytes, cacheSize, controlPointsCount); + shader = New(initializer, header, bytecode, controlPointsCount); break; } case ShaderStage::Domain: { - shader = New(initializer, header, cacheBytes, cacheSize); + shader = New(initializer, header, bytecode); break; } #else @@ -109,18 +47,18 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const #if GPU_ALLOW_GEOMETRY_SHADERS case ShaderStage::Geometry: { - shader = New(initializer, header, cacheBytes, cacheSize); + shader = New(initializer, header, bytecode); break; } #endif case ShaderStage::Pixel: { - shader = New(initializer, header, cacheBytes, cacheSize); + shader = New(initializer, header, bytecode); break; } case ShaderStage::Compute: { - shader = New(_device, initializer, header, cacheBytes, cacheSize); + shader = New(_device, initializer, header, bytecode); break; } } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h index 48bf99f41..9727307a9 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.h @@ -46,7 +46,7 @@ public: protected: // [GPUShader] - GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override; + GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) override; }; #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h index 5195394c1..01e21c4f2 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h @@ -5,6 +5,7 @@ #if GRAPHICS_API_DIRECTX12 #include "GPUDeviceDX12.h" +#include "Engine/Core/Types/DataContainer.h" #include "Engine/Graphics/Shaders/GPUShaderProgram.h" #include "Types.h" #include "../IncludeDirectXHeaders.h" @@ -15,30 +16,27 @@ template class GPUShaderProgramDX12 : public BaseType { -protected: - Array _data; - public: - GPUShaderProgramDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) + GPUShaderProgramDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode) : Header(*header) { BaseType::Init(initializer); - _data.Set(cacheBytes, cacheSize); + Bytecode.Copy(bytecode); } public: + BytesContainer Bytecode; DxShaderHeader Header; public: // [BaseType] void* GetBufferHandle() const override { - return (void*)_data.Get(); + return (void*)Bytecode.Get(); } - uint32 GetBufferSize() const override { - return _data.Count(); + return Bytecode.Length(); } }; @@ -47,29 +45,11 @@ public: /// class GPUShaderProgramVSDX12 : public GPUShaderProgramDX12 { -private: - byte _inputLayoutSize; - D3D12_INPUT_ELEMENT_DESC _inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - public: - GPUShaderProgramVSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize, D3D12_INPUT_ELEMENT_DESC* inputLayout, byte inputLayoutSize) - : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) - , _inputLayoutSize(inputLayoutSize) + GPUShaderProgramVSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode, GPUVertexLayout* vertexLayout) + : GPUShaderProgramDX12(initializer, header, bytecode) { - for (byte i = 0; i < inputLayoutSize; i++) - _inputLayout[i] = inputLayout[i]; - } - -public: - // [GPUShaderProgramDX12] - void* GetInputLayout() const override - { - return (void*)_inputLayout; - } - - byte GetInputLayoutSize() const override - { - return _inputLayoutSize; + Layout = vertexLayout; } }; @@ -80,8 +60,8 @@ public: class GPUShaderProgramHSDX12 : public GPUShaderProgramDX12 { public: - GPUShaderProgramHSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize, int32 controlPointsCount) - : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) + GPUShaderProgramHSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode, int32 controlPointsCount) + : GPUShaderProgramDX12(initializer, header, bytecode) { _controlPointsCount = controlPointsCount; } @@ -93,8 +73,8 @@ public: class GPUShaderProgramDSDX12 : public GPUShaderProgramDX12 { public: - GPUShaderProgramDSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) + GPUShaderProgramDSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode) + : GPUShaderProgramDX12(initializer, header, bytecode) { } }; @@ -107,8 +87,8 @@ public: class GPUShaderProgramGSDX12 : public GPUShaderProgramDX12 { public: - GPUShaderProgramGSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) + GPUShaderProgramGSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode) + : GPUShaderProgramDX12(initializer, header, bytecode) { } }; @@ -120,8 +100,8 @@ public: class GPUShaderProgramPSDX12 : public GPUShaderProgramDX12 { public: - GPUShaderProgramPSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) + GPUShaderProgramPSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode) + : GPUShaderProgramDX12(initializer, header, bytecode) { } }; @@ -137,8 +117,8 @@ private: ID3D12PipelineState* _state; public: - GPUShaderProgramCSDX12(GPUDeviceDX12* device, const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) + GPUShaderProgramCSDX12(GPUDeviceDX12* device, const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode) + : GPUShaderProgramDX12(initializer, header, bytecode) , _device(device) , _state(nullptr) { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h b/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h index e1a4e1de1..2e3a6fdf4 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h @@ -17,8 +17,6 @@ struct DxShaderHeader /// The UAV dimensions per-slot. /// byte UaDimensions[4]; - - // .. rest is just a actual data array }; #endif diff --git a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h index fc185a587..d8a5fb7a8 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h @@ -116,7 +116,7 @@ public: { } - void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr) override + void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr, GPUVertexLayout* vertexLayout = nullptr) override { } diff --git a/Source/Engine/GraphicsDevice/Null/GPUShaderNull.h b/Source/Engine/GraphicsDevice/Null/GPUShaderNull.h index 77912ad7a..526841d6d 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUShaderNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUShaderNull.h @@ -14,7 +14,7 @@ class GPUShaderNull : public GPUShader protected: // [GPUShader] - GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override + GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) override { return nullptr; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 106ffadf8..ebf95f7cc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -10,6 +10,7 @@ #include "GPUBufferVulkan.h" #include "GPUShaderVulkan.h" #include "GPUSamplerVulkan.h" +#include "GPUVertexLayoutVulkan.h" #include "GPUPipelineStateVulkan.h" #include "Engine/Profiler/RenderStats.h" #include "GPUShaderProgramVulkan.h" @@ -650,8 +651,14 @@ void GPUContextVulkan::OnDrawCall() } // Bind any missing vertex buffers to null if required by the current state - const auto vertexInputState = pipelineState->GetVertexInputState(); - const int32 missingVBs = vertexInputState->vertexBindingDescriptionCount - _vbCount; + GPUVertexLayoutVulkan* vertexLayout = _vertexLayout ? _vertexLayout : pipelineState->VertexShaderLayout; +#if GPU_ENABLE_ASSERTION_LOW_LAYERS + if (!vertexLayout && pipelineState && !pipelineState->VertexShaderLayout && (pipelineState->UsedStagesMask & (1 << (int32)DescriptorSet::Vertex)) != 0 && !_vertexLayout && _vbCount) + { + LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); + } +#endif + const int32 missingVBs = vertexLayout ? (int32)vertexLayout->CreateInfo.vertexBindingDescriptionCount - _vbCount : 0; if (missingVBs > 0) { VkBuffer buffers[GPU_MAX_VB_BINDED]; @@ -676,7 +683,7 @@ void GPUContextVulkan::OnDrawCall() { _psDirtyFlag = false; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - const auto pipeline = pipelineState->GetState(_renderPass); + const auto pipeline = pipelineState->GetState(_renderPass, _vertexLayout); vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); RENDER_STAT_PS_STATE_CHANGE(); } @@ -715,6 +722,7 @@ void GPUContextVulkan::FrameBegin() _stencilRef = 0; _renderPass = nullptr; _currentState = nullptr; + _vertexLayout = nullptr; _rtDepth = nullptr; Platform::MemoryClear(_rtHandles, sizeof(_rtHandles)); Platform::MemoryClear(_cbHandles, sizeof(_cbHandles)); @@ -1023,9 +1031,10 @@ void GPUContextVulkan::BindUA(int32 slot, GPUResourceView* view) } } -void GPUContextVulkan::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets) +void GPUContextVulkan::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets, GPUVertexLayout* vertexLayout) { _vbCount = vertexBuffers.Length(); + _vertexLayout = (GPUVertexLayoutVulkan*)(vertexLayout ? vertexLayout : GPUVertexLayout::Get(vertexBuffers)); if (vertexBuffers.Length() == 0) return; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h index 07ffa330b..b90cd1571 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h @@ -14,6 +14,7 @@ class CmdBufferManagerVulkan; class ResourceOwnerVulkan; class GPUTextureViewVulkan; class GPUBufferVulkan; +class GPUVertexLayoutVulkan; class GPUPipelineStateVulkan; class ComputePipelineStateVulkan; class GPUConstantBufferVulkan; @@ -82,6 +83,7 @@ private: RenderPassVulkan* _renderPass; GPUPipelineStateVulkan* _currentState; + GPUVertexLayoutVulkan* _vertexLayout; GPUTextureViewVulkan* _rtDepth; GPUTextureViewVulkan* _rtHandles[GPU_MAX_RT_BINDED]; DescriptorOwnerResourceVulkan* _cbHandles[GPU_MAX_CB_BINDED]; @@ -168,7 +170,7 @@ public: void BindCB(int32 slot, GPUConstantBuffer* cb) override; void BindSR(int32 slot, GPUResourceView* view) override; void BindUA(int32 slot, GPUResourceView* view) override; - void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr) override; + void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr, GPUVertexLayout* vertexLayout = nullptr) override; void BindIB(GPUBuffer* indexBuffer) override; void BindSampler(int32 slot, GPUSampler* sampler) override; void UpdateCB(GPUConstantBuffer* cb, const void* data) override; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 319dd653f..283b48e67 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -922,10 +922,10 @@ GPUTextureVulkan* HelperResourcesVulkan::GetDummyTexture(SpirvShaderResourceType GPUBufferVulkan* HelperResourcesVulkan::GetDummyBuffer(PixelFormat format) { - if (_dummyBuffers.IsEmpty()) + if (!_dummyBuffers) { - _dummyBuffers.Resize((int32)PixelFormat::MAX); - Platform::MemoryClear((void*)_dummyBuffers.Get(), (int32)PixelFormat::MAX * sizeof(void*)); + _dummyBuffers = (GPUBufferVulkan**)Allocator::Allocate((int32)PixelFormat::MAX * sizeof(void*)); + Platform::MemoryClear(_dummyBuffers, (int32)PixelFormat::MAX * sizeof(void*)); } auto& dummyBuffer = _dummyBuffers[(int32)format]; if (!dummyBuffer) @@ -941,7 +941,7 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyVertexBuffer() if (!_dummyVB) { _dummyVB = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyVertexBuffer")); - _dummyVB->Init(GPUBufferDescription::Vertex(sizeof(Color32), 1, &Color32::Transparent)); + _dummyVB->Init(GPUBufferDescription::Vertex(nullptr, sizeof(Color32), 1, &Color32::Transparent)); } return _dummyVB; } @@ -950,9 +950,16 @@ void HelperResourcesVulkan::Dispose() { SAFE_DELETE_GPU_RESOURCES(_dummyTextures); SAFE_DELETE_GPU_RESOURCE(_dummyVB); - for (GPUBuffer* buffer : _dummyBuffers) - Delete(buffer); - _dummyBuffers.Clear(); + if (_dummyBuffers) + { + for (int32 i = 0; i < (int32)PixelFormat::MAX; i++) + { + if (GPUBufferVulkan* buffer = _dummyBuffers[i]) + Delete(buffer); + } + Allocator::Free(_dummyBuffers); + _dummyBuffers = nullptr; + } for (int32 i = 0; i < ARRAY_COUNT(_staticSamplers); i++) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index e5e9c7de8..8b64eeb9c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -308,7 +308,7 @@ class HelperResourcesVulkan private: GPUDeviceVulkan* _device; GPUTextureVulkan* _dummyTextures[6]; - Array _dummyBuffers; + GPUBufferVulkan** _dummyBuffers = nullptr; GPUBufferVulkan* _dummyVB; VkSampler _staticSamplers[GPU_STATIC_SAMPLERS_COUNT]; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 17fbd6a36..2b50ef626 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -3,10 +3,12 @@ #if GRAPHICS_API_VULKAN #include "GPUPipelineStateVulkan.h" +#include "GPUVertexLayoutVulkan.h" #include "RenderToolsVulkan.h" #include "DescriptorSetVulkan.h" #include "GPUShaderProgramVulkan.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/Pair.h" #include "Engine/Profiler/ProfilerCPU.h" static VkStencilOp ToVulkanStencilOp(const StencilOperation value) @@ -169,7 +171,6 @@ ComputePipelineStateVulkan::~ComputePipelineStateVulkan() GPUPipelineStateVulkan::GPUPipelineStateVulkan(GPUDeviceVulkan* device) : GPUResourceVulkan(device, StringView::Empty) - , _pipelines(16) , _layout(nullptr) { } @@ -201,25 +202,30 @@ PipelineLayoutVulkan* GPUPipelineStateVulkan::GetLayout() return _layout; } -VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass) +VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVertexLayoutVulkan* vertexLayout) { ASSERT(renderPass); + if (!vertexLayout) + vertexLayout = VertexShaderLayout; // Try reuse cached version VkPipeline pipeline = VK_NULL_HANDLE; - if (_pipelines.TryGet(renderPass, pipeline)) + const Pair key(renderPass, vertexLayout); + if (_pipelines.TryGet(key, pipeline)) { #if BUILD_DEBUG // Verify - RenderPassVulkan* refKey = nullptr; + Pair refKey(nullptr, nullptr); _pipelines.KeyOf(pipeline, &refKey); - ASSERT(refKey == renderPass); + ASSERT(refKey == key); #endif return pipeline; } - PROFILE_CPU_NAMED("Create Pipeline"); + // Bind vertex input + _desc.pVertexInputState = vertexLayout ? &vertexLayout->CreateInfo : nullptr; + // Update description to match the pipeline _descColorBlend.attachmentCount = renderPass->Layout.RTsCount; _descMultisample.rasterizationSamples = (VkSampleCountFlagBits)renderPass->Layout.MSAA; @@ -245,7 +251,7 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass) } // Cache it - _pipelines.Add(renderPass, pipeline); + _pipelines.Add(key, pipeline); return pipeline; } @@ -278,12 +284,9 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) { ASSERT(!IsValid()); - // Create description + // Reset description RenderToolsVulkan::ZeroStruct(_desc, VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO); - // Vertex Input - _desc.pVertexInputState = (VkPipelineVertexInputStateCreateInfo*)desc.VS->GetInputLayout(); - // Stages UsedStagesMask = 0; HasDescriptorsPerStageMask = 0; @@ -318,6 +321,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _desc.pStages = _shaderStages; // Input Assembly + VertexShaderLayout = desc.VS ? (GPUVertexLayoutVulkan*)desc.VS->Layout : nullptr; RenderToolsVulkan::ZeroStruct(_descInputAssembly, VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO);; switch (desc.PrimitiveTopology) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h index 9e21adbb5..600cb1841 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h @@ -9,6 +9,7 @@ #if GRAPHICS_API_VULKAN +class GPUVertexLayoutVulkan; class PipelineLayoutVulkan; class ComputePipelineStateVulkan @@ -89,7 +90,7 @@ public: class GPUPipelineStateVulkan : public GPUResourceVulkan { private: - Dictionary _pipelines; + Dictionary, VkPipeline> _pipelines; VkGraphicsPipelineCreateInfo _desc; VkPipelineShaderStageCreateInfo _shaderStages[ShaderStage_Count - 1]; VkPipelineInputAssemblyStateCreateInfo _descInputAssembly; @@ -140,18 +141,13 @@ public: /// const SpirvShaderDescriptorInfo* DescriptorInfoPerStage[DescriptorSet::GraphicsStagesCount]; - const VkPipelineVertexInputStateCreateInfo* GetVertexInputState() const - { - return _desc.pVertexInputState; - } - DescriptorSetWriteContainerVulkan DSWriteContainer; DescriptorSetWriterVulkan DSWriter[DescriptorSet::GraphicsStagesCount]; const DescriptorSetLayoutVulkan* DescriptorSetsLayout = nullptr; TypedDescriptorPoolSetVulkan* CurrentTypedDescriptorPoolSet = nullptr; + GPUVertexLayoutVulkan* VertexShaderLayout = nullptr; Array DescriptorSetHandles; - Array DynamicOffsets; public: @@ -184,8 +180,9 @@ public: /// Gets the Vulkan graphics pipeline object for the given rendering state. Uses depth buffer and render targets formats and multi-sample levels to setup a proper PSO. Uses caching. /// /// The render pass. + /// The vertex layout. /// Vulkan graphics pipeline object. - VkPipeline GetState(RenderPassVulkan* renderPass); + VkPipeline GetState(RenderPassVulkan* renderPass, GPUVertexLayoutVulkan* vertexLayout); public: // [GPUPipelineState] diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h index 457b8998a..9f6391265 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h @@ -21,13 +21,6 @@ protected: GPUDeviceVulkan* _device; public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. GPUShaderProgramVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule) : _device(device) , ShaderModule(shaderModule) @@ -36,9 +29,6 @@ public: BaseType::Init(initializer); } - /// - /// Finalizes an instance of the class. - /// ~GPUShaderProgramVulkan() { if (ShaderModule) @@ -64,7 +54,6 @@ public: { return 0; } - void* GetBufferHandle() const override { return (void*)ShaderModule; @@ -77,33 +66,10 @@ public: class GPUShaderProgramVSVulkan : public GPUShaderProgramVulkan { public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. - GPUShaderProgramVSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule) + GPUShaderProgramVSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule, GPUVertexLayout* vertexLayout) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) { - } - -public: - VkPipelineVertexInputStateCreateInfo VertexInputState; - VkVertexInputBindingDescription VertexBindingDescriptions[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - VkVertexInputAttributeDescription VertexAttributeDescriptions[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; - -public: - // [GPUShaderProgramVulkan] - void* GetInputLayout() const override - { - return (void*)&VertexInputState; - } - - byte GetInputLayoutSize() const override - { - return 0; + Layout = vertexLayout; } }; @@ -114,14 +80,6 @@ public: class GPUShaderProgramHSVulkan : public GPUShaderProgramVulkan { public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. - /// The control points used by the hull shader for processing. GPUShaderProgramHSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule, int32 controlPointsCount) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) { @@ -135,13 +93,6 @@ public: class GPUShaderProgramDSVulkan : public GPUShaderProgramVulkan { public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. GPUShaderProgramDSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) { @@ -156,13 +107,6 @@ public: class GPUShaderProgramGSVulkan : public GPUShaderProgramVulkan { public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. GPUShaderProgramGSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) { @@ -176,13 +120,6 @@ public: class GPUShaderProgramPSVulkan : public GPUShaderProgramVulkan { public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. GPUShaderProgramPSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) { @@ -198,29 +135,18 @@ private: ComputePipelineStateVulkan* _pipelineState; public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The program descriptors usage info. - /// The shader module object. GPUShaderProgramCSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) , _pipelineState(nullptr) { } - /// - /// Finalizes an instance of the class. - /// ~GPUShaderProgramCSVulkan(); public: /// /// Gets the state of the pipeline for the compute shader execution or creates a new one if missing. /// - /// The compute pipeline state. ComputePipelineStateVulkan* GetOrCreateState(); }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp index 854500527..d36919b5f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp @@ -98,17 +98,16 @@ void UniformBufferUploaderVulkan::OnReleaseGPU() } } -GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) +GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) { // Extract the SPIR-V shader header from the cache - SpirvShaderHeader* header = (SpirvShaderHeader*)cacheBytes; - cacheBytes += sizeof(SpirvShaderHeader); - cacheSize -= sizeof(SpirvShaderHeader); + SpirvShaderHeader* header = (SpirvShaderHeader*)bytecode.Get(); + bytecode = Span(bytecode.Get() + sizeof(SpirvShaderHeader), bytecode.Length() - sizeof(SpirvShaderHeader)); // Extract the SPIR-V bytecode BytesContainer spirv; ASSERT(header->Type == SpirvShaderHeader::Types::Raw); - spirv.Link(cacheBytes, cacheSize); + spirv.Link(bytecode); // Create shader module from SPIR-V bytecode VkShaderModule shaderModule = VK_NULL_HANDLE; @@ -139,59 +138,8 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons { case ShaderStage::Vertex: { - // Create object - auto vsShader = New(_device, initializer, header->DescriptorInfo, shaderModule); - shader = vsShader; - VkPipelineVertexInputStateCreateInfo& inputState = vsShader->VertexInputState; - VkVertexInputBindingDescription* vertexBindingDescriptions = vsShader->VertexBindingDescriptions; - VkVertexInputAttributeDescription* vertexAttributeDescriptions = vsShader->VertexAttributeDescriptions; - RenderToolsVulkan::ZeroStruct(inputState, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); - for (int32 i = 0; i < VERTEX_SHADER_MAX_INPUT_ELEMENTS; i++) - { - vertexBindingDescriptions[i].binding = i; - vertexBindingDescriptions[i].stride = 0; - vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - } - - // Load Input Layout (it may be empty) - byte inputLayoutSize; - stream.ReadByte(&inputLayoutSize); - ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS); - uint32 attributesCount = inputLayoutSize; - uint32 bindingsCount = 0; - int32 offset = 0; - for (int32 a = 0; a < inputLayoutSize; a++) - { - // Read description - GPUShaderProgramVS::InputElement inputElement; - stream.Read(inputElement); - - const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format); - if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN) - offset = inputElement.AlignedByteOffset; - - auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot]; - vertexBindingDescription.binding = inputElement.InputSlot; - vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size)); - vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; - ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1); - - auto& vertexAttributeDescription = vertexAttributeDescriptions[a]; - vertexAttributeDescription.location = a; - vertexAttributeDescription.binding = inputElement.InputSlot; - vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format); - vertexAttributeDescription.offset = offset; - - bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1); - offset += size; - } - - inputState.vertexBindingDescriptionCount = bindingsCount; - inputState.pVertexBindingDescriptions = vertexBindingDescriptions; - - inputState.vertexAttributeDescriptionCount = attributesCount; - inputState.pVertexAttributeDescriptions = vertexAttributeDescriptions; - + GPUVertexLayout* vertexLayout = ReadVertexLayout(stream); + shader = New(_device, initializer, header->DescriptorInfo, shaderModule, vertexLayout); break; } #if GPU_ALLOW_TESSELLATION_SHADERS diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h index ee70e8125..43017cd94 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h @@ -130,7 +130,7 @@ public: protected: // [GPUShader] - GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override; + GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) override; }; #endif diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 3840ab5ce..83abc6198 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -300,7 +300,7 @@ void StaticModel::FlushVertexColors() vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors")); if (vertexColorsBuffer->GetSize() != size) { - if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count()))) + if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(Color32), vertexColorsData.Count(), nullptr))) break; } GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 205c87a87..9305c7931 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -14,6 +14,7 @@ #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Renderer/RenderList.h" @@ -49,17 +50,22 @@ public: { if (VB) return false; - VB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer,VB")); + VB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer.VB")); IB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer.IB")); - static SpriteParticleVertex vertexBuffer[] = + SpriteParticleVertex vertexBuffer[] = { { -0.5f, -0.5f, 0.0f, 0.0f }, { +0.5f, -0.5f, 1.0f, 0.0f }, { +0.5f, +0.5f, 1.0f, 1.0f }, { -0.5f, +0.5f, 0.0f, 1.0f }, }; - static uint16 indexBuffer[] = { 0, 1, 2, 0, 2, 3, }; - return VB->Init(GPUBufferDescription::Vertex(sizeof(SpriteParticleVertex), VertexCount, vertexBuffer)) || IB->Init(GPUBufferDescription::Index(sizeof(uint16), IndexCount, indexBuffer)); + uint16 indexBuffer[] = { 0, 1, 2, 0, 2, 3, }; + auto layout = GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32_Float }, + { VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R32G32_Float }, + }); + return VB->Init(GPUBufferDescription::Vertex(layout, sizeof(SpriteParticleVertex), VertexCount, vertexBuffer)) || + IB->Init(GPUBufferDescription::Index(sizeof(uint16), IndexCount, indexBuffer)); } void Dispose() diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 942cb9e0b..fd746225a 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -19,6 +19,7 @@ #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Animations/AnimationUtils.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Half.h" @@ -604,6 +605,14 @@ bool Render2DService::Init() GUIShader.Get()->OnReloading.Bind(); #endif + VB.SetLayout(GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32_Float }, + { VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float }, + { VertexElement::Types::Color, 0, 0, 0, PixelFormat::R32G32B32A32_Float }, + { VertexElement::Types::TexCoord1, 0, 0, 0, PixelFormat::R32G32B32A32_Float }, + { VertexElement::Types::TexCoord2, 0, 0, 0, PixelFormat::R32G32B32A32_Float }, + })); + DrawCalls.EnsureCapacity(RENDER2D_INITIAL_DRAW_CALL_CAPACITY); return false; diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index be5992bde..45a5efdb7 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -20,6 +20,7 @@ #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/Async/GPUSyncPoint.h" #include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Renderer/ColorGradingPass.h" @@ -767,7 +768,14 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co for (SceneRendering* scene : renderContext.List->Scenes) surfaceAtlasData.ListenSceneRendering(scene); if (!_vertexBuffer) - _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); + { + auto layout = GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R16G16_Float }, + { VertexElement::Types::TexCoord0, 0, 0, 0, PixelFormat::R16G16_Float }, + { VertexElement::Types::TexCoord1, 0, 0, 0, PixelFormat::R32_UInt }, + }); + _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer"), layout); + } // Ensure that async objects drawing ended _surfaceAtlasData = &surfaceAtlasData; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 69032a2b7..1d9f4301f 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -15,6 +15,7 @@ #include "Engine/Profiler/Profiler.h" #include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Core/Log.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Level/Scene/Lightmap.h" #include "Engine/Level/Actors/PostFxVolume.h" @@ -444,7 +445,7 @@ RenderList::RenderList(const SpawnParams& params) , Blendable(32) , ObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Bufffer")) , TempObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Bufffer")) - , _instanceBuffer(0, sizeof(ShaderObjectDrawInstanceData), TEXT("Instance Buffer")) + , _instanceBuffer(0, sizeof(ShaderObjectDrawInstanceData), TEXT("Instance Buffer"), GPUVertexLayout::Get({ { VertexElement::Types::Attribute0, 3, 0, 1, PixelFormat::R32_UInt } })) { } diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index d9a77bc37..88e382481 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -9,6 +9,7 @@ #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Content/Content.h" #include "Engine/Engine/Engine.h" @@ -627,10 +628,11 @@ void VolumetricFogPass::InitCircleBuffer() } // Create buffers - ASSERT(_vbCircleRasterize == nullptr && _ibCircleRasterize == nullptr); + ASSERT_LOW_LAYER(_vbCircleRasterize == nullptr && _ibCircleRasterize == nullptr); _vbCircleRasterize = GPUDevice::Instance->CreateBuffer(TEXT("VolumetricFog.CircleRasterize.VB")); _ibCircleRasterize = GPUDevice::Instance->CreateBuffer(TEXT("VolumetricFog.CircleRasterize.IB")); - if (_vbCircleRasterize->Init(GPUBufferDescription::Vertex(sizeof(Float2), vertices, vbData)) + auto layout = GPUVertexLayout::Get({{ VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R32G32_Float }}); + if (_vbCircleRasterize->Init(GPUBufferDescription::Vertex(layout, sizeof(Float2), vertices, vbData)) || _ibCircleRasterize->Init(GPUBufferDescription::Index(sizeof(uint16), triangles * 3, ibData))) { LOG(Fatal, "Failed to setup volumetric fog buffers."); diff --git a/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.VS.h b/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.VS.h index 591b5e59f..d0be836cc 100644 --- a/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.VS.h +++ b/Source/Engine/ShadersCompilation/Parser/ShaderFunctionReader.VS.h @@ -84,6 +84,11 @@ namespace ShaderProcessing parser->OnError(TEXT("Cannot parse token.")); return; } + else if (element.AlignedByteOffset > MAX_uint8) + { + parser->OnError(TEXT("Too big vertex element byte offset.")); + return; + } // Input slot class text.ReadToken(&token); @@ -140,9 +145,9 @@ namespace ShaderProcessing void OnParseAfter(IShaderParser* parser, Reader& text) override { // Check if errors in specified input layout - if (_current.InputLayout.Count() > VERTEX_SHADER_MAX_INPUT_ELEMENTS) + if (_current.InputLayout.Count() > GPU_MAX_VS_ELEMENTS) { - parser->OnError(String::Format(TEXT("Vertex Shader \'{0}\' has too many input layout elements specified. Maximum allowed amount is {1}."), String(_current.Name), VERTEX_SHADER_MAX_INPUT_ELEMENTS)); + parser->OnError(String::Format(TEXT("Vertex Shader \'{0}\' has too many input layout elements specified. Maximum allowed amount is {1}."), String(_current.Name), GPU_MAX_VS_ELEMENTS)); return; } diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index bf7456b9e..84c881092 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -10,9 +10,11 @@ #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/Shaders/VertexElement.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryWriteStream.h" +#include "FlaxEngine.Gen.h" namespace IncludedFiles { @@ -65,14 +67,11 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) return true; // [Output] Constant Buffers + output->WriteByte((byte)_constantBuffers.Count()); + for (const ShaderResourceBuffer& cb : _constantBuffers) { - ASSERT(_constantBuffers.Count() == meta->CB.Count()); - output->WriteByte((byte)_constantBuffers.Count()); - for (const ShaderResourceBuffer& cb : _constantBuffers) - { - output->WriteByte(cb.Slot); - output->WriteUint32(cb.Size); - } + output->WriteByte(cb.Slot); + output->WriteUint32(cb.Size); } // Additional Data Start @@ -314,13 +313,21 @@ bool ShaderCompiler::WriteShaderFunctionEnd(ShaderCompilationContext* context, S bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros) { + // [Deprecated in v1.10] auto output = context->Output; auto& metaVS = *(VertexShaderMeta*)&meta; auto& layout = metaVS.InputLayout; +#if FLAXENGINE_VERSION_MAJOR > 2 || (FLAXENGINE_VERSION_MAJOR == 2 && FLAXENGINE_VERSION_MINOR >= 1) + if (layout.HasItems()) + LOG(Warning, "Vertex Shader '{}' (asset '{}') uses explicit vertex layout via 'META_VS_IN_ELEMENT' macros which has been deprecated. Remove this code and migrate to GPUVertexLayout with VertexElement array in code (assigned to vertex buffer).", String(meta.Name), context->Options->TargetName); +#elif FLAXENGINE_VERSION_MAJOR == 1 && FLAXENGINE_VERSION_MINOR >= 11 + if (layout.HasItems()) + LOG(Warning, "Vertex Shader '{}' (asset '{}') uses explicit vertex layout via 'META_VS_IN_ELEMENT' macros which has been deprecated. Remove this code and migrate to GPUVertexLayout with VertexElement array in code (assigned to vertex buffer).", String(meta.Name), context->Options->TargetName); +#endif // Get visible entries (based on `visible` flag switch) int32 layoutSize = 0; - bool layoutVisible[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; + bool layoutVisible[GPU_MAX_VS_ELEMENTS]; for (int32 i = 0; i < layout.Count(); i++) { auto& element = layout[i]; @@ -361,14 +368,93 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader auto& element = layout[a]; if (!layoutVisible[a]) continue; - GPUShaderProgramVS::InputElement data; - data.Type = static_cast(element.Type); - data.Index = element.Index; - data.Format = static_cast(element.Format); - data.InputSlot = element.InputSlot; - data.AlignedByteOffset = element.AlignedByteOffset; - data.InputSlotClass = element.InputSlotClass; - data.InstanceDataStepRate = element.InstanceDataStepRate; + VertexElement data; + switch (element.Type) + { + case VertexShaderMeta::InputType::POSITION: + data.Type = VertexElement::Types::Position; + break; + case VertexShaderMeta::InputType::COLOR: + data.Type = VertexElement::Types::Color; + break; + case VertexShaderMeta::InputType::TEXCOORD: + switch (element.Index) + { + case 0: + data.Type = VertexElement::Types::TexCoord0; + break; + case 1: + data.Type = VertexElement::Types::TexCoord1; + break; + case 2: + data.Type = VertexElement::Types::TexCoord2; + break; + case 3: + data.Type = VertexElement::Types::TexCoord3; + break; + case 4: + data.Type = VertexElement::Types::TexCoord4; + break; + case 5: + data.Type = VertexElement::Types::TexCoord5; + break; + case 6: + data.Type = VertexElement::Types::TexCoord6; + break; + case 7: + data.Type = VertexElement::Types::TexCoord7; + break; + default: + LOG(Error, "Vertex Shader '{}' (asset '{}') uses deprecated texcoord attribute index. Valid range is 0-7.", String(meta.Name), context->Options->TargetName); + data.Type = VertexElement::Types::TexCoord; + break; + } + break; + case VertexShaderMeta::InputType::NORMAL: + data.Type = VertexElement::Types::Normal; + break; + case VertexShaderMeta::InputType::TANGENT: + data.Type = VertexElement::Types::Tangent; + break; + case VertexShaderMeta::InputType::BITANGENT: + LOG(Error, "Vertex Shader '{}' (asset '{}') uses deprecated attribute 'BITANGENT'. Remapping it to `ATTRIBUTE`.", String(meta.Name), context->Options->TargetName); + data.Type = VertexElement::Types::Attribute; + break; + case VertexShaderMeta::InputType::ATTRIBUTE: + switch (element.Index) + { + case 0: + data.Type = VertexElement::Types::Attribute0; + break; + case 1: + data.Type = VertexElement::Types::Attribute1; + break; + case 2: + data.Type = VertexElement::Types::Attribute2; + break; + case 3: + data.Type = VertexElement::Types::Attribute3; + break; + default: + LOG(Error, "Vertex Shader '{}' (asset '{}') uses deprecated attribute index. Valid range is 0-3.", String(meta.Name), context->Options->TargetName); + data.Type = VertexElement::Types::Attribute; + break; + } + break; + case VertexShaderMeta::InputType::BLENDINDICES: + data.Type = VertexElement::Types::BlendIndices; + break; + case VertexShaderMeta::InputType::BLENDWEIGHT: + data.Type = VertexElement::Types::BlendWeight; + break; + default: + data.Type = VertexElement::Types::Unknown; + break; + } + data.Slot = element.InputSlot; + data.Offset = element.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN && element.AlignedByteOffset <= MAX_uint8 ? element.AlignedByteOffset : 0; + data.PerInstance = element.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA; + data.Format = element.Format; output->Write(data); } diff --git a/Source/Engine/Terrain/TerrainManager.cpp b/Source/Engine/Terrain/TerrainManager.cpp index 0c699cf67..116451f7e 100644 --- a/Source/Engine/Terrain/TerrainManager.cpp +++ b/Source/Engine/Terrain/TerrainManager.cpp @@ -13,6 +13,7 @@ #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/AssetReference.h" #include "Engine/Core/Log.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Renderer/DrawCall.h" // Must match structure defined in Terrain.shader @@ -25,7 +26,6 @@ struct TerrainVertex class GeometryData { public: - GPUBuffer* VertexBuffer; GPUBuffer* IndexBuffer; uint32 IndicesCount; @@ -49,11 +49,11 @@ ChunkedArray Pool; Dictionary Lookup; Array TempData; AssetReference DefaultTerrainMaterial; +GPUVertexLayout* TerrainVertexLayout = nullptr; class TerrainManagerService : public EngineService { public: - TerrainManagerService() : EngineService(TEXT("Terrain Manager"), 40) { @@ -132,7 +132,14 @@ bool TerrainManager::GetChunkGeometry(DrawCall& drawCall, int32 chunkSize, int32 vertex++; } } - auto desc = GPUBufferDescription::Vertex(sizeof(TerrainVertex), vertexCount2, TempData.Get()); + if (!TerrainVertexLayout) + { + TerrainVertexLayout = GPUVertexLayout::Get({ + { VertexElement::Types::TexCoord0, 0, 0, 0, PixelFormat::R32G32_Float }, + { VertexElement::Types::TexCoord1, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm }, + }); + } + auto desc = GPUBufferDescription::Vertex(TerrainVertexLayout, sizeof(TerrainVertex), vertexCount2, TempData.Get()); if (vb->Init(desc)) { Delete(vb); diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 5ff02cf36..70697adfd 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2242,7 +2242,7 @@ void TerrainPatch::CacheDebugLines() typedef DebugDraw::Vertex Vertex; if (_debugLines->GetElementsCount() != count) { - if (_debugLines->Init(GPUBufferDescription::Vertex(sizeof(Vertex), count))) + if (_debugLines->Init(GPUBufferDescription::Vertex(Vertex::GetLayout(), sizeof(Vertex), count))) return; } Array debugLines; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 05c6cdf73..9edb322f5 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -27,9 +27,9 @@ TextRender::TextRender(const SpawnParams& params) : Actor(params) , _size(32) , _ib(0, sizeof(uint16)) - , _vb0(0, sizeof(VB0ElementType)) - , _vb1(0, sizeof(VB1ElementType)) - , _vb2(0, sizeof(VB2ElementType)) + , _vb0(0, sizeof(VB0ElementType), String::Empty, VB0ElementType::GetLayout()) + , _vb1(0, sizeof(VB1ElementType), String::Empty, VB1ElementType::GetLayout()) + , _vb2(0, sizeof(VB2ElementType), String::Empty, VB2ElementType::GetLayout()) { _color = Color::White; _localBox = BoundingBox(Vector3::Zero); diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index 11da02c41..0b4d3e2f8 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -33,7 +33,7 @@ // Meta macros used by shaders parser #define META_VS(isVisible, minFeatureLevel) -#define META_VS_IN_ELEMENT(type, index, format, slot, offset, slotClass, stepRate, isVisible) +#define META_VS_IN_ELEMENT(type, index, format, slot, offset, slotClass, stepRate, isVisible) // [Deprecated in v1.10] #define META_HS(isVisible, minFeatureLevel) #define META_HS_PATCH(inControlPoints) #define META_DS(isVisible, minFeatureLevel) diff --git a/Source/Shaders/DebugDraw.shader b/Source/Shaders/DebugDraw.shader index d10dbbf06..05e993fbc 100644 --- a/Source/Shaders/DebugDraw.shader +++ b/Source/Shaders/DebugDraw.shader @@ -18,8 +18,6 @@ struct VS2PS Texture2D SceneDepthTexture : register(t0); META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 0, ALIGN, PER_VERTEX, 0, true) VS2PS VS(float3 Position : POSITION, float4 Color : COLOR) { VS2PS output; diff --git a/Source/Shaders/Editor/Grid.shader b/Source/Shaders/Editor/Grid.shader index f8b0b0b81..249e209ef 100644 --- a/Source/Shaders/Editor/Grid.shader +++ b/Source/Shaders/Editor/Grid.shader @@ -42,7 +42,6 @@ struct PixelInput // Vertex shader function for grid rendering META_VS(true, FEATURE_LEVEL_ES2) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) VertexOutput VS_Grid(ModelInput input) { VertexOutput output; diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 0d9532041..360915d89 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -40,9 +40,6 @@ struct AtlasVertexOutput // Vertex shader for Global Surface Atlas rendering (custom vertex buffer to render per-tile) META_VS(true, FEATURE_LEVEL_SM5) -META_VS_IN_ELEMENT(POSITION, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true) AtlasVertexOutput VS_Atlas(AtlasVertexInput input) { AtlasVertexOutput output; diff --git a/Source/Shaders/GUI.shader b/Source/Shaders/GUI.shader index 10e0487fe..e4d60d0c3 100644 --- a/Source/Shaders/GUI.shader +++ b/Source/Shaders/GUI.shader @@ -23,11 +23,6 @@ META_CB_END Texture2D Image : register(t0); META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R32G32B32A32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R32G32B32A32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 2, R32G32B32A32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) VS2PS VS(Render2DVertex input) { VS2PS output; diff --git a/Source/Shaders/Quad.shader b/Source/Shaders/Quad.shader index 0a6c2ba80..607fa239d 100644 --- a/Source/Shaders/Quad.shader +++ b/Source/Shaders/Quad.shader @@ -16,30 +16,22 @@ META_CB_END // Vertex Shader for screen space quad rendering META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) Quad_VS2PS VS(float2 Position : POSITION0, float2 TexCoord : TEXCOORD0) { Quad_VS2PS output; - output.Position = float4(Position, 0, 1); output.TexCoord = TexCoord; - return output; } // Vertex Shader function for postFx materials rendering META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) MaterialVertexOutput VS_PostFx(float2 Position : POSITION0, float2 TexCoord : TEXCOORD0) { MaterialVertexOutput output; - output.Position = float4(Position, 0, 1); output.WorldPosition = output.Position.xyz; output.TexCoord = TexCoord; - return output; } diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader index d04a2eb92..1c4f1cb58 100644 --- a/Source/Shaders/VolumetricFog.shader +++ b/Source/Shaders/VolumetricFog.shader @@ -112,7 +112,6 @@ float3 GetVolumeUV(float3 worldPosition, float4x4 worldToClip) // Vertex shader that writes to a range of slices of a volume texture META_VS(true, FEATURE_LEVEL_SM5) META_FLAG(VertexToGeometryShader) -META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) Quad_VS2GS VS_WriteToSlice(float2 TexCoord : TEXCOORD0, uint LayerIndex : SV_InstanceID) { Quad_VS2GS output; From 59b475537994251c052ac951443fd6305c18b2ed Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Dec 2024 20:07:42 +0100 Subject: [PATCH 086/215] Move mesh code to shared base class and reordanize files --- .../Graphics/{ => Materials}/MaterialInfo.cs | 0 Source/Engine/Graphics/Models/Mesh.cpp | 238 +---------------- Source/Engine/Graphics/{ => Models}/Mesh.cs | 90 +------ Source/Engine/Graphics/Models/Mesh.h | 133 +--------- Source/Engine/Graphics/Models/MeshBase.cpp | 240 ++++++++++++++++++ Source/Engine/Graphics/Models/MeshBase.cs | 96 +++++++ Source/Engine/Graphics/Models/MeshBase.h | 148 ++++++++++- Source/Engine/Graphics/{ => Models}/Model.cs | 0 Source/Engine/Graphics/Models/SkinnedMesh.cpp | 101 +------- .../Graphics/{ => Models}/SkinnedMesh.cs | 14 +- Source/Engine/Graphics/Models/SkinnedMesh.h | 55 ---- Source/Engine/Graphics/RenderTools.cpp | 17 -- .../Graphics/{ => Textures}/TextureBase.cs | 0 13 files changed, 507 insertions(+), 625 deletions(-) rename Source/Engine/Graphics/{ => Materials}/MaterialInfo.cs (100%) rename Source/Engine/Graphics/{ => Models}/Mesh.cs (87%) create mode 100644 Source/Engine/Graphics/Models/MeshBase.cpp create mode 100644 Source/Engine/Graphics/Models/MeshBase.cs rename Source/Engine/Graphics/{ => Models}/Model.cs (100%) rename Source/Engine/Graphics/{ => Models}/SkinnedMesh.cs (97%) rename Source/Engine/Graphics/{ => Textures}/TextureBase.cs (100%) diff --git a/Source/Engine/Graphics/MaterialInfo.cs b/Source/Engine/Graphics/Materials/MaterialInfo.cs similarity index 100% rename from Source/Engine/Graphics/MaterialInfo.cs rename to Source/Engine/Graphics/Materials/MaterialInfo.cs diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index b0ab1356b..f948ab51d 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -6,7 +6,6 @@ #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Core/Log.h" -#include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" @@ -132,27 +131,9 @@ namespace const auto colors = colorsObj ? MCore::Array::GetAddress(colorsObj) : nullptr; return UpdateMesh(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } - - template - bool UpdateTriangles(Mesh* mesh, int32 triangleCount, const MArray* trianglesObj) - { - const auto model = mesh->GetModel(); - ASSERT(model && model->IsVirtual() && trianglesObj); - - // Get buffer data - ASSERT(MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount); - auto ib = MCore::Array::GetAddress(trianglesObj); - - return mesh->UpdateTriangles(triangleCount, ib); - } #endif } -bool Mesh::HasVertexColors() const -{ - return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated(); -} - bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices) { auto model = (Model*)_model; @@ -188,31 +169,6 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* ve return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } -bool Mesh::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices) -{ - // Cache data - uint32 indicesCount = triangleCount * 3; - uint32 ibStride = use16BitIndices ? sizeof(uint16) : sizeof(uint32); - - // Create index buffer - GPUBuffer* indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty); - if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib))) - { - Delete(indexBuffer); - return true; - } - - // TODO: update collision proxy - - // Initialize - SAFE_DELETE_GPU_RESOURCE(_indexBuffer); - _indexBuffer = indexBuffer; - _triangles = triangleCount; - _use16BitIndexBuffer = use16BitIndices; - - return false; -} - void Mesh::Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs) { _model = model; @@ -231,15 +187,6 @@ void Mesh::Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotInd _indexBuffer = nullptr; } -Mesh::~Mesh() -{ - // Release buffers - SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]); - SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]); - SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]); - SAFE_DELETE_GPU_RESOURCE(_indexBuffer); -} - bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer) { // Cache data @@ -292,9 +239,9 @@ bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* _triangles = triangles; _vertices = vertices; _use16BitIndexBuffer = use16BitIndexBuffer; - _cachedVertexBuffer[0].Clear(); - _cachedVertexBuffer[1].Clear(); - _cachedVertexBuffer[2].Clear(); + _cachedVertexBuffers[0].Clear(); + _cachedVertexBuffers[1].Clear(); + _cachedVertexBuffers[2].Clear(); return false; @@ -308,108 +255,6 @@ ERROR_LOAD_END: return true; } -void Mesh::Unload() -{ - SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]); - SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]); - SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]); - SAFE_DELETE_GPU_RESOURCE(_indexBuffer); - _triangles = 0; - _vertices = 0; - _use16BitIndexBuffer = false; - _cachedIndexBuffer.Resize(0); - _cachedVertexBuffer[0].Clear(); - _cachedVertexBuffer[1].Clear(); - _cachedVertexBuffer[2].Clear(); -} - -bool Mesh::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const -{ - // Get bounding box of the mesh bounds transformed by the instance world matrix - Vector3 corners[8]; - _box.GetCorners(corners); - Vector3 tmp; - Vector3::Transform(corners[0], world, tmp); - Vector3 min = tmp; - Vector3 max = tmp; - for (int32 i = 1; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - const BoundingBox transformedBox(min, max); - - // Test ray on box -#if USE_PRECISE_MESH_INTERSECTS - if (transformedBox.Intersects(ray, distance)) - { - // Use exact test on raw geometry - return _collisionProxy.Intersects(ray, world, distance, normal); - } - distance = 0; - normal = Vector3::Up; - return false; -#else - return transformedBox.Intersects(ray, distance, normal); -#endif -} - -bool Mesh::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const -{ - // Get bounding box of the mesh bounds transformed by the instance world matrix - Vector3 corners[8]; - _box.GetCorners(corners); - Vector3 tmp; - transform.LocalToWorld(corners[0], tmp); - Vector3 min = tmp; - Vector3 max = tmp; - for (int32 i = 1; i < 8; i++) - { - transform.LocalToWorld(corners[i], tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - const BoundingBox transformedBox(min, max); - - // Test ray on box -#if USE_PRECISE_MESH_INTERSECTS - if (transformedBox.Intersects(ray, distance)) - { - // Use exact test on raw geometry - return _collisionProxy.Intersects(ray, transform, distance, normal); - } - distance = 0; - normal = Vector3::Up; - return false; -#else - return transformedBox.Intersects(ray, distance, normal); -#endif -} - -void Mesh::GetDrawCallGeometry(DrawCall& drawCall) const -{ - drawCall.Geometry.IndexBuffer = _indexBuffer; - drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; - drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1]; - drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2]; - drawCall.Geometry.VertexBuffersOffsets[0] = 0; - drawCall.Geometry.VertexBuffersOffsets[1] = 0; - drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Draw.StartIndex = 0; - drawCall.Draw.IndicesCount = _triangles * 3; -} - -void Mesh::Render(GPUContext* context) const -{ - if (!IsInitialized()) - return; - - context->BindVB(ToSpan((GPUBuffer**)_vertexBuffers, 3)); - context->BindIB(_indexBuffer); - context->DrawIndexed(_triangles * 3); -} - void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder) const { if (!material || !material->IsSurface() || !IsInitialized()) @@ -579,51 +424,9 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder); } -bool Mesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const -{ - GPUBuffer* buffer = nullptr; - switch (type) - { - case MeshBufferType::Index: - buffer = _indexBuffer; - break; - case MeshBufferType::Vertex0: - buffer = _vertexBuffers[0]; - break; - case MeshBufferType::Vertex1: - buffer = _vertexBuffers[1]; - break; - case MeshBufferType::Vertex2: - buffer = _vertexBuffers[2]; - break; - } - return buffer && buffer->DownloadData(result); -} - -Task* Mesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const -{ - GPUBuffer* buffer = nullptr; - switch (type) - { - case MeshBufferType::Index: - buffer = _indexBuffer; - break; - case MeshBufferType::Vertex0: - buffer = _vertexBuffers[0]; - break; - case MeshBufferType::Vertex1: - buffer = _vertexBuffers[1]; - break; - case MeshBufferType::Vertex2: - buffer = _vertexBuffers[2]; - break; - } - return buffer ? buffer->DownloadDataAsync(result) : nullptr; -} - bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { - if (_cachedVertexBuffer[0].IsEmpty()) + if (_cachedVertexBuffers[0].IsEmpty()) { PROFILE_CPU(); auto model = GetModel(); @@ -679,10 +482,10 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c // Cache mesh data _cachedIndexBufferCount = indicesCount; _cachedIndexBuffer.Set(ib, indicesCount * ibStride); - _cachedVertexBuffer[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType)); - _cachedVertexBuffer[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType)); + _cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType)); + _cachedVertexBuffers[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType)); if (hasColors) - _cachedVertexBuffer[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType)); + _cachedVertexBuffers[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType)); break; } } @@ -694,16 +497,16 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c count = _cachedIndexBufferCount; break; case MeshBufferType::Vertex0: - result.Link(_cachedVertexBuffer[0]); - count = _cachedVertexBuffer[0].Count() / sizeof(VB0ElementType); + result.Link(_cachedVertexBuffers[0]); + count = _cachedVertexBuffers[0].Count() / sizeof(VB0ElementType); break; case MeshBufferType::Vertex1: - result.Link(_cachedVertexBuffer[1]); - count = _cachedVertexBuffer[1].Count() / sizeof(VB1ElementType); + result.Link(_cachedVertexBuffers[1]); + count = _cachedVertexBuffers[1].Count() / sizeof(VB1ElementType); break; case MeshBufferType::Vertex2: - result.Link(_cachedVertexBuffer[2]); - count = _cachedVertexBuffer[2].Count() / sizeof(VB2ElementType); + result.Link(_cachedVertexBuffers[2]); + count = _cachedVertexBuffers[2].Count() / sizeof(VB2ElementType); break; default: return true; @@ -711,11 +514,6 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c return false; } -ScriptingObject* Mesh::GetParentModel() -{ - return _model; -} - #if !COMPILE_WITHOUT_CSHARP bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) @@ -728,16 +526,6 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } -bool Mesh::UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj) -{ - return ::UpdateTriangles(this, triangleCount, trianglesObj); -} - -bool Mesh::UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj) -{ - return ::UpdateTriangles(this, triangleCount, trianglesObj); -} - enum class InternalBufferType { VB0 = 0, diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Models/Mesh.cs similarity index 87% rename from Source/Engine/Graphics/Mesh.cs rename to Source/Engine/Graphics/Models/Mesh.cs index 42604c872..8870c14f6 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Models/Mesh.cs @@ -101,16 +101,6 @@ namespace FlaxEngine /// public Model ParentModel => (Model)Internal_GetParentModel(__unmanagedPtr); - /// - /// Gets the material slot used by this mesh during rendering. - /// - public MaterialSlot MaterialSlot => ParentModel.MaterialSlots[MaterialSlotIndex]; - - /// - /// Gets a format of the mesh index buffer. - /// - public PixelFormat IndexBufferFormat => Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt; - /// /// Updates the model mesh vertex and index buffer data. /// Can be used only for virtual assets (see and ). @@ -435,82 +425,6 @@ namespace FlaxEngine UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); } - /// - /// Updates the model mesh index buffer data. - /// Can be used only for virtual assets (see and ). - /// Mesh data will be cached and uploaded to the GPU with a delay. - /// - /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. - public void UpdateTriangles(int[] triangles) - { - if (!ParentModel.IsVirtual) - throw new InvalidOperationException("Only virtual models can be updated at runtime."); - if (triangles == null) - throw new ArgumentNullException(nameof(triangles)); - if (triangles.Length == 0 || triangles.Length % 3 != 0) - throw new ArgumentOutOfRangeException(nameof(triangles)); - - if (Internal_UpdateTrianglesUInt(__unmanagedPtr, triangles.Length / 3, triangles)) - throw new Exception("Failed to update mesh data."); - } - - /// - /// Updates the model mesh index buffer data. - /// Can be used only for virtual assets (see and ). - /// Mesh data will be cached and uploaded to the GPU with a delay. - /// - /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. - public void UpdateTriangles(List triangles) - { - if (!ParentModel.IsVirtual) - throw new InvalidOperationException("Only virtual models can be updated at runtime."); - if (triangles == null) - throw new ArgumentNullException(nameof(triangles)); - if (triangles.Count == 0 || triangles.Count % 3 != 0) - throw new ArgumentOutOfRangeException(nameof(triangles)); - - if (Internal_UpdateTrianglesUInt(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles))) - throw new Exception("Failed to update mesh data."); - } - - /// - /// Updates the model mesh index buffer data. - /// Can be used only for virtual assets (see and ). - /// Mesh data will be cached and uploaded to the GPU with a delay. - /// - /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. - public void UpdateTriangles(ushort[] triangles) - { - if (!ParentModel.IsVirtual) - throw new InvalidOperationException("Only virtual models can be updated at runtime."); - if (triangles == null) - throw new ArgumentNullException(nameof(triangles)); - if (triangles.Length == 0 || triangles.Length % 3 != 0) - throw new ArgumentOutOfRangeException(nameof(triangles)); - - if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Length / 3, triangles)) - throw new Exception("Failed to update mesh data."); - } - - /// - /// Updates the model mesh index buffer data. - /// Can be used only for virtual assets (see and ). - /// Mesh data will be cached and uploaded to the GPU with a delay. - /// - /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. - public void UpdateTriangles(List triangles) - { - if (!ParentModel.IsVirtual) - throw new InvalidOperationException("Only virtual models can be updated at runtime."); - if (triangles == null) - throw new ArgumentNullException(nameof(triangles)); - if (triangles.Count == 0 || triangles.Count % 3 != 0) - throw new ArgumentOutOfRangeException(nameof(triangles)); - - if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles))) - throw new Exception("Failed to update mesh data."); - } - internal enum InternalBufferType { VB0 = 0, @@ -609,7 +523,7 @@ namespace FlaxEngine /// /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). /// - /// If mesh index buffer format (see ) is then it's faster to call . + /// If mesh index buffer format (see ) is then it's faster to call . /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. public uint[] DownloadIndexBuffer(bool forceGpu = false) @@ -623,7 +537,7 @@ namespace FlaxEngine /// /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). /// - /// If mesh index buffer format (see ) is then data won't be downloaded. + /// If mesh index buffer format (see ) is then data won't be downloaded. /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. public ushort[] DownloadIndexBufferUShort(bool forceGpu = false) diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 813836b85..425bfbec7 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -6,9 +6,6 @@ #include "ModelInstanceEntry.h" #include "Config.h" #include "Types.h" -#if USE_PRECISE_MESH_INTERSECTS -#include "CollisionProxy.h" -#endif class Lightmap; @@ -18,16 +15,9 @@ class Lightmap; API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase); + protected: bool _hasLightmapUVs; - GPUBuffer* _vertexBuffers[3] = {}; - GPUBuffer* _indexBuffer = nullptr; -#if USE_PRECISE_MESH_INTERSECTS - CollisionProxy _collisionProxy; -#endif - mutable Array _cachedVertexBuffer[3]; - mutable Array _cachedIndexBuffer; - mutable int32 _cachedIndexBufferCount; public: Mesh(const Mesh& other) @@ -38,11 +28,6 @@ public: #endif } - /// - /// Finalizes an instance of the class. - /// - ~Mesh(); - public: /// /// Gets the model owning this mesh. @@ -52,37 +37,6 @@ public: return (Model*)_model; } - /// - /// Gets the index buffer. - /// - FORCE_INLINE GPUBuffer* GetIndexBuffer() const - { - return _indexBuffer; - } - - /// - /// Gets the vertex buffer. - /// - /// The index. - /// The buffer. - FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const - { - return _vertexBuffers[index]; - } - - /// - /// Determines whether this mesh is initialized (has vertex and index buffers initialized). - /// - FORCE_INLINE bool IsInitialized() const - { - return _vertexBuffers[0] != nullptr; - } - - /// - /// Determines whether this mesh has a vertex colors buffer. - /// - API_PROPERTY() bool HasVertexColors() const; - /// /// Determines whether this mesh contains valid lightmap texture coordinates data. /// @@ -91,16 +45,6 @@ public: return _hasLightmapUVs; } -#if USE_PRECISE_MESH_INTERSECTS - /// - /// Gets the collision proxy used by the mesh. - /// - FORCE_INLINE const CollisionProxy& GetCollisionProxy() const - { - return _collisionProxy; - } -#endif - public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). @@ -179,38 +123,6 @@ public: /// True if failed, otherwise false. bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr); -public: - /// - /// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load). - /// - /// The amount of triangles in the index buffer. - /// The index buffer. - /// True if failed, otherwise false. - FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint32* ib) - { - return UpdateTriangles(triangleCount, ib, false); - } - - /// - /// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load). - /// - /// The amount of triangles in the index buffer. - /// The index buffer. - /// True if failed, otherwise false. - FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint16* ib) - { - return UpdateTriangles(triangleCount, ib, true); - } - - /// - /// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load). - /// - /// The amount of triangles in the index buffer. - /// The index buffer. - /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. - /// True if failed, otherwise false. - bool UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices); - public: /// /// Initializes instance of the class. @@ -237,45 +149,7 @@ public: /// True if cannot load data, otherwise false. bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer); - /// - /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. - /// - void Unload(); - public: - /// - /// Determines if there is an intersection between the mesh and a ray in given world - /// - /// The ray to test - /// World to transform box - /// When the method completes and returns true, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const; - - /// - /// Determines if there is an intersection between the mesh and a ray in given world - /// - /// The ray to test - /// The instance transformation. - /// When the method completes and returns true, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const; - -public: - /// - /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. - /// - /// The draw call. - void GetDrawCallGeometry(DrawCall& drawCall) const; - - /// - /// Draws the mesh. Binds vertex and index buffers and invokes the draw call. - /// - /// The GPU context. - void Render(GPUContext* context) const; - /// /// Draws the mesh. /// @@ -307,18 +181,13 @@ public: public: // [MeshBase] - bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override; - Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override; bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: // Internal bindings - API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj); API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj); - API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj); - API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj); API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI); #endif }; diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp new file mode 100644 index 000000000..c95b58acf --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -0,0 +1,240 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "MeshBase.h" +#include "Engine/Core/Log.h" +#include "Engine/Content/Assets/ModelBase.h" +#include "Engine/Core/Math/Transform.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Scripting/ManagedCLR/MCore.h" + +namespace +{ +#if !COMPILE_WITHOUT_CSHARP + template + bool UpdateTriangles(MeshBase* mesh, int32 triangleCount, const MArray* trianglesObj) + { + const auto model = mesh->GetModelBase(); + ASSERT(model && model->IsVirtual() && trianglesObj); + + // Get buffer data + ASSERT(MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount); + auto ib = MCore::Array::GetAddress(trianglesObj); + + return mesh->UpdateTriangles(triangleCount, ib); + } +#endif +} + +MeshBase::~MeshBase() +{ + SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]); + SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]); + SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]); + SAFE_DELETE_GPU_RESOURCE(_indexBuffer); +} + +bool MeshBase::HasVertexColors() const +{ + return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated(); +} + +void MeshBase::SetMaterialSlotIndex(int32 value) +{ + if (value < 0 || value >= _model->MaterialSlots.Count()) + { + LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count()); + return; + } + + _materialSlotIndex = value; +} + +void MeshBase::SetBounds(const BoundingBox& box) +{ + _box = box; + BoundingSphere::FromBox(box, _sphere); +} + +void MeshBase::Unload() +{ + SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]); + SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]); + SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]); + SAFE_DELETE_GPU_RESOURCE(_indexBuffer); + _triangles = 0; + _vertices = 0; + _use16BitIndexBuffer = false; + _cachedIndexBuffer.Resize(0); + _cachedVertexBuffers[0].Clear(); + _cachedVertexBuffers[1].Clear(); + _cachedVertexBuffers[2].Clear(); +} + +bool MeshBase::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices) +{ + uint32 indicesCount = triangleCount * 3; + uint32 ibStride = use16BitIndices ? sizeof(uint16) : sizeof(uint32); + if (!_indexBuffer) + _indexBuffer = GPUDevice::Instance->CreateBuffer(TEXT("DynamicMesh.IB")); + if (_indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib))) + { + _triangles = 0; + return true; + } + + // TODO: update collision proxy + + _triangles = triangleCount; + _use16BitIndexBuffer = use16BitIndices; + return false; +} + +bool MeshBase::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const +{ + // Get bounding box of the mesh bounds transformed by the instance world matrix + Vector3 corners[8]; + _box.GetCorners(corners); + Vector3 tmp; + Vector3::Transform(corners[0], world, tmp); + Vector3 min = tmp; + Vector3 max = tmp; + for (int32 i = 1; i < 8; i++) + { + Vector3::Transform(corners[i], world, tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + const BoundingBox transformedBox(min, max); + + // Test ray on box +#if USE_PRECISE_MESH_INTERSECTS + if (transformedBox.Intersects(ray, distance)) + { + // Use exact test on raw geometry + return _collisionProxy.Intersects(ray, world, distance, normal); + } + distance = 0; + normal = Vector3::Up; + return false; +#else + return transformedBox.Intersects(ray, distance, normal); +#endif +} + +bool MeshBase::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const +{ + // Get bounding box of the mesh bounds transformed by the instance world matrix + Vector3 corners[8]; + _box.GetCorners(corners); + Vector3 tmp; + transform.LocalToWorld(corners[0], tmp); + Vector3 min = tmp; + Vector3 max = tmp; + for (int32 i = 1; i < 8; i++) + { + transform.LocalToWorld(corners[i], tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + const BoundingBox transformedBox(min, max); + + // Test ray on box +#if USE_PRECISE_MESH_INTERSECTS + if (transformedBox.Intersects(ray, distance)) + { + // Use exact test on raw geometry + return _collisionProxy.Intersects(ray, transform, distance, normal); + } + distance = 0; + normal = Vector3::Up; + return false; +#else + return transformedBox.Intersects(ray, distance, normal); +#endif +} + +bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const +{ + GPUBuffer* buffer = nullptr; + switch (type) + { + case MeshBufferType::Index: + buffer = _indexBuffer; + break; + case MeshBufferType::Vertex0: + buffer = _vertexBuffers[0]; + break; + case MeshBufferType::Vertex1: + buffer = _vertexBuffers[1]; + break; + case MeshBufferType::Vertex2: + buffer = _vertexBuffers[2]; + break; + } + return buffer && buffer->DownloadData(result); +} + +Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const +{ + GPUBuffer* buffer = nullptr; + switch (type) + { + case MeshBufferType::Index: + buffer = _indexBuffer; + break; + case MeshBufferType::Vertex0: + buffer = _vertexBuffers[0]; + break; + case MeshBufferType::Vertex1: + buffer = _vertexBuffers[1]; + break; + case MeshBufferType::Vertex2: + buffer = _vertexBuffers[2]; + break; + } + return buffer ? buffer->DownloadDataAsync(result) : nullptr; +} + +void MeshBase::GetDrawCallGeometry(DrawCall& drawCall) const +{ + drawCall.Geometry.IndexBuffer = _indexBuffer; + drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; + drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1]; + drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2]; + drawCall.Geometry.VertexBuffersOffsets[0] = 0; + drawCall.Geometry.VertexBuffersOffsets[1] = 0; + drawCall.Geometry.VertexBuffersOffsets[2] = 0; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; +} + +void MeshBase::Render(GPUContext* context) const +{ + if (!IsInitialized()) + return; + context->BindVB(ToSpan(_vertexBuffers, 3)); + context->BindIB(_indexBuffer); + context->DrawIndexed(_triangles * 3); +} + +ScriptingObject* MeshBase::GetParentModel() const +{ + return _model; +} + +#if !COMPILE_WITHOUT_CSHARP + +bool MeshBase::UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj) +{ + return ::UpdateTriangles(this, triangleCount, trianglesObj); +} + +bool MeshBase::UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj) +{ + return ::UpdateTriangles(this, triangleCount, trianglesObj); +} + +#endif diff --git a/Source/Engine/Graphics/Models/MeshBase.cs b/Source/Engine/Graphics/Models/MeshBase.cs new file mode 100644 index 000000000..5156c20a6 --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshBase.cs @@ -0,0 +1,96 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; + +namespace FlaxEngine +{ + partial class MeshBase + { + /// + /// Gets the material slot used by this mesh during rendering. + /// + public MaterialSlot MaterialSlot => ModelBase.MaterialSlots[MaterialSlotIndex]; + + /// + /// Gets a format of the mesh index buffer. + /// + public PixelFormat IndexBufferFormat => Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt; + + /// + /// Updates the model mesh index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. + public void UpdateTriangles(int[] triangles) + { + if (!ModelBase.IsVirtual) + throw new InvalidOperationException("Only virtual models can be updated at runtime."); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Length == 0 || triangles.Length % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + + if (Internal_UpdateTrianglesUInt(__unmanagedPtr, triangles.Length / 3, triangles)) + throw new Exception("Failed to update mesh data."); + } + + /// + /// Updates the model mesh index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null. + public void UpdateTriangles(List triangles) + { + if (!ModelBase.IsVirtual) + throw new InvalidOperationException("Only virtual models can be updated at runtime."); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Count == 0 || triangles.Count % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + + if (Internal_UpdateTrianglesUInt(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles))) + throw new Exception("Failed to update mesh data."); + } + + /// + /// Updates the model mesh index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. + public void UpdateTriangles(ushort[] triangles) + { + if (!ModelBase.IsVirtual) + throw new InvalidOperationException("Only virtual models can be updated at runtime."); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Length == 0 || triangles.Length % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + + if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Length / 3, triangles)) + throw new Exception("Failed to update mesh data."); + } + + /// + /// Updates the model mesh index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null. + public void UpdateTriangles(List triangles) + { + if (!ModelBase.IsVirtual) + throw new InvalidOperationException("Only virtual models can be updated at runtime."); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Count == 0 || triangles.Count % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + + if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles))) + throw new Exception("Failed to update mesh data."); + } + } +} diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 3ada38b34..309fea9e5 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -5,10 +5,15 @@ #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Graphics/Enums.h" #include "Engine/Graphics/Models/Types.h" #include "Engine/Level/Types.h" #include "Engine/Scripting/ScriptingObject.h" +#include "Config.h" +#if USE_PRECISE_MESH_INTERSECTS +#include "CollisionProxy.h" +#endif struct GeometryDrawStateData; struct RenderContext; @@ -21,15 +26,17 @@ class SkinnedMeshDrawData; class BlendShapesInstance; /// -/// Base class for model resources meshes. +/// Base class for mesh objects. /// API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public ScriptingObject { DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase); + protected: ModelBase* _model; BoundingBox _box; BoundingSphere _sphere; + int32 _index; int32 _lodIndex; uint32 _vertices; @@ -37,12 +44,25 @@ protected: int32 _materialSlotIndex; bool _use16BitIndexBuffer; + GPUBuffer* _vertexBuffers[3] = {}; + GPUBuffer* _indexBuffer = nullptr; + + mutable Array _cachedVertexBuffers[3]; + mutable Array _cachedIndexBuffer; + mutable int32 _cachedIndexBufferCount; + +#if USE_PRECISE_MESH_INTERSECTS + CollisionProxy _collisionProxy; +#endif + explicit MeshBase(const SpawnParams& params) : ScriptingObject(params) { } public: + ~MeshBase(); + /// /// Gets the model owning this mesh. /// @@ -107,6 +127,29 @@ public: return _use16BitIndexBuffer; } +#if USE_PRECISE_MESH_INTERSECTS + /// + /// Gets the collision proxy used by the mesh. + /// + FORCE_INLINE const CollisionProxy& GetCollisionProxy() const + { + return _collisionProxy; + } +#endif + + /// + /// Determines whether this mesh is initialized (has vertex and index buffers initialized). + /// + FORCE_INLINE bool IsInitialized() const + { + return _vertexBuffers[0] != nullptr; + } + + /// + /// Determines whether this mesh has a vertex colors buffer. + /// + API_PROPERTY() bool HasVertexColors() const; + /// /// Gets the index of the material slot to use during this mesh rendering. /// @@ -126,14 +169,91 @@ public: /// The bounding box. void SetBounds(const BoundingBox& box); + /// + /// Gets the index buffer. + /// + FORCE_INLINE GPUBuffer* GetIndexBuffer() const + { + return _indexBuffer; + } + + /// + /// Gets the vertex buffer. + /// + /// The bind slot index. + /// The buffer or null if not used. + FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const + { + return _vertexBuffers[index]; + } + public: /// - /// Extract mesh buffer data from GPU. Cannot be called from the main thread. + /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. + /// + void Unload(); + +public: + /// + /// Updates the model mesh index buffer. + /// + /// The amount of triangles in the index buffer. + /// The index buffer. + /// True if failed, otherwise false. + FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint32* ib) + { + return UpdateTriangles(triangleCount, ib, false); + } + + /// + /// Updates the model mesh index buffer. + /// + /// The amount of triangles in the index buffer. + /// The index buffer. + /// True if failed, otherwise false. + FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint16* ib) + { + return UpdateTriangles(triangleCount, ib, true); + } + + /// + /// Updates the model mesh index buffer. + /// + /// The amount of triangles in the index buffer. + /// The index buffer. + /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. + /// True if failed, otherwise false. + bool UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices); + +public: + /// + /// Determines if there is an intersection between the mesh and a ray in given world. + /// + /// The ray to test. + /// The mesh instance transformation. + /// When the method completes and returns true, contains the distance of the intersection (if any valid). + /// When the method completes, contains the intersection surface normal vector (if any valid). + /// True whether the two objects intersected, otherwise false. + bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const; + + /// + /// Determines if there is an intersection between the mesh and a ray in given world + /// + /// The ray to test + /// The mesh instance transformation. + /// When the method completes and returns true, contains the distance of the intersection (if any valid). + /// When the method completes, contains the intersection surface normal vector (if any valid). + /// True whether the two objects intersected, otherwise false. + bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const; + +public: + /// + /// Extracts mesh buffer data from a GPU. Cannot be called from the main thread. /// /// Buffer type /// The result data /// True if failed, otherwise false - virtual bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const = 0; + bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const; /// /// Extracts mesh buffer data from GPU in the async task. @@ -141,7 +261,7 @@ public: /// Buffer type /// The result data /// Created async task used to gather the buffer data. - virtual Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const = 0; + Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const; /// /// Extract mesh buffer data from CPU. Cached internally. @@ -246,4 +366,24 @@ public: float LightmapScale = -1.0f; #endif }; + + /// + /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. + /// + /// The draw call. + void GetDrawCallGeometry(struct DrawCall& drawCall) const; + + /// + /// Draws the mesh. Binds vertex and index buffers and invokes the draw call. + /// + /// The GPU context. + void Render(GPUContext* context) const; + +private: + // Internal bindings + API_FUNCTION(NoProxy) ScriptingObject* GetParentModel() const; +#if !COMPILE_WITHOUT_CSHARP + API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj); + API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj); +#endif }; diff --git a/Source/Engine/Graphics/Model.cs b/Source/Engine/Graphics/Models/Model.cs similarity index 100% rename from Source/Engine/Graphics/Model.cs rename to Source/Engine/Graphics/Models/Model.cs diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 1d9ab1df0..b7e2ddc6a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -105,19 +105,13 @@ void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 m _sphere = sphere; _vertices = 0; _triangles = 0; - _vertexBuffer = nullptr; + _vertexBuffers[0] = nullptr; _indexBuffer = nullptr; _cachedIndexBuffer.Clear(); - _cachedVertexBuffer.Clear(); + _cachedVertexBuffers[0].Clear(); BlendShapes.Clear(); } -SkinnedMesh::~SkinnedMesh() -{ - SAFE_DELETE_GPU_RESOURCE(_vertexBuffer); - SAFE_DELETE_GPU_RESOURCE(_indexBuffer); -} - bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer) { // Cache data @@ -146,7 +140,7 @@ bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const goto ERROR_LOAD_END; // Initialize - _vertexBuffer = vertexBuffer; + _vertexBuffers[0] = vertexBuffer; _indexBuffer = indexBuffer; _triangles = triangles; _vertices = vertices; @@ -161,17 +155,6 @@ ERROR_LOAD_END: return true; } -void SkinnedMesh::Unload() -{ - SAFE_DELETE_GPU_RESOURCE(_vertexBuffer); - SAFE_DELETE_GPU_RESOURCE(_indexBuffer); - _cachedIndexBuffer.Clear(); - _cachedVertexBuffer.Clear(); - _triangles = 0; - _vertices = 0; - _use16BitIndexBuffer = false; -} - bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices) { auto model = (SkinnedModel*)_model; @@ -192,37 +175,6 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0 return failed; } -bool SkinnedMesh::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const -{ - // Transform points - BoundingBox transformedBox; - Vector3::Transform(_box.Minimum, world, transformedBox.Minimum); - Vector3::Transform(_box.Maximum, world, transformedBox.Maximum); - - // Test ray on a transformed box - return transformedBox.Intersects(ray, distance, normal); -} - -bool SkinnedMesh::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const -{ - // Transform points - BoundingBox transformedBox; - transform.LocalToWorld(_box.Minimum, transformedBox.Minimum); - transform.LocalToWorld(_box.Maximum, transformedBox.Maximum); - - // Test ray on a transformed box - return transformedBox.Intersects(ray, distance, normal); -} - -void SkinnedMesh::Render(GPUContext* context) const -{ - ASSERT(IsInitialized()); - - context->BindVB(ToSpan(&_vertexBuffer, 1)); - context->BindIB(_indexBuffer); - context->DrawIndexed(_triangles * 3); -} - void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const { const auto& entry = info.Buffer->At(_materialSlotIndex); @@ -250,7 +202,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; - drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; + drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; if (info.Deformation) info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); drawCall.Draw.StartIndex = 0; @@ -292,7 +244,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; - drawCall.Geometry.VertexBuffers[0] = _vertexBuffer; + drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; if (info.Deformation) info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]); drawCall.Draw.IndicesCount = _triangles * 3; @@ -315,39 +267,9 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, StaticFlags::None, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder); } -bool SkinnedMesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const -{ - GPUBuffer* buffer = nullptr; - switch (type) - { - case MeshBufferType::Index: - buffer = _indexBuffer; - break; - case MeshBufferType::Vertex0: - buffer = _vertexBuffer; - break; - } - return buffer && buffer->DownloadData(result); -} - -Task* SkinnedMesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const -{ - GPUBuffer* buffer = nullptr; - switch (type) - { - case MeshBufferType::Index: - buffer = _indexBuffer; - break; - case MeshBufferType::Vertex0: - buffer = _vertexBuffer; - break; - } - return buffer ? buffer->DownloadDataAsync(result) : nullptr; -} - bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { - if (_cachedVertexBuffer.IsEmpty()) + if (_cachedVertexBuffers[0].IsEmpty()) { PROFILE_CPU(); auto model = GetSkinnedModel(); @@ -409,7 +331,7 @@ bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, i // Cache mesh data _cachedIndexBufferCount = indicesCount; _cachedIndexBuffer.Set(ib, indicesCount * ibStride); - _cachedVertexBuffer.Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType)); + _cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType)); break; } } @@ -421,8 +343,8 @@ bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, i count = _cachedIndexBufferCount; break; case MeshBufferType::Vertex0: - result.Link(_cachedVertexBuffer); - count = _cachedVertexBuffer.Count() / sizeof(VB0SkinnedElementType); + result.Link(_cachedVertexBuffers[0]); + count = _cachedVertexBuffers[0].Count() / sizeof(VB0SkinnedElementType); break; default: return true; @@ -430,11 +352,6 @@ bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, i return false; } -ScriptingObject* SkinnedMesh::GetParentModel() -{ - return _model; -} - #if !COMPILE_WITHOUT_CSHARP template diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/Models/SkinnedMesh.cs similarity index 97% rename from Source/Engine/Graphics/SkinnedMesh.cs rename to Source/Engine/Graphics/Models/SkinnedMesh.cs index 0381622b2..f60305e3c 100644 --- a/Source/Engine/Graphics/SkinnedMesh.cs +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cs @@ -88,16 +88,6 @@ namespace FlaxEngine /// public SkinnedModel ParentSkinnedModel => (SkinnedModel)Internal_GetParentModel(__unmanagedPtr); - /// - /// Gets the material slot used by this mesh during rendering. - /// - public MaterialSlot MaterialSlot => ParentSkinnedModel.MaterialSlots[MaterialSlotIndex]; - - /// - /// Gets a format of the mesh index buffer. - /// - public PixelFormat IndexBufferFormat => Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt; - /// /// Updates the skinned model mesh vertex and index buffer data. /// Can be used only for virtual assets (see and ). @@ -313,7 +303,7 @@ namespace FlaxEngine /// /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). /// - /// If mesh index buffer format (see ) is then it's faster to call . + /// If mesh index buffer format (see ) is then it's faster to call . /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. public uint[] DownloadIndexBuffer(bool forceGpu = false) @@ -327,7 +317,7 @@ namespace FlaxEngine /// /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). /// - /// If mesh index buffer format (see ) is then data won't be downloaded. + /// If mesh index buffer format (see ) is then data won't be downloaded. /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. public ushort[] DownloadIndexBufferUShort(bool forceGpu = false) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index 963511671..b8dd38696 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -3,7 +3,6 @@ #pragma once #include "MeshBase.h" -#include "Types.h" #include "BlendShape.h" /// @@ -12,12 +11,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public MeshBase { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, MeshBase); -protected: - GPUBuffer* _vertexBuffer = nullptr; - GPUBuffer* _indexBuffer = nullptr; - mutable Array _cachedIndexBuffer; - mutable Array _cachedVertexBuffer; - mutable int32 _cachedIndexBufferCount; public: SkinnedMesh(const SkinnedMesh& other) @@ -28,11 +21,6 @@ public: #endif } - /// - /// Finalizes an instance of the class. - /// - ~SkinnedMesh(); - public: /// /// Gets the skinned model owning this mesh. @@ -42,14 +30,6 @@ public: return (SkinnedModel*)_model; } - /// - /// Determines whether this mesh is initialized (has vertex and index buffers initialized). - /// - FORCE_INLINE bool IsInitialized() const - { - return _vertexBuffer != nullptr; - } - /// /// Blend shapes used by this mesh. /// @@ -78,11 +58,6 @@ public: /// True if cannot load data, otherwise false. bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer); - /// - /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. - /// - void Unload(); - public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). @@ -135,33 +110,6 @@ public: bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices); public: - /// - /// Determines if there is an intersection between the mesh and a ray in given world - /// - /// The ray to test - /// World to transform box - /// When the method completes and returns true, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const; - - /// - /// Determines if there is an intersection between the mesh and a ray in given world - /// - /// The ray to test - /// Instance transformation - /// When the method completes and returns true, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const; - -public: - /// - /// Draws the mesh. Binds vertex and index buffers and invokes the draw call. - /// - /// The GPU context. - void Render(GPUContext* context) const; - /// /// Draws the mesh. /// @@ -180,13 +128,10 @@ public: public: // [MeshBase] - bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override; - Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override; bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: // Internal bindings - API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) bool UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj); API_FUNCTION(NoProxy) bool UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj); diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index 75ca25d9a..f2a2c565e 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -640,20 +640,3 @@ int32 MipLevelsCount(int32 width, int32 height, int32 depth) } return result; } - -void MeshBase::SetMaterialSlotIndex(int32 value) -{ - if (value < 0 || value >= _model->MaterialSlots.Count()) - { - LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count()); - return; - } - - _materialSlotIndex = value; -} - -void MeshBase::SetBounds(const BoundingBox& box) -{ - _box = box; - BoundingSphere::FromBox(box, _sphere); -} diff --git a/Source/Engine/Graphics/TextureBase.cs b/Source/Engine/Graphics/Textures/TextureBase.cs similarity index 100% rename from Source/Engine/Graphics/TextureBase.cs rename to Source/Engine/Graphics/Textures/TextureBase.cs From d1ac9cd2bce382ef4e046beb9c36c060b37b7351 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Dec 2024 20:07:53 +0100 Subject: [PATCH 087/215] Minor fixes --- Source/Engine/Core/Collections/Array.h | 20 +++++++++---------- Source/Engine/Graphics/GPUBufferDescription.h | 1 - .../Graphics/Models/MeshDeformation.cpp | 2 +- .../Engine/Graphics/Models/MeshDeformation.h | 4 ++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index ec2d28f49..4b9511671 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -125,8 +125,8 @@ public: /// Initializes a new instance of the class. /// /// The other collection to copy. - template - explicit Array(const Array& other) noexcept + template + explicit Array(const Array& other) noexcept { _capacity = other.Capacity(); _count = other.Count(); @@ -512,8 +512,8 @@ public: /// Adds the other collection to the collection. /// /// The other collection to add. - template - FORCE_INLINE void Add(const Array& other) + template + FORCE_INLINE void Add(const Array& other) { Add(other.Get(), other.Count()); } @@ -629,8 +629,8 @@ public: /// /// The item to check. /// True if item has been found in the collection, otherwise false. - template - bool Contains(const TComparableType& item) const + template + bool Contains(const Other& item) const { const T* data = _allocation.Get(); for (int32 i = 0; i < _count; i++) @@ -914,8 +914,8 @@ public: } public: - template - bool operator==(const Array& other) const + template + bool operator==(const Array& other) const { if (_count == other.Count()) { @@ -931,8 +931,8 @@ public: return false; } - template - bool operator!=(const Array& other) const + template + bool operator!=(const Array& other) const { return !operator==(other); } diff --git a/Source/Engine/Graphics/GPUBufferDescription.h b/Source/Engine/Graphics/GPUBufferDescription.h index 73f459da2..adb7f3cf2 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.h +++ b/Source/Engine/Graphics/GPUBufferDescription.h @@ -281,7 +281,6 @@ public: auto bufferFlags = GPUBufferFlags::Structured | GPUBufferFlags::ShaderResource; if (isUnorderedAccess) bufferFlags |= GPUBufferFlags::UnorderedAccess; - return Buffer(elementCount * elementSize, bufferFlags, PixelFormat::Unknown, nullptr, elementSize); } diff --git a/Source/Engine/Graphics/Models/MeshDeformation.cpp b/Source/Engine/Graphics/Models/MeshDeformation.cpp index 5db0db6a0..2183fd7ea 100644 --- a/Source/Engine/Graphics/Models/MeshDeformation.cpp +++ b/Source/Engine/Graphics/Models/MeshDeformation.cpp @@ -127,7 +127,7 @@ void MeshDeformation::RunDeformers(const MeshBase* mesh, MeshBufferType type, GP } if (!deformation) { - deformation = New(key, type, vertexStride); + deformation = New(key, type, vertexStride, vertexBuffer->GetVertexLayout()); deformation->VertexBuffer.Data.Resize(vertexBuffer->GetSize()); deformation->Bounds = mesh->GetBox(); _deformations.Add(deformation); diff --git a/Source/Engine/Graphics/Models/MeshDeformation.h b/Source/Engine/Graphics/Models/MeshDeformation.h index 15dae488b..dbc7db87c 100644 --- a/Source/Engine/Graphics/Models/MeshDeformation.h +++ b/Source/Engine/Graphics/Models/MeshDeformation.h @@ -20,10 +20,10 @@ struct MeshDeformationData BoundingBox Bounds; DynamicVertexBuffer VertexBuffer; - MeshDeformationData(uint64 key, MeshBufferType type, uint32 stride) + MeshDeformationData(uint64 key, MeshBufferType type, uint32 stride, GPUVertexLayout* layout) : Key(key) , Type(type) - , VertexBuffer(0, stride, TEXT("MeshDeformation")) + , VertexBuffer(0, stride, TEXT("MeshDeformation"), layout) { } From 80299c85521ce03f7478aa8f3203e25f5dba65c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 17 Dec 2024 15:58:26 +0100 Subject: [PATCH 088/215] Add stride to GPUVertexLayout --- Source/Engine/Graphics/GPUBuffer.cpp | 15 +++++++++++++++ Source/Engine/Graphics/GPUBufferDescription.h | 18 ++++++++++++++++++ .../Graphics/Shaders/GPUVertexLayout.cpp | 18 +++++++++++++++++- .../Engine/Graphics/Shaders/GPUVertexLayout.h | 14 +++++++++++++- .../DirectX/DX11/GPUDeviceDX11.cpp | 6 +++--- .../DirectX/DX12/GPUDeviceDX12.cpp | 6 +++--- .../GraphicsDevice/Null/GPUVertexLayoutNull.h | 2 +- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 8 ++++---- 8 files changed, 74 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index b0d081d4c..eca708af9 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -6,6 +6,7 @@ #include "GPUBufferDescription.h" #include "PixelFormatExtensions.h" #include "RenderTask.h" +#include "Shaders/GPUVertexLayout.h" #include "Async/Tasks/GPUCopyResourceTask.h" #include "Engine/Core/Utilities.h" #include "Engine/Core/Types/String.h" @@ -75,6 +76,20 @@ GPUBufferDescription GPUBufferDescription::Vertex(GPUVertexLayout* layout, uint3 return desc; } +GPUBufferDescription GPUBufferDescription::Vertex(GPUVertexLayout* layout, uint32 elementsCount, const void* data) +{ + const uint32 stride = layout ? layout->GetStride() : 0; + CHECK_RETURN_DEBUG(stride, GPUBufferDescription()); + return Vertex(layout, stride, elementsCount, data); +} + +GPUBufferDescription GPUBufferDescription::Vertex(GPUVertexLayout* layout, uint32 elementsCount, GPUResourceUsage usage) +{ + const uint32 stride = layout ? layout->GetStride() : 0; + CHECK_RETURN_DEBUG(stride, GPUBufferDescription()); + return Vertex(layout, stride, elementsCount, usage); +} + void GPUBufferDescription::Clear() { Platform::MemoryClear(this, sizeof(GPUBufferDescription)); diff --git a/Source/Engine/Graphics/GPUBufferDescription.h b/Source/Engine/Graphics/GPUBufferDescription.h index adb7f3cf2..4861d626c 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.h +++ b/Source/Engine/Graphics/GPUBufferDescription.h @@ -202,6 +202,24 @@ public: /// The buffer description. static GPUBufferDescription Vertex(GPUVertexLayout* layout, uint32 elementStride, uint32 elementsCount, GPUResourceUsage usage = GPUResourceUsage::Default); + /// + /// Creates vertex buffer description. + /// + /// The vertex buffer layout. + /// The elements count. + /// The data. + /// The buffer description. + static GPUBufferDescription Vertex(GPUVertexLayout* layout, uint32 elementsCount, const void* data); + + /// + /// Creates vertex buffer description. + /// + /// The vertex buffer layout. + /// The elements count. + /// The usage mode. + /// The buffer description. + static GPUBufferDescription Vertex(GPUVertexLayout* layout, uint32 elementsCount, GPUResourceUsage usage = GPUResourceUsage::Default); + /// /// Creates vertex buffer description. /// [Deprecated in v1.10] diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 6a4b8e677..996a177c2 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -5,9 +5,11 @@ #include "Engine/Core/Log.h" #endif #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Math/Math.h" #include "Engine/Core/Types/Span.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/PixelFormatExtensions.h" #if GPU_ENABLE_RESOURCE_NAMING #include "Engine/Scripting/Enums.h" #endif @@ -74,6 +76,20 @@ GPUVertexLayout::GPUVertexLayout() { } +void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_MAX_VS_ELEMENTS]) +{ + _elements = elements; + uint32 strides[GPU_MAX_VB_BINDED] = {}; + for (int32 i = 0; i < elements.Count(); i++) + { + const VertexElement& e = elements[i]; + strides[e.Slot] = Math::Max(strides[e.Slot], offsets[i]); + } + _stride = 0; + for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) + _stride += strides[i]; +} + GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) { // Hash input layout @@ -144,7 +160,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span& vertexBuffers) anyValid = true; int32 start = elements.Count(); elements.Add(layouts.Layouts[slot]->GetElements()); - for (int32 j = start; j < elements.Count() ;j++) + for (int32 j = start; j < elements.Count(); j++) elements.Get()[j].Slot = (byte)slot; } } diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index 8d60573b8..e234b32b1 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -16,11 +16,15 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUVertexLayout : public GPUReso DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUVertexLayout); typedef Array> Elements; -protected: +private: Elements _elements; + uint32 _stride; +protected: GPUVertexLayout(); + void SetElements(const Elements& elements, uint32 offsets[GPU_MAX_VS_ELEMENTS]); + public: /// /// Gets the list of elements used by this layout. @@ -30,6 +34,14 @@ public: return _elements; } + /// + /// Gets the size in bytes of all elements in the layout structure (including their offsets). + /// + API_PROPERTY() FORCE_INLINE uint32 GetStride() const + { + return _stride; + } + /// /// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list. /// diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 4a9e4a6d2..35656606a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -152,11 +152,10 @@ GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& : GPUResourceBase(device, StringView::Empty) , InputElementsCount(elements.Count()) { - _elements = elements; uint32 offsets[GPU_MAX_VB_BINDED] = {}; - for (int32 i = 0; i < _elements.Count(); i++) + for (int32 i = 0; i < elements.Count(); i++) { - const VertexElement& src = _elements.Get()[i]; + const VertexElement& src = elements.Get()[i]; D3D11_INPUT_ELEMENT_DESC& dst = InputElements[i]; uint32& offset = offsets[src.Slot]; if (src.Offset != 0) @@ -169,6 +168,7 @@ GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& dst.InstanceDataStepRate = src.PerInstance ? 1 : 0; offset += PixelFormatExtensions::SizeInBytes(src.Format); } + SetElements(elements, offsets); } GPUDevice* GPUDeviceDX11::Create() diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index eb252be54..ab4a0ab81 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -40,11 +40,10 @@ GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& : GPUResourceDX12(device, StringView::Empty) , InputElementsCount(elements.Count()) { - _elements = elements; uint32 offsets[GPU_MAX_VB_BINDED] = {}; - for (int32 i = 0; i < _elements.Count(); i++) + for (int32 i = 0; i < elements.Count(); i++) { - const VertexElement& src = _elements.Get()[i]; + const VertexElement& src = elements.Get()[i]; D3D12_INPUT_ELEMENT_DESC& dst = InputElements[i]; uint32& offset = offsets[src.Slot]; if (src.Offset != 0) @@ -57,6 +56,7 @@ GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& dst.InstanceDataStepRate = src.PerInstance ? 1 : 0; offset += PixelFormatExtensions::SizeInBytes(src.Format); } + SetElements(elements, offsets); } GPUDevice* GPUDeviceDX12::Create() diff --git a/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h b/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h index c1b7e96a7..f1a5f0abd 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h @@ -15,7 +15,7 @@ public: GPUVertexLayoutNull(const Elements& elements) : GPUVertexLayout() { - _elements = elements; + SetElements(elements, {}); } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 283b48e67..e5f7f2bcc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -452,7 +452,6 @@ uint32 GetHash(const FramebufferVulkan::Key& key) GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements) : GPUResourceVulkan(device, StringView::Empty) { - _elements = elements; uint32 offsets[GPU_MAX_VB_BINDED] = {}; for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) { @@ -462,9 +461,9 @@ GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elem binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; } uint32 bindingsCount = 0; - for (int32 i = 0; i < _elements.Count(); i++) + for (int32 i = 0; i < elements.Count(); i++) { - const VertexElement& src = _elements.Get()[i]; + const VertexElement& src = elements.Get()[i]; uint32& offset = offsets[src.Slot]; if (src.Offset != 0) offset = src.Offset; @@ -485,11 +484,12 @@ GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elem bindingsCount = Math::Max(bindingsCount, (uint32)src.Slot + 1); offset += size; } + SetElements(elements, offsets); RenderToolsVulkan::ZeroStruct(CreateInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); CreateInfo.vertexBindingDescriptionCount = bindingsCount; CreateInfo.pVertexBindingDescriptions = Bindings; - CreateInfo.vertexAttributeDescriptionCount = _elements.Count(); + CreateInfo.vertexAttributeDescriptionCount = elements.Count(); CreateInfo.pVertexAttributeDescriptions = Attributes; } From 5b98603c16a823213de245e58f83b94164dedb80 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Dec 2024 18:38:47 +0100 Subject: [PATCH 089/215] Fix incorrect vertex layout stride calculation --- Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 996a177c2..880dc1f00 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -83,7 +83,8 @@ void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_M for (int32 i = 0; i < elements.Count(); i++) { const VertexElement& e = elements[i]; - strides[e.Slot] = Math::Max(strides[e.Slot], offsets[i]); + ASSERT(e.Slot < GPU_MAX_VB_BINDED); + strides[e.Slot] = Math::Max(strides[e.Slot], offsets[e.Slot]); } _stride = 0; for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) From 8eaa906e0c094cc3022030488ab60cf0fff5dfd2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 18 Dec 2024 18:39:58 +0100 Subject: [PATCH 090/215] Remove some redundant code --- Source/Engine/Audio/AudioClip.cpp | 5 ----- Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp | 4 +--- Source/Engine/Graphics/Textures/TextureBase.cpp | 5 ----- Source/Engine/Render2D/FontAsset.cpp | 7 ------- 4 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 83907ec1f..3760c1428 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -302,11 +302,6 @@ void AudioClip::CancelStreamingTasks() bool AudioClip::init(AssetInitData& initData) { // Validate - if (initData.SerializedVersion != SerializedVersion) - { - LOG(Warning, "Invalid audio clip serialized version."); - return true; - } if (initData.CustomData.Length() != sizeof(AudioHeader)) { LOG(Warning, "Missing audio data."); diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index acc1dc7db..c1c17c26b 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -78,14 +78,12 @@ int32 ShaderAssetBase::GetCacheChunkIndex(ShaderProfile profile) bool ShaderAssetBase::initBase(AssetInitData& initData) { - // Validate version + // Validate if (initData.SerializedVersion != ShaderStorage::Header::Version) { LOG(Warning, "Invalid shader serialized version."); return true; } - - // Validate data if (initData.CustomData.Length() != sizeof(_shaderHeader)) { LOG(Warning, "Invalid shader header."); diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index f396abb79..61b8945bb 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -777,11 +777,6 @@ bool TextureBase::init(AssetInitData& initData) { if (IsVirtual()) return false; - if (initData.SerializedVersion != TexturesSerializedVersion) - { - LOG(Error, "Invalid serialized texture version."); - return true; - } // Get texture header for asset custom data (fast access) TextureHeader textureHeader; diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 6caee6e24..3161c65ef 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -228,13 +228,6 @@ bool FontAsset::init(AssetInitData& initData) { if (IsVirtual()) return false; - - // Validate - if (initData.SerializedVersion != SerializedVersion) - { - LOG(Error, "Invalid serialized font asset version."); - return true; - } if (initData.CustomData.Length() != sizeof(_options)) { LOG(Error, "Missing font asset header."); From 1bf29c042b8f36caa3834e166158d5d42c6c3fb6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 19 Dec 2024 00:20:08 +0100 Subject: [PATCH 091/215] Refactor models and meshes to share more code in a base class --- Source/Engine/Content/Assets/Model.cpp | 331 ++++++---------- Source/Engine/Content/Assets/Model.h | 80 ---- Source/Engine/Content/Assets/ModelBase.cpp | 311 +++++++++++++++ Source/Engine/Content/Assets/ModelBase.h | 84 +++++ Source/Engine/Content/Assets/SkinnedModel.cpp | 355 +++++++----------- Source/Engine/Content/Assets/SkinnedModel.h | 72 ---- .../Engine/Graphics/Models/CollisionProxy.h | 4 +- Source/Engine/Graphics/Models/Mesh.cpp | 122 ++---- Source/Engine/Graphics/Models/Mesh.h | 29 +- Source/Engine/Graphics/Models/MeshBase.cpp | 88 ++++- Source/Engine/Graphics/Models/MeshBase.h | 62 ++- Source/Engine/Graphics/Models/ModelLOD.cpp | 161 -------- Source/Engine/Graphics/Models/ModelLOD.h | 28 +- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 75 +--- Source/Engine/Graphics/Models/SkinnedMesh.h | 15 +- .../Graphics/Models/SkinnedModelLOD.cpp | 200 ---------- .../Engine/Graphics/Models/SkinnedModelLOD.h | 18 - .../Graphics/Shaders/GPUVertexLayout.cpp | 4 +- 18 files changed, 842 insertions(+), 1197 deletions(-) create mode 100644 Source/Engine/Content/Assets/ModelBase.cpp delete mode 100644 Source/Engine/Graphics/Models/ModelLOD.cpp delete mode 100644 Source/Engine/Graphics/Models/SkinnedModelLOD.cpp diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 37e0d1cc8..8ba26658c 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -16,101 +16,16 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Async/GPUTask.h" #include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Threading/Threading.h" #include "Engine/Tools/ModelTool/ModelTool.h" -#include "Engine/Tools/ModelTool/MeshAccelerationStructure.h" -#if GPU_ENABLE_ASYNC_RESOURCES_CREATION -#include "Engine/Threading/ThreadPoolTask.h" -#define STREAM_TASK_BASE ThreadPoolTask -#else -#include "Engine/Threading/MainThreadTask.h" -#define STREAM_TASK_BASE MainThreadTask -#endif - -#define CHECK_INVALID_BUFFER(model, buffer) \ - if (buffer->IsValidFor(model) == false) \ - { \ - buffer->Setup(model); \ - } REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase"); -/// -/// Model LOD streaming task. -/// -class StreamModelLODTask : public STREAM_TASK_BASE -{ -private: - WeakAssetReference _asset; - int32 _lodIndex; - FlaxStorage::LockData _dataLock; - -public: - StreamModelLODTask(Model* model, int32 lodIndex) - : _asset(model) - , _lodIndex(lodIndex) - , _dataLock(model->Storage->Lock()) - { - } - -public: - bool HasReference(Object* resource) const override - { - return _asset == resource; - } - - bool Run() override - { - AssetReference model = _asset.Get(); - if (model == nullptr) - return true; - - // Get data - BytesContainer data; - model->GetLODData(_lodIndex, data); - if (data.IsInvalid()) - { - LOG(Warning, "Missing data chunk"); - return true; - } - MemoryReadStream stream(data.Get(), data.Length()); - - // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering) - - // Load model LOD (initialize vertex and index buffers) - if (model->LODs[_lodIndex].Load(stream)) - { - LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex); - return true; - } - - // Update residency level - model->_loadedLODs++; - model->ResidencyChanged(); - - return false; - } - - void OnEnd() override - { - // Unlink - if (_asset) - { - ASSERT(_asset->_streamingTask == this); - _asset->_streamingTask = nullptr; - _asset = nullptr; - } - _dataLock.Release(); - - // Base - STREAM_TASK_BASE::OnEnd(); - } -}; - class StreamModelSDFTask : public GPUUploadTextureMipTask { private: @@ -211,9 +126,10 @@ FORCE_INLINE void ModelDraw(Model* model, const RenderContext& renderContext, co ASSERT(info.Buffer); if (!model->CanBeRendered()) return; + if (!info.Buffer->IsValidFor(model)) + info.Buffer->Setup(model); const auto frame = Engine::FrameCount; const auto modelFrame = info.DrawState->PrevFrame + 1; - CHECK_INVALID_BUFFER(model, info.Buffer); // Select a proper LOD index (model may be culled) int32 lodIndex; @@ -719,16 +635,14 @@ bool Model::Init(const Span& meshesCountPerLod) SAFE_DELETE_GPU_RESOURCE(SDF.Texture); // Setup LODs - for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - LODs[lodIndex].Dispose(); LODs.Resize(meshesCountPerLod.Length()); + _initialized = true; // Setup meshes for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++) { auto& lod = LODs[lodIndex]; - lod._model = this; - lod._lodIndex = lodIndex; + lod.Link(this, lodIndex); lod.ScreenSize = 1.0f; const int32 meshesCount = meshesCountPerLod[lodIndex]; if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES) @@ -737,7 +651,7 @@ bool Model::Init(const Span& meshesCountPerLod) lod.Meshes.Resize(meshesCount); for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { - lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, 0, BoundingBox::Zero, BoundingSphere::Empty, true); + lod.Meshes[meshIndex].Link(this, lodIndex, meshIndex); } } @@ -787,99 +701,16 @@ void Model::InitAsVirtual() BinaryAsset::InitAsVirtual(); } -void Model::CancelStreaming() -{ - Asset::CancelStreaming(); - CancelStreamingTasks(); -} - -#if USE_EDITOR - -void Model::GetReferences(Array& assets, Array& files) const -{ - // Base - BinaryAsset::GetReferences(assets, files); - - for (int32 i = 0; i < MaterialSlots.Count(); i++) - assets.Add(MaterialSlots[i].Material.GetID()); -} - -#endif - int32 Model::GetMaxResidency() const { return LODs.Count(); } -int32 Model::GetCurrentResidency() const -{ - return _loadedLODs; -} - int32 Model::GetAllocatedResidency() const { return LODs.Count(); } -bool Model::CanBeUpdated() const -{ - // Check if is ready and has no streaming tasks running - return IsInitialized() && _streamingTask == nullptr; -} - -Task* Model::UpdateAllocation(int32 residency) -{ - // Models are not using dynamic allocation feature - return nullptr; -} - -Task* Model::CreateStreamingTask(int32 residency) -{ - ScopeLock lock(Locker); - - ASSERT(IsInitialized() && Math::IsInRange(residency, 0, LODs.Count()) && _streamingTask == nullptr); - Task* result = nullptr; - const int32 lodCount = residency - GetCurrentResidency(); - - // Switch if go up or down with residency - if (lodCount > 0) - { - // Allow only to change LODs count by 1 - ASSERT(Math::Abs(lodCount) == 1); - - int32 lodIndex = HighestResidentLODIndex() - 1; - - // Request LOD data - result = (Task*)RequestLODDataAsync(lodIndex); - - // Add upload data task - _streamingTask = New(this, lodIndex); - if (result) - result->ContinueWith(_streamingTask); - else - result = _streamingTask; - } - else - { - // Do the quick data release - for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++) - LODs[i].Unload(); - _loadedLODs = residency; - ResidencyChanged(); - } - - return result; -} - -void Model::CancelStreamingTasks() -{ - if (_streamingTask) - { - _streamingTask->Cancel(); - ASSERT_LOW_LAYER(_streamingTask == nullptr); - } -} - Asset::LoadResult Model::load() { // Get header chunk @@ -922,6 +753,7 @@ Asset::LoadResult Model::load() if (lods == 0 || lods > MODEL_MAX_LODS) return LoadResult::InvalidData; LODs.Resize(lods); + _initialized = true; // For each LOD for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) @@ -946,6 +778,9 @@ Asset::LoadResult Model::load() // For each mesh for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { + Mesh& mesh = lod.Meshes[meshIndex]; + mesh.Link(this, lodIndex, meshIndex); + // Material Slot index int32 materialSlotIndex; stream->ReadInt32(&materialSlotIndex); @@ -954,19 +789,18 @@ Asset::LoadResult Model::load() LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); return LoadResult::InvalidData; } + mesh.SetMaterialSlotIndex(materialSlotIndex); - // Box + // Bounds BoundingBox box; stream->ReadBoundingBox(&box); - - // Sphere BoundingSphere sphere; stream->ReadBoundingSphere(&sphere); + mesh.SetBounds(box, sphere); // Has Lightmap UVs bool hasLightmapUVs = stream->ReadBool(); - - lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, materialSlotIndex, box, sphere, hasLightmapUVs); + mesh.LightmapUVsIndex = hasLightmapUVs ? 1 : -1; } } @@ -1039,33 +873,11 @@ Asset::LoadResult Model::load() void Model::unload(bool isReloading) { - // End streaming (if still active) - if (_streamingTask != nullptr) - { - // Cancel streaming task - _streamingTask->Cancel(); - _streamingTask = nullptr; - } + ModelBase::unload(isReloading); // Cleanup SAFE_DELETE_GPU_RESOURCE(SDF.Texture); - MaterialSlots.Resize(0); - for (int32 i = 0; i < LODs.Count(); i++) - LODs[i].Dispose(); LODs.Clear(); - _loadedLODs = 0; -} - -bool Model::init(AssetInitData& initData) -{ - // Validate - if (initData.SerializedVersion != SerializedVersion) - { - LOG(Error, "Invalid serialized model version."); - return true; - } - - return false; } AssetChunksFlag Model::getChunksToPreload() const @@ -1074,32 +886,101 @@ AssetChunksFlag Model::getChunksToPreload() const return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15); } -void ModelBase::SetupMaterialSlots(int32 slotsCount) +bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh) { - CHECK(slotsCount >= 0 && slotsCount < 4096); - if (!IsVirtual() && WaitForLoaded()) - return; - - ScopeLock lock(Locker); - - const int32 prevCount = MaterialSlots.Count(); - MaterialSlots.Resize(slotsCount, false); - - // Initialize slot names - for (int32 i = prevCount; i < slotsCount; i++) - MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1); -} - -MaterialSlot* ModelBase::GetSlot(const StringView& name) -{ - MaterialSlot* result = nullptr; - for (auto& slot : MaterialSlots) + bool result = false; + Real closest = MAX_Real; + Vector3 closestNormal = Vector3::Up; + for (int32 i = 0; i < Meshes.Count(); i++) { - if (slot.Name == name) + Real dst; + Vector3 nrm; + if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest) { - result = &slot; - break; + result = true; + *mesh = &Meshes[i]; + closest = dst; + closestNormal = nrm; } } + distance = closest; + normal = closestNormal; return result; } + +bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh) +{ + bool result = false; + Real closest = MAX_Real; + Vector3 closestNormal = Vector3::Up; + for (int32 i = 0; i < Meshes.Count(); i++) + { + Real dst; + Vector3 nrm; + if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest) + { + result = true; + *mesh = &Meshes[i]; + closest = dst; + closestNormal = nrm; + } + } + distance = closest; + normal = closestNormal; + return result; +} + +BoundingBox ModelLOD::GetBox(const Matrix& world) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + { + const auto& mesh = Meshes[meshIndex]; + mesh.GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + Vector3::Transform(corners[i], world, tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + +BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + { + const auto& mesh = Meshes[meshIndex]; + BoundingBox box = mesh.GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + transform.LocalToWorld(corners[i], tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + +BoundingBox ModelLOD::GetBox() const +{ + Vector3 min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + { + Meshes[meshIndex].GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + min = Vector3::Min(min, corners[i]); + max = Vector3::Max(max, corners[i]); + } + } + return BoundingBox(min, max); +} diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index dd52ba34a..3f78f973d 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -6,7 +6,6 @@ #include "Engine/Graphics/Models/ModelLOD.h" class Mesh; -class StreamModelLODTask; /// /// Model asset that contains model object made of meshes which can rendered on the GPU. @@ -15,10 +14,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase { DECLARE_BINARY_ASSET_HEADER(Model, 25); friend Mesh; - friend StreamModelLODTask; -private: - int32 _loadedLODs = 0; - StreamModelLODTask* _streamingTask = nullptr; public: /// @@ -38,40 +33,6 @@ public: ~Model(); public: - /// - /// Gets a value indicating whether this instance is initialized. - /// - FORCE_INLINE bool IsInitialized() const - { - return LODs.HasItems(); - } - - /// - /// Gets the amount of loaded model LODs. - /// - API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const - { - return _loadedLODs; - } - - /// - /// Clamps the index of the LOD to be valid for rendering (only loaded LODs). - /// - /// The index. - /// The resident LOD index. - FORCE_INLINE int32 ClampLODIndex(int32 index) const - { - return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1); - } - - /// - /// Gets index of the highest resident LOD (may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality) - /// - FORCE_INLINE int32 HighestResidentLODIndex() const - { - return LODs.Count() - _loadedLODs; - } - /// /// Determines whether any LOD has been initialized. /// @@ -80,37 +41,6 @@ public: return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized(); } - /// - /// Determines whether this model can be rendered. - /// - FORCE_INLINE bool CanBeRendered() const - { - return _loadedLODs > 0; - } - -public: - /// - /// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here). - /// - /// Index of the LOD. - /// Task that will gather chunk data or null if already here. - ContentLoadTask* RequestLODDataAsync(int32 lodIndex) - { - const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - return RequestChunkDataAsync(chunkIndex); - } - - /// - /// Gets the model LOD data (links bytes). - /// - /// Index of the LOD. - /// The data (may be missing if failed to get it). - void GetLODData(int32 lodIndex, BytesContainer& data) const - { - const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - GetChunkData(chunkIndex, data); - } - public: /// /// Determines if there is an intersection between the Model and a Ray in given world using given instance. @@ -250,24 +180,14 @@ public: int32 GetLODsCount() const override; void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; - void CancelStreaming() override; -#if USE_EDITOR - void GetReferences(Array& assets, Array& files) const override; -#endif // [StreamableResource] int32 GetMaxResidency() const override; - int32 GetCurrentResidency() const override; int32 GetAllocatedResidency() const override; - bool CanBeUpdated() const override; - Task* UpdateAllocation(int32 residency) override; - Task* CreateStreamingTask(int32 residency) override; - void CancelStreamingTasks() override; protected: // [ModelBase] LoadResult load() override; void unload(bool isReloading) override; - bool init(AssetInitData& initData) override; AssetChunksFlag getChunksToPreload() const override; }; diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp new file mode 100644 index 000000000..6e7fc3457 --- /dev/null +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -0,0 +1,311 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "ModelBase.h" +#include "Engine/Core/Log.h" +#include "Engine/Content/WeakAssetReference.h" +#include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Graphics/Config.h" +#if GPU_ENABLE_ASYNC_RESOURCES_CREATION +#include "Engine/Threading/ThreadPoolTask.h" +#define STREAM_TASK_BASE ThreadPoolTask +#else +#include "Engine/Threading/MainThreadTask.h" +#define STREAM_TASK_BASE MainThreadTask +#endif +#include "SkinnedModel.h" // TODO: remove this +#include "Model.h" // TODO: remove this + +/// +/// Model LOD streaming task. +/// +class StreamModelLODTask : public STREAM_TASK_BASE +{ +private: + WeakAssetReference _model; + int32 _lodIndex; + FlaxStorage::LockData _dataLock; + +public: + StreamModelLODTask(ModelBase* model, int32 lodIndex) + : _model(model) + , _lodIndex(lodIndex) + , _dataLock(model->Storage->Lock()) + { + } + + bool HasReference(Object* resource) const override + { + return _model == resource; + } + + bool Run() override + { + AssetReference model = _model.Get(); + if (model == nullptr) + return true; + + // Get data + BytesContainer data; + model->GetLODData(_lodIndex, data); + if (data.IsInvalid()) + { + LOG(Warning, "Missing data chunk"); + return true; + } + MemoryReadStream stream(data.Get(), data.Length()); + + // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering) + + // Load model LOD (initialize vertex and index buffers) + // TODO: reformat model data storage to be the same for both assets (only custom per-mesh type data like BlendShapes) + Array meshes; + model->GetMeshes(meshes, _lodIndex); + if (model->Is()) + { + byte version = stream.ReadByte(); + for (int32 i = 0; i < meshes.Count(); i++) + { + auto& mesh = (SkinnedMesh&)*meshes[i]; + + // #MODEL_DATA_FORMAT_USAGE + uint32 vertices; + stream.ReadUint32(&vertices); + uint32 triangles; + stream.ReadUint32(&triangles); + uint16 blendShapesCount; + stream.ReadUint16(&blendShapesCount); + if (blendShapesCount != mesh.BlendShapes.Count()) + { + LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Incorrect blend shapes amount: {} (expected: {})", i, _lodIndex, model->ToString(), blendShapesCount, mesh.BlendShapes.Count()); + return true; + } + for (auto& blendShape : mesh.BlendShapes) + { + blendShape.UseNormals = stream.ReadBool(); + stream.ReadUint32(&blendShape.MinVertexIndex); + stream.ReadUint32(&blendShape.MaxVertexIndex); + uint32 blendShapeVertices; + stream.ReadUint32(&blendShapeVertices); + blendShape.Vertices.Resize(blendShapeVertices); + stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex)); + } + const uint32 indicesCount = triangles * 3; + const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + if (vertices == 0 || triangles == 0) + return true; + const auto vb0 = stream.Move(vertices); + const auto ib = stream.Move(indicesCount * ibStride); + + // Setup GPU resources + if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer)) + { + LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles); + return true; + } + } + } + else + { + for (int32 i = 0; i < meshes.Count(); i++) + { + auto& mesh = (Mesh&)*meshes[i]; + + // #MODEL_DATA_FORMAT_USAGE + uint32 vertices; + stream.ReadUint32(&vertices); + uint32 triangles; + stream.ReadUint32(&triangles); + uint32 indicesCount = triangles * 3; + bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + if (vertices == 0 || triangles == 0) + return true; + auto vb0 = stream.Move(vertices); + auto vb1 = stream.Move(vertices); + bool hasColors = stream.ReadBool(); + VB2ElementType18* vb2 = nullptr; + if (hasColors) + { + vb2 = stream.Move(vertices); + } + auto ib = stream.Move(indicesCount * ibStride); + + // Setup GPU resources + if (mesh.Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer)) + { + LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles); + return true; + } + } + } + + // Update residency level + model->_loadedLODs++; + model->ResidencyChanged(); + + return false; + } + + void OnEnd() override + { + // Unlink + ModelBase* model = _model.Get(); + if (model) + { + ASSERT(model->_streamingTask == this); + model->_streamingTask = nullptr; + _model = nullptr; + } + _dataLock.Release(); + + STREAM_TASK_BASE::OnEnd(); + } +}; + +void ModelBase::SetupMaterialSlots(int32 slotsCount) +{ + CHECK(slotsCount >= 0 && slotsCount < 4096); + if (!IsVirtual() && WaitForLoaded()) + return; + + ScopeLock lock(Locker); + + const int32 prevCount = MaterialSlots.Count(); + MaterialSlots.Resize(slotsCount, false); + + // Initialize slot names + for (int32 i = prevCount; i < slotsCount; i++) + MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1); +} + +MaterialSlot* ModelBase::GetSlot(const StringView& name) +{ + MaterialSlot* result = nullptr; + for (auto& slot : MaterialSlots) + { + if (slot.Name == name) + { + result = &slot; + break; + } + } + return result; +} + +ContentLoadTask* ModelBase::RequestLODDataAsync(int32 lodIndex) +{ + const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); + return RequestChunkDataAsync(chunkIndex); +} + +void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const +{ + const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); + GetChunkData(chunkIndex, data); +} + +void ModelBase::CancelStreaming() +{ + BinaryAsset::CancelStreaming(); + + CancelStreamingTasks(); +} + +#if USE_EDITOR + +void ModelBase::GetReferences(Array& assets, Array& files) const +{ + BinaryAsset::GetReferences(assets, files); + + for (auto& slot : MaterialSlots) + assets.Add(slot.Material.GetID()); +} + +#endif + +int32 ModelBase::GetCurrentResidency() const +{ + return _loadedLODs; +} + +bool ModelBase::CanBeUpdated() const +{ + // Check if is ready and has no streaming tasks running + return IsInitialized() && _streamingTask == nullptr; +} + +Task* ModelBase::UpdateAllocation(int32 residency) +{ + // Models are not using dynamic allocation feature + return nullptr; +} + +Task* ModelBase::CreateStreamingTask(int32 residency) +{ + ScopeLock lock(Locker); + + const int32 lodMax = GetLODsCount(); + ASSERT(IsInitialized() && Math::IsInRange(residency, 0, lodMax) && _streamingTask == nullptr); + Task* result = nullptr; + const int32 lodCount = residency - GetCurrentResidency(); + + // Switch if go up or down with residency + if (lodCount > 0) + { + // Allow only to change LODs count by 1 + ASSERT(Math::Abs(lodCount) == 1); + + int32 lodIndex = HighestResidentLODIndex() - 1; + + // Request LOD data + result = (Task*)RequestLODDataAsync(lodIndex); + + // Add upload data task + _streamingTask = New(this, lodIndex); + if (result) + result->ContinueWith(_streamingTask); + else + result = _streamingTask; + } + else + { + // Do the quick data release + Array meshes; + for (int32 i = HighestResidentLODIndex(); i < lodMax - residency; i++) + { + GetMeshes(meshes, i); + for (MeshBase* mesh : meshes) + mesh->Release(); + } + _loadedLODs = residency; + ResidencyChanged(); + } + + return result; +} + +void ModelBase::CancelStreamingTasks() +{ + if (_streamingTask) + { + _streamingTask->Cancel(); + ASSERT_LOW_LAYER(_streamingTask == nullptr); + } +} + +void ModelBase::unload(bool isReloading) +{ + // End streaming (if still active) + if (_streamingTask != nullptr) + { + // Cancel streaming task + _streamingTask->Cancel(); + _streamingTask = nullptr; + } + + // Cleanup + MaterialSlots.Resize(0); + _initialized = false; + _loadedLODs = 0; +} diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index a0d0091a5..ab683c116 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -4,6 +4,7 @@ #include "../BinaryAsset.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Graphics/Models/Config.h" #include "Engine/Graphics/Models/MaterialSlot.h" #include "Engine/Streaming/StreamableResource.h" @@ -26,6 +27,14 @@ struct RenderContextBatch; API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset, public StreamableResource { DECLARE_ASSET_HEADER(ModelBase); + friend MeshBase; + friend class StreamModelLODTask; + +protected: + bool _initialized = false; + int32 _loadedLODs = 0; + StreamModelLODTask* _streamingTask = nullptr; + public: /// /// The Sign Distant Field (SDF) data for the model. @@ -106,6 +115,48 @@ public: return MaterialSlots.Count(); } + /// + /// Gets a value indicating whether this instance is initialized. + /// + FORCE_INLINE bool IsInitialized() const + { + return _initialized; + } + + /// + /// Clamps the index of the LOD to be valid for rendering (only loaded LODs). + /// + /// The index. + /// The resident LOD index. + FORCE_INLINE int32 ClampLODIndex(int32 index) const + { + return Math::Clamp(index, HighestResidentLODIndex(), GetLODsCount() - 1); + } + + /// + /// Gets index of the highest resident LOD (it may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality) + /// + FORCE_INLINE int32 HighestResidentLODIndex() const + { + return GetLODsCount() - _loadedLODs; + } + + /// + /// Gets the amount of loaded model LODs. + /// + API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const + { + return _loadedLODs; + } + + /// + /// Determines whether this model can be rendered. + /// + FORCE_INLINE bool CanBeRendered() const + { + return _loadedLODs > 0; + } + /// /// Resizes the material slots collection. Updates meshes that were using removed slots. /// @@ -127,4 +178,37 @@ public: /// Gets the meshes for a particular LOD index. /// virtual void GetMeshes(Array& meshes, int32 lodIndex = 0) = 0; + +public: + /// + /// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here). + /// + /// Index of the LOD. + /// Task that will gather chunk data or null if already here. + ContentLoadTask* RequestLODDataAsync(int32 lodIndex); + + /// + /// Gets the model LOD data (links bytes). + /// + /// Index of the LOD. + /// The data (it may be missing if failed to get it). + void GetLODData(int32 lodIndex, BytesContainer& data) const; + +public: + // [BinaryAsset] + void CancelStreaming() override; +#if USE_EDITOR + void GetReferences(Array& assets, Array& files) const override; +#endif + + // [StreamableResource] + int32 GetCurrentResidency() const override; + bool CanBeUpdated() const override; + Task* UpdateAllocation(int32 residency) override; + Task* CreateStreamingTask(int32 residency) override; + void CancelStreamingTasks() override; + +protected: + // [BinaryAsset] + void unload(bool isReloading) override; }; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 80131b03b..0b9c69275 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -13,101 +13,13 @@ #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Graphics/Models/Config.h" #include "Engine/Content/Content.h" -#include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" -#define CHECK_INVALID_BUFFER(model, buffer) \ - if (buffer->IsValidFor(model) == false) \ - { \ - buffer->Setup(model); \ - } - -/// -/// Skinned model data streaming task -/// -class StreamSkinnedModelLODTask : public ThreadPoolTask -{ -private: - WeakAssetReference _asset; - int32 _lodIndex; - FlaxStorage::LockData _dataLock; - -public: - /// - /// Init - /// - /// Parent model - /// LOD to stream index - StreamSkinnedModelLODTask(SkinnedModel* model, int32 lodIndex) - : _asset(model) - , _lodIndex(lodIndex) - , _dataLock(model->Storage->Lock()) - { - } - -public: - // [ThreadPoolTask] - bool HasReference(Object* resource) const override - { - return _asset == resource; - } - -protected: - // [ThreadPoolTask] - bool Run() override - { - AssetReference model = _asset.Get(); - if (model == nullptr) - { - return true; - } - - // Get data - BytesContainer data; - model->GetLODData(_lodIndex, data); - if (data.IsInvalid()) - { - LOG(Warning, "Missing data chunk"); - return true; - } - MemoryReadStream stream(data.Get(), data.Length()); - - // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering) - - // Load model LOD (initialize vertex and index buffers) - if (model->LODs[_lodIndex].Load(stream)) - { - LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex); - return true; - } - - // Update residency level - model->_loadedLODs++; - model->ResidencyChanged(); - - return false; - } - - void OnEnd() override - { - // Unlink - if (_asset) - { - ASSERT(_asset->_streamingTask == this); - _asset->_streamingTask = nullptr; - _asset = nullptr; - } - _dataLock.Release(); - - // Base - ThreadPoolTask::OnEnd(); - } -}; - REGISTER_BINARY_ASSET_WITH_UPGRADER(SkinnedModel, "FlaxEngine.SkinnedModel", SkinnedModelAssetUpgrader, true); SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info) @@ -143,18 +55,6 @@ Array SkinnedModel::GetBlendShapes() return result; } -ContentLoadTask* SkinnedModel::RequestLODDataAsync(int32 lodIndex) -{ - const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - return RequestChunkDataAsync(chunkIndex); -} - -void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const -{ - const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - GetChunkData(chunkIndex, data); -} - SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget) { SkeletonMapping mapping; @@ -328,9 +228,10 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren ASSERT(info.Buffer); if (!model->CanBeRendered()) return; + if (!info.Buffer->IsValidFor(model)) + info.Buffer->Setup(model); const auto frame = Engine::FrameCount; const auto modelFrame = info.DrawState->PrevFrame + 1; - CHECK_INVALID_BUFFER(model, info.Buffer); // Select a proper LOD index (model may be culled) int32 lodIndex; @@ -846,8 +747,6 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) // Setup MaterialSlots.Resize(1); MinScreenSize = 0.0f; - for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - LODs[lodIndex].Dispose(); LODs.Resize(meshesCountPerLod.Length()); _initialized = true; @@ -865,7 +764,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) lod.Meshes.Resize(meshesCount); for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { - lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, 0, BoundingBox::Zero, BoundingSphere::Empty); + lod.Meshes[meshIndex].Link(this, lodIndex, meshIndex); } } @@ -971,99 +870,16 @@ void SkinnedModel::InitAsVirtual() BinaryAsset::InitAsVirtual(); } -void SkinnedModel::CancelStreaming() -{ - Asset::CancelStreaming(); - CancelStreamingTasks(); -} - -#if USE_EDITOR - -void SkinnedModel::GetReferences(Array& assets, Array& files) const -{ - // Base - BinaryAsset::GetReferences(assets, files); - - for (int32 i = 0; i < MaterialSlots.Count(); i++) - assets.Add(MaterialSlots[i].Material.GetID()); -} - -#endif - int32 SkinnedModel::GetMaxResidency() const { return LODs.Count(); } -int32 SkinnedModel::GetCurrentResidency() const -{ - return _loadedLODs; -} - int32 SkinnedModel::GetAllocatedResidency() const { return LODs.Count(); } -bool SkinnedModel::CanBeUpdated() const -{ - // Check if is ready and has no streaming tasks running - return IsInitialized() && _streamingTask == nullptr; -} - -Task* SkinnedModel::UpdateAllocation(int32 residency) -{ - // SkinnedModels are not using dynamic allocation feature - return nullptr; -} - -Task* SkinnedModel::CreateStreamingTask(int32 residency) -{ - ScopeLock lock(Locker); - - ASSERT(IsInitialized() && Math::IsInRange(residency, 0, LODs.Count()) && _streamingTask == nullptr); - Task* result = nullptr; - const int32 lodCount = residency - GetCurrentResidency(); - - // Switch if go up or down with residency - if (lodCount > 0) - { - // Allow only to change LODs count by 1 - ASSERT(Math::Abs(lodCount) == 1); - - int32 lodIndex = HighestResidentLODIndex() - 1; - - // Request LOD data - result = (Task*)RequestLODDataAsync(lodIndex); - - // Add upload data task - _streamingTask = New(this, lodIndex); - if (result) - result->ContinueWith(_streamingTask); - else - result = _streamingTask; - } - else - { - // Do the quick data release - for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++) - LODs[i].Unload(); - _loadedLODs = residency; - ResidencyChanged(); - } - - return result; -} - -void SkinnedModel::CancelStreamingTasks() -{ - if (_streamingTask) - { - _streamingTask->Cancel(); - ASSERT_LOW_LAYER(_streamingTask == nullptr); - } -} - Asset::LoadResult SkinnedModel::load() { // Get header chunk @@ -1134,7 +950,8 @@ Asset::LoadResult SkinnedModel::load() // For each mesh for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { - auto& mesh = lod.Meshes[meshIndex]; + SkinnedMesh& mesh = lod.Meshes[meshIndex]; + mesh.Link(this, lodIndex, meshIndex); // Material Slot index int32 materialSlotIndex; @@ -1144,17 +961,14 @@ Asset::LoadResult SkinnedModel::load() LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); return LoadResult::InvalidData; } + mesh.SetMaterialSlotIndex(materialSlotIndex); - // Box + // Bounds BoundingBox box; stream->ReadBoundingBox(&box); - - // Sphere BoundingSphere sphere; stream->ReadBoundingSphere(&sphere); - - // Create mesh object - mesh.Init(this, lodIndex, meshIndex, materialSlotIndex, box, sphere); + mesh.SetBounds(box, sphere); // Blend Shapes uint16 blendShapes; @@ -1221,40 +1035,143 @@ Asset::LoadResult SkinnedModel::load() void SkinnedModel::unload(bool isReloading) { - // End streaming (if still active) - if (_streamingTask != nullptr) - { - // Cancel streaming task - _streamingTask->Cancel(); - _streamingTask = nullptr; - } + ModelBase::unload(isReloading); // Cleanup - MaterialSlots.Resize(0); - for (int32 i = 0; i < LODs.Count(); i++) - LODs[i].Dispose(); LODs.Clear(); Skeleton.Dispose(); - _initialized = false; - _loadedLODs = 0; _skeletonRetargets.Clear(); ClearSkeletonMapping(); } -bool SkinnedModel::init(AssetInitData& initData) -{ - // Validate - if (initData.SerializedVersion != SerializedVersion) - { - LOG(Error, "Invalid serialized model version."); - return true; - } - - return false; -} - AssetChunksFlag SkinnedModel::getChunksToPreload() const { // Note: we don't preload any meshes here because it's done by the Streaming Manager return GET_CHUNK_FLAG(0); } + +bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh) +{ + // Check all meshes + bool result = false; + Real closest = MAX_float; + Vector3 closestNormal = Vector3::Up; + for (int32 i = 0; i < Meshes.Count(); i++) + { + // Test intersection with mesh and check if is closer than previous + Real dst; + Vector3 nrm; + if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest) + { + result = true; + *mesh = &Meshes[i]; + closest = dst; + closestNormal = nrm; + } + } + + distance = closest; + normal = closestNormal; + return result; +} + +bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh) +{ + // Check all meshes + bool result = false; + Real closest = MAX_float; + Vector3 closestNormal = Vector3::Up; + for (int32 i = 0; i < Meshes.Count(); i++) + { + // Test intersection with mesh and check if is closer than previous + Real dst; + Vector3 nrm; + if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest) + { + result = true; + *mesh = &Meshes[i]; + closest = dst; + closestNormal = nrm; + } + } + + distance = closest; + normal = closestNormal; + return result; +} + +BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const +{ + // Find minimum and maximum points of all the meshes + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 j = 0; j < Meshes.Count(); j++) + { + const auto& mesh = Meshes[j]; + mesh.GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + Vector3::Transform(corners[i], world, tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + + return BoundingBox(min, max); +} + +BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + { + const auto& mesh = Meshes[meshIndex]; + BoundingBox box = mesh.GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + transform.LocalToWorld(corners[i], tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + +BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const +{ + // Find minimum and maximum points of the mesh + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + const auto& mesh = Meshes[meshIndex]; + mesh.GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + Vector3::Transform(corners[i], world, tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + + return BoundingBox(min, max); +} + +BoundingBox SkinnedModelLOD::GetBox() const +{ + // Find minimum and maximum points of the mesh in given world + Vector3 min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 j = 0; j < Meshes.Count(); j++) + { + Meshes[j].GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + min = Vector3::Min(min, corners[i]); + max = Vector3::Max(max, corners[i]); + } + } + + return BoundingBox(min, max); +} diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index cf778027d..fc64e9339 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -8,8 +8,6 @@ #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Graphics/Models/SkinnedModelLOD.h" -class StreamSkinnedModelLODTask; - /// /// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning. /// @@ -17,7 +15,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase { DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5); friend SkinnedMesh; - friend StreamSkinnedModelLODTask; public: // Skeleton mapping descriptor. struct FLAXENGINE_API SkeletonMapping @@ -37,9 +34,6 @@ private: Span NodesMapping; }; - bool _initialized = false; - int32 _loadedLODs = 0; - StreamSkinnedModelLODTask* _streamingTask = nullptr; Dictionary _skeletonMappingCache; public: @@ -60,53 +54,11 @@ public: ~SkinnedModel(); public: - /// - /// Gets a value indicating whether this instance is initialized. - /// - FORCE_INLINE bool IsInitialized() const - { - return _initialized; - } - - /// - /// Gets the amount of loaded model LODs. - /// - API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const - { - return _loadedLODs; - } - - /// - /// Clamps the index of the LOD to be valid for rendering (only loaded LODs). - /// - /// The index. - /// The resident LOD index. - FORCE_INLINE int32 ClampLODIndex(int32 index) const - { - return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1); - } - - /// - /// Gets index of the highest resident LOD (may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality) - /// - FORCE_INLINE int32 HighestResidentLODIndex() const - { - return LODs.Count() - _loadedLODs; - } - /// /// Determines whether any LOD has been initialized. /// bool HasAnyLODInitialized() const; - /// - /// Determines whether this model can be rendered. - /// - FORCE_INLINE bool CanBeRendered() const - { - return _loadedLODs > 0; - } - /// /// Gets the skeleton nodes hierarchy. /// @@ -159,20 +111,6 @@ public: API_PROPERTY() Array GetBlendShapes(); public: - /// - /// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here). - /// - /// Index of the LOD. - /// Task that will gather chunk data or null if already here. - ContentLoadTask* RequestLODDataAsync(int32 lodIndex); - - /// - /// Gets the model LOD data (links bytes). - /// - /// Index of the LOD. - /// The data (may be missing if failed to get it). - void GetLODData(int32 lodIndex, BytesContainer& data) const; - /// /// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup. /// @@ -322,24 +260,14 @@ public: int32 GetLODsCount() const override; void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; - void CancelStreaming() override; -#if USE_EDITOR - void GetReferences(Array& assets, Array& files) const override; -#endif // [StreamableResource] int32 GetMaxResidency() const override; - int32 GetCurrentResidency() const override; int32 GetAllocatedResidency() const override; - bool CanBeUpdated() const override; - Task* UpdateAllocation(int32 residency) override; - Task* CreateStreamingTask(int32 residency) override; - void CancelStreamingTasks() override; protected: // [ModelBase] LoadResult load() override; void unload(bool isReloading) override; - bool init(AssetInitData& initData) override; AssetChunksFlag getChunksToPreload() const override; }; diff --git a/Source/Engine/Graphics/Models/CollisionProxy.h b/Source/Engine/Graphics/Models/CollisionProxy.h index af3873d58..00a6a6d51 100644 --- a/Source/Engine/Graphics/Models/CollisionProxy.h +++ b/Source/Engine/Graphics/Models/CollisionProxy.h @@ -31,12 +31,12 @@ public: } template - void Init(uint32 vertices, uint32 triangles, Float3* positions, IndexType* indices) + void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices) { Triangles.Clear(); Triangles.EnsureCapacity(triangles, false); - IndexType* it = indices; + const IndexType* it = indices; for (uint32 i = 0; i < triangles; i++) { auto i0 = *(it++); diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index f948ab51d..c6dcf8979 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -136,26 +136,17 @@ namespace bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices) { - auto model = (Model*)_model; - - Unload(); + Release(); // Setup GPU resources - model->LODs[_lodIndex]._verticesCount -= _vertices; const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices); if (!failed) { - model->LODs[_lodIndex]._verticesCount += _vertices; - // Calculate mesh bounds BoundingBox bounds; BoundingBox::FromPoints((const Float3*)vb0, vertexCount, bounds); SetBounds(bounds); - - // Send event (actors using this model can update bounds, etc.) - model->onLoaded(); } - return failed; } @@ -169,90 +160,19 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* ve return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } -void Mesh::Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs) -{ - _model = model; - _lodIndex = lodIndex; - _index = index; - _materialSlotIndex = materialSlotIndex; - _use16BitIndexBuffer = false; - _hasLightmapUVs = hasLightmapUVs; - _box = box; - _sphere = sphere; - _vertices = 0; - _triangles = 0; - _vertexBuffers[0] = nullptr; - _vertexBuffers[1] = nullptr; - _vertexBuffers[2] = nullptr; - _indexBuffer = nullptr; -} - bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer) { - // Cache data - uint32 indicesCount = triangles * 3; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - - GPUBuffer* vertexBuffer0 = nullptr; - GPUBuffer* vertexBuffer1 = nullptr; - GPUBuffer* vertexBuffer2 = nullptr; - GPUBuffer* indexBuffer = nullptr; - - // Create GPU buffers -#if GPU_ENABLE_RESOURCE_NAMING -#define MESH_BUFFER_NAME(postfix) GetModel()->GetPath() + TEXT(postfix) -#else -#define MESH_BUFFER_NAME(postfix) String::Empty -#endif - vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0")); - if (vertexBuffer0->Init(GPUBufferDescription::Vertex(VB0ElementType::GetLayout(), sizeof(VB0ElementType), vertices, vb0))) - goto ERROR_LOAD_END; - vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1")); - if (vertexBuffer1->Init(GPUBufferDescription::Vertex(VB1ElementType::GetLayout(), sizeof(VB1ElementType), vertices, vb1))) - goto ERROR_LOAD_END; + Array> vbData; + vbData.Add(vb0); + if (vb1) + vbData.Add(vb1); if (vb2) - { - vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2")); - if (vertexBuffer2->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(VB2ElementType), vertices, vb2))) - goto ERROR_LOAD_END; - } - indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB")); - if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib))) - goto ERROR_LOAD_END; - - // Init collision proxy -#if USE_PRECISE_MESH_INTERSECTS - if (!_collisionProxy.HasData()) - { - if (use16BitIndexBuffer) - _collisionProxy.Init(vertices, triangles, (Float3*)vb0, (uint16*)ib); - else - _collisionProxy.Init(vertices, triangles, (Float3*)vb0, (uint32*)ib); - } -#endif - - // Initialize - _vertexBuffers[0] = vertexBuffer0; - _vertexBuffers[1] = vertexBuffer1; - _vertexBuffers[2] = vertexBuffer2; - _indexBuffer = indexBuffer; - _triangles = triangles; - _vertices = vertices; - _use16BitIndexBuffer = use16BitIndexBuffer; - _cachedVertexBuffers[0].Clear(); - _cachedVertexBuffers[1].Clear(); - _cachedVertexBuffers[2].Clear(); - - return false; - -#undef MESH_BUFFER_NAME -ERROR_LOAD_END: - - SAFE_DELETE_GPU_RESOURCE(vertexBuffer0); - SAFE_DELETE_GPU_RESOURCE(vertexBuffer1); - SAFE_DELETE_GPU_RESOURCE(vertexBuffer2); - SAFE_DELETE_GPU_RESOURCE(indexBuffer); - return true; + vbData.Add(vb2); + Array> vbLayout; + vbLayout.Add(VB0ElementType::GetLayout()); + vbLayout.Add(VB1ElementType::GetLayout()); + vbLayout.Add(VB2ElementType::GetLayout()); + return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout); } void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder) const @@ -424,6 +344,26 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder); } +bool Mesh::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) +{ + if (MeshBase::Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) + return true; + + auto model = (Model*)_model; + if (model) + model->LODs[_lodIndex]._verticesCount += _vertices; + return false; +} + +void Mesh::Release() +{ + auto model = (Model*)_model; + if (model) + model->LODs[_lodIndex]._verticesCount -= _vertices; + + MeshBase::Release(); +} + bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { if (_cachedVertexBuffers[0].IsEmpty()) diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 425bfbec7..8dba159a2 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -16,9 +16,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase); -protected: - bool _hasLightmapUVs; - public: Mesh(const Mesh& other) : Mesh() @@ -40,11 +37,16 @@ public: /// /// Determines whether this mesh contains valid lightmap texture coordinates data. /// - API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const + API_PROPERTY() bool HasLightmapUVs() const { - return _hasLightmapUVs; + return LightmapUVsIndex != -1; } + /// + /// Lightmap texture coordinates channel index. + /// + API_FIELD() int32 LightmapUVsIndex = -1; + public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). @@ -124,20 +126,9 @@ public: bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr); public: - /// - /// Initializes instance of the class. - /// - /// The model. - /// The LOD index. - /// The mesh index. - /// The material slot index to use. - /// The bounding box. - /// The bounding sphere. - /// The lightmap UVs flag. - void Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs); - /// /// Load mesh data and Initialize GPU buffers + /// [Deprecated in v1.10] /// /// Amount of vertices in the vertex buffer /// Amount of triangles in the index buffer @@ -147,7 +138,7 @@ public: /// Index buffer data /// True if use 16 bit indices for the index buffer (true: uint16, false: uint32). /// True if cannot load data, otherwise false. - bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer); + DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer); public: /// @@ -181,6 +172,8 @@ public: public: // [MeshBase] + bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) override; + void Release() override; bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index c95b58acf..f58527337 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -48,7 +48,6 @@ void MeshBase::SetMaterialSlotIndex(int32 value) LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count()); return; } - _materialSlotIndex = value; } @@ -56,9 +55,94 @@ void MeshBase::SetBounds(const BoundingBox& box) { _box = box; BoundingSphere::FromBox(box, _sphere); + _hasBounds = true; } -void MeshBase::Unload() +void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere) +{ + _box = box; + _sphere = sphere; + _hasBounds = true; + if (_model && _model->IsLoaded()) + { + // Send event (actors using this model can update bounds, etc.) + _model->onLoaded(); + } +} + +bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) +{ + CHECK_RETURN(vbData.HasItems() && vertices, true); + CHECK_RETURN(ibData, true); + CHECK_RETURN(vbLayout.Count() >= vbData.Count(), true); + ASSERT(_model); + GPUBuffer* vertexBuffer0 = nullptr; + GPUBuffer* vertexBuffer1 = nullptr; + GPUBuffer* vertexBuffer2 = nullptr; + GPUBuffer* indexBuffer = nullptr; + + // Create GPU buffers +#if GPU_ENABLE_RESOURCE_NAMING + const String& modelPath = _model->GetPath(); +#define MESH_BUFFER_NAME(postfix) modelPath + TEXT(postfix) +#else +#define MESH_BUFFER_NAME(postfix) String::Empty +#endif + vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0")); + if (vertexBuffer0->Init(GPUBufferDescription::Vertex(vbLayout[0], vertices, vbData[0]))) + goto ERROR_LOAD_END; + if (vbData.Count() >= 2 && vbData[1]) + { + vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1")); + if (vertexBuffer1->Init(GPUBufferDescription::Vertex(vbLayout[1], vertices, vbData[1]))) + goto ERROR_LOAD_END; + } + if (vbData.Count() >= 3 && vbData[2]) + { + vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2")); + if (vertexBuffer2->Init(GPUBufferDescription::Vertex(vbLayout[2], vertices, vbData[2]))) + goto ERROR_LOAD_END; + } + indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB")); + if (indexBuffer->Init(GPUBufferDescription::Index(use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32), triangles * 3, ibData))) + goto ERROR_LOAD_END; + + // Init collision proxy +#if USE_PRECISE_MESH_INTERSECTS + if (!_collisionProxy.HasData()) + { + if (use16BitIndexBuffer) + _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData); + else + _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData); + } +#endif + + // Initialize + _vertexBuffers[0] = vertexBuffer0; + _vertexBuffers[1] = vertexBuffer1; + _vertexBuffers[2] = vertexBuffer2; + _indexBuffer = indexBuffer; + _triangles = triangles; + _vertices = vertices; + _use16BitIndexBuffer = use16BitIndexBuffer; + _cachedVertexBuffers[0].Clear(); + _cachedVertexBuffers[1].Clear(); + _cachedVertexBuffers[2].Clear(); + + return false; + +#undef MESH_BUFFER_NAME +ERROR_LOAD_END: + + SAFE_DELETE_GPU_RESOURCE(vertexBuffer0); + SAFE_DELETE_GPU_RESOURCE(vertexBuffer1); + SAFE_DELETE_GPU_RESOURCE(vertexBuffer2); + SAFE_DELETE_GPU_RESOURCE(indexBuffer); + return true; +} + +void MeshBase::Release() { SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]); SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]); diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 309fea9e5..4dc861e6a 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -31,30 +31,40 @@ class BlendShapesInstance; API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public ScriptingObject { DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase); + friend class Model; + friend class SkinnedModel; protected: - ModelBase* _model; - BoundingBox _box; - BoundingSphere _sphere; + ModelBase* _model = nullptr; + BoundingBox _box = BoundingBox::Zero; + BoundingSphere _sphere = BoundingSphere::Empty; - int32 _index; - int32 _lodIndex; - uint32 _vertices; - uint32 _triangles; - int32 _materialSlotIndex; - bool _use16BitIndexBuffer; + int32 _index = 0; + int32 _lodIndex = 0; + uint32 _vertices = 0; + uint32 _triangles = 0; + int32 _materialSlotIndex = 0; + bool _use16BitIndexBuffer = false; + bool _hasBounds = false; GPUBuffer* _vertexBuffers[3] = {}; GPUBuffer* _indexBuffer = nullptr; mutable Array _cachedVertexBuffers[3]; mutable Array _cachedIndexBuffer; - mutable int32 _cachedIndexBufferCount; + mutable int32 _cachedIndexBufferCount = 0; #if USE_PRECISE_MESH_INTERSECTS CollisionProxy _collisionProxy; #endif + void Link(ModelBase* model, int32 lodIndex, int32 index) + { + _model = model; + _lodIndex = lodIndex; + _index = index; + } + explicit MeshBase(const SpawnParams& params) : ScriptingObject(params) { @@ -169,6 +179,13 @@ public: /// The bounding box. void SetBounds(const BoundingBox& box); + /// + /// Sets the mesh bounds. + /// + /// The bounding box. + /// The bounding sphere. + void SetBounds(const BoundingBox& box, const BoundingSphere& sphere); + /// /// Gets the index buffer. /// @@ -189,9 +206,30 @@ public: public: /// - /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. + /// Initializes the mesh buffers. /// - void Unload(); + /// Amount of vertices in the vertex buffer. + /// Amount of triangles in the index buffer. + /// Array with pointers to vertex buffers initial data (layout defined by ). + /// Pointer to index buffer data. Data is uint16 or uint32 depending on value. + /// True to use 16-bit indices for the index buffer (true: uint16, false: uint32). + /// Layout descriptors for the vertex buffers attributes (one for each vertex buffer). + /// True if failed, otherwise false. + virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout); + + /// + /// Releases the mesh data (GPU buffers and local cache). + /// + virtual void Release(); + + /// + /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. + /// [Deprecated in v1.10] + /// + DEPRECATED("Use Release instead.") void Unload() + { + Release(); + } public: /// diff --git a/Source/Engine/Graphics/Models/ModelLOD.cpp b/Source/Engine/Graphics/Models/ModelLOD.cpp deleted file mode 100644 index 095c14f89..000000000 --- a/Source/Engine/Graphics/Models/ModelLOD.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#include "ModelLOD.h" -#include "MeshDeformation.h" -#include "Engine/Core/Log.h" -#include "Engine/Core/Math/Transform.h" -#include "Engine/Graphics/GPUDevice.h" -#include "Engine/Serialization/MemoryReadStream.h" - -bool ModelLOD::Load(MemoryReadStream& stream) -{ - // Load LOD for each mesh - _verticesCount = 0; - for (int32 i = 0; i < Meshes.Count(); i++) - { - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - _verticesCount += vertices; - uint32 triangles; - stream.ReadUint32(&triangles); - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Move(vertices); - } - auto ib = stream.Move(indicesCount * ibStride); - - // Setup GPU resources - if (Meshes[i].Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer)) - { - LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles); - return true; - } - } - - return false; -} - -void ModelLOD::Unload() -{ - // Unload LOD for each mesh - for (int32 i = 0; i < Meshes.Count(); i++) - { - Meshes[i].Unload(); - } -} - -void ModelLOD::Dispose() -{ - _model = nullptr; - ScreenSize = 0.0f; - Meshes.Resize(0); -} - -bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh) -{ - bool result = false; - Real closest = MAX_Real; - Vector3 closestNormal = Vector3::Up; - for (int32 i = 0; i < Meshes.Count(); i++) - { - Real dst; - Vector3 nrm; - if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest) - { - result = true; - *mesh = &Meshes[i]; - closest = dst; - closestNormal = nrm; - } - } - distance = closest; - normal = closestNormal; - return result; -} - -bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh) -{ - bool result = false; - Real closest = MAX_Real; - Vector3 closestNormal = Vector3::Up; - for (int32 i = 0; i < Meshes.Count(); i++) - { - Real dst; - Vector3 nrm; - if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest) - { - result = true; - *mesh = &Meshes[i]; - closest = dst; - closestNormal = nrm; - } - } - distance = closest; - normal = closestNormal; - return result; -} - -BoundingBox ModelLOD::GetBox(const Matrix& world) const -{ - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - const auto& mesh = Meshes[meshIndex]; - mesh.GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - return BoundingBox(min, max); -} - -BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const -{ - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - const auto& mesh = Meshes[meshIndex]; - BoundingBox box = mesh.GetBox(); - if (deformation) - deformation->GetBounds(_lodIndex, meshIndex, box); - box.GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - transform.LocalToWorld(corners[i], tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - return BoundingBox(min, max); -} - -BoundingBox ModelLOD::GetBox() const -{ - Vector3 min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - Meshes[meshIndex].GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - min = Vector3::Min(min, corners[i]); - max = Vector3::Max(max, corners[i]); - } - } - return BoundingBox(min, max); -} diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h index a6937739e..4934e6cc2 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.h +++ b/Source/Engine/Graphics/Models/ModelLOD.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Core/Collections/Array.h" #include "Mesh.h" class MemoryReadStream; @@ -18,7 +17,14 @@ API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject private: Model* _model = nullptr; int32 _lodIndex = 0; - uint32 _verticesCount; + uint32 _verticesCount = 0; + + void Link(Model* model, int32 lodIndex) + { + _model = model; + _lodIndex = lodIndex; + _verticesCount = 0; + } public: /// @@ -57,24 +63,6 @@ public: return _verticesCount; } -public: - /// - /// Initializes the LOD from the data stream. - /// - /// The stream. - /// True if fails, otherwise false. - bool Load(MemoryReadStream& stream); - - /// - /// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load. - /// - void Unload(); - - /// - /// Cleanups the data. - /// - void Dispose(); - public: /// /// Determines if there is an intersection between the Model and a Ray in given world using given instance diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b7e2ddc6a..5473b549a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -94,71 +94,17 @@ void SkeletonData::Dispose() Bones.Resize(0); } -void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere) -{ - _model = model; - _index = index; - _lodIndex = lodIndex; - _materialSlotIndex = materialSlotIndex; - _use16BitIndexBuffer = false; - _box = box; - _sphere = sphere; - _vertices = 0; - _triangles = 0; - _vertexBuffers[0] = nullptr; - _indexBuffer = nullptr; - _cachedIndexBuffer.Clear(); - _cachedVertexBuffers[0].Clear(); - BlendShapes.Clear(); -} - bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer) { - // Cache data - uint32 indicesCount = triangles * 3; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - - GPUBuffer* vertexBuffer = nullptr; - GPUBuffer* indexBuffer = nullptr; - - // Create vertex buffer -#if GPU_ENABLE_RESOURCE_NAMING - vertexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->GetPath() + TEXT(".VB")); -#else - vertexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty); -#endif - if (vertexBuffer->Init(GPUBufferDescription::Vertex(VB0SkinnedElementType::GetLayout(), sizeof(VB0SkinnedElementType), vertices, vb0))) - goto ERROR_LOAD_END; - - // Create index buffer -#if GPU_ENABLE_RESOURCE_NAMING - indexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->GetPath() + TEXT(".IB")); -#else - indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty); -#endif - if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib))) - goto ERROR_LOAD_END; - - // Initialize - _vertexBuffers[0] = vertexBuffer; - _indexBuffer = indexBuffer; - _triangles = triangles; - _vertices = vertices; - _use16BitIndexBuffer = use16BitIndexBuffer; - - return false; - -ERROR_LOAD_END: - - SAFE_DELETE_GPU_RESOURCE(vertexBuffer); - SAFE_DELETE_GPU_RESOURCE(indexBuffer); - return true; + Array> vbData; + vbData.Add(vb0); + Array> vbLayout; + vbLayout.Add(VB0SkinnedElementType::GetLayout()); + return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout); } bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices) { - auto model = (SkinnedModel*)_model; - // Setup GPU resources const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices); if (!failed) @@ -167,11 +113,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0 BoundingBox bounds; BoundingBox::FromPoints((const Float3*)vb, vertexCount, bounds); SetBounds(bounds); - - // Send event (actors using this model can update bounds, etc.) - model->onLoaded(); } - return failed; } @@ -267,6 +209,13 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, StaticFlags::None, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder); } +void SkinnedMesh::Release() +{ + MeshBase::Release(); + + BlendShapes.Clear(); +} + bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { if (_cachedVertexBuffers[0].IsEmpty()) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index b8dd38696..f28ea7fcd 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -36,19 +36,9 @@ public: Array BlendShapes; public: - /// - /// Initializes a new instance of the class. - /// - /// The model. - /// The model LOD index. - /// The mesh index. - /// The material slot index to use. - /// The bounding box. - /// The bounding sphere. - void Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere); - /// /// Load mesh data and Initialize GPU buffers + /// [Deprecated in v1.10] /// /// Amount of vertices in the vertex buffer /// Amount of triangles in the index buffer @@ -56,7 +46,7 @@ public: /// Index buffer data /// True if use 16 bit indices for the index buffer (true: uint16, false: uint32). /// True if cannot load data, otherwise false. - bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer); + DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer); public: /// @@ -128,6 +118,7 @@ public: public: // [MeshBase] + void Release() override; bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp deleted file mode 100644 index e16a78291..000000000 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#include "SkinnedModelLOD.h" -#include "MeshDeformation.h" -#include "Engine/Core/Log.h" -#include "Engine/Core/Math/Transform.h" -#include "Engine/Graphics/GPUDevice.h" -#include "Engine/Content/Assets/Model.h" -#include "Engine/Serialization/MemoryReadStream.h" - -bool SkinnedModelLOD::Load(MemoryReadStream& stream) -{ - // Load LOD for each mesh - byte version = stream.ReadByte(); - for (int32 i = 0; i < Meshes.Count(); i++) - { - auto& mesh = Meshes[i]; - - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint16 blendShapesCount; - stream.ReadUint16(&blendShapesCount); - if (blendShapesCount != mesh.BlendShapes.Count()) - { - LOG(Warning, "Cannot initialize mesh {0}. Incorect blend shapes amount: {1} (expected: {2})", i, blendShapesCount, mesh.BlendShapes.Count()); - return true; - } - for (auto& blendShape : mesh.BlendShapes) - { - blendShape.UseNormals = stream.ReadBool(); - stream.ReadUint32(&blendShape.MinVertexIndex); - stream.ReadUint32(&blendShape.MaxVertexIndex); - uint32 blendShapeVertices; - stream.ReadUint32(&blendShapeVertices); - blendShape.Vertices.Resize(blendShapeVertices); - stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex)); - } - const uint32 indicesCount = triangles * 3; - const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - const auto vb0 = stream.Move(vertices); - const auto ib = stream.Move(indicesCount * ibStride); - - // Setup GPU resources - if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer)) - { - LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles); - return true; - } - } - - return false; -} - -void SkinnedModelLOD::Unload() -{ - // Unload LOD for each mesh - for (int32 i = 0; i < Meshes.Count(); i++) - { - Meshes[i].Unload(); - } -} - -void SkinnedModelLOD::Dispose() -{ - _model = nullptr; - ScreenSize = 0.0f; - Meshes.Resize(0); -} - -bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh) -{ - // Check all meshes - bool result = false; - Real closest = MAX_float; - Vector3 closestNormal = Vector3::Up; - for (int32 i = 0; i < Meshes.Count(); i++) - { - // Test intersection with mesh and check if is closer than previous - Real dst; - Vector3 nrm; - if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest) - { - result = true; - *mesh = &Meshes[i]; - closest = dst; - closestNormal = nrm; - } - } - - distance = closest; - normal = closestNormal; - return result; -} - -bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh) -{ - // Check all meshes - bool result = false; - Real closest = MAX_float; - Vector3 closestNormal = Vector3::Up; - for (int32 i = 0; i < Meshes.Count(); i++) - { - // Test intersection with mesh and check if is closer than previous - Real dst; - Vector3 nrm; - if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest) - { - result = true; - *mesh = &Meshes[i]; - closest = dst; - closestNormal = nrm; - } - } - - distance = closest; - normal = closestNormal; - return result; -} - -BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const -{ - // Find minimum and maximum points of all the meshes - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) - { - const auto& mesh = Meshes[j]; - mesh.GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - - return BoundingBox(min, max); -} - -BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const -{ - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - const auto& mesh = Meshes[meshIndex]; - BoundingBox box = mesh.GetBox(); - if (deformation) - deformation->GetBounds(_lodIndex, meshIndex, box); - box.GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - transform.LocalToWorld(corners[i], tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - return BoundingBox(min, max); -} - -BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const -{ - // Find minimum and maximum points of the mesh - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - const auto& mesh = Meshes[meshIndex]; - mesh.GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - - return BoundingBox(min, max); -} - -BoundingBox SkinnedModelLOD::GetBox() const -{ - // Find minimum and maximum points of the mesh in given world - Vector3 min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) - { - Meshes[j].GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - min = Vector3::Min(min, corners[i]); - max = Vector3::Max(max, corners[i]); - } - } - - return BoundingBox(min, max); -} diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h index 66cf77759..f7c78f3d1 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Core/Collections/Array.h" #include "SkinnedMesh.h" class MemoryReadStream; @@ -46,23 +45,6 @@ public: return Meshes.HasItems() && Meshes.Last().IsInitialized(); } -public: - /// - /// Initializes the LOD from the data stream. - /// - /// The stream. - /// True if fails, otherwise false. - bool Load(MemoryReadStream& stream); - - /// - /// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load. - /// - void Unload(); - - /// - /// Cleanups the data. - /// - void Dispose(); public: /// diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 880dc1f00..659bcb344 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -87,8 +87,8 @@ void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_M strides[e.Slot] = Math::Max(strides[e.Slot], offsets[e.Slot]); } _stride = 0; - for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) - _stride += strides[i]; + for (uint32 stride : strides) + _stride += stride; } GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) From 9f648caac8b18d30fa57f4df99950bdb0b724408 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Dec 2024 00:48:01 +0100 Subject: [PATCH 092/215] Remove deprecated asset data upgrades and old model vertex structures --- .../Content/Assets/MaterialInstance.cpp | 3 + Source/Engine/Content/Assets/Model.cpp | 4 + Source/Engine/Content/Assets/SkinnedModel.cpp | 3 + .../Content/Upgraders/AudioClipUpgrader.h | 105 +- .../Content/Upgraders/FontAssetUpgrader.h | 48 +- .../Upgraders/MaterialInstanceUpgrader.h | 82 +- .../Content/Upgraders/ModelAssetUpgrader.h | 1200 +---------------- .../Content/Upgraders/ShaderAssetUpgrader.h | 90 +- .../Content/Upgraders/SkeletonMaskUpgrader.h | 75 +- .../Upgraders/SkinnedModelAssetUpgrader.h | 635 +-------- .../Content/Upgraders/TextureAssetUpgrader.h | 797 +---------- Source/Engine/Graphics/Models/ModelData.cpp | 87 -- Source/Engine/Graphics/Models/ModelData.h | 25 - Source/Engine/Graphics/Models/Types.h | 32 - 14 files changed, 18 insertions(+), 3168 deletions(-) diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index ca9e793b3..537fe0243 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -8,6 +8,9 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Threading.h" +#if USE_EDITOR +#include "Engine/Serialization/MemoryWriteStream.h" +#endif REGISTER_BINARY_ASSET_WITH_UPGRADER(MaterialInstance, "FlaxEngine.MaterialInstance", MaterialInstanceUpgrader, true); diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 8ba26658c..52f633e1b 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -23,6 +23,9 @@ #include "Engine/Renderer/DrawCall.h" #include "Engine/Threading/Threading.h" #include "Engine/Tools/ModelTool/ModelTool.h" +#if USE_EDITOR +#include "Engine/Serialization/MemoryWriteStream.h" +#endif REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase"); @@ -456,6 +459,7 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path) return true; } + // #MODEL_DATA_FORMAT_USAGE meshesStream.WriteUint32(vertices); meshesStream.WriteUint32(triangles); diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 0b9c69275..933643f63 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -19,6 +19,9 @@ #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" +#if USE_EDITOR +#include "Engine/Serialization/MemoryWriteStream.h" +#endif REGISTER_BINARY_ASSET_WITH_UPGRADER(SkinnedModel, "FlaxEngine.SkinnedModel", SkinnedModelAssetUpgrader, true); diff --git a/Source/Engine/Content/Upgraders/AudioClipUpgrader.h b/Source/Engine/Content/Upgraders/AudioClipUpgrader.h index 98d3e3e68..cf1e2594a 100644 --- a/Source/Engine/Content/Upgraders/AudioClipUpgrader.h +++ b/Source/Engine/Content/Upgraders/AudioClipUpgrader.h @@ -5,9 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Audio/AudioClip.h" -#include "Engine/Tools/AudioTool/OggVorbisDecoder.h" -#include "Engine/Serialization/MemoryReadStream.h" /// /// Audio Clip asset upgrader. @@ -23,112 +20,12 @@ public: { static const Upgrader upgraders[] = { - { 1, 2, &Upgrade_1_To_2 }, + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } private: - // ============================================ - // Version 1: - // Designed: 26.02.2018 - // Header version 1. - // Chunks with audio data. - // ============================================ - // Version 2: - // Designed: 08.08.2019 - // Header version 2. - // Chunks with audio data. - // ============================================ - - /// - /// Audio Clip resource header structure, version 1. Added on 26.02.2018. - /// [Deprecated on 08/08/2019, expires on 08/09/2020] - /// - struct Header1 - { - AudioFormat Format; - AudioDataInfo Info; - bool Is3D; - bool Streamable; - uint32 OriginalSize; - uint32 ImportedSize; - }; - - typedef AudioClip::Header Header2; - - static bool Upgrade_1_To_2(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 2); - - // Copy all chunks - if (CopyChunks(context)) - return true; - - // Convert header - if (context.Input.CustomData.IsInvalid()) - return true; - auto& oldHeader = *(Header1*)context.Input.CustomData.Get(); - Header2 newHeader; - newHeader.Format = oldHeader.Format; - newHeader.Info = oldHeader.Info; - newHeader.Is3D = oldHeader.Is3D; - newHeader.Streamable = oldHeader.Streamable; - newHeader.OriginalSize = oldHeader.OriginalSize; - newHeader.ImportedSize = oldHeader.ImportedSize; - Platform::MemoryClear(newHeader.SamplesPerChunk, sizeof(newHeader.SamplesPerChunk)); - for (int32 chunkIndex = 0; chunkIndex < ASSET_FILE_DATA_CHUNKS; chunkIndex++) - { - const auto chunk = context.Output.Header.Chunks[chunkIndex]; - if (!chunk) - continue; - - int32 numSamples; - switch (oldHeader.Format) - { - case AudioFormat::Raw: - { - numSamples = chunk->Size() / (oldHeader.Info.BitDepth / 8); - break; - } - case AudioFormat::Vorbis: - { -#if COMPILE_WITH_OGG_VORBIS - OggVorbisDecoder decoder; - MemoryReadStream stream(chunk->Get(), chunk->Size()); - AudioDataInfo outInfo; - if (!decoder.Open(&stream, outInfo, 0)) - { - LOG(Warning, "Audio data open failed (OggVorbisDecoder)."); - return true; - } - numSamples = outInfo.NumSamples; -#else - LOG(Warning, "OggVorbisDecoder is disabled."); - return true; -#endif - break; - } - default: - LOG(Warning, "Unknown audio data format."); - return true; - } - newHeader.SamplesPerChunk[chunkIndex] = numSamples; - } - context.Output.CustomData.Copy(&newHeader); - - // Validate total amount of samples calculated from the asset chunks - uint32 totalSamplesInChunks = 0; - for (int32 chunkIndex = 0; chunkIndex < ASSET_FILE_DATA_CHUNKS; chunkIndex++) - totalSamplesInChunks += newHeader.SamplesPerChunk[chunkIndex]; - if (totalSamplesInChunks != newHeader.Info.NumSamples) - { - LOG(Warning, "Invalid amount of audio data samples in data chunks {0}, audio clip has: {1}.", totalSamplesInChunks, newHeader.Info.NumSamples); - return true; - } - - return false; - } }; #endif diff --git a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h index 4377f1904..4149816cd 100644 --- a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h @@ -5,7 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Render2D/FontAsset.h" /// /// Font Asset Upgrader @@ -21,55 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 1, 2, &Upgrade_1_To_2 }, - { 2, 3, &Upgrade_2_To_3 }, + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - // ============================================ - // Version 1: - // Designed: long time ago in a galaxy far far away - // Custom Data: not used - // Chunk 0: Header - // Chunk 1: Font File data - // ============================================ - // Version 2: - // Designed: 4/20/2017 - // Custom Data: not used - // Chunk 0: Font File data - // ============================================ - // Version 3: - // Designed: 10/24/2019 - // Custom Data: Header1 - // Chunk 0: Font File data - // ============================================ - - typedef FontOptions Header1; - - static bool Upgrade_1_To_2(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 2); - - if (context.AllocateChunk(0)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(context.Input.Header.Chunks[1]->Data); - - return false; - } - - static bool Upgrade_2_To_3(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 2 && context.Output.SerializedVersion == 3); - - Header1 header; - header.Hinting = FontHinting::Default; - header.Flags = FontFlags::AntiAliasing; - context.Output.CustomData.Copy(&header); - - return CopyChunk(context, 0); - } }; #endif diff --git a/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h b/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h index 75029e10e..e2081e3d3 100644 --- a/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h +++ b/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h @@ -5,10 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Core/Core.h" -#include "Engine/Platform/Platform.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Serialization/MemoryWriteStream.h" /// /// Material Instance Upgrader @@ -24,86 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 1, 3, &Upgrade_1_To_3 }, - { 3, 4, &Upgrade_3_To_4 }, + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - // ============================================ - // Version 4: - // Designed: 5/18/2017 - // Custom Data: not used - // Chunk 0: - // - Parent material: Guid - // - Material Params - // ============================================ - // Version 3: - // Designed: 1/24/2016 - // Custom Data: not used - // Chunk 0: - // - Magic Code: int32 ('MI\0@', 1073760589) - // - Version: int32 - // - Parent material: Guid - // - Material Params - // - Ending char: 1 byte (~) - // ============================================ - - static bool Upgrade_1_To_3(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 3); - - auto chunk = context.Input.Header.Chunks[0]; - if (chunk == nullptr || chunk->IsMissing()) - { - LOG(Warning, "Missing material instance data chunk."); - return true; - } - if (context.AllocateChunk(0)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(chunk->Data); - - return false; - } - - static bool Upgrade_3_To_4(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4); - - auto chunk = context.Input.Header.Chunks[0]; - if (chunk == nullptr || chunk->IsMissing()) - { - LOG(Warning, "Missing material instance data chunk."); - return true; - } - if (context.AllocateChunk(0)) - return true; - MemoryReadStream inStream(chunk->Data.Get(), chunk->Data.Length()); - MemoryWriteStream outStream(chunk->Size()); - - // Header - int32 magicCode; - inStream.ReadInt32(&magicCode); - int32 version; - inStream.ReadInt32(&version); - if (magicCode != 1073760589 || version != 3) - { - LOG(Warning, "Invalid material instance header."); - return true; - } - - // Parent material + Params - // TODO: add tool function to copy stream -> stream - while (inStream.CanRead()) - { - byte b = inStream.ReadByte(); - outStream.WriteByte(b); - } - context.Output.Header.Chunks[0]->Data.Copy(outStream.GetHandle(), outStream.GetPosition()); - - return false; - } }; #endif diff --git a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h index 712b5eda9..8b7d70084 100644 --- a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h @@ -5,12 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Core/Core.h" -#include "Engine/Platform/Platform.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Serialization/MemoryWriteStream.h" -#include "Engine/Graphics/Models/ModelData.h" -#include "Engine/Content/Asset.h" /// /// Model Asset Upgrader @@ -26,1202 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 24, 25, &Upgrade_With_Repack }, // [Deprecated on 28.04.2023, expires on 01.01.2024] - { 23, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024] - { 22, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024] - { 1, 24, &Upgrade_Old_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024] + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - // ============================================ - // Version 25: - // The same as version 24 except Vertex Buffer 1 has `Color32 Color` component per vertex added - // ============================================ - // Version 24: - // Designed: 9/24/2017 - // Custom Data: not used - // Chunk 0: - // - Min Screen Size: float - // - Amount of material slots: int32 - // - For each material slot: - // - Material: Guid - // - Shadows Mode: byte - // - Name: String (offset: 11) - // - Amount of LODs: byte - // - For each LOD: - // - Screen Size: float - // - Amount of meshes: uint16 - // - For each mesh: - // - Material Slot index: int32 - // - Box: BoundingBox - // - Sphere: BoundingSphere - // - HasLightmapUVs: bool - // Next chunks: (the highest LOD is at chunk no. 1, followed by the lower LODs: - // - For each mesh in the LOD: - // - Vertices Count: uint32 - // - Triangles Count: uint32 - // - Vertex Buffer 0: byte[] - // - For each vertex - // - Position : R32G32B32_Float - // - Vertex Buffer 1: byte[] - // - For each vertex - // - TexCoord : R16G16_Float - // - Normal : R10G10B10A2_UNorm - // - Tangent : R10G10B10A2_UNorm - // - LightampUV : R16G16_Float - // - Index Buffer: uint16[] or uint32[] (based on amount of triangles) - // ============================================ - // Version 23: - // Designed: 9/18/2017 - // Custom Data: not used - // Chunk 0: - // - Amount of material slots: int32 - // - For each material slot: - // - Material: Guid - // - Shadows Mode: byte - // - Name: String (offset: 11) - // - Amount of LODs: byte - // - For each LOD: - // - Amount of meshes: uint16 - // - For each mesh: - // - Material Slot index: int32 - // - Box: BoundingBox - // - Sphere: BoundingSphere - // Next chunks: (the highest LOD is at chunk no. 1, followed by the lower LODs: - // - For each mesh in the LOD: - // - Vertices Count: uint32 - // - Triangles Count: uint32 - // - Vertex Buffer 0: byte[] - // - For each vertex - // - Position : R32G32B32_Float - // - Vertex Buffer 1: byte[] - // - For each vertex - // - TexCoord : R16G16_Float - // - Normal : R10G10B10A2_UNorm - // - Tangent : R10G10B10A2_UNorm - // - LightampUV : R16G16_Float - // - Index Buffer: uint16[] or uint32[] (based on amount of triangles) - // ============================================ - // Version 22: - // Designed: 4/24/2017 - // Custom Data: not used - // Chunk 0: - // - Amount of LODs: byte - // - For each LOD: - // - Amount of meshes: uint16 - // - For each mesh: - // - Name: String (offset: 7) - // - Force Two Sided: bool - // - Cast Shadows: bool - // - Material ID: Guid - // - Box: BoundingBox - // - Sphere: BoundingSphere - // Next chunks: (the highest LOD is at chunk no. 1, followed by the lower LODs: - // - For each mesh in the LOD: - // - Vertices Count: uint32 - // - Triangles Count: uint32 - // - Vertex Buffer 0: byte[] - // - For each vertex - // - Position : R32G32B32_Float - // - Vertex Buffer 1: byte[] - // - For each vertex - // - TexCoord : R16G16_Float - // - Normal : R10G10B10A2_UNorm - // - Tangent : R10G10B10A2_UNorm - // - LightampUV : R16G16_Float - // - Index Buffer: uint16[] or uint32[] (based on amount of triangles) - // ============================================ - - static Asset::LoadResult LoadOld(AssetMigrationContext& context, int32 version, ReadStream& headerStream, ModelData& modelData) - { - // Load version - Asset::LoadResult result; - switch (version) - { - case 25: - result = loadVersion25(context, &headerStream, &modelData); - break; - case 24: - result = loadVersion24(context, &headerStream, &modelData); - break; - case 23: - result = loadVersion23(context, &headerStream, &modelData); - break; - case 22: - result = loadVersion22(context, &headerStream, &modelData); - break; - case 20: - result = loadVersion20(context, &headerStream, &modelData); - break; - case 19: - result = loadVersion19(context, &headerStream, &modelData); - break; - case 18: - result = loadVersion18(context, &headerStream, &modelData); - break; - case 17: - result = loadVersion17(context, &headerStream, &modelData); - break; - case 16: - result = loadVersion16(context, &headerStream, &modelData); - break; - case 15: - result = loadVersion15(context, &headerStream, &modelData); - break; - default: - LOG(Warning, "Unsupported model data version."); - result = Asset::LoadResult::InvalidData; - break; - } - - return result; - } - - static Asset::LoadResult loadVersion25(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Min Screen Size - headerStream->ReadFloat(&data->MinScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - headerStream->ReadInt32(&materialSlotsCount); - data->Materials.Resize(materialSlotsCount, false); - - // For each material slot - for (int32 i = 0; i < materialSlotsCount; i++) - { - auto& slot = data->Materials[i]; - - // Material - headerStream->Read(slot.AssetID); - - // Shadows Mode - slot.ShadowsMode = static_cast(headerStream->ReadByte()); - - // Name - headerStream->ReadString(&slot.Name, 11); - } - - // Amount of LODs - const int32 lodsCount = headerStream->ReadByte(); - data->LODs.Resize(lodsCount, false); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) - { - auto& lod = data->LODs[lodIndex]; - - // Screen Size - headerStream->ReadFloat(&lod.ScreenSize); - - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - lod.Meshes.Resize(meshesCount); - lod.Meshes.SetAll(nullptr); - - // Get meshes data - { - auto lodData = context.Input.Header.Chunks[1 + lodIndex]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Move(vertices); - } - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Allocate mesh - lod.Meshes[meshIndex] = New(); - auto& mesh = *lod.Meshes[meshIndex]; - - // Copy data - mesh.InitFromModelVertices(vb0, vb1, vb2, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - auto& mesh = *lod.Meshes[meshIndex]; - - // Material Slot index - int32 materialSlotIndex; - headerStream->ReadInt32(&materialSlotIndex); - if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount) - { - LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); - return Asset::LoadResult::InvalidData; - } - - // Box - BoundingBox box; - headerStream->Read(box); - - // Sphere - BoundingSphere sphere; - headerStream->Read(sphere); - - // Has Lightmap UVs - bool hasLightmapUVs = headerStream->ReadBool(); - if (!hasLightmapUVs) - mesh.LightmapUVs.Resize(0); - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion24(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Min Screen Size - headerStream->ReadFloat(&data->MinScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - headerStream->ReadInt32(&materialSlotsCount); - data->Materials.Resize(materialSlotsCount, false); - - // For each material slot - for (int32 i = 0; i < materialSlotsCount; i++) - { - auto& slot = data->Materials[i]; - - // Material - headerStream->Read(slot.AssetID); - - // Shadows Mode - slot.ShadowsMode = static_cast(headerStream->ReadByte()); - - // Name - headerStream->ReadString(&slot.Name, 11); - } - - // Amount of LODs - const int32 lodsCount = headerStream->ReadByte(); - data->LODs.Resize(lodsCount, false); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) - { - auto& lod = data->LODs[lodIndex]; - - // Screen Size - headerStream->ReadFloat(&lod.ScreenSize); - - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - lod.Meshes.Resize(meshesCount); - lod.Meshes.SetAll(nullptr); - - // Get meshes data - { - auto lodData = context.Input.Header.Chunks[1 + lodIndex]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Allocate mesh - lod.Meshes[meshIndex] = New(); - auto& mesh = *lod.Meshes[meshIndex]; - - // Copy data - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - auto& mesh = *lod.Meshes[meshIndex]; - - // Material Slot index - int32 materialSlotIndex; - headerStream->ReadInt32(&materialSlotIndex); - if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount) - { - LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); - return Asset::LoadResult::InvalidData; - } - - // Box - BoundingBox box; - headerStream->Read(box); - - // Sphere - BoundingSphere sphere; - headerStream->Read(sphere); - - // Has Lightmap UVs - bool hasLightmapUVs = headerStream->ReadBool(); - if (!hasLightmapUVs) - mesh.LightmapUVs.Resize(0); - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion23(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of material slots - int32 materialSlotsCount; - headerStream->ReadInt32(&materialSlotsCount); - data->Materials.Resize(materialSlotsCount, false); - - // For each material slot - for (int32 i = 0; i < materialSlotsCount; i++) - { - auto& slot = data->Materials[i]; - - // Material - headerStream->Read(slot.AssetID); - - // Shadows Mode - slot.ShadowsMode = static_cast(headerStream->ReadByte()); - - // Name - headerStream->ReadString(&slot.Name, 11); - } - - // Amount of LODs - const int32 lodsCount = headerStream->ReadByte(); - data->LODs.Resize(lodsCount, false); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->LODs[lodIndex].Meshes.Resize(meshesCount); - data->LODs[lodIndex].Meshes.SetAll(nullptr); - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - data->LODs[lodIndex].Meshes[meshIndex] = New(); - auto& meshEntry = *data->LODs[lodIndex].Meshes[meshIndex]; - - // Material Slot index - int32 materialSlotIndex; - headerStream->ReadInt32(&materialSlotIndex); - if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount) - { - LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); - return Asset::LoadResult::InvalidData; - } - meshEntry.MaterialSlotIndex = materialSlotIndex; - - // Box - BoundingBox box; - headerStream->Read(box); - - // Sphere - BoundingSphere sphere; - headerStream->Read(sphere); - } - - // Get meshes data - { - auto lodData = context.Input.Header.Chunks[1 + lodIndex]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[lodIndex].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion22(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of LODs - const int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - data->LODs.Resize(lodsCount, false); - - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Conversion from older format: 1-1 material slot - mesh mapping, new format allows to use the same material slot for many meshes across different lods - meshEntry.MaterialSlotIndex = i; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Force Two Sided - headerStream->ReadBool(); - - // Cast Shadows - const bool castShadows = headerStream->ReadBool(); - slot.ShadowsMode = castShadows ? ShadowsCastingMode::All : ShadowsCastingMode::None; - - // Default material ID - headerStream->Read(slot.AssetID); - - // Box - BoundingBox box; - headerStream->Read(box); - - // Sphere - BoundingSphere sphere; - headerStream->Read(sphere); - } - - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion20(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs.Resize(1, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // Amount of LODs - int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Force Two Sided - headerStream->ReadBool(); - - // Default material ID - headerStream->Read(slot.AssetID); - - // Box - BoundingBox box; - headerStream->Read(box); - - // Sphere - BoundingSphere sphere; - headerStream->Read(sphere); - } - - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion19(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs.Resize(1, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // Amount of LODs - int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Force Two Sided - headerStream->ReadBool(); - - // Default material ID - headerStream->Read(slot.AssetID); - - // Box - BoundingBox box; - headerStream->Read(box); - - // Sphere - BoundingSphere sphere; - headerStream->Read(sphere); - } - - // Load all LODs - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion18(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs.Resize(1, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // Amount of LODs - int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - - // Options version code - uint32 optionsVersionCode; - headerStream->ReadUint32(&optionsVersionCode); - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Local Transform - Transform transform; - headerStream->Read(transform); - - // Force Two Sided - headerStream->ReadBool(); - - // Default material ID - headerStream->Read(slot.AssetID); - - // Corners - BoundingBox box; - headerStream->Read(box); - } - - // Load all LODs - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion17(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs.Resize(1, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // Amount of LODs - int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - - // Options version code - uint32 optionsVersionCode; - headerStream->ReadUint32(&optionsVersionCode); - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Local Transform - Transform transform; - headerStream->Read(transform); - - // Force Two Sided - headerStream->ReadBool(); - - // Default material ID - headerStream->Read(slot.AssetID); - - // Corners - Vector3 corner; - for (int32 cornerIndex = 0; cornerIndex < 8; cornerIndex++) - headerStream->Read(corner); - } - - // Load all LODs - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - -#if COMPILE_WITH_MODEL_TOOL - if (mesh.GenerateLightmapUVs()) - return Asset::LoadResult::Failed; -#endif - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion16(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs.Resize(1, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // Amount of LODs - int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - - // Options version code - uint32 optionsVersionCode; - headerStream->ReadUint32(&optionsVersionCode); - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Local Transform - Transform transform; - headerStream->Read(transform); - - // Force Two Sided - headerStream->ReadBool(); - - // Default material ID - headerStream->Read(slot.AssetID); - } - - // Load all LODs - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffers - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb0, vb1, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - -#if COMPILE_WITH_MODEL_TOOL - if (mesh.GenerateLightmapUVs()) - return Asset::LoadResult::Failed; -#endif - } - } - - return Asset::LoadResult::Ok; - } - - static Asset::LoadResult loadVersion15(AssetMigrationContext& context, ReadStream* headerStream, ModelData* data) - { - // Amount of meshes (and material slots for older formats) - uint16 meshesCount; - headerStream->ReadUint16(&meshesCount); - data->Materials.Resize(meshesCount, false); - data->LODs.Resize(1, false); - data->LODs[0].Meshes.Resize(meshesCount); - - // Amount of LODs - int32 lodsCount = headerStream->ReadByte(); - if (lodsCount != 1) - return Asset::LoadResult::InvalidData; - - // Options version code - uint32 optionsVersionCode; - headerStream->ReadUint32(&optionsVersionCode); - - // For each mesh - for (int32 i = 0; i < meshesCount; i++) - { - data->LODs[0].Meshes[i] = New(); - auto& meshEntry = *data->LODs[0].Meshes[i]; - auto& slot = data->Materials[i]; - - // Name - headerStream->ReadString(&slot.Name, 7); - - // Local Transform - Transform transform; - headerStream->Read(transform); - - // Force Two Sided - headerStream->ReadBool(); - - // Default material ID - headerStream->Read(slot.AssetID); - } - - // Load all LODs - for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) - { - // Get data - auto lodData = context.Input.Header.Chunks[1]; - MemoryReadStream stream(lodData->Get(), lodData->Size()); - - // Load LOD for each mesh - for (int32 i = 0; i < meshesCount; i++) - { - // Load mesh data - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - if (vertices == 0 || triangles == 0) - return Asset::LoadResult::InvalidData; - - // Vertex buffer - auto vb = stream.Move(vertices); - - // Index Buffer - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - auto ib = stream.Move(indicesCount * ibStride); - - // Copy data - auto& mesh = *data->LODs[0].Meshes[i]; - mesh.InitFromModelVertices(vb, vertices); - mesh.SetIndexBuffer(ib, indicesCount); - -#if COMPILE_WITH_MODEL_TOOL - if (mesh.GenerateLightmapUVs()) - return Asset::LoadResult::Failed; -#endif - } - } - - return Asset::LoadResult::Ok; - } - - static bool Upgrade_22OrNewer_To_Newest(AssetMigrationContext& context) - { - // Get header chunk - auto chunk0 = context.Input.Header.Chunks[0]; - if (chunk0 == nullptr || chunk0->IsMissing()) - return true; - MemoryReadStream headerStream(chunk0->Get(), chunk0->Size()); - - // Load model data from the older format - ModelData modelData; - auto result = LoadOld(context, context.Input.SerializedVersion, headerStream, modelData); - if (result != Asset::LoadResult::Ok) - { - LOG(Warning, "Model old-format data conversion failed."); - return true; - } - - // Pack model header - { - if (context.AllocateChunk(0)) - return true; - - MemoryWriteStream newHeaderStream(512); - if (modelData.Pack2ModelHeader(&newHeaderStream)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(newHeaderStream.GetHandle(), newHeaderStream.GetPosition()); - } - - // Copy LODs - for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++) - { - int32 chunkIndex = lodIndex + 1; - auto srcLodData = context.Input.Header.Chunks[chunkIndex]; - if (srcLodData == nullptr || srcLodData->IsMissing()) - { - LOG(Warning, "Missing model LOD data chunk"); - return true; - } - if (context.AllocateChunk(chunkIndex)) - return true; - context.Output.Header.Chunks[chunkIndex]->Data.Copy(context.Input.Header.Chunks[chunkIndex]->Data); - } - - return false; - } - - static bool Upgrade_With_Repack(AssetMigrationContext& context) - { - // Get header chunk - auto chunk0 = context.Input.Header.Chunks[0]; - if (chunk0 == nullptr || chunk0->IsMissing()) - return true; - MemoryReadStream headerStream(chunk0->Get(), chunk0->Size()); - - // Load model data from the older format - ModelData modelData; - auto result = LoadOld(context, context.Input.SerializedVersion, headerStream, modelData); - if (result != Asset::LoadResult::Ok) - { - LOG(Warning, "Model old-format data conversion failed."); - return true; - } - - // Pack model header - { - if (context.AllocateChunk(0)) - return true; - - MemoryWriteStream newHeaderStream(512); - if (modelData.Pack2ModelHeader(&newHeaderStream)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(newHeaderStream.GetHandle(), newHeaderStream.GetPosition()); - } - - // Pack model LODs data - MemoryWriteStream stream(4095); - const auto lodsCount = modelData.GetLODsCount(); - for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) - { - stream.SetPosition(0); - - // Pack meshes - auto& meshes = modelData.LODs[lodIndex].Meshes; - for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) - { - if (meshes[meshIndex]->Pack2Model(&stream)) - { - LOG(Warning, "Cannot pack mesh."); - return true; - } - } - - const int32 chunkIndex = lodIndex + 1; - if (context.AllocateChunk(chunkIndex)) - return true; - context.Output.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); - } - - return false; - } - - static bool Upgrade_Old_To_Newest(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1); - - // Get header chunk - auto chunk0 = context.Input.Header.Chunks[0]; - if (chunk0 == nullptr || chunk0->IsMissing()) - return true; - MemoryReadStream headerStream(chunk0->Get(), chunk0->Size()); - - // Load header entry - int32 magicCode; - headerStream.ReadInt32(&magicCode); - if (magicCode != -842185139) - { - LOG(Warning, "Invalid header data."); - return true; - } - int32 version; - headerStream.ReadInt32(&version); - - // Load model data from the older format - ModelData modelData; - auto result = LoadOld(context, version, headerStream, modelData); - if (result != Asset::LoadResult::Ok) - { - LOG(Warning, "Model old-format data conversion failed."); - return true; - } - - // Pack model header - { - if (context.AllocateChunk(0)) - return true; - - MemoryWriteStream newHeaderStream(512); - if (modelData.Pack2ModelHeader(&newHeaderStream)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(newHeaderStream.GetHandle(), newHeaderStream.GetPosition()); - } - - // Copy LOD (older versons were using just a single LOD) - { - auto srcLodData = context.Input.Header.Chunks[1]; - if (srcLodData == nullptr || srcLodData->IsMissing()) - { - LOG(Warning, "Missing model LOD data chunk"); - return true; - } - if (context.AllocateChunk(1)) - return true; - context.Output.Header.Chunks[1]->Data.Copy(context.Input.Header.Chunks[1]->Data); - } - - return false; - } }; #endif diff --git a/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h b/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h index 09958f96e..97c41dabd 100644 --- a/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h @@ -5,8 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Platform/Platform.h" -#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h" /// /// Material Asset and Shader Asset Upgrader @@ -22,96 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 18, 19, &Upgrade_18_To_19 }, - { 19, 20, &Upgrade_19_To_20 }, + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - // ============================================ - // Versions 18,19,20: - // Designed: 7/24/2019 - // Custom Data: Header - // Chunk 0: Material Params - // Chunk 1: Internal SM5 cache - // Chunk 2: Internal SM4 cache - // Chunk 14: Visject Surface data - // Chunk 15: Source code: ANSI text (encrypted) - // ============================================ - - typedef ShaderStorage::Header Header; - typedef ShaderStorage::Header20 Header20; - typedef ShaderStorage::Header19 Header19; - typedef ShaderStorage::Header18 Header18; - - static bool Upgrade_19_To_20(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 19 && context.Output.SerializedVersion == 20); - - // Convert header - if (context.Input.CustomData.IsInvalid()) - return true; - auto& oldHeader = *(Header19*)context.Input.CustomData.Get(); - Header20 newHeader; - Platform::MemoryClear(&newHeader, sizeof(newHeader)); - if (context.Input.Header.TypeName == TEXT("FlaxEngine.ParticleEmitter")) - { - newHeader.ParticleEmitter.GraphVersion = oldHeader.ParticleEmitter.GraphVersion; - newHeader.ParticleEmitter.CustomDataSize = oldHeader.ParticleEmitter.CustomDataSize; - } - else if (context.Input.Header.TypeName == TEXT("FlaxEngine.Material")) - { - newHeader.Material.GraphVersion = oldHeader.Material.GraphVersion; - newHeader.Material.Info = oldHeader.Material.Info; - } - else if (context.Input.Header.TypeName == TEXT("FlaxEngine.Shader")) - { - } - else - { - LOG(Warning, "Unknown input asset type."); - return true; - } - context.Output.CustomData.Copy(&newHeader); - - // Copy all chunks - return CopyChunks(context); - } - - static bool Upgrade_18_To_19(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 18 && context.Output.SerializedVersion == 19); - - // Convert header - if (context.Input.CustomData.IsInvalid()) - return true; - auto& oldHeader = *(Header18*)context.Input.CustomData.Get(); - Header19 newHeader; - Platform::MemoryClear(&newHeader, sizeof(newHeader)); - if (context.Input.Header.TypeName == TEXT("FlaxEngine.ParticleEmitter")) - { - newHeader.ParticleEmitter.GraphVersion = oldHeader.MaterialVersion; - newHeader.ParticleEmitter.CustomDataSize = oldHeader.MaterialInfo.MaxTessellationFactor; - } - else if (context.Input.Header.TypeName == TEXT("FlaxEngine.Material")) - { - newHeader.Material.GraphVersion = oldHeader.MaterialVersion; - newHeader.Material.Info = oldHeader.MaterialInfo; - } - else if (context.Input.Header.TypeName == TEXT("FlaxEngine.Shader")) - { - } - else - { - LOG(Warning, "Unknown input asset type."); - return true; - } - context.Output.CustomData.Copy(&newHeader); - - // Copy all chunks - return CopyChunks(context); - } }; #endif diff --git a/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h b/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h index 93d01639c..0c7944b91 100644 --- a/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h @@ -5,10 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Content/Assets/SkinnedModel.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Serialization/MemoryWriteStream.h" -#include "Engine/Threading/Threading.h" /// /// Skeleton Mask asset upgrader. @@ -24,79 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 1, 2, &Upgrade_1_To_2 }, + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - static bool Upgrade_1_To_2(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 2); - - // Load data - AssetReference skeleton; - Array bonesMask; - { - auto dataChunk = context.Input.Header.Chunks[0]; - if (dataChunk == nullptr || dataChunk->IsMissing()) - { - LOG(Warning, "Missing data chunk."); - return true; - } - MemoryReadStream stream(dataChunk->Get(), dataChunk->Size()); - - Guid skeletonId; - stream.Read(skeletonId); - int32 maskCount; - stream.ReadInt32(&maskCount); - bonesMask.Resize(maskCount, false); - if (maskCount > 0) - stream.ReadBytes(bonesMask.Get(), maskCount * sizeof(bool)); - - skeleton = skeletonId; - if (skeleton && skeleton->WaitForLoaded()) - { - LOG(Warning, "Failed to load skeleton to update skeleton mask."); - return true; - } - } - - // Build nodes mask from bones - Array nodesMask; - if (skeleton) - { - ScopeLock lock(skeleton->Locker); - - for (int32 i = 0; i < bonesMask.Count(); i++) - { - if (bonesMask[i]) - { - int32 nodeIndex = skeleton->Skeleton.Bones[i].NodeIndex; - nodesMask.Add(skeleton->Skeleton.Nodes[nodeIndex].Name); - } - } - } - - // Save data - { - if (context.AllocateChunk(0)) - return true; - MemoryWriteStream stream(4096); - - const Guid skeletonId = skeleton.GetID(); - stream.Write(skeletonId); - stream.WriteInt32(nodesMask.Count()); - for (auto& e : nodesMask) - { - stream.WriteString(e, -13); - } - - context.Output.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); - } - - return false; - } }; #endif diff --git a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h index b7800238c..fdd9e66a5 100644 --- a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h @@ -5,14 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Platform/Platform.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Serialization/MemoryWriteStream.h" -#include "Engine/Graphics/Models/Types.h" -#include "Engine/Core/Math/BoundingBox.h" -#include "Engine/Core/Math/BoundingSphere.h" -#include "Engine/Core/Math/Matrix.h" -#include "Engine/Core/Math/Transform.h" /// /// Skinned Model Asset Upgrader @@ -28,635 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 1, 2, &Upgrade_1_To_2 }, // [Deprecated on 28.04.2023, expires on 01.01.2024] - { 2, 3, &Upgrade_2_To_3 }, // [Deprecated on 28.04.2023, expires on 01.01.2024] - { 3, 4, &Upgrade_3_To_4 }, // [Deprecated on 28.04.2023, expires on 01.01.2024] - { 4, 5, &Upgrade_4_To_5 }, // [Deprecated on 28.04.2023, expires on 28.04.2026] + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - static bool Upgrade_1_To_2(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 2); - - // Copy header chunk (header format is the same) - if (CopyChunk(context, 0)) - return true; - - // Get mesh data chunk - const auto srcData = context.Input.Header.Chunks[1]; - if (srcData == nullptr || srcData->IsMissing()) - { - LOG(Warning, "Missing model data chunk"); - return true; - } - - // Upgrade meshes data - MemoryReadStream stream(srcData->Get(), srcData->Size()); - MemoryWriteStream output(srcData->Size()); - do - { - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - const uint32 indicesCount = triangles * 3; - const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - const auto vb0 = stream.Move(vertices); - const auto ib = stream.Move(indicesCount * ibStride); - - // Write back - output.WriteUint32(vertices); - output.WriteUint32(triangles); - for (uint32 i = 0; i < vertices; i++) - { - const VB0SkinnedElementType1 oldVertex = vb0[i]; - VB0SkinnedElementType2 newVertex; - newVertex.Position = oldVertex.Position; - newVertex.TexCoord = oldVertex.TexCoord; - newVertex.Normal = oldVertex.Normal; - newVertex.Tangent = oldVertex.Tangent; - newVertex.BlendIndices = oldVertex.BlendIndices; - Float4 blendWeights(oldVertex.BlendWeights.R / 255.0f, oldVertex.BlendWeights.G / 255.0f, oldVertex.BlendWeights.B / 255.0f, oldVertex.BlendWeights.A / 255.0f); - const float sum = blendWeights.SumValues(); - const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; - blendWeights *= invSum; - newVertex.BlendWeights = Half4(blendWeights); - output.WriteBytes(&newVertex, sizeof(newVertex)); - } - output.WriteBytes(ib, indicesCount * ibStride); - } while (stream.CanRead()); - - // Save new data - if (context.AllocateChunk(1)) - return true; - context.Output.Header.Chunks[1]->Data.Copy(output.GetHandle(), output.GetPosition()); - - return false; - } - - static bool Upgrade_2_To_3(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 2 && context.Output.SerializedVersion == 3); - - // Copy meshes data chunk (format is the same) - if (CopyChunk(context, 1)) - return true; - - // Rewrite header chunk (added LOD count) - const auto srcData = context.Input.Header.Chunks[0]; - if (srcData == nullptr || srcData->IsMissing()) - { - LOG(Warning, "Missing model header chunk"); - return true; - } - MemoryReadStream stream(srcData->Get(), srcData->Size()); - MemoryWriteStream output(srcData->Size()); - { - // Min Screen Size - float minScreenSize; - stream.ReadFloat(&minScreenSize); - output.WriteFloat(minScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - stream.ReadInt32(&materialSlotsCount); - output.WriteInt32(materialSlotsCount); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) - { - // Material - Guid materialId; - stream.Read(materialId); - output.Write(materialId); - - // Shadows Mode - output.WriteByte(stream.ReadByte()); - - // Name - String name; - stream.ReadString(&name, 11); - output.WriteString(name, 11); - } - - // Amount of LODs - output.WriteByte(1); - - // Screen Size - output.WriteFloat(1.0f); - - // Amount of meshes - uint16 meshesCount; - stream.ReadUint16(&meshesCount); - output.WriteUint16(meshesCount); - - // For each mesh - for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - // Material Slot index - int32 materialSlotIndex; - stream.ReadInt32(&materialSlotIndex); - output.WriteInt32(materialSlotIndex); - - // Box - BoundingBox box; - stream.Read(box); - output.Write(box); - - // Sphere - BoundingSphere sphere; - stream.Read(sphere); - output.Write(sphere); - } - - // Skeleton - { - int32 nodesCount; - stream.ReadInt32(&nodesCount); - output.WriteInt32(nodesCount); - - // For each node - for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) - { - int32 parentIndex; - stream.ReadInt32(&parentIndex); - output.WriteInt32(parentIndex); - - Transform localTransform; - stream.Read(localTransform); - output.Write(localTransform); - - String name; - stream.ReadString(&name, 71); - output.WriteString(name, 71); - } - - int32 bonesCount; - stream.ReadInt32(&bonesCount); - output.WriteInt32(bonesCount); - - // For each bone - for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) - { - int32 parentIndex; - stream.ReadInt32(&parentIndex); - output.WriteInt32(parentIndex); - - int32 nodeIndex; - stream.ReadInt32(&nodeIndex); - output.WriteInt32(nodeIndex); - - Transform localTransform; - stream.Read(localTransform); - output.Write(localTransform); - - Matrix offsetMatrix; - stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); - output.WriteBytes(&offsetMatrix, sizeof(Matrix)); - } - } - } - - // Save new header data - if (context.AllocateChunk(0)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); - - return false; - } - - static bool Upgrade_3_To_4(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4); - - // Rewrite header chunk (added blend shapes count) - byte lodCount; - Array meshesCounts; - { - const auto srcData = context.Input.Header.Chunks[0]; - if (srcData == nullptr || srcData->IsMissing()) - { - LOG(Warning, "Missing model header chunk"); - return true; - } - MemoryReadStream stream(srcData->Get(), srcData->Size()); - MemoryWriteStream output(srcData->Size()); - - // Min Screen Size - float minScreenSize; - stream.ReadFloat(&minScreenSize); - output.WriteFloat(minScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - stream.ReadInt32(&materialSlotsCount); - output.WriteInt32(materialSlotsCount); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) - { - // Material - Guid materialId; - stream.Read(materialId); - output.Write(materialId); - - // Shadows Mode - output.WriteByte(stream.ReadByte()); - - // Name - String name; - stream.ReadString(&name, 11); - output.WriteString(name, 11); - } - - // Amount of LODs - stream.ReadByte(&lodCount); - output.WriteByte(lodCount); - meshesCounts.Resize(lodCount); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) - { - // Screen Size - float screenSize; - stream.ReadFloat(&screenSize); - output.WriteFloat(screenSize); - - // Amount of meshes - uint16 meshesCount; - stream.ReadUint16(&meshesCount); - output.WriteUint16(meshesCount); - meshesCounts[lodIndex] = meshesCount; - - // For each mesh - for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - // Material Slot index - int32 materialSlotIndex; - stream.ReadInt32(&materialSlotIndex); - output.WriteInt32(materialSlotIndex); - - // Box - BoundingBox box; - stream.Read(box); - output.Write(box); - - // Sphere - BoundingSphere sphere; - stream.Read(sphere); - output.Write(sphere); - - // Blend Shapes - output.WriteUint16(0); - } - } - - // Skeleton - { - int32 nodesCount; - stream.ReadInt32(&nodesCount); - output.WriteInt32(nodesCount); - - // For each node - for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) - { - int32 parentIndex; - stream.ReadInt32(&parentIndex); - output.WriteInt32(parentIndex); - - Transform localTransform; - stream.Read(localTransform); - output.Write(localTransform); - - String name; - stream.ReadString(&name, 71); - output.WriteString(name, 71); - } - - int32 bonesCount; - stream.ReadInt32(&bonesCount); - output.WriteInt32(bonesCount); - - // For each bone - for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) - { - int32 parentIndex; - stream.ReadInt32(&parentIndex); - output.WriteInt32(parentIndex); - - int32 nodeIndex; - stream.ReadInt32(&nodeIndex); - output.WriteInt32(nodeIndex); - - Transform localTransform; - stream.Read(localTransform); - output.Write(localTransform); - - Matrix offsetMatrix; - stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); - output.WriteBytes(&offsetMatrix, sizeof(Matrix)); - } - } - - // Save new data - if (stream.GetPosition() != stream.GetLength()) - { - LOG(Error, "Invalid position after upgrading skinned model header data."); - return true; - } - if (context.AllocateChunk(0)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); - } - - // Rewrite meshes data chunks (blend shapes added) - for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) - { - const int32 chunkIndex = lodIndex + 1; - const auto srcData = context.Input.Header.Chunks[chunkIndex]; - if (srcData == nullptr || srcData->IsMissing()) - { - LOG(Warning, "Missing skinned model LOD meshes data chunk"); - return true; - } - MemoryReadStream stream(srcData->Get(), srcData->Size()); - MemoryWriteStream output(srcData->Size()); - - for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++) - { - uint32 vertices; - stream.ReadUint32(&vertices); - output.WriteUint32(vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - output.WriteUint32(triangles); - uint16 blendShapesCount = 0; - output.WriteUint16(blendShapesCount); - const uint32 indicesCount = triangles * 3; - const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - const auto vb0 = stream.Move(vertices); - output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType)); - const auto ib = stream.Move(indicesCount * ibStride); - output.WriteBytes(ib, indicesCount * ibStride); - } - - // Save new data - if (stream.GetPosition() != stream.GetLength()) - { - LOG(Error, "Invalid position after upgrading skinned model LOD meshes data."); - return true; - } - if (context.AllocateChunk(chunkIndex)) - return true; - context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); - } - - return false; - } - - static bool Upgrade_4_To_5(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5); - - // Changes: - // - added version number to header (for easier changes in future) - // - added version number to mesh data (for easier changes in future) - // - added skeleton retarget setups to header - - // Rewrite header chunk (added header version and retarget entries) - byte lodCount; - Array meshesCounts; - { - const auto srcData = context.Input.Header.Chunks[0]; - if (srcData == nullptr || srcData->IsMissing()) - { - LOG(Warning, "Missing model header chunk"); - return true; - } - MemoryReadStream stream(srcData->Get(), srcData->Size()); - MemoryWriteStream output(srcData->Size()); - - // Header Version - output.WriteByte(1); - - // Min Screen Size - float minScreenSize; - stream.ReadFloat(&minScreenSize); - output.WriteFloat(minScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - stream.ReadInt32(&materialSlotsCount); - output.WriteInt32(materialSlotsCount); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) - { - // Material - Guid materialId; - stream.Read(materialId); - output.Write(materialId); - - // Shadows Mode - output.WriteByte(stream.ReadByte()); - - // Name - String name; - stream.ReadString(&name, 11); - output.WriteString(name, 11); - } - - // Amount of LODs - stream.ReadByte(&lodCount); - output.WriteByte(lodCount); - meshesCounts.Resize(lodCount); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) - { - // Screen Size - float screenSize; - stream.ReadFloat(&screenSize); - output.WriteFloat(screenSize); - - // Amount of meshes - uint16 meshesCount; - stream.ReadUint16(&meshesCount); - output.WriteUint16(meshesCount); - meshesCounts[lodIndex] = meshesCount; - - // For each mesh - for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - // Material Slot index - int32 materialSlotIndex; - stream.ReadInt32(&materialSlotIndex); - output.WriteInt32(materialSlotIndex); - - // Box - BoundingBox box; - stream.Read(box); - output.Write(box); - - // Sphere - BoundingSphere sphere; - stream.Read(sphere); - output.Write(sphere); - - // Blend Shapes - uint16 blendShapes; - stream.ReadUint16(&blendShapes); - output.WriteUint16(blendShapes); - for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++) - { - String blendShapeName; - stream.ReadString(&blendShapeName, 13); - output.WriteString(blendShapeName, 13); - float blendShapeWeight; - stream.ReadFloat(&blendShapeWeight); - output.WriteFloat(blendShapeWeight); - } - } - } - - // Skeleton - { - int32 nodesCount; - stream.ReadInt32(&nodesCount); - output.WriteInt32(nodesCount); - - // For each node - for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) - { - int32 parentIndex; - stream.ReadInt32(&parentIndex); - output.WriteInt32(parentIndex); - - Transform localTransform; - stream.Read(localTransform); - output.Write(localTransform); - - String name; - stream.ReadString(&name, 71); - output.WriteString(name, 71); - } - - int32 bonesCount; - stream.ReadInt32(&bonesCount); - output.WriteInt32(bonesCount); - - // For each bone - for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) - { - int32 parentIndex; - stream.ReadInt32(&parentIndex); - output.WriteInt32(parentIndex); - - int32 nodeIndex; - stream.ReadInt32(&nodeIndex); - output.WriteInt32(nodeIndex); - - Transform localTransform; - stream.Read(localTransform); - output.Write(localTransform); - - Matrix offsetMatrix; - stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); - output.WriteBytes(&offsetMatrix, sizeof(Matrix)); - } - } - - // Retargeting - { - output.WriteInt32(0); - } - - // Save new data - if (stream.GetPosition() != stream.GetLength()) - { - LOG(Error, "Invalid position after upgrading skinned model header data."); - return true; - } - if (context.AllocateChunk(0)) - return true; - context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); - } - - // Rewrite meshes data chunks - for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) - { - const int32 chunkIndex = lodIndex + 1; - const auto srcData = context.Input.Header.Chunks[chunkIndex]; - if (srcData == nullptr || srcData->IsMissing()) - { - LOG(Warning, "Missing skinned model LOD meshes data chunk"); - return true; - } - MemoryReadStream stream(srcData->Get(), srcData->Size()); - MemoryWriteStream output(srcData->Size()); - - // Mesh Data Version - output.WriteByte(1); - - for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++) - { - uint32 vertices; - stream.ReadUint32(&vertices); - output.WriteUint32(vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - output.WriteUint32(triangles); - uint16 blendShapesCount; - stream.ReadUint16(&blendShapesCount); - output.WriteUint16(blendShapesCount); - for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++) - { - output.WriteBool(stream.ReadBool()); - uint32 minVertexIndex, maxVertexIndex; - stream.ReadUint32(&minVertexIndex); - output.WriteUint32(minVertexIndex); - stream.ReadUint32(&maxVertexIndex); - output.WriteUint32(maxVertexIndex); - uint32 blendShapeVertices; - stream.ReadUint32(&blendShapeVertices); - output.WriteUint32(blendShapeVertices); - const uint32 blendShapeDataSize = blendShapeVertices * sizeof(BlendShapeVertex); - const auto blendShapeData = stream.Move(blendShapeDataSize); - output.WriteBytes(blendShapeData, blendShapeDataSize); - } - const uint32 indicesCount = triangles * 3; - const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - const auto vb0 = stream.Move(vertices); - output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType)); - const auto ib = stream.Move(indicesCount * ibStride); - output.WriteBytes(ib, indicesCount * ibStride); - } - - // Save new data - if (stream.GetPosition() != stream.GetLength()) - { - LOG(Error, "Invalid position after upgrading skinned model LOD meshes data."); - return true; - } - if (context.AllocateChunk(chunkIndex)) - return true; - context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); - } - - return false; - } }; #endif diff --git a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h index 53b224b79..efe5c6547 100644 --- a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h @@ -5,19 +5,6 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" -#include "Engine/Core/Core.h" -#include "Engine/Platform/Platform.h" -#include "Engine/Graphics/RenderTools.h" -#include "Engine/Content/Assets/CubeTexture.h" -#include "Engine/Content/Assets/IESProfile.h" -#include "Engine/Content/Assets/Texture.h" -#include "Engine/Utilities/Encryption.h" -#include "Engine/Serialization/JsonTools.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Render2D/SpriteAtlas.h" -#if USE_EDITOR -#include "Editor/Content/PreviewsCache.h" -#endif /// /// Texture Asset Upgrader @@ -33,792 +20,10 @@ public: { static const Upgrader upgraders[] = { - { 1, 4, &Upgrade_1_To_4 }, + {}, }; setup(upgraders, ARRAY_COUNT(upgraders)); } - -private: - // ============================================ - // Versions 1, 2 and 3: - // Designed: long time ago in a galaxy far far away - // Custom Data: not used - // Chunk 0: Header (sprite atlas also stored sprites) - // Chunk 1-14: Texture Mips - // Chunk 15: Texture Import options - // ============================================ - // - // Note: we merge CubeTexture, Texture, AssetPreviewsCache, IESProfile and AssetSpriteAtlas storage backend. - // This simplifies textures operations like importing/exporting and allows to reduce amount of code to maintain. - // - // Firstly store texture header inside a CustomData field and get it just after asset creation. - // Secondly move texture import metadata (format, max size, etc.) to the json metadata. - // Lastly store all texture mips in chunks 1-14 and use 15th and 16th chunk for custom data per asset type (sprites or thumbnails meta, etc.). - // Note: mips order has been changed, Chunk[0..13] contain now Mip[0..13]. Internal storage layer should manage data chunks order inside a file, not an asset. - // Additionally texture header contains a few bytes to be used by the assets (for eg. IES Profile uses it to store Brightness parameter) - // - // Old AssetPreviewsCache: - // Chunk 0: header - // Chunk 1: assets ID table - // Chunk 2: atlas image - // ============================================ - // Version 4: - // Designed: 4/25/2017 - // Custom Data: Header - // Metadata: Import Options - // Chunk 0-13: Mips (from the highest quality to the lowest) - // Chunk 15: Custom Asset Data: - // - AssetSpriteAtlas: sprites - // - AssetPreviewsCache: asset previews meta - // ============================================ - - enum class PixelFormatOld - { - PF_Unknown = 0, - PF_R8G8B8A8 = 1, - PF_R16G16B16A16 = 2, - PF_R32G32B32A32 = 3, - PF_A8 = 4, - PF_R16F = 5, - PF_R32 = 6, - PF_R16G16 = 7, - PF_R32G32 = 8, - PF_R10G10B10A2 = 9, - PF_R11G11B10 = 10, - PF_BC1 = 11, - PF_BC2 = 12, - PF_BC3 = 13, - PF_BC4 = 14, - PF_BC5 = 15, - PF_B8G8R8A8 = 16, - PF_DepthStencil = 17, - PF_ShadowDepth = 18, - PF_D16 = 19, - PF_D24 = 20, - PF_D32 = 21, - PF_R16 = 22, - PF_B5G6R5 = 23, - PF_R8G8_SNORM = 24, - PF_R8G8_UNORM = 25, - }; - - static PixelFormat PixelFormatOldToNew(PixelFormatOld format) - { - switch (format) - { - case PixelFormatOld::PF_Unknown: - return PixelFormat::Unknown; - case PixelFormatOld::PF_R8G8B8A8: - return PixelFormat::R8G8B8A8_UNorm; - case PixelFormatOld::PF_R16G16B16A16: - return PixelFormat::R16G16B16A16_Float; - case PixelFormatOld::PF_R32G32B32A32: - return PixelFormat::R32G32B32A32_Float; - case PixelFormatOld::PF_A8: - return PixelFormat::A8_UNorm; - case PixelFormatOld::PF_R16F: - return PixelFormat::R16_Float; - case PixelFormatOld::PF_R32: - return PixelFormat::R32_Float; - case PixelFormatOld::PF_R16G16: - return PixelFormat::R16G16_Float; - case PixelFormatOld::PF_R32G32: - return PixelFormat::R32G32_Float; - case PixelFormatOld::PF_R10G10B10A2: - return PixelFormat::R10G10B10A2_UNorm; - case PixelFormatOld::PF_R11G11B10: - return PixelFormat::R11G11B10_Float; - case PixelFormatOld::PF_BC1: - return PixelFormat::BC1_UNorm; - case PixelFormatOld::PF_BC2: - return PixelFormat::BC2_UNorm; - case PixelFormatOld::PF_BC3: - return PixelFormat::BC3_UNorm; - case PixelFormatOld::PF_BC4: - return PixelFormat::BC4_UNorm; - case PixelFormatOld::PF_BC5: - return PixelFormat::BC5_UNorm; - case PixelFormatOld::PF_B8G8R8A8: - return PixelFormat::B8G8R8A8_UNorm; - case PixelFormatOld::PF_DepthStencil: - return PixelFormat::D24_UNorm_S8_UInt; - case PixelFormatOld::PF_ShadowDepth: - return PixelFormat::D32_Float; - case PixelFormatOld::PF_D16: - return PixelFormat::D16_UNorm; - case PixelFormatOld::PF_D24: - return PixelFormat::D24_UNorm_S8_UInt; - case PixelFormatOld::PF_D32: - return PixelFormat::D32_Float; - case PixelFormatOld::PF_R16: - return PixelFormat::R16_UNorm; - case PixelFormatOld::PF_B5G6R5: - return PixelFormat::B5G6R5_UNorm; - case PixelFormatOld::PF_R8G8_SNORM: - return PixelFormat::R8G8_SNorm; - case PixelFormatOld::PF_R8G8_UNORM: - return PixelFormat::R8G8_UNorm; - default: ; - } - - CRASH; - return PixelFormat::Unknown; - } - - static PixelFormatOld PixelFormatToOld(PixelFormat format) - { - switch (format) - { - case PixelFormat::Unknown: - return PixelFormatOld::PF_Unknown; - case PixelFormat::R32G32B32A32_Typeless: - case PixelFormat::R32G32B32A32_Float: - case PixelFormat::R32G32B32A32_UInt: - case PixelFormat::R32G32B32A32_SInt: - return PixelFormatOld::PF_R32G32B32A32; - //case PixelFormat::R32G32B32_Typeless: - //case PixelFormat::R32G32B32_Float: - //case PixelFormat::R32G32B32_UInt: - //case PixelFormat::R32G32B32_SInt: - case PixelFormat::R16G16B16A16_Typeless: - case PixelFormat::R16G16B16A16_Float: - case PixelFormat::R16G16B16A16_UNorm: - case PixelFormat::R16G16B16A16_UInt: - case PixelFormat::R16G16B16A16_SNorm: - case PixelFormat::R16G16B16A16_SInt: - return PixelFormatOld::PF_R16G16B16A16; - case PixelFormat::R32G32_Typeless: - case PixelFormat::R32G32_Float: - case PixelFormat::R32G32_UInt: - case PixelFormat::R32G32_SInt: - case PixelFormat::R32G8X24_Typeless: - return PixelFormatOld::PF_R32G32; - case PixelFormat::D32_Float_S8X24_UInt: - case PixelFormat::R32_Float_X8X24_Typeless: - case PixelFormat::X32_Typeless_G8X24_UInt: - return PixelFormatOld::PF_R32G32B32A32; - case PixelFormat::R10G10B10A2_Typeless: - case PixelFormat::R10G10B10A2_UNorm: - case PixelFormat::R10G10B10A2_UInt: - return PixelFormatOld::PF_R10G10B10A2; - case PixelFormat::R11G11B10_Float: - return PixelFormatOld::PF_R11G11B10; - case PixelFormat::R8G8B8A8_Typeless: - case PixelFormat::R8G8B8A8_UNorm: - case PixelFormat::R8G8B8A8_UNorm_sRGB: - case PixelFormat::R8G8B8A8_UInt: - case PixelFormat::R8G8B8A8_SNorm: - case PixelFormat::R8G8B8A8_SInt: - return PixelFormatOld::PF_R8G8B8A8; - case PixelFormat::R16G16_Typeless: - case PixelFormat::R16G16_Float: - case PixelFormat::R16G16_UNorm: - case PixelFormat::R16G16_UInt: - case PixelFormat::R16G16_SNorm: - case PixelFormat::R16G16_SInt: - return PixelFormatOld::PF_R16G16; - case PixelFormat::R32_Typeless: - return PixelFormatOld::PF_R32; - case PixelFormat::D32_Float: - return PixelFormatOld::PF_DepthStencil; - case PixelFormat::R32_Float: - case PixelFormat::R32_UInt: - case PixelFormat::R32_SInt: - return PixelFormatOld::PF_R32; - case PixelFormat::R24G8_Typeless: - case PixelFormat::D24_UNorm_S8_UInt: - case PixelFormat::R24_UNorm_X8_Typeless: - return PixelFormatOld::PF_DepthStencil; - //case PixelFormat::X24_Typeless_G8_UInt: - case PixelFormat::R8G8_Typeless: - case PixelFormat::R8G8_UNorm: - case PixelFormat::R8G8_UInt: - return PixelFormatOld::PF_R8G8_UNORM; - case PixelFormat::R8G8_SNorm: - case PixelFormat::R8G8_SInt: - return PixelFormatOld::PF_R8G8_SNORM; - case PixelFormat::R16_Float: - return PixelFormatOld::PF_R16F; - case PixelFormat::R16_Typeless: - case PixelFormat::D16_UNorm: - case PixelFormat::R16_UNorm: - case PixelFormat::R16_UInt: - case PixelFormat::R16_SNorm: - case PixelFormat::R16_SInt: - return PixelFormatOld::PF_R16; - case PixelFormat::R8_Typeless: - case PixelFormat::R8_UNorm: - case PixelFormat::R8_UInt: - case PixelFormat::R8_SNorm: - case PixelFormat::R8_SInt: - case PixelFormat::A8_UNorm: - return PixelFormatOld::PF_A8; - case PixelFormat::R1_UNorm: - //case PixelFormat::R9G9B9E5_Sharedexp: - //case PixelFormat::R8G8_B8G8_UNorm: - //case PixelFormat::G8R8_G8B8_UNorm: - case PixelFormat::BC1_Typeless: - case PixelFormat::BC1_UNorm: - case PixelFormat::BC1_UNorm_sRGB: - return PixelFormatOld::PF_BC1; - case PixelFormat::BC2_Typeless: - case PixelFormat::BC2_UNorm: - case PixelFormat::BC2_UNorm_sRGB: - return PixelFormatOld::PF_BC2; - case PixelFormat::BC3_Typeless: - case PixelFormat::BC3_UNorm: - case PixelFormat::BC3_UNorm_sRGB: - return PixelFormatOld::PF_BC3; - case PixelFormat::BC4_Typeless: - case PixelFormat::BC4_UNorm: - case PixelFormat::BC4_SNorm: - return PixelFormatOld::PF_BC4; - case PixelFormat::BC5_Typeless: - case PixelFormat::BC5_UNorm: - case PixelFormat::BC5_SNorm: - return PixelFormatOld::PF_BC5; - case PixelFormat::B5G6R5_UNorm: - return PixelFormatOld::PF_B5G6R5; - //case PixelFormat::B5G5R5A1_UNorm: - //case PixelFormat::B8G8R8A8_UNorm: - //case PixelFormat::B8G8R8X8_UNorm: - //case PixelFormat::R10G10B10_Xr_Bias_A2_UNorm: - //case PixelFormat::B8G8R8A8_Typeless: - //case PixelFormat::B8G8R8A8_UNorm_sRGB: - //case PixelFormat::B8G8R8X8_Typeless: - //case PixelFormat::B8G8R8X8_UNorm_sRGB: - //case PixelFormat::BC6H_Typeless: - //case PixelFormat::BC6H_Uf16: - //case PixelFormat::BC6H_Sf16: - //case PixelFormat::BC7_Typeless: - //case PixelFormat::BC7_UNorm: - //case PixelFormat::BC7_UNorm_sRGB: - default: - break; - } - - CRASH; - return PixelFormatOld::PF_Unknown; - } - - struct TextureHeader1 - { - int32 Width; - int32 Height; - byte MipLevels; - bool NeverStream; - TextureFormatType Type; - PixelFormatOld Format; - bool IsSRGB; - }; - - struct TextureHeader2 - { - int32 Width; - int32 Height; - byte MipLevels; - bool NeverStream; - TextureFormatType Type; - PixelFormatOld Format; - bool IsSRGB; - }; - - struct TextureHeader3 - { - int32 Width; - int32 Height; - int32 MipLevels; - bool IsCubeMap; - bool NeverStream; - TextureFormatType Type; - PixelFormat Format; - bool IsSRGB; - }; - - struct CubeTextureHeader1 - { - int32 Size; - byte MipLevels; - PixelFormatOld Format; - bool IsSRGB; - }; - - enum class TextureOwnerType1 - { - Texture, - CubeTexture, - SpriteAtlas, - IESProfile, - PreviewsCache, - }; - - static bool GetOwnerType(AssetMigrationContext& context, TextureOwnerType1& type) - { - auto typeName = context.Input.Header.TypeName; - - if (typeName == Texture::TypeName) - { - type = TextureOwnerType1::Texture; - return false; - } - if (typeName == CubeTexture::TypeName) - { - type = TextureOwnerType1::CubeTexture; - return false; - } - if (typeName == SpriteAtlas::TypeName) - { - type = TextureOwnerType1::SpriteAtlas; - return false; - } - if (typeName == IESProfile::TypeName) - { - type = TextureOwnerType1::IESProfile; - return false; - } -#if USE_EDITOR - if (typeName == PreviewsCache::TypeName) - { - type = TextureOwnerType1::PreviewsCache; - return false; - } -#endif - - LOG(Warning, "Invalid input asset type."); - return true; - } - - static bool Upgrade_1_To_4_ImportOptions(AssetMigrationContext& context) - { - // Check if has no 15th chunk - auto chunk = context.Input.Header.Chunks[15]; - if (chunk == nullptr || chunk->IsMissing()) - return false; - -#if USE_EDITOR - - // Get import metadata json - rapidjson_flax::Document importMeta; - if (context.Input.Metadata.IsValid()) - { - importMeta.Parse((const char*)context.Input.Metadata.Get(), context.Input.Metadata.Length()); - if (importMeta.HasParseError()) - { - LOG(Warning, "Failed to parse import meta json."); - return true; - } - } - - // Get import options json - rapidjson_flax::Document importOptions; - { - // Decrypt bytes - int32 length = chunk->Size(); - Encryption::DecryptBytes(chunk->Get(), length); - - // Load Json - static_assert(sizeof(char) == sizeof(rapidjson_flax::Document::Ch), "Invalid rapidJson document char size."); - int32 tmpJsonSize = length; - importOptions.Parse((const char*)chunk->Get(), tmpJsonSize); - if (importOptions.HasParseError()) - { - LOG(Warning, "Failed to parse import options json."); - return true; - } - } - - // Merge deprecated json meta from chunk 15 with import metadata - JsonTools::MergeDocuments(importMeta, importOptions); - - // Save json metadata - { - rapidjson_flax::StringBuffer buffer; - rapidjson_flax::Writer writer(buffer); - if (!importMeta.Accept(writer)) - { - LOG(Warning, "Failed to serialize metadata json."); - return true; - } - context.Output.Metadata.Copy((const byte*)buffer.GetString(), (uint32)buffer.GetSize()); - } - -#endif - - return false; - } - - static bool Upgrade_1_To_4_Texture(AssetMigrationContext& context) - { - // Get texture header - auto headerChunk = context.Input.Header.Chunks[0]; - if (headerChunk == nullptr || headerChunk->IsMissing()) - { - LOG(Warning, "Missing texture header chunk."); - return true; - } - MemoryReadStream headerStream(headerChunk->Get(), headerChunk->Size()); - - // Load texture header - int32 textureHeaderVersion; - headerStream.ReadInt32(&textureHeaderVersion); - if (textureHeaderVersion != 1) - { - LOG(Warning, "Invalid texture header version."); - return true; - } - TextureHeader1 textureHeader; - headerStream.ReadBytes(&textureHeader, sizeof(textureHeader)); - - // Create new header - TextureHeader newHeader; - newHeader.Width = textureHeader.Width; - newHeader.Height = textureHeader.Height; - newHeader.MipLevels = textureHeader.MipLevels; - newHeader.Format = PixelFormatOldToNew(textureHeader.Format); - newHeader.IsSRGB = textureHeader.IsSRGB; - newHeader.NeverStream = textureHeader.NeverStream; - newHeader.Type = textureHeader.Type; - context.Output.CustomData.Copy(&newHeader); - - // Convert import options - if (Upgrade_1_To_4_ImportOptions(context)) - return true; - - // Copy mip maps - for (int32 mipIndex = 0; mipIndex < newHeader.MipLevels; mipIndex++) - { - auto srcChunk = context.Input.Header.Chunks[newHeader.MipLevels - mipIndex]; - if (srcChunk == nullptr || srcChunk->IsMissing()) - { - LOG(Warning, "Missing data chunk with a mipmap"); - return true; - } - if (context.AllocateChunk(mipIndex)) - return true; - auto dstChunk = context.Output.Header.Chunks[mipIndex]; - - dstChunk->Data.Copy(srcChunk->Data); - } - - return false; - } - - static bool Upgrade_1_To_4_CubeTexture(AssetMigrationContext& context) - { - // Get texture header - auto headerChunk = context.Input.Header.Chunks[0]; - if (headerChunk == nullptr || headerChunk->IsMissing()) - { - LOG(Warning, "Missing cube texture header chunk."); - return true; - } - MemoryReadStream headerStream(headerChunk->Get(), headerChunk->Size()); - - // Load texture header - int32 textureHeaderVersion; - headerStream.ReadInt32(&textureHeaderVersion); - if (textureHeaderVersion != 1) - { - LOG(Warning, "Invalid cube texture header version."); - return true; - } - CubeTextureHeader1 textureHeader; - headerStream.ReadBytes(&textureHeader, sizeof(textureHeader)); - - // Create new header - TextureHeader newHeader; - newHeader.Width = textureHeader.Size; - newHeader.Height = textureHeader.Size; - newHeader.MipLevels = textureHeader.MipLevels; - newHeader.Format = PixelFormatOldToNew(textureHeader.Format); - newHeader.IsSRGB = textureHeader.IsSRGB; - newHeader.Type = TextureFormatType::ColorRGB; - newHeader.IsCubeMap = true; - context.Output.CustomData.Copy(&newHeader); - - // Convert import options - if (Upgrade_1_To_4_ImportOptions(context)) - return true; - - // Conversion from single chunk to normal texture chunks mapping mode (single mip per chunk) - { - auto cubeTextureData = context.Input.Header.Chunks[1]; - if (cubeTextureData == nullptr || cubeTextureData->IsMissing()) - { - LOG(Warning, "Missing data chunk with a cubemap"); - return true; - } - uint32 dataSize = cubeTextureData->Size(); - auto data = cubeTextureData->Get(); - - auto mipLevels = textureHeader.MipLevels; - auto textureSize = textureHeader.Size; - ASSERT(mipLevels > 0 && mipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS); - ASSERT(textureSize > 0 && textureSize <= GPU_MAX_TEXTURE_SIZE); - - //Platform::MemorySet(cubeTextureData.Get(), cubeTextureData.Length(), MAX_uint32); - - byte* position = (byte*)data; - for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) - { - uint32 mipSize = textureSize >> mipIndex; - - uint32 rowPitch, slicePitch; - RenderTools::ComputePitch(PixelFormatOldToNew(textureHeader.Format), mipSize, mipSize, rowPitch, slicePitch); - - uint32 mipDataSize = slicePitch * 6; - - // Create mip - if (context.AllocateChunk(mipIndex)) - return true; - auto dstChunk = context.Output.Header.Chunks[mipIndex]; - dstChunk->Data.Copy(position, mipDataSize); - - position += mipDataSize; - } - ASSERT(dataSize == (uint32)(position - data)); - } - - return false; - } - - static bool Upgrade_1_To_4_SpriteAtlas(AssetMigrationContext& context) - { - // Get texture header - auto headerChunk = context.Input.Header.Chunks[0]; - if (headerChunk == nullptr || headerChunk->IsMissing()) - { - LOG(Warning, "Missing texture header chunk."); - return true; - } - MemoryReadStream headerStream(headerChunk->Get(), headerChunk->Size()); - - // Load texture header - int32 textureHeaderVersion; - headerStream.ReadInt32(&textureHeaderVersion); - if (textureHeaderVersion != 1) - { - LOG(Warning, "Invalid texture header version."); - return true; - } - TextureHeader1 textureHeader; - headerStream.ReadBytes(&textureHeader, sizeof(textureHeader)); - - // Create new header - TextureHeader newHeader; - newHeader.Width = textureHeader.Width; - newHeader.Height = textureHeader.Height; - newHeader.MipLevels = textureHeader.MipLevels; - newHeader.Format = PixelFormatOldToNew(textureHeader.Format); - newHeader.IsSRGB = textureHeader.IsSRGB; - newHeader.NeverStream = textureHeader.NeverStream; - newHeader.Type = textureHeader.Type; - context.Output.CustomData.Copy(&newHeader); - - // Copy sprite atlas data from old header stream to chunk (chunk 15th) - if (context.AllocateChunk(15)) - return true; - context.Output.Header.Chunks[15]->Data.Copy(headerStream.GetPositionHandle(), headerStream.GetLength() - headerStream.GetPosition()); - - // Convert import options - if (Upgrade_1_To_4_ImportOptions(context)) - return true; - - // Copy mip maps - for (int32 mipIndex = 0; mipIndex < newHeader.MipLevels; mipIndex++) - { - auto srcChunk = context.Input.Header.Chunks[newHeader.MipLevels - mipIndex]; - if (srcChunk == nullptr || srcChunk->IsMissing()) - { - LOG(Warning, "Missing data chunk with a mipmap"); - return true; - } - if (context.AllocateChunk(mipIndex)) - return true; - auto dstChunk = context.Output.Header.Chunks[mipIndex]; - - dstChunk->Data.Copy(srcChunk->Data); - } - - return false; - } - - static bool Upgrade_1_To_4_IESProfile(AssetMigrationContext& context) - { - // Get texture header - auto headerChunk = context.Input.Header.Chunks[0]; - if (headerChunk == nullptr || headerChunk->IsMissing()) - { - LOG(Warning, "Missing IES profile texture header chunk."); - return true; - } - MemoryReadStream headerStream(headerChunk->Get(), headerChunk->Size()); - - // Load IES header - int32 iesHeaderVersion; - headerStream.ReadInt32(&iesHeaderVersion); - if (iesHeaderVersion != 1) - { - LOG(Warning, "Invalid IES profile header."); - return true; - } - float brightness, multiplier; - headerStream.ReadFloat(&brightness); - headerStream.ReadFloat(&multiplier); - - // Load texture header - int32 textureHeaderVersion; - headerStream.ReadInt32(&textureHeaderVersion); - if (textureHeaderVersion != 1) - { - LOG(Warning, "Invalid texture header version."); - return true; - } - TextureHeader1 textureHeader; - headerStream.ReadBytes(&textureHeader, sizeof(textureHeader)); - if (textureHeader.MipLevels != 1) - { - LOG(Warning, "Invalid IES profile texture header."); - return true; - } - - // Create new header - TextureHeader newHeader; - newHeader.Width = textureHeader.Width; - newHeader.Height = textureHeader.Height; - newHeader.MipLevels = textureHeader.MipLevels; - newHeader.Format = PixelFormatOldToNew(textureHeader.Format); - newHeader.IsSRGB = textureHeader.IsSRGB; - newHeader.NeverStream = textureHeader.NeverStream; - newHeader.Type = textureHeader.Type; - auto data = (IESProfile::CustomDataLayout*)newHeader.CustomData; - data->Brightness = brightness; - data->TextureMultiplier = multiplier; - context.Output.CustomData.Copy(&newHeader); - - // Convert import options - if (Upgrade_1_To_4_ImportOptions(context)) - return true; - - // Copy texture - { - auto srcChunk = context.Input.Header.Chunks[1]; - if (srcChunk == nullptr || srcChunk->IsMissing()) - { - LOG(Warning, "Missing data chunk with a mipmap"); - return true; - } - if (context.AllocateChunk(0)) - return true; - auto dstChunk = context.Output.Header.Chunks[0]; - - dstChunk->Data.Copy(srcChunk->Data); - } - - return false; - } - - static bool Upgrade_1_To_4_PreviewsCache(AssetMigrationContext& context) - { - // Get texture header - auto headerChunk = context.Input.Header.Chunks[0]; - if (headerChunk == nullptr || headerChunk->IsMissing()) - { - LOG(Warning, "Missing texture header chunk."); - return true; - } - MemoryReadStream headerStream(headerChunk->Get(), headerChunk->Size()); - - // Load atlas header - struct AtlasHeader1 - { - int32 Version; - int32 TextureHeaderVersion; - TextureHeader1 TextureHeader; - }; - AtlasHeader1 atlasHeader; - headerStream.ReadBytes(&atlasHeader, sizeof(atlasHeader)); - if (atlasHeader.Version != 1 || atlasHeader.TextureHeaderVersion != 1) - { - LOG(Warning, "Invalid asset previews header."); - return true; - } - auto& textureHeader = atlasHeader.TextureHeader; - if (textureHeader.MipLevels != 1) - { - LOG(Warning, "Invalid asset previews texture header."); - return true; - } - - // Create new header - TextureHeader newHeader; - newHeader.Width = textureHeader.Width; - newHeader.Height = textureHeader.Height; - newHeader.MipLevels = textureHeader.MipLevels; - newHeader.Format = PixelFormatOldToNew(textureHeader.Format); - newHeader.IsSRGB = textureHeader.IsSRGB; - newHeader.NeverStream = textureHeader.NeverStream; - newHeader.Type = textureHeader.Type; - context.Output.CustomData.Copy(&newHeader); - - // Copy asset previews IDs mapping data from old chunk to new one - auto idsChunk = context.Input.Header.Chunks[1]; - if (idsChunk == nullptr || idsChunk->IsMissing()) - { - LOG(Warning, "Missing asset previews IDs mapping data chunk."); - return true; - } - if (context.AllocateChunk(15)) - return true; - context.Output.Header.Chunks[15]->Data.Copy(idsChunk->Data); - - // Convert import options - if (Upgrade_1_To_4_ImportOptions(context)) - return true; - - // Copy atlas image (older version has only single mip) - { - auto srcChunk = context.Input.Header.Chunks[2]; - if (srcChunk == nullptr || srcChunk->IsMissing()) - { - LOG(Warning, "Missing data chunk with a mipmap"); - return true; - } - if (context.AllocateChunk(0)) - return true; - auto dstChunk = context.Output.Header.Chunks[0]; - - dstChunk->Data.Copy(srcChunk->Data); - } - - return false; - } - - static bool Upgrade_1_To_4(AssetMigrationContext& context) - { - ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 4); - - // Peek asset type - TextureOwnerType1 type; - if (GetOwnerType(context, type)) - return true; - - switch (type) - { - case TextureOwnerType1::Texture: - return Upgrade_1_To_4_Texture(context); - case TextureOwnerType1::CubeTexture: - return Upgrade_1_To_4_CubeTexture(context); - case TextureOwnerType1::SpriteAtlas: - return Upgrade_1_To_4_SpriteAtlas(context); - case TextureOwnerType1::IESProfile: - return Upgrade_1_To_4_IESProfile(context); - case TextureOwnerType1::PreviewsCache: - return Upgrade_1_To_4_PreviewsCache(context); - } - - return true; - } }; #endif diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index b42288f7f..ff80fadfd 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -97,55 +97,6 @@ void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCou } } -void MeshData::InitFromModelVertices(ModelVertex18* vertices, uint32 verticesCount) -{ - Positions.Resize(verticesCount, false); - UVs.Resize(verticesCount, false); - Normals.Resize(verticesCount, false); - Tangents.Resize(verticesCount, false); - BitangentSigns.Resize(0); - LightmapUVs.Resize(verticesCount, false); - Colors.Resize(0); - BlendIndices.Resize(0); - BlendWeights.Resize(0); - BlendShapes.Resize(0); - - for (uint32 i = 0; i < verticesCount; i++) - { - Positions[i] = vertices->Position; - UVs[i] = vertices->TexCoord.ToFloat2(); - Normals[i] = vertices->Normal.ToFloat3() * 2.0f - 1.0f; - Tangents[i] = vertices->Tangent.ToFloat3() * 2.0f - 1.0f; - LightmapUVs[i] = vertices->LightmapUVs.ToFloat2(); - - vertices++; - } -} - -void MeshData::InitFromModelVertices(ModelVertex15* vertices, uint32 verticesCount) -{ - Positions.Resize(verticesCount, false); - UVs.Resize(verticesCount, false); - Normals.Resize(verticesCount, false); - Tangents.Resize(verticesCount, false); - BitangentSigns.Resize(0); - LightmapUVs.Resize(0); - Colors.Resize(0); - BlendIndices.Resize(0); - BlendWeights.Resize(0); - BlendShapes.Resize(0); - - for (uint32 i = 0; i < verticesCount; i++) - { - Positions[i] = vertices->Position; - UVs[i] = vertices->TexCoord.ToFloat2(); - Normals[i] = vertices->Normal.ToFloat3() * 2.0f - 1.0f; - Tangents[i] = vertices->Tangent.ToFloat3() * 2.0f - 1.0f; - - vertices++; - } -} - void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, uint32 verticesCount) { Positions.Resize(verticesCount, false); @@ -210,31 +161,6 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb } } -void MeshData::InitFromModelVertices(VB0ElementType15* vb0, VB1ElementType15* vb1, uint32 verticesCount) -{ - Positions.Resize(verticesCount, false); - UVs.Resize(verticesCount, false); - Normals.Resize(verticesCount, false); - Tangents.Resize(verticesCount, false); - BitangentSigns.Resize(0); - LightmapUVs.Resize(0, false); - Colors.Resize(0); - BlendIndices.Resize(0); - BlendWeights.Resize(0); - BlendShapes.Resize(0); - - for (uint32 i = 0; i < verticesCount; i++) - { - Positions[i] = vb0->Position; - UVs[i] = vb1->TexCoord.ToFloat2(); - Normals[i] = vb1->Normal.ToFloat3() * 2.0f - 1.0f; - Tangents[i] = vb1->Tangent.ToFloat3() * 2.0f - 1.0f; - - vb0++; - vb1++; - } -} - void MeshData::SetIndexBuffer(void* data, uint32 indicesCount) { bool use16BitIndexBuffer = indicesCount <= MAX_uint16; @@ -339,19 +265,6 @@ bool MeshData::Pack2Model(WriteStream* stream) const vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast(bitangentSign < 0 ? 1 : 0)); vb1.LightmapUVs = Half2(lightmapUV); stream->WriteBytes(&vb1, sizeof(vb1)); - - // Pack TBN matrix into a quaternion - /*Quaternion quaternionTBN; - bool invertedHandednessTBN; - CalculateQuaternionFromTBN(tangent, bitangent, normal, &quaternionTBN, &invertedHandednessTBN); - quaternionTBN.Normalize(); - uint32 packedQuaternionTBN = QuantizeNormalizedQuaternionWithHandedness(quaternionTBN, invertedHandednessTBN); - - Float4 unpackedQuaternionTBN = Float4(quaternionTBN.X, quaternionTBN.Y, quaternionTBN.Z, ((invertedHandednessTBN ? 0.0f : 128.0f) + (127.0f * (quaternionTBN.W * 0.5f + 0.5f))) / 255.0f); - - //lods.WriteUint32(packedQuaternionTBN); - //lods.WriteFloat4(unpackedQuaternionTBN); - */ } // Vertex Buffer 2 diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 87b0abe63..ace5709bb 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -149,22 +149,6 @@ public: /// Amount of vertices void InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount); - /// - /// Init from model vertices array - /// [Deprecated on 28.04.2023, expires on 01.01.2024] - /// - /// Array of vertices - /// Amount of vertices - void InitFromModelVertices(ModelVertex18* vertices, uint32 verticesCount); - - /// - /// Init from model vertices array - /// [Deprecated on 28.04.2023, expires on 01.01.2024] - /// - /// Array of vertices - /// Amount of vertices - void InitFromModelVertices(ModelVertex15* vertices, uint32 verticesCount); - /// /// Init from model vertices array /// @@ -182,15 +166,6 @@ public: /// Amount of vertices void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount); - /// - /// Init from model vertices array - /// [Deprecated on 28.04.2023, expires on 01.01.2024] - /// - /// Array of data for vertex buffer 0 - /// Array of data for vertex buffer 1 - /// Amount of vertices - void InitFromModelVertices(VB0ElementType15* vb0, VB1ElementType15* vb1, uint32 verticesCount); - /// /// Sets the index buffer data. /// diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index 1f57cfa2c..20d574029 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -70,25 +70,6 @@ enum class MeshBufferType }; // Vertex structure for all models (versioned) -// [Deprecated on 28.04.2023, expires on 01.01.2024] -PACK_STRUCT(struct ModelVertex15 - { - Float3 Position; - Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; - }); - -// [Deprecated on 28.04.2023, expires on 01.01.2024] -PACK_STRUCT(struct ModelVertex18 - { - Float3 Position; - Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; - Half2 LightmapUVs; - }); - // [Deprecated in v1.10] PACK_STRUCT(struct ModelVertex19 { @@ -116,19 +97,6 @@ struct RawModelVertex }; // For vertex data we use three buffers: one with positions, one with other attributes, and one with colors -// [Deprecated on 28.04.2023, expires on 01.01.2024] -PACK_STRUCT(struct VB0ElementType15 - { - Float3 Position; - }); -// [Deprecated on 28.04.2023, expires on 01.01.2024] -PACK_STRUCT(struct VB1ElementType15 - { - Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; - }); - // [Deprecated in v1.10] PACK_STRUCT(struct VB0ElementType18 { From 723a882824940b829cb1d78d4421b5e0b67401b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Dec 2024 23:28:01 +0100 Subject: [PATCH 093/215] Merge more code together for meshes --- Source/Engine/Content/Assets/Model.cpp | 10 +- Source/Engine/Content/Assets/Model.h | 162 ++++++++++++++++-- Source/Engine/Content/Assets/ModelBase.cpp | 6 +- Source/Engine/Content/Assets/ModelBase.h | 2 + Source/Engine/Content/Assets/SkinnedModel.cpp | 7 +- Source/Engine/Content/Assets/SkinnedModel.h | 130 +++++++++++++- Source/Engine/Graphics/Models/ModelLOD.h | 159 ----------------- .../Engine/Graphics/Models/SkinnedModelLOD.h | 139 --------------- 8 files changed, 299 insertions(+), 316 deletions(-) delete mode 100644 Source/Engine/Graphics/Models/ModelLOD.h delete mode 100644 Source/Engine/Graphics/Models/SkinnedModelLOD.h diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 52f633e1b..bf58c8782 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -79,9 +79,9 @@ Model::Model(const SpawnParams& params, const AssetInfo* info) } } -Model::~Model() +bool Model::HasAnyLODInitialized() const { - ASSERT(_streamingTask == nullptr); + return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized(); } bool Model::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh, int32 lodIndex) @@ -890,6 +890,12 @@ AssetChunksFlag Model::getChunksToPreload() const return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15); } +bool ModelLOD::HasAnyMeshInitialized() const +{ + // Note: we initialize all meshes at once so the last one can be used to check it. + return Meshes.HasItems() && Meshes.Last().IsInitialized(); +} + bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh) { bool result = false; diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index 3f78f973d..de17f37e1 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -3,9 +3,156 @@ #pragma once #include "ModelBase.h" -#include "Engine/Graphics/Models/ModelLOD.h" +#include "Engine/Graphics/Models/Mesh.h" -class Mesh; +/// +/// Represents single Level Of Detail for the model. Contains a collection of the meshes. +/// +API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ScriptingObject); + friend Model; + friend Mesh; + +private: + Model* _model = nullptr; + int32 _lodIndex = 0; + uint32 _verticesCount = 0; + + void Link(Model* model, int32 lodIndex) + { + _model = model; + _lodIndex = lodIndex; + _verticesCount = 0; + } + +public: + /// + /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. + /// + API_FIELD() float ScreenSize = 1.0f; + + /// + /// The meshes array. + /// + API_FIELD(ReadOnly) Array Meshes; + + /// + /// Determines whether any mesh has been initialized. + /// + /// True if any mesh has been initialized, otherwise false. + bool HasAnyMeshInitialized() const; + + /// + /// Gets the model LOD index. + /// + API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const + { + return _lodIndex; + } + + /// + /// Gets the vertex count for this model LOD level. + /// + API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const + { + return _verticesCount; + } + +public: + /// + /// Determines if there is an intersection between the Model and a Ray in given world using given instance + /// + /// The ray to test + /// World to test + /// When the method completes, contains the distance of the intersection (if any valid). + /// When the method completes, contains the intersection surface normal vector (if any valid). + /// Mesh, or null + /// True whether the two objects intersected + bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh); + + /// + /// Determines if there is an intersection between the Model and a Ray in given world using given instance + /// + /// The ray to test + /// The instance transformation. + /// When the method completes, contains the distance of the intersection (if any valid). + /// When the method completes, contains the intersection surface normal vector (if any valid). + /// Mesh, or null + /// True whether the two objects intersected + bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh); + + /// + /// Get model bounding box in transformed world matrix. + /// + /// World matrix + /// Bounding box + BoundingBox GetBox(const Matrix& world) const; + + /// + /// Get model bounding box in transformed world. + /// + /// The instance transformation. + /// The meshes deformation container (optional). + /// Bounding box + BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; + + /// + /// Gets the bounding box combined for all meshes in this model LOD. + /// + API_PROPERTY() BoundingBox GetBox() const; + + /// + /// Draws the meshes. Binds vertex and index buffers and invokes the draw calls. + /// + /// The GPU context to draw with. + FORCE_INLINE void Render(GPUContext* context) + { + for (int32 i = 0; i < Meshes.Count(); i++) + Meshes.Get()[i].Render(context); + } + + /// + /// Draws the meshes from the model LOD. + /// + /// The rendering context. + /// The material to use for rendering. + /// The world transformation of the model. + /// The object static flags. + /// True if rendered geometry can receive decals, otherwise false. + /// The draw passes to use for rendering this object. + /// The random per-instance value (normalized to range 0-1). + /// Object sorting key. + API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true, DrawPass drawModes = DrawPass::Default, float perInstanceRandom = 0.0f, int8 sortOrder = 0) const + { + for (int32 i = 0; i < Meshes.Count(); i++) + Meshes.Get()[i].Draw(renderContext, material, world, flags, receiveDecals, drawModes, perInstanceRandom, sortOrder); + } + + /// + /// Draws all the meshes from the model LOD. + /// + /// The rendering context. + /// The packed drawing info data. + /// The LOD transition dither factor. + FORCE_INLINE void Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info, float lodDitherFactor) const + { + for (int32 i = 0; i < Meshes.Count(); i++) + Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor); + } + + /// + /// Draws all the meshes from the model LOD. + /// + /// The rendering context batch. + /// The packed drawing info data. + /// The LOD transition dither factor. + FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const Mesh::DrawInfo& info, float lodDitherFactor) const + { + for (int32 i = 0; i < Meshes.Count(); i++) + Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor); + } +}; /// /// Model asset that contains model object made of meshes which can rendered on the GPU. @@ -26,20 +173,11 @@ public: /// API_FIELD(ReadOnly) SDFData SDF; -public: - /// - /// Finalizes an instance of the class. - /// - ~Model(); - public: /// /// Determines whether any LOD has been initialized. /// - FORCE_INLINE bool HasAnyLODInitialized() const - { - return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized(); - } + bool HasAnyLODInitialized() const; public: /// diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 6e7fc3457..220c1b4f7 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -163,12 +163,16 @@ public: } }; +ModelBase::~ModelBase() +{ + ASSERT(_streamingTask == nullptr); +} + void ModelBase::SetupMaterialSlots(int32 slotsCount) { CHECK(slotsCount >= 0 && slotsCount < 4096); if (!IsVirtual() && WaitForLoaded()) return; - ScopeLock lock(Locker); const int32 prevCount = MaterialSlots.Count(); diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index ab683c116..34d7ee23b 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -97,6 +97,8 @@ protected: } public: + ~ModelBase(); + /// /// The minimum screen size to draw this model (the bottom limit). Used to cull small models. Set to 0 to disable this feature. /// diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 933643f63..9450ddf64 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -32,7 +32,6 @@ SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info) SkinnedModel::~SkinnedModel() { - ASSERT(_streamingTask == nullptr); ASSERT(_skeletonMappingCache.Count() == 0); } @@ -1053,6 +1052,12 @@ AssetChunksFlag SkinnedModel::getChunksToPreload() const return GET_CHUNK_FLAG(0); } +bool SkinnedModelLOD::HasAnyMeshInitialized() const +{ + // Note: we initialize all meshes at once so the last one can be used to check it. + return Meshes.HasItems() && Meshes.Last().IsInitialized(); +} + bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh) { // Check all meshes diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index fc64e9339..78350285c 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -4,9 +4,135 @@ #include "ModelBase.h" #include "Engine/Core/Collections/Dictionary.h" -#include "Engine/Graphics/Models/Config.h" +#include "Engine/Graphics/Models/SkinnedMesh.h" #include "Engine/Graphics/Models/SkeletonData.h" -#include "Engine/Graphics/Models/SkinnedModelLOD.h" + +/// +/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes. +/// +API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject +{ + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ScriptingObject); + friend SkinnedModel; +private: + SkinnedModel* _model = nullptr; + int32 _lodIndex = 0; + +public: + /// + /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. + /// + API_FIELD() float ScreenSize = 1.0f; + + /// + /// The meshes array. + /// + API_FIELD(ReadOnly) Array Meshes; + + /// + /// Gets the model LOD index. + /// + API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const + { + return _lodIndex; + } + + /// + /// Determines whether any mesh has been initialized. + /// + bool HasAnyMeshInitialized() const; + +public: + /// + /// Determines if there is an intersection between the Model and a Ray in given world using given instance + /// + /// The ray to test + /// World to test + /// When the method completes, contains the distance of the intersection (if any valid). + /// When the method completes, contains the intersection surface normal vector (if any valid). + /// Mesh, or null + /// True whether the two objects intersected + bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh); + + /// + /// Determines if there is an intersection between the Model and a Ray in given world using given instance + /// + /// The ray to test + /// Instance transformation + /// When the method completes, contains the distance of the intersection (if any valid). + /// When the method completes, contains the intersection surface normal vector (if any valid). + /// Mesh, or null + /// True whether the two objects intersected + bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh); + + /// + /// Get model bounding box in transformed world for given instance buffer + /// + /// World matrix + /// Bounding box + BoundingBox GetBox(const Matrix& world) const; + + /// + /// Get model bounding box in transformed world. + /// + /// The instance transformation. + /// The meshes deformation container (optional). + /// Bounding box + BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; + + /// + /// Get model bounding box in transformed world for given instance buffer for only one mesh + /// + /// World matrix + /// esh index + /// Bounding box + BoundingBox GetBox(const Matrix& world, int32 meshIndex) const; + + /// + /// Gets the bounding box combined for all meshes in this model LOD. + /// + API_PROPERTY() BoundingBox GetBox() const; + + /// + /// Draws the meshes. Binds vertex and index buffers and invokes the draw calls. + /// + /// The GPU context to draw with. + FORCE_INLINE void Render(GPUContext* context) + { + for (int32 i = 0; i < Meshes.Count(); i++) + { + Meshes.Get()[i].Render(context); + } + } + + /// + /// Draws all the meshes from the model LOD. + /// + /// The rendering context. + /// The packed drawing info data. + /// The LOD transition dither factor. + FORCE_INLINE void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const + { + for (int32 i = 0; i < Meshes.Count(); i++) + { + Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor); + } + } + + /// + /// Draws all the meshes from the model LOD. + /// + /// The rendering context batch. + /// The packed drawing info data. + /// The LOD transition dither factor. + FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const + { + for (int32 i = 0; i < Meshes.Count(); i++) + { + Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor); + } + } +}; /// /// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning. diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h deleted file mode 100644 index 4934e6cc2..000000000 --- a/Source/Engine/Graphics/Models/ModelLOD.h +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Mesh.h" - -class MemoryReadStream; - -/// -/// Represents single Level Of Detail for the model. Contains a collection of the meshes. -/// -API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject -{ - DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ScriptingObject); - friend Model; - friend Mesh; -private: - Model* _model = nullptr; - int32 _lodIndex = 0; - uint32 _verticesCount = 0; - - void Link(Model* model, int32 lodIndex) - { - _model = model; - _lodIndex = lodIndex; - _verticesCount = 0; - } - -public: - /// - /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. - /// - API_FIELD() float ScreenSize = 1.0f; - - /// - /// The meshes array. - /// - API_FIELD(ReadOnly) Array Meshes; - - /// - /// Determines whether any mesh has been initialized. - /// - /// True if any mesh has been initialized, otherwise false. - FORCE_INLINE bool HasAnyMeshInitialized() const - { - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); - } - - /// - /// Gets the model LOD index. - /// - API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const - { - return _lodIndex; - } - - /// - /// Gets the vertex count for this model LOD level. - /// - API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const - { - return _verticesCount; - } - -public: - /// - /// Determines if there is an intersection between the Model and a Ray in given world using given instance - /// - /// The ray to test - /// World to test - /// When the method completes, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// Mesh, or null - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh); - - /// - /// Determines if there is an intersection between the Model and a Ray in given world using given instance - /// - /// The ray to test - /// The instance transformation. - /// When the method completes, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// Mesh, or null - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh); - - /// - /// Get model bounding box in transformed world matrix. - /// - /// World matrix - /// Bounding box - BoundingBox GetBox(const Matrix& world) const; - - /// - /// Get model bounding box in transformed world. - /// - /// The instance transformation. - /// The meshes deformation container (optional). - /// Bounding box - BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; - - /// - /// Gets the bounding box combined for all meshes in this model LOD. - /// - API_PROPERTY() BoundingBox GetBox() const; - - /// - /// Draws the meshes. Binds vertex and index buffers and invokes the draw calls. - /// - /// The GPU context to draw with. - FORCE_INLINE void Render(GPUContext* context) - { - for (int32 i = 0; i < Meshes.Count(); i++) - Meshes.Get()[i].Render(context); - } - - /// - /// Draws the meshes from the model LOD. - /// - /// The rendering context. - /// The material to use for rendering. - /// The world transformation of the model. - /// The object static flags. - /// True if rendered geometry can receive decals, otherwise false. - /// The draw passes to use for rendering this object. - /// The random per-instance value (normalized to range 0-1). - /// Object sorting key. - API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true, DrawPass drawModes = DrawPass::Default, float perInstanceRandom = 0.0f, int8 sortOrder = 0) const - { - for (int32 i = 0; i < Meshes.Count(); i++) - Meshes.Get()[i].Draw(renderContext, material, world, flags, receiveDecals, drawModes, perInstanceRandom, sortOrder); - } - - /// - /// Draws all the meshes from the model LOD. - /// - /// The rendering context. - /// The packed drawing info data. - /// The LOD transition dither factor. - FORCE_INLINE void Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info, float lodDitherFactor) const - { - for (int32 i = 0; i < Meshes.Count(); i++) - Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor); - } - - /// - /// Draws all the meshes from the model LOD. - /// - /// The rendering context batch. - /// The packed drawing info data. - /// The LOD transition dither factor. - FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const Mesh::DrawInfo& info, float lodDitherFactor) const - { - for (int32 i = 0; i < Meshes.Count(); i++) - Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor); - } -}; diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h deleted file mode 100644 index f7c78f3d1..000000000 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#pragma once - -#include "SkinnedMesh.h" - -class MemoryReadStream; - -/// -/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes. -/// -API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject -{ - DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ScriptingObject); - friend SkinnedModel; -private: - SkinnedModel* _model = nullptr; - int32 _lodIndex = 0; - -public: - /// - /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. - /// - API_FIELD() float ScreenSize = 1.0f; - - /// - /// The meshes array. - /// - API_FIELD(ReadOnly) Array Meshes; - - /// - /// Gets the model LOD index. - /// - API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const - { - return _lodIndex; - } - - /// - /// Determines whether any mesh has been initialized. - /// - bool HasAnyMeshInitialized() const - { - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); - } - - -public: - /// - /// Determines if there is an intersection between the Model and a Ray in given world using given instance - /// - /// The ray to test - /// World to test - /// When the method completes, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// Mesh, or null - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh); - - /// - /// Determines if there is an intersection between the Model and a Ray in given world using given instance - /// - /// The ray to test - /// Instance transformation - /// When the method completes, contains the distance of the intersection (if any valid). - /// When the method completes, contains the intersection surface normal vector (if any valid). - /// Mesh, or null - /// True whether the two objects intersected - bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh); - - /// - /// Get model bounding box in transformed world for given instance buffer - /// - /// World matrix - /// Bounding box - BoundingBox GetBox(const Matrix& world) const; - - /// - /// Get model bounding box in transformed world. - /// - /// The instance transformation. - /// The meshes deformation container (optional). - /// Bounding box - BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; - - /// - /// Get model bounding box in transformed world for given instance buffer for only one mesh - /// - /// World matrix - /// esh index - /// Bounding box - BoundingBox GetBox(const Matrix& world, int32 meshIndex) const; - - /// - /// Gets the bounding box combined for all meshes in this model LOD. - /// - API_PROPERTY() BoundingBox GetBox() const; - - /// - /// Draws the meshes. Binds vertex and index buffers and invokes the draw calls. - /// - /// The GPU context to draw with. - FORCE_INLINE void Render(GPUContext* context) - { - for (int32 i = 0; i < Meshes.Count(); i++) - { - Meshes.Get()[i].Render(context); - } - } - - /// - /// Draws all the meshes from the model LOD. - /// - /// The rendering context. - /// The packed drawing info data. - /// The LOD transition dither factor. - FORCE_INLINE void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const - { - for (int32 i = 0; i < Meshes.Count(); i++) - { - Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor); - } - } - - /// - /// Draws all the meshes from the model LOD. - /// - /// The rendering context batch. - /// The packed drawing info data. - /// The LOD transition dither factor. - FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const - { - for (int32 i = 0; i < Meshes.Count(); i++) - { - Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor); - } - } -}; From fee0ab74ff6ae25b22cbbc70578227ea15680758 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 28 Dec 2024 21:31:21 +0100 Subject: [PATCH 094/215] Update old doc comments --- .../Content/Upgraders/AudioClipUpgrader.h | 5 +- .../Content/Upgraders/FontAssetUpgrader.h | 5 +- .../Upgraders/MaterialInstanceUpgrader.h | 5 +- .../Content/Upgraders/ShaderAssetUpgrader.h | 5 +- .../Content/Upgraders/SkeletonMaskUpgrader.h | 5 +- .../Content/Upgraders/TextureAssetUpgrader.h | 5 +- Source/Engine/Core/Types/CommonValue.h | 1 + Source/Engine/Core/Types/TimeSpan.h | 39 +++++++----- Source/Engine/Graphics/RenderView.h | 13 ++-- Source/Engine/Serialization/JsonTools.cpp | 1 + Source/Engine/Serialization/JsonWriter.cpp | 1 + .../Engine/Serialization/MemoryReadStream.h | 44 +++++++------ .../Engine/Serialization/MemoryWriteStream.h | 34 +++++----- Source/Engine/Serialization/Stream.cpp | 2 + Source/Engine/Serialization/WriteStream.h | 62 +++++++------------ Source/Engine/Threading/ConcurrentBuffer.h | 6 +- 16 files changed, 103 insertions(+), 130 deletions(-) diff --git a/Source/Engine/Content/Upgraders/AudioClipUpgrader.h b/Source/Engine/Content/Upgraders/AudioClipUpgrader.h index cf1e2594a..f141c5522 100644 --- a/Source/Engine/Content/Upgraders/AudioClipUpgrader.h +++ b/Source/Engine/Content/Upgraders/AudioClipUpgrader.h @@ -13,12 +13,9 @@ class AudioClipUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// AudioClipUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { {}, }; diff --git a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h index 4149816cd..beb1bc8ad 100644 --- a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h @@ -13,12 +13,9 @@ class FontAssetUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// FontAssetUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { {}, }; diff --git a/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h b/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h index e2081e3d3..9209061f6 100644 --- a/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h +++ b/Source/Engine/Content/Upgraders/MaterialInstanceUpgrader.h @@ -13,12 +13,9 @@ class MaterialInstanceUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// MaterialInstanceUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { {}, }; diff --git a/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h b/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h index 97c41dabd..9f815806e 100644 --- a/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/ShaderAssetUpgrader.h @@ -13,12 +13,9 @@ class ShaderAssetUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// ShaderAssetUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { {}, }; diff --git a/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h b/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h index 0c7944b91..dbaaf13c2 100644 --- a/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkeletonMaskUpgrader.h @@ -13,12 +13,9 @@ class SkeletonMaskUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// SkeletonMaskUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { {}, }; diff --git a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h index efe5c6547..afdc9da48 100644 --- a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h @@ -13,12 +13,9 @@ class TextureAssetUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// TextureAssetUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { {}, }; diff --git a/Source/Engine/Core/Types/CommonValue.h b/Source/Engine/Core/Types/CommonValue.h index 951634cbb..f021b1070 100644 --- a/Source/Engine/Core/Types/CommonValue.h +++ b/Source/Engine/Core/Types/CommonValue.h @@ -16,6 +16,7 @@ /// /// Common values types. +/// [Deprecated on 31.07.2020, expires on 31.07.2022] /// enum class CommonType { diff --git a/Source/Engine/Core/Types/TimeSpan.h b/Source/Engine/Core/Types/TimeSpan.h index 01831c897..d5405acb3 100644 --- a/Source/Engine/Core/Types/TimeSpan.h +++ b/Source/Engine/Core/Types/TimeSpan.h @@ -53,31 +53,37 @@ public: { } - // Init - // @param Days Amount of days - // @param Hours Amount of hours - // @param Minutes Amount of minutes + /// + /// Initializes a new instance of the struct. + /// + /// Amount of days. + /// Amount of hours. + /// Amount of minutes. TimeSpan(int32 days, int32 hours, int32 minutes) { Set(days, hours, minutes, 0, 0); } - // Init - // @param Days Amount of days - // @param Hours Amount of hours - // @param Minutes Amount of minutes - // @param Seconds Amount of seconds + /// + /// Initializes a new instance of the struct. + /// + /// Amount of days. + /// Amount of hours. + /// Amount of minutes. + /// Amount of seconds. TimeSpan(int32 days, int32 hours, int32 minutes, int32 seconds) { Set(days, hours, minutes, seconds, 0); } - // Init - // @param Days Amount of days - // @param Hours Amount of hours - // @param Minutes Amount of minutes - // @param Seconds Amount of seconds - // @param Milliseconds Amount of milliseconds + /// + /// Initializes a new instance of the struct. + /// + /// Amount of days. + /// Amount of hours. + /// Amount of minutes. + /// Amount of seconds. + /// Amount of milliseconds TimeSpan(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds) { Set(days, hours, minutes, seconds, milliseconds); @@ -87,8 +93,7 @@ public: // Get string String ToString() const; - // Get string - // @param option Custom formatting. Possible values: + // Get string with custom formatting. Possible values: // a: 11:54:22.097 // default: 11:54:22.0972244 String ToString(const char option) const; diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 0b9f931e7..27bb719d8 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -276,17 +276,14 @@ public: /// void UpdateCachedData(); - // Set up view with custom params - // @param viewProjection View * Projection matrix + // Setups view with custom params. void SetUp(const Matrix& viewProjection); - // Set up view with custom params - // @param view View matrix - // @param projection Projection matrix + // Setups view with custom params. void SetUp(const Matrix& view, const Matrix& projection); /// - /// Set up view for cube rendering + /// Setups view for cube rendering. /// /// Near plane /// Far plane @@ -294,13 +291,13 @@ public: void SetUpCube(float nearPlane, float farPlane, const Float3& position); /// - /// Set up view for given face of the cube rendering + /// Setups view for given face of the cube rendering. /// /// Face index(0-5) void SetFace(int32 faceIndex); /// - /// Set up view for cube rendering + /// Setups view for cube rendering. /// /// Near plane /// Far plane diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index 3d6ec8ebf..689ce58c2 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -286,6 +286,7 @@ DateTime JsonTools::GetDateTime(const Value& value) CommonValue JsonTools::GetCommonValue(const Value& value) { + // [Deprecated on 31.07.2020, expires on 31.07.2022] CommonValue result; const auto typeMember = value.FindMember("Type"); const auto valueMember = value.FindMember("Value"); diff --git a/Source/Engine/Serialization/JsonWriter.cpp b/Source/Engine/Serialization/JsonWriter.cpp index c8cccc83d..58ee2c126 100644 --- a/Source/Engine/Serialization/JsonWriter.cpp +++ b/Source/Engine/Serialization/JsonWriter.cpp @@ -246,6 +246,7 @@ void JsonWriter::Matrix(const ::Matrix& value) void JsonWriter::CommonValue(const ::CommonValue& value) { + // [Deprecated on 31.07.2020, expires on 31.07.2022] StartObject(); JKEY("Type"); diff --git a/Source/Engine/Serialization/MemoryReadStream.h b/Source/Engine/Serialization/MemoryReadStream.h index 1c5a488d9..3db90f1a6 100644 --- a/Source/Engine/Serialization/MemoryReadStream.h +++ b/Source/Engine/Serialization/MemoryReadStream.h @@ -6,53 +6,63 @@ #include "Engine/Platform/Platform.h" /// -/// Super fast advanced data reading from raw bytes without any overhead at all +/// Direct memory reading stream that uses a single allocation buffer. /// class FLAXENGINE_API MemoryReadStream : public ReadStream { private: - const byte* _buffer; const byte* _position; uint32 _length; public: - /// /// Init (empty, cannot access before Init()) /// - MemoryReadStream(); - /// - /// Init + /// Initializes a new instance of the class. /// - /// Bytes with data to read from it (no memory cloned, using input buffer) + MemoryReadStream(); + + /// + /// Initializes a new instance of the class. + /// + /// Bytes with data to read from it (no memory cloned, using input buffer). /// Amount of bytes MemoryReadStream(const byte* bytes, uint32 length); - + /// - /// Init + /// Initializes a new instance of the class. /// - /// Array with data to read from + /// Array with data to read from. template MemoryReadStream(const Array& data) : MemoryReadStream(data.Get(), data.Count() * sizeof(T)) { } + + /// + /// Initializes a new instance of the class. + /// + /// Span with data to read from. + template + MemoryReadStream(const Span& data) + : MemoryReadStream(data.Get(), data.Count() * sizeof(T)) + { + } public: - /// - /// Init stream to the custom buffer location + /// Init stream to the custom buffer location. /// - /// Bytes with data to read from it (no memory cloned, using input buffer) - /// Amount of bytes + /// Bytes with data to read from it (no memory cloned, using input buffer). + /// Amount of bytes. void Init(const byte* bytes, uint32 length); /// - /// Init stream to the custom buffer location + /// Init stream to the custom buffer location. /// - /// Array with data to read from + /// Array with data to read from. template FORCE_INLINE void Init(const Array& data) { @@ -62,7 +72,6 @@ public: /// /// Gets the current handle to position in buffer. /// - /// The position of the buffer in memory. const byte* GetPositionHandle() const { return _position; @@ -104,7 +113,6 @@ public: } public: - // [ReadStream] void Flush() override; void Close() override; diff --git a/Source/Engine/Serialization/MemoryWriteStream.h b/Source/Engine/Serialization/MemoryWriteStream.h index 568da6585..6b181d3b0 100644 --- a/Source/Engine/Serialization/MemoryWriteStream.h +++ b/Source/Engine/Serialization/MemoryWriteStream.h @@ -5,22 +5,23 @@ #include "WriteStream.h" /// -/// Implementation of of the stream that can be used for fast data writing to the memory. +/// Direct memory writing stream that uses a single allocation buffer. /// class FLAXENGINE_API MemoryWriteStream : public WriteStream { private: - byte* _buffer; byte* _position; uint32 _capacity; public: - - MemoryWriteStream(); - /// - /// Init + /// Initializes a new instance of the class. + /// + MemoryWriteStream(); + + /// + /// Initializes a new instance of the class. /// /// Initial write buffer capacity (in bytes). MemoryWriteStream(uint32 capacity); @@ -31,38 +32,33 @@ public: ~MemoryWriteStream(); public: - /// - /// Gets buffer handle + /// Gets the pointer to the buffer in memory. /// - /// Pointer to the buffer in memory FORCE_INLINE byte* GetHandle() const { return _buffer; } /// - /// Gets current capacity of the memory stream + /// Gets the current capacity of the stream. /// - /// Stream capacity in bytes FORCE_INLINE uint32 GetCapacity() const { return _capacity; } /// - /// Gets current stream length (capacity in bytes) + /// Gets current stream length (capacity in bytes). /// - /// Stream length in bytes FORCE_INLINE uint32 GetLength() const { return _capacity; } /// - /// Gets current position in the stream (in bytes) + /// Gets current position in the stream (in bytes). /// - /// Stream position in bytes FORCE_INLINE uint32 GetPosition() const { return static_cast(_position - _buffer); @@ -97,7 +93,6 @@ public: } public: - /// /// Cleanups the buffers, resets the position and allocated the new memory chunk. /// @@ -105,14 +100,13 @@ public: void Reset(uint32 capacity); /// - /// Saves current buffer contents to the file + /// Saves current buffer contents to the file. /// - /// Filepath - /// True if cannot save data, otherwise false + /// The file path. + /// True if cannot save data, otherwise false. bool SaveToFile(const StringView& path) const; public: - // [WriteStream] void Flush() override; void Close() override; diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index 18060b62f..fa9554fbb 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -107,6 +107,7 @@ void ReadStream::Read(String& data, int16 lock) void ReadStream::Read(CommonValue& data) { + // [Deprecated on 31.07.2020, expires on 31.07.2022] byte type; ReadByte(&type); switch (static_cast(type)) @@ -716,6 +717,7 @@ void WriteStream::Write(const StringAnsiView& data, int8 lock) void WriteStream::Write(const CommonValue& data) { + // [Deprecated on 31.07.2020, expires on 31.07.2022] WriteByte(static_cast(data.Type)); switch (data.Type) { diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 028588ac4..e362e8258 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -12,129 +12,111 @@ class FLAXENGINE_API WriteStream : public Stream { public: /// - /// Writes bytes to the stream + /// Writes bytes to the stream. /// - /// Data to write - /// Amount of bytes to write + /// Pointer to data to write. + /// Amount of bytes to write. virtual void WriteBytes(const void* data, uint32 bytes) = 0; public: - // Writes byte to the stream - // @param data Data to write + // Writes byte to the stream. FORCE_INLINE void WriteByte(byte data) { WriteBytes(&data, sizeof(byte)); } - // Writes bool to the stream - // @param data Data to write + // Writes bool to the stream. FORCE_INLINE void WriteBool(bool data) { WriteBytes(&data, sizeof(bool)); } - // Writes char to the stream - // @param data Data to write + // Writes char to the stream. FORCE_INLINE void WriteChar(char data) { WriteBytes(&data, sizeof(char)); } - // Writes Char to the stream - // @param data Data to write + // Writes Char to the stream. FORCE_INLINE void WriteChar(Char data) { WriteBytes(&data, sizeof(Char)); } - // Writes uint8 to the stream - // @param data Data to write + // Writes uint8 to the stream. FORCE_INLINE void WriteUint8(uint8 data) { WriteBytes(&data, sizeof(uint8)); } - // Writes int8 to the stream - // @param data Data to write + // Writes int8 to the stream. FORCE_INLINE void WriteInt8(int8 data) { WriteBytes(&data, sizeof(int8)); } - // Writes uint16 to the stream - // @param data Data to write + // Writes uint16 to the stream. FORCE_INLINE void WriteUint16(uint16 data) { WriteBytes(&data, sizeof(uint16)); } - // Writes int16 to the stream - // @param data Data to write + // Writes int16 to the stream. FORCE_INLINE void WriteInt16(int16 data) { WriteBytes(&data, sizeof(int16)); } - // Writes uint32 to the stream - // @param data Data to write + // Writes uint32 to the stream. FORCE_INLINE void WriteUint32(uint32 data) { WriteBytes(&data, sizeof(uint32)); } - // Writes int32 to the stream - // @param data Data to write + // Writes int32 to the stream. FORCE_INLINE void WriteInt32(int32 data) { WriteBytes(&data, sizeof(int32)); } - // Writes int64 to the stream - // @param data Data to write + // Writes int64 to the stream. FORCE_INLINE void WriteInt64(int64 data) { WriteBytes(&data, sizeof(int64)); } - // Writes uint64 to the stream - // @param data Data to write + // Writes uint64 to the stream. FORCE_INLINE void WriteUint64(uint64 data) { WriteBytes(&data, sizeof(uint64)); } - // Writes float to the stream - // @param data Data to write + // Writes float to the stream. FORCE_INLINE void WriteFloat(float data) { WriteBytes(&data, sizeof(float)); } - // Writes double to the stream - // @param data Data to write + // Writes double to the stream. FORCE_INLINE void WriteDouble(double data) { WriteBytes(&data, sizeof(double)); } public: - // Writes text to the stream - // @param data Text to write - // @param length Text length + // Writes text to the stream. void WriteText(const char* text, int32 length) { WriteBytes((const void*)text, sizeof(char) * length); } - // Writes text to the stream - // @param data Text to write - // @param length Text length + // Writes text to the stream. void WriteText(const Char* text, int32 length) { WriteBytes((const void*)text, sizeof(Char) * length); } - // Write UTF BOM character sequence + // Write UTF BOM character sequence. void WriteBOM() { WriteByte(0xEF); @@ -142,8 +124,7 @@ public: WriteByte(0xBF); } - // Writes text to the stream - // @param data Text to write + // Writes text to the stream. void WriteText(const StringView& text); void WriteText(const StringAnsiView& text); @@ -249,7 +230,6 @@ public: // Writes Ansi String to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - // @param data Data to write void WriteStringAnsi(const StringAnsiView& data); // Writes Ansi String to the stream diff --git a/Source/Engine/Threading/ConcurrentBuffer.h b/Source/Engine/Threading/ConcurrentBuffer.h index b109d5629..a5712a046 100644 --- a/Source/Engine/Threading/ConcurrentBuffer.h +++ b/Source/Engine/Threading/ConcurrentBuffer.h @@ -234,8 +234,10 @@ public: return index; } - // Add collection of items to the collection - // @param collection Array with the items to add + /// + /// Adds a collection of items to the collection. + /// + /// The collection of items to add. FORCE_INLINE void Add(ConcurrentBuffer& collection) { Add(collection.Get(), collection.Count()); From 668f3fa68db4c8f3b0a570b458635f7bc792bbad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Dec 2024 23:00:40 +0100 Subject: [PATCH 095/215] Update read and write streaming api to use the newest format --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 10 +- .../SceneAnimations/SceneAnimation.cpp | 10 +- Source/Engine/Content/Assets/Animation.cpp | 222 +++++++++--------- Source/Engine/Content/Assets/SkeletonMask.cpp | 4 +- Source/Engine/Content/Assets/VisualScript.cpp | 28 +-- Source/Engine/Content/Assets/VisualScript.h | 2 +- Source/Engine/Content/Cache/AssetsCache.cpp | 34 +-- Source/Engine/Content/Cache/AssetsCache.h | 2 +- Source/Engine/Content/Storage/FlaxStorage.cpp | 12 +- .../Engine/ContentImporters/CreateAnimation.h | 16 +- .../ContentImporters/CreateVisualScript.h | 6 +- .../Engine/ContentImporters/ImportTexture.cpp | 18 +- Source/Engine/Engine/GameplayGlobals.cpp | 24 +- .../Graphics/Materials/MaterialParams.cpp | 8 +- .../Shaders/Cache/ShaderAssetBase.cpp | 2 +- Source/Engine/Graphics/Shaders/GPUShader.cpp | 2 +- Source/Engine/Level/Actor.cpp | 6 +- Source/Engine/Networking/NetworkStream.cpp | 4 +- Source/Engine/Networking/NetworkStream.h | 4 +- Source/Engine/Particles/ParticleSystem.cpp | 23 +- Source/Engine/Render2D/SpriteAtlas.cpp | 12 +- Source/Engine/Serialization/ReadStream.h | 44 ++-- Source/Engine/Serialization/Stream.cpp | 196 ++++++++++------ Source/Engine/Serialization/WriteStream.h | 53 ++++- .../ShadersCompilation/ShaderCompiler.cpp | 38 +-- .../ShadersCompilation/ShadersCompilation.cpp | 2 +- Source/Engine/Visject/Graph.h | 22 +- Source/Engine/Visject/GraphUtilities.cpp | 2 + Source/Engine/Visject/VisjectGraph.cpp | 16 +- 29 files changed, 444 insertions(+), 378 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index dddfc3ff9..f74a21103 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -166,7 +166,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) Guid id; file->Read(id); String typeName; - file->ReadString(&typeName); + file->Read(typeName); DateTime fileModified; file->Read(fileModified); int32 fileDependenciesCount; @@ -176,7 +176,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) for (int32 j = 0; j < fileDependenciesCount; j++) { Pair& f = fileDependencies[j]; - file->ReadString(&f.First, 10); + file->Read(f.First, 10); file->Read(f.Second); } @@ -311,9 +311,9 @@ void CookAssetsStep::CacheData::Save(CookingData& data) { auto& e = i->Value; file->Write(e.ID); - file->WriteString(e.TypeName); + file->Write(e.TypeName); file->Write(e.FileModified); - file->WriteInt32(e.FileDependencies.Count()); + file->Write(e.FileDependencies.Count()); for (auto& f : e.FileDependencies) { file->Write(f.First, 10); @@ -1249,7 +1249,7 @@ bool CookAssetsStep::Perform(CookingData& data) *(int32*)(bytes.Get() + 804) = contentKey; *(Guid*)(bytes.Get() + 808) = gameSettings->SplashScreen; Encryption::EncryptBytes(bytes.Get(), bytes.Count()); - stream->WriteArray(bytes); + stream->Write(bytes); Delete(stream); } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index 71cc8f1f4..afe4446bc 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -112,19 +112,19 @@ Asset::LoadResult SceneAnimation::load() // Load properties int32 version; - stream.ReadInt32(&version); + stream.Read(version); switch (version) { case 2: // [Deprecated in 2020 expires on 03.09.2023] case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023] case 4: { - stream.ReadFloat(&FramesPerSecond); - stream.ReadInt32(&DurationFrames); + stream.Read(FramesPerSecond); + stream.Read(DurationFrames); // Load tracks int32 tracksCount; - stream.ReadInt32(&tracksCount); + stream.Read(tracksCount); Tracks.Resize(tracksCount, false); for (int32 i = 0; i < tracksCount; i++) { @@ -134,7 +134,7 @@ Asset::LoadResult SceneAnimation::load() track.Flag = (Track::Flags)stream.ReadByte(); stream.ReadInt32(&track.ParentIndex); stream.ReadInt32(&track.ChildrenCount); - stream.ReadString(&track.Name, -13); + stream.Read(track.Name, -13); stream.Read(track.Color); track.Disabled = (int32)track.Flag & (int32)Track::Flags::Mute || (track.ParentIndex != -1 && Tracks[track.ParentIndex].Disabled); track.TrackStateIndex = -1; diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 6f8b23ec7..88ee5a3bd 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -86,15 +86,17 @@ void Animation::LoadTimeline(BytesContainer& result) const // Meta float fps = (float)Data.FramesPerSecond; const float fpsInv = 1.0f / fps; - stream.WriteFloat(fps); - stream.WriteInt32((int32)Data.Duration); + stream.Write(fps); + stream.Write((int32)Data.Duration); int32 tracksCount = Data.Channels.Count() + NestedAnims.Count() + Events.Count(); for (auto& channel : Data.Channels) + { tracksCount += (channel.Position.GetKeyframes().HasItems() ? 1 : 0) + (channel.Rotation.GetKeyframes().HasItems() ? 1 : 0) + (channel.Scale.GetKeyframes().HasItems() ? 1 : 0); - stream.WriteInt32(tracksCount); + } + stream.Write(tracksCount); // Tracks int32 trackIndex = 0; @@ -107,11 +109,11 @@ void Animation::LoadTimeline(BytesContainer& result) const (channel.Scale.GetKeyframes().HasItems() ? 1 : 0); // Animation Channel track - stream.WriteByte(17); // Track Type - stream.WriteByte(0); // Track Flags - stream.WriteInt32(-1); // Parent Index - stream.WriteInt32(childrenCount); // Children Count - stream.WriteString(channel.NodeName, -13); // Name + stream.Write((byte)17); // Track Type + stream.Write((byte)0); // Track Flags + stream.Write(-1); // Parent Index + stream.Write(childrenCount); // Children Count + stream.Write(channel.NodeName, -13); // Name stream.Write(Color32::White); // Color const int32 parentIndex = trackIndex++; @@ -119,17 +121,17 @@ void Animation::LoadTimeline(BytesContainer& result) const if (position.HasItems()) { // Animation Channel Data track (position) - stream.WriteByte(18); // Track Type - stream.WriteByte(0); // Track Flags - stream.WriteInt32(parentIndex); // Parent Index - stream.WriteInt32(0); // Children Count - stream.WriteString(String::Format(TEXT("Track_{0}_Position"), i), -13); // Name + stream.Write((byte)18); // Track Type + stream.Write((byte)0); // Track Flags + stream.Write(parentIndex); // Parent Index + stream.Write(0); // Children Count + stream.Write(String::Format(TEXT("Track_{0}_Position"), i), -13); // Name stream.Write(Color32::White); // Color - stream.WriteByte(0); // Type - stream.WriteInt32(position.Count()); // Keyframes Count + stream.Write((byte)0); // Type + stream.Write(position.Count()); // Keyframes Count for (auto& k : position) { - stream.WriteFloat(k.Time * fpsInv); + stream.Write(k.Time * fpsInv); stream.Write(k.Value); } trackIndex++; @@ -139,17 +141,17 @@ void Animation::LoadTimeline(BytesContainer& result) const if (rotation.HasItems()) { // Animation Channel Data track (rotation) - stream.WriteByte(18); // Track Type - stream.WriteByte(0); // Track Flags - stream.WriteInt32(parentIndex); // Parent Index - stream.WriteInt32(0); // Children Count - stream.WriteString(String::Format(TEXT("Track_{0}_Rotation"), i), -13); // Name + stream.Write((byte)18); // Track Type + stream.Write((byte)0); // Track Flags + stream.Write(parentIndex); // Parent Index + stream.Write(0); // Children Count + stream.Write(String::Format(TEXT("Track_{0}_Rotation"), i), -13); // Name stream.Write(Color32::White); // Color - stream.WriteByte(1); // Type - stream.WriteInt32(rotation.Count()); // Keyframes Count + stream.Write((byte)1); // Type + stream.Write(rotation.Count()); // Keyframes Count for (auto& k : rotation) { - stream.WriteFloat(k.Time * fpsInv); + stream.Write(k.Time * fpsInv); stream.Write(k.Value); } trackIndex++; @@ -159,17 +161,17 @@ void Animation::LoadTimeline(BytesContainer& result) const if (scale.HasItems()) { // Animation Channel Data track (scale) - stream.WriteByte(18); // Track Type - stream.WriteByte(0); // Track Flags - stream.WriteInt32(parentIndex); // Parent Index - stream.WriteInt32(0); // Children Count - stream.WriteString(String::Format(TEXT("Track_{0}_Scale"), i), -13); // Name + stream.Write((byte)18); // Track Type + stream.Write((byte)0); // Track Flags + stream.Write(parentIndex); // Parent Index + stream.Write(0); // Children Count + stream.Write(String::Format(TEXT("Track_{0}_Scale"), i), -13); // Name stream.Write(Color32::White); // Color - stream.WriteByte(2); // Type - stream.WriteInt32(scale.Count()); // Keyframes Count + stream.Write((byte)2); // Type + stream.Write(scale.Count()); // Keyframes Count for (auto& k : scale) { - stream.WriteFloat(k.Time * fpsInv); + stream.Write(k.Time * fpsInv); stream.Write(k.Value); } trackIndex++; @@ -186,33 +188,33 @@ void Animation::LoadTimeline(BytesContainer& result) const Guid id = nestedAnim.Anim.GetID(); // Nested Animation track - stream.WriteByte(20); // Track Type - stream.WriteByte(flags); // Track Flags - stream.WriteInt32(-1); // Parent Index - stream.WriteInt32(0); // Children Count - stream.WriteString(e.First, -13); // Name + stream.Write((byte)20); // Track Type + stream.Write(flags); // Track Flags + stream.Write(-1); // Parent Index + stream.Write(0); // Children Count + stream.Write(e.First, -13); // Name stream.Write(Color32::White); // Color stream.Write(id); - stream.WriteFloat(nestedAnim.Time); - stream.WriteFloat(nestedAnim.Duration); - stream.WriteFloat(nestedAnim.Speed); - stream.WriteFloat(nestedAnim.StartTime); + stream.Write(nestedAnim.Time); + stream.Write(nestedAnim.Duration); + stream.Write(nestedAnim.Speed); + stream.Write(nestedAnim.StartTime); } for (auto& e : Events) { // Animation Event track - stream.WriteByte(19); // Track Type - stream.WriteByte(0); // Track Flags - stream.WriteInt32(-1); // Parent Index - stream.WriteInt32(0); // Children Count - stream.WriteString(e.First, -13); // Name + stream.Write((byte)19); // Track Type + stream.Write((byte)0); // Track Flags + stream.Write(-1); // Parent Index + stream.Write(0); // Children Count + stream.Write(e.First, -13); // Name stream.Write(Color32::White); // Color - stream.WriteInt32(e.Second.GetKeyframes().Count()); // Events Count + stream.Write(e.Second.GetKeyframes().Count()); // Events Count for (const auto& k : e.Second.GetKeyframes()) { - stream.WriteFloat(k.Time); - stream.WriteFloat(k.Value.Duration); - stream.WriteStringAnsi(k.Value.TypeName, 13); + stream.Write(k.Time); + stream.Write(k.Value.Duration); + stream.Write(k.Value.TypeName, 13); stream.WriteJson(k.Value.Instance); } } @@ -237,7 +239,7 @@ bool Animation::SaveTimeline(BytesContainer& data) // Version int32 version; - stream.ReadInt32(&version); + stream.Read(version); switch (version) { case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023] @@ -245,13 +247,13 @@ bool Animation::SaveTimeline(BytesContainer& data) { // Meta float fps; - stream.ReadFloat(&fps); + stream.Read(fps); Data.FramesPerSecond = static_cast(fps); int32 duration; - stream.ReadInt32(&duration); + stream.Read(duration); Data.Duration = static_cast(duration); int32 tracksCount; - stream.ReadInt32(&tracksCount); + stream.Read(tracksCount); // Tracks Data.Channels.Clear(); @@ -264,10 +266,10 @@ bool Animation::SaveTimeline(BytesContainer& data) const byte trackType = stream.ReadByte(); const byte trackFlags = stream.ReadByte(); int32 parentIndex, childrenCount; - stream.ReadInt32(&parentIndex); - stream.ReadInt32(&childrenCount); + stream.Read(parentIndex); + stream.Read(childrenCount); String name; - stream.ReadString(&name, -13); + stream.Read(name, -13); Color32 color; stream.Read(color); switch (trackType) @@ -286,7 +288,7 @@ bool Animation::SaveTimeline(BytesContainer& data) // Animation Channel Data track const byte type = stream.ReadByte(); int32 keyframesCount; - stream.ReadInt32(&keyframesCount); + stream.Read(keyframesCount); int32 channelIndex; if (!animationChannelTrackIndexToChannelIndex.TryGet(parentIndex, channelIndex)) { @@ -301,7 +303,7 @@ bool Animation::SaveTimeline(BytesContainer& data) for (int32 i = 0; i < keyframesCount; i++) { LinearCurveKeyframe& k = channel.Position.GetKeyframes()[i]; - stream.ReadFloat(&k.Time); + stream.Read(k.Time); k.Time *= fps; stream.Read(k.Value); } @@ -311,7 +313,7 @@ bool Animation::SaveTimeline(BytesContainer& data) for (int32 i = 0; i < keyframesCount; i++) { LinearCurveKeyframe& k = channel.Rotation.GetKeyframes()[i]; - stream.ReadFloat(&k.Time); + stream.Read(k.Time); k.Time *= fps; stream.Read(k.Value); } @@ -321,7 +323,7 @@ bool Animation::SaveTimeline(BytesContainer& data) for (int32 i = 0; i < keyframesCount; i++) { LinearCurveKeyframe& k = channel.Scale.GetKeyframes()[i]; - stream.ReadFloat(&k.Time); + stream.Read(k.Time); k.Time *= fps; stream.Read(k.Value); } @@ -340,9 +342,9 @@ bool Animation::SaveTimeline(BytesContainer& data) for (int32 i = 0; i < count; i++) { auto& k = eventTrack.Second.GetKeyframes()[i]; - stream.ReadFloat(&k.Time); - stream.ReadFloat(&k.Value.Duration); - stream.ReadStringAnsi(&k.Value.TypeName, 13); + stream.Read(k.Time); + stream.Read(k.Value.Duration); + stream.Read(k.Value.TypeName, 13); const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(k.Value.TypeName); k.Value.Instance = NewObject(typeHandle); stream.ReadJson(k.Value.Instance); @@ -367,10 +369,10 @@ bool Animation::SaveTimeline(BytesContainer& data) auto& nestedAnim = nestedTrack.Second; Guid id; stream.Read(id); - stream.ReadFloat(&nestedAnim.Time); - stream.ReadFloat(&nestedAnim.Duration); - stream.ReadFloat(&nestedAnim.Speed); - stream.ReadFloat(&nestedAnim.StartTime); + stream.Read(nestedAnim.Time); + stream.Read(nestedAnim.Duration); + stream.Read(nestedAnim.Speed); + stream.Read(nestedAnim.StartTime); nestedAnim.Anim = id; nestedAnim.Enabled = (trackFlags & (byte)SceneAnimation::Track::Flags::Mute) == 0; nestedAnim.Loop = (trackFlags & (byte)SceneAnimation::Track::Flags::Loop) != 0; @@ -415,18 +417,18 @@ bool Animation::Save(const StringView& path) MemoryWriteStream stream(4096); // Info - stream.WriteInt32(103); - stream.WriteDouble(Data.Duration); - stream.WriteDouble(Data.FramesPerSecond); - stream.WriteByte((byte)Data.RootMotionFlags); - stream.WriteString(Data.RootNodeName, 13); + stream.Write(103); + stream.Write(Data.Duration); + stream.Write(Data.FramesPerSecond); + stream.Write((byte)Data.RootMotionFlags); + stream.Write(Data.RootNodeName, 13); // Animation channels stream.WriteInt32(Data.Channels.Count()); for (int32 i = 0; i < Data.Channels.Count(); i++) { auto& anim = Data.Channels[i]; - stream.WriteString(anim.NodeName, 172); + stream.Write(anim.NodeName, 172); Serialization::Serialize(stream, anim.Position); Serialization::Serialize(stream, anim.Rotation); Serialization::Serialize(stream, anim.Scale); @@ -437,13 +439,13 @@ bool Animation::Save(const StringView& path) for (int32 i = 0; i < Events.Count(); i++) { auto& e = Events[i]; - stream.WriteString(e.First, 172); - stream.WriteInt32(e.Second.GetKeyframes().Count()); + stream.Write(e.First, 172); + stream.Write(e.Second.GetKeyframes().Count()); for (const auto& k : e.Second.GetKeyframes()) { - stream.WriteFloat(k.Time); - stream.WriteFloat(k.Value.Duration); - stream.WriteStringAnsi(k.Value.TypeName, 17); + stream.Write(k.Time); + stream.Write(k.Value.Duration); + stream.Write(k.Value.TypeName, 17); stream.WriteJson(k.Value.Instance); } } @@ -453,7 +455,7 @@ bool Animation::Save(const StringView& path) for (int32 i = 0; i < NestedAnims.Count(); i++) { auto& e = NestedAnims[i]; - stream.WriteString(e.First, 172); + stream.Write(e.First, 172); auto& nestedAnim = e.Second; Guid id = nestedAnim.Anim.GetID(); byte flags = 0; @@ -461,12 +463,12 @@ bool Animation::Save(const StringView& path) flags |= 1; if (nestedAnim.Loop) flags |= 2; - stream.WriteByte(flags); + stream.Write(flags); stream.Write(id); - stream.WriteFloat(nestedAnim.Time); - stream.WriteFloat(nestedAnim.Duration); - stream.WriteFloat(nestedAnim.Speed); - stream.WriteFloat(nestedAnim.StartTime); + stream.Write(nestedAnim.Time); + stream.Write(nestedAnim.Duration); + stream.Write(nestedAnim.Speed); + stream.Write(nestedAnim.StartTime); } // Set data to the chunk asset @@ -563,24 +565,24 @@ Asset::LoadResult Animation::load() switch (headerVersion) { case 103: - stream.ReadInt32(&headerVersion); - stream.ReadDouble(&Data.Duration); - stream.ReadDouble(&Data.FramesPerSecond); - stream.ReadByte((byte*)&Data.RootMotionFlags); - stream.ReadString(&Data.RootNodeName, 13); + stream.Read(headerVersion); + stream.Read(Data.Duration); + stream.Read(Data.FramesPerSecond); + stream.Read((byte&)Data.RootMotionFlags); + stream.Read(Data.RootNodeName, 13); break; case 100: case 101: case 102: - stream.ReadInt32(&headerVersion); - stream.ReadDouble(&Data.Duration); - stream.ReadDouble(&Data.FramesPerSecond); + stream.Read(headerVersion); + stream.Read(Data.Duration); + stream.Read(Data.FramesPerSecond); Data.RootMotionFlags = stream.ReadBool() ? AnimationRootMotionFlags::RootPositionXZ : AnimationRootMotionFlags::None; - stream.ReadString(&Data.RootNodeName, 13); + stream.Read(Data.RootNodeName, 13); break; default: - stream.ReadDouble(&Data.Duration); - stream.ReadDouble(&Data.FramesPerSecond); + stream.Read(Data.Duration); + stream.Read(Data.FramesPerSecond); break; } if (Data.Duration < ZeroTolerance || Data.FramesPerSecond < ZeroTolerance) @@ -597,7 +599,7 @@ Asset::LoadResult Animation::load() { auto& anim = Data.Channels[i]; - stream.ReadString(&anim.NodeName, 172); + stream.Read(anim.NodeName, 172); bool failed = Serialization::Deserialize(stream, anim.Position); failed |= Serialization::Deserialize(stream, anim.Rotation); failed |= Serialization::Deserialize(stream, anim.Scale); @@ -613,7 +615,7 @@ Asset::LoadResult Animation::load() if (headerVersion >= 101) { int32 eventTracksCount; - stream.ReadInt32(&eventTracksCount); + stream.Read(eventTracksCount); Events.Resize(eventTracksCount, false); #if !USE_EDITOR StringAnsi typeName; @@ -621,18 +623,18 @@ Asset::LoadResult Animation::load() for (int32 i = 0; i < eventTracksCount; i++) { auto& e = Events[i]; - stream.ReadString(&e.First, 172); + stream.Read(e.First, 172); int32 eventsCount; - stream.ReadInt32(&eventsCount); + stream.Read(eventsCount); e.Second.GetKeyframes().Resize(eventsCount); for (auto& k : e.Second.GetKeyframes()) { - stream.ReadFloat(&k.Time); - stream.ReadFloat(&k.Value.Duration); + stream.Read(k.Time); + stream.Read(k.Value.Duration); #if USE_EDITOR StringAnsi& typeName = k.Value.TypeName; #endif - stream.ReadStringAnsi(&typeName, 17); + stream.Read(typeName, 17); const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); k.Value.Instance = NewObject(typeHandle); stream.ReadJson(k.Value.Instance); @@ -656,24 +658,24 @@ Asset::LoadResult Animation::load() if (headerVersion >= 102) { int32 nestedAnimationsCount; - stream.ReadInt32(&nestedAnimationsCount); + stream.Read(nestedAnimationsCount); NestedAnims.Resize(nestedAnimationsCount, false); for (int32 i = 0; i < nestedAnimationsCount; i++) { auto& e = NestedAnims[i]; - stream.ReadString(&e.First, 172); + stream.Read(e.First, 172); auto& nestedAnim = e.Second; byte flags; - stream.ReadByte(&flags); + stream.Read(flags); nestedAnim.Enabled = flags & 1; nestedAnim.Loop = flags & 2; Guid id; stream.Read(id); nestedAnim.Anim = id; - stream.ReadFloat(&nestedAnim.Time); - stream.ReadFloat(&nestedAnim.Duration); - stream.ReadFloat(&nestedAnim.Speed); - stream.ReadFloat(&nestedAnim.StartTime); + stream.Read(nestedAnim.Time); + stream.Read(nestedAnim.Duration); + stream.Read(nestedAnim.Speed); + stream.Read(nestedAnim.StartTime); } } diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 5720b94d4..260e8ddf0 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -29,7 +29,7 @@ Asset::LoadResult SkeletonMask::load() _maskedNodes.Resize(maskedNodesCount); for (auto& e : _maskedNodes) { - stream.ReadString(&e, -13); + stream.Read(e, -13); } Skeleton = skeletonId; @@ -87,7 +87,7 @@ bool SkeletonMask::Save(const StringView& path) stream.WriteInt32(_maskedNodes.Count()); for (auto& e : _maskedNodes) { - stream.WriteString(e, -13); + stream.Write(e, -13); } // Save diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index b410d62f7..1cba4a709 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -433,7 +433,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& if (version == 4) { signature.IsStatic = stream.ReadBool(); - stream.ReadVariantType(&signature.ReturnType); + stream.Read(signature.ReturnType); int32 signatureParamsCount; stream.ReadInt32(&signatureParamsCount); signature.Params.Resize(signatureParamsCount); @@ -443,7 +443,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& int32 parameterNameLength; stream.ReadInt32(¶meterNameLength); stream.SetPosition(stream.GetPosition() + parameterNameLength * sizeof(Char)); - stream.ReadVariantType(¶m.Type); + stream.Read(param.Type); param.IsOut = stream.ReadBool(); } } @@ -1317,13 +1317,13 @@ Asset::LoadResult VisualScript::load() return LoadResult::MissingDataChunk; MemoryReadStream metadataStream(metadataChunk->Get(), metadataChunk->Size()); int32 version; - metadataStream.ReadInt32(&version); + metadataStream.Read(version); switch (version) { case 1: { - metadataStream.ReadString(&Meta.BaseTypename, 31); - metadataStream.ReadInt32((int32*)&Meta.Flags); + metadataStream.Read(Meta.BaseTypename, 31); + metadataStream.Read((int32&)Meta.Flags); break; } default: @@ -1376,10 +1376,10 @@ Asset::LoadResult VisualScript::load() { case 1: { - signatureStream.ReadStringAnsi(&method.Name, 71); + signatureStream.Read(method.Name, 71); method.MethodFlags = (MethodFlags)signatureStream.ReadByte(); method.Signature.IsStatic = ((byte)method.MethodFlags & (byte)MethodFlags::Static) != 0; - signatureStream.ReadVariantType(&method.Signature.ReturnType); + signatureStream.Read(method.Signature.ReturnType); int32 parametersCount; signatureStream.ReadInt32(¶metersCount); method.Signature.Params.Resize(parametersCount); @@ -1387,8 +1387,8 @@ Asset::LoadResult VisualScript::load() for (int32 i = 0; i < parametersCount; i++) { auto& param = method.Signature.Params[i]; - signatureStream.ReadStringAnsi(&method.ParamNames[i], 13); - signatureStream.ReadVariantType(¶m.Type); + signatureStream.Read(method.ParamNames[i], 13); + signatureStream.Read(param.Type); param.IsOut = signatureStream.ReadByte() != 0; bool hasDefaultValue = signatureStream.ReadByte() != 0; } @@ -1699,9 +1699,9 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri { // Special case for C# object property in Visual Script so duplicate the object instead of cloning the reference to it MemoryWriteStream writeStream; - writeStream.WriteVariant(param); + writeStream.Write(param); MemoryReadStream readStream(writeStream.GetHandle(), writeStream.GetPosition()); - readStream.ReadVariant(¶m); + readStream.Read(param); } } @@ -2227,9 +2227,9 @@ bool VisualScript::SaveSurface(const BytesContainer& data, const Metadata& meta) // Set metadata MemoryWriteStream metaStream(512); { - metaStream.WriteInt32(1); - metaStream.WriteString(meta.BaseTypename, 31); - metaStream.WriteInt32((int32)meta.Flags); + metaStream.Write(1); + metaStream.Write(meta.BaseTypename, 31); + metaStream.Write((int32)meta.Flags); } GetOrCreateChunk(1)->Data.Copy(metaStream.GetHandle(), metaStream.GetPosition()); diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index c07e3883a..a69429db0 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -65,7 +65,7 @@ public: /// /// Visual Script flag types. /// - API_ENUM(Attributes="Flags") enum class Flags + API_ENUM(Attributes="Flags") enum class Flags : int32 { /// /// No flags. diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp index 188a52583..eef5a777d 100644 --- a/Source/Engine/Content/Cache/AssetsCache.cpp +++ b/Source/Engine/Content/Cache/AssetsCache.cpp @@ -43,7 +43,7 @@ void AssetsCache::Init() // Load version int32 version; - stream->ReadInt32(&version); + stream->Read(version); if (version != FLAXENGINE_VERSION_BUILD) { LOG(Warning, "Corrupted or not supported Asset Cache file. Version: {0}", version); @@ -52,12 +52,12 @@ void AssetsCache::Init() // Load paths String enginePath, projectPath; - stream->ReadString(&enginePath, -410); - stream->ReadString(&projectPath, -410); + stream->Read(enginePath, -410); + stream->Read(projectPath, -410); // Flags AssetsCacheFlags flags; - stream->ReadInt32((int32*)&flags); + stream->Read((int32&)flags); // Check if other workspace instance used this cache if (EnumHasNoneFlags(flags, AssetsCacheFlags::RelativePaths) && enginePath != Globals::StartupFolder) @@ -75,7 +75,7 @@ void AssetsCache::Init() _isDirty = false; // Load elements count - stream->ReadInt32(&count); + stream->Read(count); _registry.Clear(); _registry.EnsureCapacity(count); @@ -84,8 +84,8 @@ void AssetsCache::Init() for (int32 i = 0; i < count; i++) { stream->Read(e.Info.ID); - stream->ReadString(&e.Info.TypeName, i - 13); - stream->ReadString(&e.Info.Path, i); + stream->Read(e.Info.TypeName, i - 13); + stream->Read(e.Info.Path, i); #if ENABLE_ASSETS_DISCOVERY stream->Read(e.FileModified); #else @@ -115,7 +115,7 @@ void AssetsCache::Init() Guid id; stream->Read(id); String mappedPath; - stream->ReadString(&mappedPath, i + 73); + stream->Read(mappedPath, i + 73); if (EnumHasAnyFlags(flags, AssetsCacheFlags::RelativePaths) && mappedPath.HasChars()) { @@ -177,17 +177,17 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa return true; // Version - stream->WriteInt32(FLAXENGINE_VERSION_BUILD); + stream->Write((int32)FLAXENGINE_VERSION_BUILD); // Paths - stream->WriteString(Globals::StartupFolder, -410); - stream->WriteString(Globals::ProjectFolder, -410); + stream->Write(Globals::StartupFolder, -410); + stream->Write(Globals::ProjectFolder, -410); // Flags - stream->WriteInt32((int32)flags); + stream->Write((int32)flags); // Items count - stream->WriteInt32(entries.Count()); + stream->Write(entries.Count()); // Data int32 index = 0; @@ -195,12 +195,12 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa { auto& e = i->Value; stream->Write(e.Info.ID); - stream->WriteString(e.Info.TypeName, index - 13); - stream->WriteString(e.Info.Path, index); + stream->Write(e.Info.TypeName, index - 13); + stream->Write(e.Info.Path, index); #if ENABLE_ASSETS_DISCOVERY stream->Write(e.FileModified); #else - stream->WriteInt64(0); + stream->Write((int64)0); #endif index++; } @@ -211,7 +211,7 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa for (auto i = pathsMapping.Begin(); i.IsNotEnd(); ++i) { stream->Write(i->Value); - stream->WriteString(i->Key, index + 73); + stream->Write(i->Key, index + 73); index++; } diff --git a/Source/Engine/Content/Cache/AssetsCache.h b/Source/Engine/Content/Cache/AssetsCache.h index 6dd9471e3..217d56ebd 100644 --- a/Source/Engine/Content/Cache/AssetsCache.h +++ b/Source/Engine/Content/Cache/AssetsCache.h @@ -19,7 +19,7 @@ class FlaxStorage; /// /// Assets cache flags. /// -enum class AssetsCacheFlags +enum class AssetsCacheFlags : int32 { /// /// The none. diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 46d9fdbf4..2786b0618 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -544,8 +544,8 @@ bool FlaxStorage::Load() DateTime tmpDate; String strTmp; stream->Read(tmpDate); - stream->ReadString(&strTmp, -76); - stream->ReadString(&strTmp, 1301); + stream->Read(strTmp, -76); + stream->Read(strTmp, 1301); } // Check char @@ -974,9 +974,9 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d stream->WriteUint32(header.SerializedVersion); // Chunks mapping - for (int32 chunkIndex = 0; chunkIndex < ARRAY_COUNT(header.Header.Chunks); chunkIndex++) + for (FlaxChunk* chunk : header.Header.Chunks) { - const int32 index = chunks.Find(header.Header.Chunks[chunkIndex]); + const int32 index = chunks.Find(chunk); stream->WriteInt32(index); } @@ -1250,8 +1250,8 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) DateTime importDate; String importPath, importUsername; stream->Read(importDate); - stream->ReadString(&importPath, -76); - stream->ReadString(&importUsername, 1301); + stream->Read(importPath, -76); + stream->Read(importUsername, 1301); #if USE_EDITOR // Convert old metadata into the new format diff --git a/Source/Engine/ContentImporters/CreateAnimation.h b/Source/Engine/ContentImporters/CreateAnimation.h index ec81acc68..dd6db2caf 100644 --- a/Source/Engine/ContentImporters/CreateAnimation.h +++ b/Source/Engine/ContentImporters/CreateAnimation.h @@ -24,20 +24,20 @@ public: MemoryWriteStream stream(256); { // Info - stream.WriteInt32(102); - stream.WriteDouble(5 * 60.0); - stream.WriteDouble(60.0); - stream.WriteBool(false); - stream.WriteString(StringView::Empty, 13); + stream.Write(102); + stream.Write(5 * 60.0); + stream.Write(60.0); + stream.Write(false); + stream.Write(StringView::Empty, 13); // Animation channels - stream.WriteInt32(0); + stream.Write(0); // Animation events - stream.WriteInt32(0); + stream.Write(0); // Nested animations - stream.WriteInt32(0); + stream.Write(0); } // Copy to asset chunk diff --git a/Source/Engine/ContentImporters/CreateVisualScript.h b/Source/Engine/ContentImporters/CreateVisualScript.h index 6fbc27d2e..5d5378f3d 100644 --- a/Source/Engine/ContentImporters/CreateVisualScript.h +++ b/Source/Engine/ContentImporters/CreateVisualScript.h @@ -41,9 +41,9 @@ public: return CreateAssetResult::CannotAllocateChunk; { MemoryWriteStream stream(256); - stream.WriteInt32(1); - stream.WriteString(*baseTypename, 31); - stream.WriteInt32((int32)VisualScript::Flags::None); + stream.Write(1); + stream.Write(*baseTypename, 31); + stream.Write((int32)VisualScript::Flags::None); context.Data.Header.Chunks[1]->Data.Copy(stream.GetHandle(), stream.GetPosition()); } diff --git a/Source/Engine/ContentImporters/ImportTexture.cpp b/Source/Engine/ContentImporters/ImportTexture.cpp index 1b67ae2fd..03a221b76 100644 --- a/Source/Engine/ContentImporters/ImportTexture.cpp +++ b/Source/Engine/ContentImporters/ImportTexture.cpp @@ -43,17 +43,17 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options { MemoryReadStream stream(chunk15->Data.Get(), chunk15->Data.Length()); int32 tilesVersion, tilesCount; - stream.ReadInt32(&tilesVersion); + stream.Read(tilesVersion); if (tilesVersion == 1) { options.Sprites.Clear(); - stream.ReadInt32(&tilesCount); + stream.Read(tilesCount); for (int32 i = 0; i < tilesCount; i++) { // Load sprite Sprite t; stream.Read(t.Area); - stream.ReadString(&t.Name, 49); + stream.Read(t.Name, 49); options.Sprites.Add(t); } } @@ -168,13 +168,13 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu if (options.IsAtlas) { MemoryWriteStream stream(256); - stream.WriteInt32(1); // Version - stream.WriteInt32(options.Sprites.Count()); // Amount of tiles + stream.Write(1); // Version + stream.Write(options.Sprites.Count()); // Amount of tiles for (int32 i = 0; i < options.Sprites.Count(); i++) { auto& sprite = options.Sprites[i]; stream.Write(sprite.Area); - stream.WriteString(sprite.Name, 49); + stream.Write(sprite.Name, 49); } if (context.AllocateChunk(15)) return CreateAssetResult::CannotAllocateChunk; @@ -307,13 +307,13 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu if (options.IsAtlas) { MemoryWriteStream stream(256); - stream.WriteInt32(1); // Version - stream.WriteInt32(options.Sprites.Count()); // Amount of tiles + stream.Write(1); // Version + stream.Write(options.Sprites.Count()); // Amount of tiles for (int32 i = 0; i < options.Sprites.Count(); i++) { auto& sprite = options.Sprites[i]; stream.Write(sprite.Area); - stream.WriteString(sprite.Name, 49); + stream.Write(sprite.Name, 49); } if (context.AllocateChunk(15)) return CreateAssetResult::CannotAllocateChunk; diff --git a/Source/Engine/Engine/GameplayGlobals.cpp b/Source/Engine/Engine/GameplayGlobals.cpp index eedd30d3a..d5f6fae65 100644 --- a/Source/Engine/Engine/GameplayGlobals.cpp +++ b/Source/Engine/Engine/GameplayGlobals.cpp @@ -14,22 +14,21 @@ class GameplayGlobalsUpgrader : public BinaryAssetUpgrader { public: - GameplayGlobalsUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { - { 1, 2, &Upgrade_1_To_2 }, + { 1, 2, &Upgrade_1_To_2 }, // [Deprecated on 31.07.2020, expires on 31.07.2022] }; setup(upgraders, ARRAY_COUNT(upgraders)); } private: - static bool Upgrade_1_To_2(AssetMigrationContext& context) { + // [Deprecated on 31.07.2020, expires on 31.07.2022] + PRAGMA_DISABLE_DEPRECATION_WARNINGS ASSERT(context.Input.SerializedVersion == 1 && context.Output.SerializedVersion == 2); - if (context.AllocateChunk(0)) return true; auto& data = context.Input.Header.Chunks[0]->Data; @@ -41,13 +40,14 @@ private: String name; for (int32 i = 0; i < count; i++) { - stream.ReadString(&name, 71); + stream.Read(name, 71); CommonValue commonValue; stream.ReadCommonValue(&commonValue); Variant variant(commonValue); output.WriteVariant(variant); } context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS return false; } @@ -170,11 +170,11 @@ bool GameplayGlobals::Save(const StringView& path) // Save to bytes MemoryWriteStream stream(1024); - stream.WriteInt32(Variables.Count()); + stream.Write(Variables.Count()); for (auto& e : Variables) { - stream.WriteString(e.Key, 71); - stream.WriteVariant(e.Value.DefaultValue); + stream.Write(e.Key, 71); + stream.Write(e.Value.DefaultValue); } // Set chunk data @@ -226,14 +226,14 @@ Asset::LoadResult GameplayGlobals::load() // Load all variables int32 count; - stream.ReadInt32(&count); + stream.Read(count); Variables.EnsureCapacity(count); String name; for (int32 i = 0; i < count; i++) { - stream.ReadString(&name, 71); + stream.Read(name, 71); auto& e = Variables[name]; - stream.ReadVariant(&e.DefaultValue); + stream.Read(e.DefaultValue); e.Value = e.DefaultValue; } if (stream.HasError()) diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 30064222b..0e56c33a8 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -812,7 +812,7 @@ bool MaterialParams::Load(ReadStream* stream) stream->Read(param->_paramId); param->_isPublic = stream->ReadBool(); param->_override = stream->ReadBool(); - stream->ReadString(¶m->_name, 10421); + stream->Read(param->_name, 10421); param->_registerIndex = stream->ReadByte(); stream->ReadUint16(¶m->_offset); @@ -826,7 +826,7 @@ bool MaterialParams::Load(ReadStream* stream) case MaterialParameterType::SceneTexture: case MaterialParameterType::ChannelMask: case MaterialParameterType::TextureGroupSampler: - stream->ReadInt32(¶m->_asInteger); + stream->Read(param->_asInteger); break; case MaterialParameterType::Float: stream->ReadFloat(¶m->_asFloat); @@ -904,7 +904,7 @@ void MaterialParams::Save(WriteStream* stream) stream->Write(param->_paramId); stream->WriteBool(param->_isPublic); stream->WriteBool(param->_override); - stream->WriteString(param->_name, 10421); + stream->Write(param->_name, 10421); stream->WriteByte(param->_registerIndex); stream->WriteUint16(param->_offset); @@ -980,7 +980,7 @@ void MaterialParams::Save(WriteStream* stream, const ArrayWrite(param.ID); stream->WriteBool(param.IsPublic); stream->WriteBool(param.Override); - stream->WriteString(param.Name, 10421); + stream->Write(param.Name, 10421); stream->WriteByte(param.RegisterIndex); stream->WriteUint16(param.Offset); diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index c1c17c26b..2869eacd9 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -136,7 +136,7 @@ bool IsValidShaderCache(DataContainer& shaderCache, Array& include for (int32 i = 0; i < includesCount; i++) { String& include = includes.AddOne(); - stream.ReadString(&include, 11); + stream.Read(include, 11); include = ShadersCompilation::ResolveShaderPath(include); DateTime lastEditTime; stream.Read(lastEditTime); diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 33f54582f..82983b1b9 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -65,7 +65,7 @@ bool GPUShader::Create(MemoryReadStream& stream) ASSERT(Math::IsInRange(permutationsCount, 1, SHADER_PERMUTATIONS_MAX_COUNT)); // Load shader name - stream.ReadStringAnsi(&initializer.Name, 11); + stream.Read(initializer.Name, 11); ASSERT(initializer.Name.HasChars()); // Load shader flags diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index bbc90ab7f..2a594bdde 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1639,7 +1639,7 @@ bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output) output.WriteInt32(FLAXENGINE_VERSION_BUILD); // Serialized objects ids (for references mapping) - output.WriteArray(ids); + output.Write(ids); // Objects data rapidjson_flax::StringBuffer buffer; @@ -1694,7 +1694,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Serialized objects ids (for references mapping) Array ids; - stream.ReadArray(&ids); + stream.Read(ids); int32 objectsCount = ids.Count(); if (objectsCount < 0) return true; @@ -1866,7 +1866,7 @@ Array Actor::TryGetSerializedObjectsIds(const Span& data) if (engineBuild <= FLAXENGINE_VERSION_BUILD && engineBuild >= 6165) { // Serialized objects ids (for references mapping) - stream.ReadArray(&result); + stream.Read(result); } } diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index e35405ae8..fe597a848 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -152,7 +152,7 @@ void NetworkStream::Read(Quaternion& data) NetworkQuaternion::Read(this, data); } -void NetworkStream::Read(Transform& data) +void NetworkStream::Read(Transform& data, bool useDouble) { struct NonQuantized { @@ -181,7 +181,7 @@ void NetworkStream::Write(const Quaternion& data) NetworkQuaternion::Write(this, data); } -void NetworkStream::Write(const Transform& data) +void NetworkStream::Write(const Transform& data, bool useDouble) { struct NonQuantized { diff --git a/Source/Engine/Networking/NetworkStream.h b/Source/Engine/Networking/NetworkStream.h index c13ad37d8..1a0e1433f 100644 --- a/Source/Engine/Networking/NetworkStream.h +++ b/Source/Engine/Networking/NetworkStream.h @@ -73,13 +73,13 @@ public: void Read(INetworkSerializable& obj); void Read(INetworkSerializable* obj); void Read(Quaternion& data); - void Read(Transform& data); + void Read(Transform& data, bool useDouble = false); using WriteStream::Write; void Write(INetworkSerializable& obj); void Write(INetworkSerializable* obj); void Write(const Quaternion& data); - void Write(const Transform& data); + void Write(const Transform& data, bool useDouble = false); public: // [Stream] diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 4c0fcc076..0af499ed0 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -74,7 +74,7 @@ BytesContainer ParticleSystem::LoadTimeline() stream.WriteByte((byte)track.Flag); stream.WriteInt32(track.ParentIndex); stream.WriteInt32(track.ChildrenCount); - stream.WriteString(track.Name, -13); + stream.Write(track.Name, -13); stream.Write(track.Color); Guid id; @@ -102,7 +102,7 @@ BytesContainer ParticleSystem::LoadTimeline() { stream.WriteInt32(i->Key.First); stream.Write(i->Key.Second); - stream.WriteVariant(i->Value); + stream.Write(i->Value); } } } @@ -212,7 +212,7 @@ Asset::LoadResult ParticleSystem::load() MemoryReadStream stream(chunk0->Data.Get(), chunk0->Data.Length()); int32 version; - stream.ReadInt32(&version); + stream.Read(version); #if USE_EDITOR // Skip unused parameters #define SKIP_UNUSED_PARAM_OVERRIDE() if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First] == nullptr || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr) continue @@ -394,7 +394,7 @@ Asset::LoadResult ParticleSystem::load() track.Flag = (Track::Flags)stream.ReadByte(); stream.ReadInt32(&track.ParentIndex); stream.ReadInt32(&track.ChildrenCount); - stream.ReadString(&track.Name, -13); + stream.Read(track.Name, -13); track.Disabled = (int32)track.Flag & (int32)Track::Flags::Mute || (track.ParentIndex != -1 && Tracks[track.ParentIndex].Disabled); stream.Read(track.Color); @@ -422,19 +422,22 @@ Asset::LoadResult ParticleSystem::load() Emitters[i]->WaitForLoaded(); } - // Load parameters overrides - int32 overridesCount = 0; - if (stream.CanRead()) - stream.ReadInt32(&overridesCount); - if (overridesCount != 0) + if (version <= 3) { + // [Deprecated on 03.09.2021 expires on 03.09.2023] + } + else + { + // Load parameters overrides + int32 overridesCount = 0; + stream.ReadInt32(&overridesCount); EmitterParameterOverrideKey key; Variant value; for (int32 i = 0; i < overridesCount; i++) { stream.ReadInt32(&key.First); stream.Read(key.Second); - stream.ReadVariant(&value); + stream.Read(value); SKIP_UNUSED_PARAM_OVERRIDE(); EmittersParametersOverrides[key] = value; } diff --git a/Source/Engine/Render2D/SpriteAtlas.cpp b/Source/Engine/Render2D/SpriteAtlas.cpp index cc4dfa295..5a5dbaa4c 100644 --- a/Source/Engine/Render2D/SpriteAtlas.cpp +++ b/Source/Engine/Render2D/SpriteAtlas.cpp @@ -108,12 +108,12 @@ bool SpriteAtlas::SaveSprites() // Write sprites data MemoryWriteStream stream(1024); - stream.WriteInt32(1); // Version - stream.WriteInt32(Sprites.Count()); // Sprites Count + stream.Write(1); // Version + stream.Write(Sprites.Count()); // Sprites Count for (Sprite& t : Sprites) { stream.Write(t.Area); - stream.WriteString(t.Name, 49); + stream.Write(t.Name, 49); } // Link sprites data (unlink after safe) @@ -148,7 +148,7 @@ bool SpriteAtlas::LoadSprites(ReadStream& stream) Sprites.Clear(); int32 tilesVersion, tilesCount; - stream.ReadInt32(&tilesVersion); + stream.Read(tilesVersion); if (tilesVersion != 1) { #if USE_EDITOR @@ -158,12 +158,12 @@ bool SpriteAtlas::LoadSprites(ReadStream& stream) LOG(Warning, "Invalid tiles version."); return true; } - stream.ReadInt32(&tilesCount); + stream.Read(tilesCount); Sprites.Resize(tilesCount); for (Sprite& t : Sprites) { stream.Read(t.Area); - stream.ReadString(&t.Name, 49); + stream.Read(t.Name, 49); } #if USE_EDITOR diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index b7cec5e30..02006e8fa 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -167,9 +167,9 @@ public: } /// - /// Read data array + /// Reads array. /// - /// Array to read + /// Array to read. template void Read(Array& data) { @@ -189,9 +189,9 @@ public: } /// - /// Read data dictionary + /// Reads dictionary. /// - /// Dictionary to read + /// Dictionary to read. template void Read(Dictionary& data) { @@ -214,34 +214,41 @@ public: /// The object to deserialize. void ReadJson(ISerializable* obj); + // Deserialization of math types with float or double depending on the context (must match serialization) + // Set useDouble=true to explicitly use 64-bit precision for serialized data + void Read(BoundingBox& box, bool useDouble = false); + void Read(BoundingSphere& sphere, bool useDouble = false); + void Read(Transform& transform, bool useDouble = false); + void Read(Ray& ray, bool useDouble = false); + public: // Reads StringAnsi from the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadStringAnsi(StringAnsi* data); + DEPRECATED("Use Read method") void ReadStringAnsi(StringAnsi* data); // Reads StringAnsi from the stream with a key /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadStringAnsi(StringAnsi* data, int8 lock); + DEPRECATED("Use Read method") void ReadStringAnsi(StringAnsi* data, int8 lock); // Reads String from the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadString(String* data); + DEPRECATED("Use Read method") void ReadString(String* data); // Reads String from the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadString(String* data, int16 lock); + DEPRECATED("Use Read method") void ReadString(String* data, int16 lock); // Reads CommonValue from the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadCommonValue(CommonValue* data); + DEPRECATED("Use Read method") void ReadCommonValue(CommonValue* data); // Reads VariantType from the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadVariantType(VariantType* data); + DEPRECATED("Use Read method") void ReadVariantType(VariantType* data); // Reads Variant from the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void ReadVariant(Variant* data); + DEPRECATED("Use Read method") void ReadVariant(Variant* data); /// /// Read data array @@ -249,18 +256,21 @@ public: /// /// Array to read template - void ReadArray(Array* data) + DEPRECATED("Use Read method") void ReadArray(Array* data) { Read(*data); } -public: // Deserialization of math types with float or double depending on the context (must match serialization) // Set useDouble=true to explicitly use 64-bit precision for serialized data - void ReadBoundingBox(BoundingBox* box, bool useDouble = false); - void ReadBoundingSphere(BoundingSphere* sphere, bool useDouble = false); - void ReadTransform(Transform* transform, bool useDouble = false); - void ReadRay(Ray* ray, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Read method") void ReadBoundingBox(BoundingBox* box, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Read method") void ReadBoundingSphere(BoundingSphere* sphere, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Read method") void ReadTransform(Transform* transform, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Read method") void ReadRay(Ray* ray, bool useDouble = false); public: // [Stream] diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index fa9554fbb..a225ca8f5 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -167,14 +167,14 @@ void ReadStream::Read(CommonValue& data) case CommonType::String: { String v; - ReadString(&v, 953); + Read(v, 953); data.Set(v); } break; case CommonType::Box: { BoundingBox v; - ReadBoundingBox(&v); + Read(v); data.Set(v); } break; @@ -188,14 +188,14 @@ void ReadStream::Read(CommonValue& data) case CommonType::Transform: { Transform v; - ReadTransform(&v); + Read(v); data.Set(v); } break; case CommonType::Sphere: { BoundingSphere v; - ReadBoundingSphere(&v); + Read(v); data.Set(v); } break; @@ -208,7 +208,7 @@ void ReadStream::Read(CommonValue& data) case CommonType::Ray: { Ray v; - ReadRay(&v); + Read(v); data.Set(v); } break; @@ -277,7 +277,7 @@ void ReadStream::Read(VariantType& data) void ReadStream::Read(Variant& data) { VariantType type; - ReadVariantType(&type); + Read(type); data.SetType(MoveTemp(type)); switch (data.Type.Type) { @@ -360,7 +360,7 @@ void ReadStream::Read(Variant& data) { // Json StringAnsi json; - ReadStringAnsi(&json, -71); + Read(json, -71); #if USE_CSHARP MCore::Thread::Attach(); MClass* klass = MUtils::GetClass(data.Type); @@ -430,22 +430,22 @@ void ReadStream::Read(Variant& data) ReadBytes(&data.AsData, sizeof(Guid)); break; case VariantType::BoundingBox: - ReadBoundingBox(&data.AsBoundingBox()); + Read(data.AsBoundingBox()); break; case VariantType::BoundingSphere: - ReadBoundingSphere(&data.AsBoundingSphere()); + Read(data.AsBoundingSphere()); break; case VariantType::Quaternion: ReadBytes(&data.AsData, sizeof(Quaternion)); break; case VariantType::Transform: - ReadTransform(&data.AsTransform()); + Read(data.AsTransform()); break; case VariantType::Rectangle: ReadBytes(&data.AsData, sizeof(Rectangle)); break; case VariantType::Ray: - ReadRay(&data.AsRay()); + Read(data.AsRay()); break; case VariantType::Matrix: ReadBytes(data.AsBlob.Data, sizeof(Matrix)); @@ -453,25 +453,25 @@ void ReadStream::Read(Variant& data) case VariantType::Array: { int32 count; - ReadInt32(&count); + Read(count); auto& array = *(Array*)data.AsData; array.Resize(count); for (int32 i = 0; i < count; i++) - ReadVariant(&array[i]); + Read(array[i]); break; } case VariantType::Dictionary: { int32 count; - ReadInt32(&count); + Read(count); auto& dictionary = *data.AsDictionary; dictionary.Clear(); dictionary.EnsureCapacity(count); for (int32 i = 0; i < count; i++) { Variant key; - ReadVariant(&key); - ReadVariant(&dictionary[MoveTemp(key)]); + Read(key); + Read(dictionary[MoveTemp(key)]); } break; } @@ -562,18 +562,18 @@ void ReadStream::ReadVariant(Variant* data) Read(*data); } -void ReadStream::ReadBoundingBox(BoundingBox* box, bool useDouble) +void ReadStream::Read(BoundingBox& box, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Read(box); + ReadBytes(&box, sizeof(BoundingBox)); else { Float3 min, max; Read(min); Read(max); - box->Minimum = min; - box->Maximum = max; + box.Minimum = min; + box.Maximum = max; } #else if (useDouble) @@ -581,27 +581,27 @@ void ReadStream::ReadBoundingBox(BoundingBox* box, bool useDouble) Double3 min, max; Read(min); Read(max); - box->Minimum = min; - box->Maximum = max; + box.Minimum = min; + box.Maximum = max; } else - Read(*box); + ReadBytes(&box, sizeof(BoundingBox)); #endif } -void ReadStream::ReadBoundingSphere(BoundingSphere* sphere, bool useDouble) +void ReadStream::Read(BoundingSphere& sphere, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Read(*sphere); + ReadBytes(&sphere, sizeof(BoundingSphere)); else { Float3 center; float radius; Read(center); Read(radius); - sphere->Center = center; - sphere->Radius = radius; + sphere.Center = center; + sphere.Radius = radius; } #else if (useDouble) @@ -610,53 +610,53 @@ void ReadStream::ReadBoundingSphere(BoundingSphere* sphere, bool useDouble) double radius; Read(center); Read(radius); - sphere->Center = center; - sphere->Radius = (float)radius; + sphere.Center = center; + sphere.Radius = (float)radius; } else - Read(*sphere); + ReadBytes(&sphere, sizeof(BoundingSphere)); #endif } -void ReadStream::ReadTransform(Transform* transform, bool useDouble) +void ReadStream::Read(Transform& transform, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Read(*transform); + ReadBytes(&transform, sizeof(Transform)); else { Float3 translation; Read(translation); - Read(transform->Orientation); - Read(transform->Scale); - transform->Translation = translation; + Read(transform.Orientation); + Read(transform.Scale); + transform.Translation = translation; } #else if (useDouble) { Double3 translation; Read(translation); - Read(transform->Orientation); - Read(transform->Scale); - transform->Translation = translation; + Read(transform.Orientation); + Read(transform.Scale); + transform.Translation = translation; } else - Read(*transform); + ReadBytes(&transform, sizeof(Transform)); #endif } -void ReadStream::ReadRay(Ray* ray, bool useDouble) +void ReadStream::Read(Ray& ray, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Read(*ray); + ReadBytes(&ray, sizeof(Ray)); else { Float3 position, direction; Read(position); Read(direction); - ray->Position = position; - ray->Direction = direction; + ray.Position = position; + ray.Direction = direction; } #else if (useDouble) @@ -664,14 +664,34 @@ void ReadStream::ReadRay(Ray* ray, bool useDouble) Double3 position, direction; Read(position); Read(direction); - ray->Position = position; - ray->Direction = direction; + ray.Position = position; + ray.Direction = direction; } else - Read(*ray); + ReadBytes(&ray, sizeof(Ray)); #endif } +void ReadStream::ReadBoundingBox(BoundingBox* box, bool useDouble) +{ + Read(*box); +} + +void ReadStream::ReadBoundingSphere(BoundingSphere* sphere, bool useDouble) +{ + Read(*sphere); +} + +void ReadStream::ReadTransform(Transform* transform, bool useDouble) +{ + Read(*transform); +} + +void ReadStream::ReadRay(Ray* ray, bool useDouble) +{ + Read(*ray); +} + void WriteStream::WriteText(const StringView& text) { WriteBytes(text.Get(), sizeof(Char) * text.Length()); @@ -722,13 +742,13 @@ void WriteStream::Write(const CommonValue& data) switch (data.Type) { case CommonType::Bool: - WriteBool(data.AsBool); + Write(data.AsBool); break; case CommonType::Integer: - WriteInt32(data.AsInteger); + Write(data.AsInteger); break; case CommonType::Float: - WriteFloat(data.AsFloat); + Write(data.AsFloat); break; case CommonType::Vector2: Write(data.AsVector2); @@ -746,25 +766,25 @@ void WriteStream::Write(const CommonValue& data) Write(data.AsGuid); break; case CommonType::String: - WriteString(data.AsString, 953); + Write(data.AsString, 953); break; case CommonType::Box: - WriteBoundingBox(data.AsBox); + Write(data.AsBox); break; case CommonType::Rotation: Write(data.AsRotation); break; case CommonType::Transform: - WriteTransform(data.AsTransform); + Write(data.AsTransform); break; case CommonType::Sphere: - WriteBoundingSphere(data.AsSphere); + Write(data.AsSphere); break; case CommonType::Rectangle: Write(data.AsRectangle); break; case CommonType::Ray: - WriteRay(data.AsRay); + Write(data.AsRay); break; case CommonType::Matrix: Write(data.AsMatrix); @@ -782,12 +802,12 @@ void WriteStream::Write(const VariantType& data) { WriteByte((byte)data.Type); WriteInt32(MAX_int32); - WriteStringAnsi(StringAnsiView(data.TypeName), 77); + Write(StringAnsiView(data.TypeName), 77); } void WriteStream::Write(const Variant& data) { - WriteVariantType(data.Type); + Write(data.Type); Guid id; switch (data.Type.Type) { @@ -826,7 +846,7 @@ void WriteStream::Write(const Variant& data) WriteUint64((uint64)(uintptr)data.AsPointer); break; case VariantType::String: - WriteString((StringView)data, -14); + Write((StringView)data, -14); break; case VariantType::Object: id = data.AsObject ? data.AsObject->GetID() : Guid::Empty; @@ -837,13 +857,13 @@ void WriteStream::Write(const Variant& data) WriteBytes(data.AsBlob.Data, data.AsBlob.Length); break; case VariantType::BoundingBox: - WriteBoundingBox(data.AsBoundingBox()); + Write(data.AsBoundingBox()); break; case VariantType::Transform: - WriteTransform(data.AsTransform()); + Write(data.AsTransform()); break; case VariantType::Ray: - WriteRay(data.AsRay()); + Write(data.AsRay()); break; case VariantType::Matrix: WriteBytes(data.AsBlob.Data, sizeof(Matrix)); @@ -883,24 +903,24 @@ void WriteStream::Write(const Variant& data) WriteBytes(data.AsData, sizeof(Rectangle)); break; case VariantType::BoundingSphere: - WriteBoundingSphere(data.AsBoundingSphere()); + Write(data.AsBoundingSphere()); break; case VariantType::Array: id.A = ((Array*)data.AsData)->Count(); WriteInt32(id.A); for (uint32 i = 0; i < id.A; i++) - WriteVariant(((Array*)data.AsData)->At(i)); + Write(((Array*)data.AsData)->At(i)); break; case VariantType::Dictionary: WriteInt32(data.AsDictionary->Count()); for (auto i = data.AsDictionary->Begin(); i.IsNotEnd(); ++i) { - WriteVariant(i->Key); - WriteVariant(i->Value); + Write(i->Key); + Write(i->Value); } break; case VariantType::Typename: - WriteStringAnsi((StringAnsiView)data, -14); + Write((StringAnsiView)data, -14); break; case VariantType::ManagedObject: case VariantType::Structure: @@ -918,7 +938,7 @@ void WriteStream::Write(const Variant& data) CompactJsonWriter writerObj(json); MCore::Thread::Attach(); ManagedSerialization::Serialize(writerObj, obj); - WriteStringAnsi(StringAnsiView(json.GetString(), (int32)json.GetSize()), -71); + Write(StringAnsiView(json.GetString(), (int32)json.GetSize()), -71); } else #endif @@ -992,11 +1012,11 @@ void WriteStream::WriteVariant(const Variant& data) Write(data); } -void WriteStream::WriteBoundingBox(const BoundingBox& box, bool useDouble) +void WriteStream::Write(const BoundingBox& box, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Write(box); + WriteBytes(&box, sizeof(BoundingBox)); else { Float3 min = box.Minimum, max = box.Maximum; @@ -1011,15 +1031,15 @@ void WriteStream::WriteBoundingBox(const BoundingBox& box, bool useDouble) Write(max); } else - Write(box); + WriteBytes(&box, sizeof(BoundingBox)); #endif } -void WriteStream::WriteBoundingSphere(const BoundingSphere& sphere, bool useDouble) +void WriteStream::Write(const BoundingSphere& sphere, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Write(sphere); + WriteBytes(&sphere, sizeof(BoundingSphere)); else { Float3 center = sphere.Center; @@ -1036,15 +1056,15 @@ void WriteStream::WriteBoundingSphere(const BoundingSphere& sphere, bool useDoub Write(radius); } else - Write(sphere); + WriteBytes(&sphere, sizeof(BoundingSphere)); #endif } -void WriteStream::WriteTransform(const Transform& transform, bool useDouble) +void WriteStream::Write(const Transform& transform, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Write(transform); + WriteBytes(&transform, sizeof(Transform)); else { Float3 translation = transform.Translation; @@ -1061,15 +1081,15 @@ void WriteStream::WriteTransform(const Transform& transform, bool useDouble) Write(transform.Scale); } else - Write(transform); + WriteBytes(&transform, sizeof(Transform)); #endif } -void WriteStream::WriteRay(const Ray& ray, bool useDouble) +void WriteStream::Write(const Ray& ray, bool useDouble) { #if USE_LARGE_WORLDS if (useDouble) - Write(ray); + WriteBytes(&ray, sizeof(Ray)); else { Float3 position = ray.Position, direction = ray.Direction; @@ -1084,10 +1104,30 @@ void WriteStream::WriteRay(const Ray& ray, bool useDouble) Write(direction); } else - Write(ray); + WriteBytes(&ray, sizeof(Ray)); #endif } +void WriteStream::WriteBoundingBox(const BoundingBox& box, bool useDouble) +{ + Write(box, useDouble); +} + +void WriteStream::WriteBoundingSphere(const BoundingSphere& sphere, bool useDouble) +{ + Write(sphere, useDouble); +} + +void WriteStream::WriteTransform(const Transform& transform, bool useDouble) +{ + Write(transform, useDouble); +} + +void WriteStream::WriteRay(const Ray& ray, bool useDouble) +{ + Write(ray, useDouble); +} + Array JsonSerializer::SaveToBytes(ISerializable* obj) { Array result; diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index e362e8258..2a18ea60b 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -178,6 +178,23 @@ public: Write(v.Get()); } + template + void Write(const Span& data) + { + const int32 size = data.Length(); + WriteInt32(size); + if (size > 0) + { + if (TIsPODType::Value && !TIsPointer::Value) + WriteBytes(data.Get(), size * sizeof(T)); + else + { + for (int32 i = 0; i < size; i++) + Write(data[i]); + } + } + } + template void Write(const Array& data) { @@ -216,42 +233,49 @@ public: void WriteJson(ISerializable* obj, const void* otherObj = nullptr); void WriteJson(const StringAnsiView& json); + // Serialization of math types with float or double depending on the context (must match deserialization) + // Set useDouble=true to explicitly use 64-bit precision for serialized data + void Write(const BoundingBox& box, bool useDouble = false); + void Write(const BoundingSphere& sphere, bool useDouble = false); + void Write(const Transform& transform, bool useDouble = false); + void Write(const Ray& ray, bool useDouble = false); + public: // Writes String to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] // @param data Data to write - void WriteString(const StringView& data); + DEPRECATED("Use Write method") void WriteString(const StringView& data); // Writes String to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] // @param data Data to write // @param lock Characters pass in the stream - void WriteString(const StringView& data, int16 lock); + DEPRECATED("Use Write method") void WriteString(const StringView& data, int16 lock); // Writes Ansi String to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] - void WriteStringAnsi(const StringAnsiView& data); + DEPRECATED("Use Write method") void WriteStringAnsi(const StringAnsiView& data); // Writes Ansi String to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] // @param data Data to write // @param lock Characters pass in the stream - void WriteStringAnsi(const StringAnsiView& data, int8 lock); + DEPRECATED("Use Write method") void WriteStringAnsi(const StringAnsiView& data, int8 lock); // Writes CommonValue to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] // @param data Data to write - void WriteCommonValue(const CommonValue& data); + DEPRECATED("Use Write method") void WriteCommonValue(const CommonValue& data); // Writes VariantType to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] // @param data Data to write - void WriteVariantType(const VariantType& data); + DEPRECATED("Use Write method") void WriteVariantType(const VariantType& data); // Writes Variant to the stream /// [Deprecated on 11.10.2022, expires on 11.10.2024] // @param data Data to write - void WriteVariant(const Variant& data); + DEPRECATED("Use Write method") void WriteVariant(const Variant& data); /// /// Write data array @@ -259,18 +283,21 @@ public: /// /// Array to write template - FORCE_INLINE void WriteArray(const Array& data) + DEPRECATED("Use Write method") FORCE_INLINE void WriteArray(const Array& data) { Write(data); } -public: // Serialization of math types with float or double depending on the context (must match deserialization) // Set useDouble=true to explicitly use 64-bit precision for serialized data - void WriteBoundingBox(const BoundingBox& box, bool useDouble = false); - void WriteBoundingSphere(const BoundingSphere& sphere, bool useDouble = false); - void WriteTransform(const Transform& transform, bool useDouble = false); - void WriteRay(const Ray& ray, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Write method") void WriteBoundingBox(const BoundingBox& box, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Write method") void WriteBoundingSphere(const BoundingSphere& sphere, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Write method") void WriteTransform(const Transform& transform, bool useDouble = false); + // [Deprecated in v1.10] + DEPRECATED("Use Write method") void WriteRay(const Ray& ray, bool useDouble = false); public: // [Stream] diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 84c881092..e8ea14971 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -67,11 +67,11 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) return true; // [Output] Constant Buffers - output->WriteByte((byte)_constantBuffers.Count()); + output->Write((byte)_constantBuffers.Count()); for (const ShaderResourceBuffer& cb : _constantBuffers) { - output->WriteByte(cb.Slot); - output->WriteUint32(cb.Size); + output->Write((byte)cb.Slot); + output->Write((uint32)cb.Size); } // Additional Data Start @@ -82,7 +82,7 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) for (auto& include : context->Includes) { String compactPath = ShadersCompilation::CompactShaderPath(include.Item); - output->WriteString(compactPath, 11); + output->Write(compactPath, 11); const auto date = FileSystem::GetFileLastEditTime(include.Item); output->Write(date); } @@ -261,19 +261,10 @@ bool ShaderCompiler::OnCompileEnd() bool ShaderCompiler::WriteShaderFunctionBegin(ShaderCompilationContext* context, ShaderFunctionMeta& meta) { auto output = context->Output; - - // [Output] Type - output->WriteByte(static_cast(meta.GetStage())); - - // [Output] Permutations count - output->WriteByte(meta.Permutations.Count()); - - // [Output] Shader function name - output->WriteStringAnsi(meta.Name, 11); - - // [Output] Shader flags - output->WriteUint32((uint32)meta.Flags); - + output->Write((byte)meta.GetStage()); + output->Write((byte)meta.Permutations.Count()); + output->Write(meta.Name, 11); + output->Write((uint32)meta.Flags); return false; } @@ -281,28 +272,19 @@ bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* co { auto output = context->Output; - // [Output] Write compiled shader cache - output->WriteUint32(cacheSize + headerSize); + output->Write((uint32)(cacheSize + headerSize)); output->WriteBytes(header, headerSize); output->WriteBytes(cache, cacheSize); - - // [Output] Shader bindings meta 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; - - // [Output] Write compiled shader cache - output->WriteUint32(cacheSize); + output->Write((uint32)cacheSize); output->WriteBytes(cache, cacheSize); - - // [Output] Shader bindings meta output->Write(bindings); - return false; } diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index aaec4e420..bca766288 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -402,7 +402,7 @@ void ShadersCompilation::ExtractShaderIncludes(byte* shaderCache, int32 shaderCa for (int32 i = 0; i < includesCount; i++) { String& include = includes.AddOne(); - stream.ReadString(&include, 11); + stream.Read(include, 11); include = ShadersCompilation::ResolveShaderPath(include); DateTime lastEditTime; stream.Read(lastEditTime); diff --git a/Source/Engine/Visject/Graph.h b/Source/Engine/Visject/Graph.h index c0ef06455..62fcfec0a 100644 --- a/Source/Engine/Visject/Graph.h +++ b/Source/Engine/Visject/Graph.h @@ -101,11 +101,11 @@ public: for (int32 i = 0; i < Parameters.Count(); i++) { const Parameter* param = &Parameters[i]; - stream->WriteVariantType(param->Type); + stream->Write(param->Type); stream->Write(param->Identifier); - stream->WriteString(param->Name, 97); + stream->Write(param->Name, 97); stream->WriteBool(param->IsPublic); - stream->WriteVariant(param->Value); + stream->Write(param->Value); if (param->Meta.Save(stream, saveMeta)) return true; } @@ -119,7 +119,7 @@ public: // Values stream->WriteInt32(node->Values.Count()); for (int32 j = 0; j < node->Values.Count(); j++) - stream->WriteVariant(node->Values[j]); + stream->Write(node->Values[j]); // Boxes node->GetBoxes(boxes); @@ -128,7 +128,7 @@ public: { const Box* box = boxes[j]; stream->WriteByte(box->ID); - stream->WriteVariantType(box->Type); + stream->Write(box->Type); stream->WriteUint16(box->Connections.Count()); for (int32 k = 0; k < box->Connections.Count(); k++) { @@ -222,7 +222,7 @@ public: // Properties auto type = stream->ReadByte(); stream->Read(param->Identifier); - stream->ReadString(¶m->Name, 97); + stream->Read(param->Name, 97); param->IsPublic = stream->ReadBool(); bool isStatic = stream->ReadBool(); bool isUIVisible = stream->ReadBool(); @@ -358,11 +358,11 @@ public: for (int32 i = 0; i < parametersCount; i++) { auto param = &Parameters[i]; - stream->ReadVariantType(¶m->Type); + stream->Read(param->Type); stream->Read(param->Identifier); - stream->ReadString(¶m->Name, 97); + stream->Read(param->Name, 97); param->IsPublic = stream->ReadBool(); - stream->ReadVariant(¶m->Value); + stream->Read(param->Value); if (param->Meta.Load(stream, loadMeta)) return true; if (onParamCreated(param)) @@ -379,7 +379,7 @@ public: stream->ReadInt32(&valuesCnt); node->Values.Resize(valuesCnt); for (int32 j = 0; j < valuesCnt; j++) - stream->ReadVariant(&node->Values[j]); + stream->Read(node->Values[j]); // Boxes uint16 boxesCount; @@ -392,7 +392,7 @@ public: Box* box = &node->Boxes[boxID]; box->Parent = node; box->ID = boxID; - stream->ReadVariantType(&box->Type); + stream->Read(box->Type); uint16 connectionsCount; stream->ReadUint16(&connectionsCount); box->Connections.Resize(connectionsCount); diff --git a/Source/Engine/Visject/GraphUtilities.cpp b/Source/Engine/Visject/GraphUtilities.cpp index 4aaeb01fe..ca25b4512 100644 --- a/Source/Engine/Visject/GraphUtilities.cpp +++ b/Source/Engine/Visject/GraphUtilities.cpp @@ -62,6 +62,7 @@ enum class GraphConnectionType_Deprecated : uint32 #include "Engine/Core/Types/CommonValue.h" #include "Engine/Level/Actor.h" +PRAGMA_DISABLE_DEPRECATION_WARNINGS FLAXENGINE_API void ReadOldGraphParamValue_Deprecated(byte graphParamType, ReadStream* stream, GraphParameter* param) { // [Deprecated on 31.07.2020, expires on 31.07.2022] @@ -290,6 +291,7 @@ FLAXENGINE_API StringView GetGraphFunctionTypeName_Deprecated(const Variant& v) } return StringView::Empty; } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, MathOp1 op) { diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 7e02f0aa5..aacaf8800 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -625,9 +625,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) { box = &node->Boxes[boxId]; String fieldName; - stream.ReadString(&fieldName, 11); + stream.Read(fieldName, 11); VariantType fieldType; - stream.ReadVariantType(&fieldType); + stream.Read(fieldType); if (box && box->HasConnection()) { StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length()); @@ -666,9 +666,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) { box = &node->Boxes[boxId]; String fieldName; - stream.ReadString(&fieldName, 11); + stream.Read(fieldName, 11); VariantType fieldType; - stream.ReadVariantType(&fieldType); + stream.Read(fieldType); if (box && box->HasConnection()) { const Variant fieldValue = eatBox(node, box->FirstConnection()); @@ -720,9 +720,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++) { String fieldName; - stream.ReadString(&fieldName, 11); + stream.Read(fieldName, 11); VariantType fieldType; - stream.ReadVariantType(&fieldType); + stream.Read(fieldType); if (box->ID == boxId) { StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length()); @@ -768,9 +768,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++) { String fieldName; - stream.ReadString(&fieldName, 11); + stream.Read(fieldName, 11); VariantType fieldType; - stream.ReadVariantType(&fieldType); + stream.Read(fieldType); if (box->ID == boxId) { type.Struct.GetField(structureValue.AsBlob.Data, fieldName, value); From e7132086a566d9149238308b8464a1e86eb0324b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Dec 2024 23:01:25 +0100 Subject: [PATCH 096/215] Add displaying asset failed text if it occurs --- Source/Editor/Surface/VisjectSurfaceWindow.cs | 10 +----- Source/Editor/Utilities/Utils.cs | 10 ++++++ .../Viewport/Previews/AnimationPreview.cs | 2 +- .../Editor/Windows/Assets/AnimationWindow.cs | 5 +-- Source/Editor/Windows/Assets/ModelWindow.cs | 22 +++---------- .../Windows/Assets/SkeletonMaskWindow.cs | 5 +-- .../Windows/Assets/SkinnedModelWindow.cs | 32 ++++--------------- Source/Editor/Windows/Assets/TextureWindow.cs | 5 +-- 8 files changed, 27 insertions(+), 64 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 6d7633b73..67320cd81 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -586,16 +586,8 @@ namespace FlaxEditor.Surface layout.Label("No parameters"); return; } - if (asset.LastLoadFailed) - { - layout.Label("Failed to load asset"); + if (Utilities.Utils.OnAssetProperties(layout, asset)) return; - } - if (!asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); - return; - } var parameters = window.VisjectSurface.Parameters; CustomEditors.Editors.GenericEditor.OnGroupsBegin(); for (int i = 0; i < parameters.Count; i++) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index c4ff4acf2..6ae10171f 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1496,5 +1496,15 @@ namespace FlaxEditor.Utilities } return path; } + + internal static bool OnAssetProperties(CustomEditors.LayoutElementsContainer layout, Asset asset) + { + if (asset == null || !asset.IsLoaded) + { + layout.Label(asset != null && asset.LastLoadFailed ? "Failed to load" : "Loading...", TextAlignment.Center); + return true; + } + return false; + } } } diff --git a/Source/Editor/Viewport/Previews/AnimationPreview.cs b/Source/Editor/Viewport/Previews/AnimationPreview.cs index eca430376..cfceec1a1 100644 --- a/Source/Editor/Viewport/Previews/AnimationPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimationPreview.cs @@ -100,7 +100,7 @@ namespace FlaxEditor.Viewport.Previews } else if (!skinnedModel.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, skinnedModel.LastLoadFailed ? "Failed to load" : "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index d0e6a3fe9..734b44af7 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -178,11 +178,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (PropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } // General properties { diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index c3e285973..55446dc0a 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Windows.Assets var asset = _window.Asset; if (asset == null || !asset.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, asset != null && asset.LastLoadFailed ? "Failed to load" : "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } @@ -159,11 +159,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (MeshesPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } proxy._materialSlotComboBoxes.Clear(); proxy._isolateCheckBoxes.Clear(); proxy._highlightCheckBoxes.Clear(); @@ -433,11 +430,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (MaterialsPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } base.Initialize(layout); } @@ -495,11 +489,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (UVsPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } base.Initialize(layout); @@ -744,11 +735,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (ImportPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } // Import Settings { diff --git a/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs b/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs index ff2446439..666ae57a1 100644 --- a/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs +++ b/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs @@ -80,11 +80,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (PropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } base.Initialize(layout); diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 67697eb67..f2ea2ac87 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -50,7 +50,7 @@ namespace FlaxEditor.Windows.Assets var asset = _window.Asset; if (asset == null || !asset.IsLoaded) { - Render2D.DrawText(style.FontLarge, "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontLarge, asset != null && asset.LastLoadFailed ? "Failed to load" : "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } } } @@ -175,11 +175,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (MeshesPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } proxy._materialSlotComboBoxes.Clear(); proxy._isolateCheckBoxes.Clear(); proxy._highlightCheckBoxes.Clear(); @@ -287,11 +284,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (SkeletonPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } var lods = proxy.Asset.LODs; var loadedLODs = proxy.Asset.LoadedLODs; var nodes = proxy.Asset.Nodes; @@ -500,11 +494,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (MaterialsPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } base.Initialize(layout); } @@ -561,11 +552,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (UVsPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } base.Initialize(layout); @@ -799,11 +787,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (RetargetPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } if (proxy.Setups == null) { proxy.Setups = new Dictionary(); @@ -1074,11 +1059,8 @@ namespace FlaxEditor.Windows.Assets public override void Initialize(LayoutElementsContainer layout) { var proxy = (ImportPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - } // Import Settings { diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 68f138978..8f673783a 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -57,11 +57,8 @@ namespace FlaxEditor.Windows.Assets { var window = ((TexturePropertiesProxy)Values[0])._window; var texture = window?.Asset; - if (texture == null || !texture.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); + if (Utilities.Utils.OnAssetProperties(layout, texture)) return; - } // Texture info var general = layout.Group("General"); From ab99a25ceecb50a123187384dc4d0ac3b0fe2610 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Dec 2024 23:03:09 +0100 Subject: [PATCH 097/215] Update mesh config defines to match a single format --- Source/Engine/Graphics/Models/Config.h | 19 +++++++++++++++---- Source/Engine/Graphics/Models/MeshBase.cpp | 9 ++++++--- Source/Engine/Graphics/Models/MeshBase.h | 6 +++--- Source/Engine/Physics/Actors/Cloth.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 8 ++++---- Source/Engine/UI/TextRender.cpp | 6 +++--- Source/Engine/UI/TextRender.h | 6 +++--- 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Source/Engine/Graphics/Models/Config.h b/Source/Engine/Graphics/Models/Config.h index f48dae903..c9488ad9c 100644 --- a/Source/Engine/Graphics/Models/Config.h +++ b/Source/Engine/Graphics/Models/Config.h @@ -5,7 +5,9 @@ #include "../Config.h" // The maximum allowed amount of material slots per model resource -#define MAX_MATERIAL_SLOTS 4096 +#define MODEL_MAX_MATERIAL_SLOTS 4096 +// [Deprecated in v1.10] Use MODEL_MAX_MATERIAL_SLOTS +#define MAX_MATERIAL_SLOTS MODEL_MAX_MATERIAL_SLOTS // Maximum amount of levels of detail for the model #define MODEL_MAX_LODS 6 @@ -13,11 +15,20 @@ // Maximum amount of meshes per model LOD #define MODEL_MAX_MESHES 4096 +// Maximum amount of texture channels (UVs) per vertex +#define MODEL_MAX_UVS 4 + // Enable/disable precise mesh collision testing (with in-build vertex buffer caching, this will increase memory usage) -#define USE_PRECISE_MESH_INTERSECTS (USE_EDITOR) +#define MODEL_USE_PRECISE_MESH_INTERSECTS (USE_EDITOR) +// [Deprecated in v1.10] Use MODEL_USE_PRECISE_MESH_INTERSECTS +#define USE_PRECISE_MESH_INTERSECTS MODEL_USE_PRECISE_MESH_INTERSECTS // Defines the maximum amount of bones affecting every vertex of the skinned mesh -#define MAX_BONES_PER_VERTEX 4 +#define MODEL_MAX_BONES_PER_VERTEX 4 +// [Deprecated in v1.10] Use MODEL_MAX_BONES_PER_VERTEX +#define MAX_BONES_PER_VERTEX MODEL_MAX_BONES_PER_VERTEX // Defines the maximum allowed amount of skeleton bones to be used with skinned model -#define MAX_BONES_PER_MODEL 256 +#define MODEL_MAX_BONES_PER_MODEL 256 +// [Deprecated in v1.10] Use MODEL_MAX_BONES_PER_MODEL +#define MAX_BONES_PER_MODEL MODEL_MAX_BONES_PER_MODEL diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index f58527337..e9b4b1493 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -108,7 +108,7 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array _cachedIndexBuffer; mutable int32 _cachedIndexBufferCount = 0; -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS CollisionProxy _collisionProxy; #endif @@ -137,7 +137,7 @@ public: return _use16BitIndexBuffer; } -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS /// /// Gets the collision proxy used by the mesh. /// diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 2781cb161..4dd1f56f5 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -214,7 +214,7 @@ void Cloth::SetPaint(Span value) bool Cloth::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) { -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS if (!Actor::IntersectsItself(ray, distance, normal)) return false; #if WITH_CLOTH diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 91c2edc06..641ab1990 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1184,7 +1184,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Special case if imported model has no bones but has valid skeleton and meshes. // We assume that every mesh uses a single bone. Copy nodes to bones. - if (data.Skeleton.Bones.IsEmpty() && Math::IsInRange(data.Skeleton.Nodes.Count(), 1, MAX_BONES_PER_MODEL)) + if (data.Skeleton.Bones.IsEmpty() && Math::IsInRange(data.Skeleton.Nodes.Count(), 1, MODEL_MAX_BONES_PER_MODEL)) { data.Skeleton.Bones.Resize(data.Skeleton.Nodes.Count()); for (int32 i = 0; i < data.Skeleton.Nodes.Count(); i++) @@ -1209,9 +1209,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } // Check bones limit currently supported by the engine - if (data.Skeleton.Bones.Count() > MAX_BONES_PER_MODEL) + if (data.Skeleton.Bones.Count() > MODEL_MAX_BONES_PER_MODEL) { - errorMsg = String::Format(TEXT("Imported model skeleton has too many bones. Imported: {0}, maximum supported: {1}. Please optimize your asset."), data.Skeleton.Bones.Count(), MAX_BONES_PER_MODEL); + errorMsg = String::Format(TEXT("Imported model skeleton has too many bones. Imported: {0}, maximum supported: {1}. Please optimize your asset."), data.Skeleton.Bones.Count(), MODEL_MAX_BONES_PER_MODEL); return true; } @@ -1317,7 +1317,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Check if use a single bone for skinning auto nodeIndex = data.Skeleton.FindNode(mesh->Name); auto boneIndex = data.Skeleton.FindBone(nodeIndex); - if (boneIndex == -1 && nodeIndex != -1 && data.Skeleton.Bones.Count() < MAX_BONES_PER_MODEL) + if (boneIndex == -1 && nodeIndex != -1 && data.Skeleton.Bones.Count() < MODEL_MAX_BONES_PER_MODEL) { // Add missing bone to be used by skinned model from animated nodes pose boneIndex = data.Skeleton.Bones.Count(); diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 9edb322f5..e1d0f5316 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -108,7 +108,7 @@ void TextRender::UpdateLayout() _localBox = BoundingBox(Vector3::Zero); BoundingBox::Transform(_localBox, _transform, _box); BoundingSphere::FromBox(_box, _sphere); -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS _collisionProxy.Clear(); #endif @@ -314,7 +314,7 @@ void TextRender::UpdateLayout() _drawChunks.Add(drawChunk); } -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS // Setup collision proxy for detailed collision detection for triangles const int32 totalIndicesCount = _ib.Data.Count() / sizeof(uint16); _collisionProxy.Init(_vb0.Data.Count() / sizeof(Float3), totalIndicesCount / 3, (Float3*)_vb0.Data.Get(), (uint16*)_ib.Data.Get()); @@ -420,7 +420,7 @@ void TextRender::OnLayerChanged() bool TextRender::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) { -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS if (_box.Intersects(ray)) { return _collisionProxy.Intersects(ray, _transform, distance, normal); diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h index 57e48ac9b..d842a9531 100644 --- a/Source/Engine/UI/TextRender.h +++ b/Source/Engine/UI/TextRender.h @@ -11,7 +11,7 @@ #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/Models/Config.h" #include "Engine/Localization/LocalizedString.h" -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS #include "Engine/Graphics/Models/CollisionProxy.h" #endif @@ -47,7 +47,7 @@ private: DynamicVertexBuffer _vb0; DynamicVertexBuffer _vb1; DynamicVertexBuffer _vb2; -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS CollisionProxy _collisionProxy; #endif Array> _drawChunks; @@ -143,7 +143,7 @@ public: /// API_FUNCTION() void UpdateLayout(); -#if USE_PRECISE_MESH_INTERSECTS +#if MODEL_USE_PRECISE_MESH_INTERSECTS /// /// Gets the collision proxy used by the text geometry. /// From 3151e47722741da632dd7ecf7cc5d3fc36671458 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 29 Dec 2024 23:21:26 +0100 Subject: [PATCH 098/215] Fix missing material instance load failure when parameters load fails --- Source/Engine/Content/Assets/MaterialInstance.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 537fe0243..13d46db09 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -218,7 +218,11 @@ Asset::LoadResult MaterialInstance::load() auto baseMaterial = Content::LoadAsync(baseMaterialId); // Load parameters - Params.Load(&headerStream); + if (Params.Load(&headerStream)) + { + LOG(Warning, "Cannot load material parameters."); + return LoadResult::CannotLoadData; + } if (baseMaterial && !baseMaterial->WaitForLoaded()) { From 1e262b69cc6efd0235c6e8f28592f07b6e0835f6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 3 Jan 2025 01:05:56 +0100 Subject: [PATCH 099/215] Add `Task::WaitAll` with a span of tasks and wrap around profiler macro --- Source/Engine/Threading/Task.cpp | 12 ++++++++++++ Source/Engine/Threading/Task.h | 17 ++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp index a0b3ddb6d..a126bdef4 100644 --- a/Source/Engine/Threading/Task.cpp +++ b/Source/Engine/Threading/Task.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Profiler/ProfilerCPU.h" void Task::Start() @@ -72,6 +73,17 @@ bool Task::Wait(double timeoutMilliseconds) const return true; } +bool Task::WaitAll(const Span& tasks, double timeoutMilliseconds) +{ + PROFILE_CPU(); + for (int32 i = 0; i < tasks.Length(); i++) + { + if (tasks[i]->Wait()) + return true; + } + return false; +} + Task* Task::ContinueWith(Task* task) { ASSERT(task != nullptr && task != this); diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h index c741da73d..11326fa3a 100644 --- a/Source/Engine/Threading/Task.h +++ b/Source/Engine/Threading/Task.h @@ -181,6 +181,14 @@ public: /// True if task failed or has been canceled or has timeout, otherwise false. bool Wait(double timeoutMilliseconds = -1) const; + /// + /// Waits for all the tasks from the list. + /// + /// The tasks list to wait for. + /// The maximum amount of milliseconds to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting. + /// True if any task failed or has been canceled or has timeout, otherwise false. + static bool WaitAll(const Span& tasks, double timeoutMilliseconds = -1); + /// /// Waits for all the tasks from the list. /// @@ -188,14 +196,9 @@ public: /// The maximum amount of milliseconds to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting. /// True if any task failed or has been canceled or has timeout, otherwise false. template - static bool WaitAll(Array& tasks, double timeoutMilliseconds = -1) + static bool WaitAll(const Array& tasks, double timeoutMilliseconds = -1) { - for (int32 i = 0; i < tasks.Count(); i++) - { - if (tasks[i]->Wait()) - return true; - } - return false; + return WaitAll(ToSpan(tasks), timeoutMilliseconds); } public: From 348f17479d0b5e4884d0a7524d6c803472be8694 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 3 Jan 2025 01:07:33 +0100 Subject: [PATCH 100/215] Fix name of `BlendWeight` to `BlendWeights` for vertex input to match `BlendIndices` --- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 2 +- Source/Engine/Graphics/Shaders/VertexElement.h | 2 +- Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp | 2 +- Source/Engine/ShadersCompilation/ShaderCompiler.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 5473b549a..56bfad9ba 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -27,7 +27,7 @@ GPUVertexLayout* VB0SkinnedElementType2::GetLayout() { VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }, { VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }, { VertexElement::Types::BlendIndices, 0, 0, 0, PixelFormat::R8G8B8A8_UInt }, - { VertexElement::Types::BlendWeight, 0, 0, 0, PixelFormat::R16G16B16A16_Float }, + { VertexElement::Types::BlendWeights, 0, 0, 0, PixelFormat::R16G16B16A16_Float }, }); } diff --git a/Source/Engine/Graphics/Shaders/VertexElement.h b/Source/Engine/Graphics/Shaders/VertexElement.h index 06bf0dbb5..f15bb86c7 100644 --- a/Source/Engine/Graphics/Shaders/VertexElement.h +++ b/Source/Engine/Graphics/Shaders/VertexElement.h @@ -30,7 +30,7 @@ PACK_BEGIN() struct FLAXENGINE_API VertexElement // Skinned bone blend indices. BlendIndices = 5, // Skinned bone blend weights. - BlendWeight = 6, + BlendWeights = 6, // Primary texture coordinate (UV). TexCoord0 = 7, // Additional texture coordinate (UV1). diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index f9d36a748..550ac5b87 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -289,7 +289,7 @@ LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& se return "TANGENT"; case VertexElement::Types::BlendIndices: return "BLENDINDICES"; - case VertexElement::Types::BlendWeight: + case VertexElement::Types::BlendWeights: return "BLENDWEIGHT"; case VertexElement::Types::TexCoord0: return "TEXCOORD"; diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index e8ea14971..21b619b86 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -427,7 +427,7 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader data.Type = VertexElement::Types::BlendIndices; break; case VertexShaderMeta::InputType::BLENDWEIGHT: - data.Type = VertexElement::Types::BlendWeight; + data.Type = VertexElement::Types::BlendWeights; break; default: data.Type = VertexElement::Types::Unknown; From 7b7dd9d142c148b2959aabd8e28cb507fe151b33 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 3 Jan 2025 01:09:25 +0100 Subject: [PATCH 101/215] Improve GPU vertex layout binding in case of missing element from the mesh --- Source/Engine/Graphics/GPUDevice.h | 2 +- .../Graphics/Shaders/GPUVertexLayout.cpp | 153 +++++++++++++----- .../Engine/Graphics/Shaders/GPUVertexLayout.h | 20 ++- .../Engine/Graphics/Shaders/VertexElement.h | 2 +- .../DirectX/DX11/GPUDeviceDX11.cpp | 17 +- .../DirectX/DX11/GPUDeviceDX11.h | 2 +- .../DirectX/DX11/GPUShaderDX11.cpp | 12 +- .../DirectX/DX11/GPUVertexLayoutDX11.h | 2 +- .../DirectX/DX12/GPUDeviceDX12.cpp | 17 +- .../DirectX/DX12/GPUDeviceDX12.h | 2 +- .../DirectX/DX12/GPUVertexLayoutDX12.h | 2 +- .../GraphicsDevice/Null/GPUDeviceNull.cpp | 2 +- .../GraphicsDevice/Null/GPUDeviceNull.h | 2 +- .../GraphicsDevice/Null/GPUVertexLayoutNull.h | 2 +- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 19 +-- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.h | 2 +- .../Vulkan/GPUVertexLayoutVulkan.h | 2 +- 17 files changed, 168 insertions(+), 92 deletions(-) diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index 23636bfdf..9fdba7885 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -401,7 +401,7 @@ public: /// Creates the vertex buffer layout. /// /// The vertex buffer layout. - API_FUNCTION() virtual GPUVertexLayout* CreateVertexLayout(const Array>& elements) = 0; + API_FUNCTION() virtual GPUVertexLayout* CreateVertexLayout(const Array>& elements, bool explicitOffsets = false) = 0; typedef Array> VertexElements; /// diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 659bcb344..91f19c701 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -45,6 +45,26 @@ namespace CriticalSection CacheLocker; Dictionary LayoutCache; Dictionary VertexBufferCache; + + GPUVertexLayout* AddCache(const VertexBufferLayouts& key, int32 count) + { + GPUVertexLayout::Elements elements; + bool anyValid = false; + for (int32 slot = 0; slot < count; slot++) + { + if (key.Layouts[slot]) + { + anyValid = true; + int32 start = elements.Count(); + elements.Add(key.Layouts[slot]->GetElements()); + for (int32 j = start; j < elements.Count(); j++) + elements.Get()[j].Slot = (byte)slot; + } + } + GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements) : nullptr; + VertexBufferCache.Add(key, result); + return result; + } } String VertexElement::ToString() const @@ -76,22 +96,27 @@ GPUVertexLayout::GPUVertexLayout() { } -void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_MAX_VS_ELEMENTS]) +void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets) { + uint32 offsets[GPU_MAX_VB_BINDED] = {}; _elements = elements; - uint32 strides[GPU_MAX_VB_BINDED] = {}; - for (int32 i = 0; i < elements.Count(); i++) + for (int32 i = 0; i < _elements.Count(); i++) { - const VertexElement& e = elements[i]; + VertexElement& e = _elements[i]; ASSERT(e.Slot < GPU_MAX_VB_BINDED); - strides[e.Slot] = Math::Max(strides[e.Slot], offsets[e.Slot]); + uint32& offset = offsets[e.Slot]; + if (e.Offset != 0 || explicitOffsets) + offset = e.Offset; + else + e.Offset = (byte)offset; + offset += PixelFormatExtensions::SizeInBytes(e.Format); } _stride = 0; - for (uint32 stride : strides) - _stride += stride; + for (uint32 offset : offsets) + _stride += offset; } -GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) +GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets) { // Hash input layout uint32 hash = 0; @@ -105,7 +130,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) GPUVertexLayout* result; if (!LayoutCache.TryGet(hash, result)) { - result = GPUDevice::Instance->CreateVertexLayout(elements); + result = GPUDevice::Instance->CreateVertexLayout(elements, explicitOffsets); if (!result) { #if GPU_ENABLE_ASSERTION_LOW_LAYERS @@ -118,16 +143,6 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements) } LayoutCache.Add(hash, result); } -#if GPU_ENABLE_ASSERTION_LOW_LAYERS - else if (result->GetElements() != elements) - { - for (auto& e : result->GetElements()) - LOG(Error, " (a) {}", e.ToString()); - for (auto& e : elements) - LOG(Error, " (b) {}", e.ToString()); - LOG(Fatal, "Vertex layout cache collision for hash {}", hash); - } -#endif CacheLocker.Unlock(); return result; @@ -141,38 +156,94 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span& vertexBuffers) return vertexBuffers.Get()[0] ? vertexBuffers.Get()[0]->GetVertexLayout() : nullptr; // Build hash key for set of buffers (in case there is layout sharing by different sets of buffers) - VertexBufferLayouts layouts; - for (int32 i = 0; i < vertexBuffers.Length(); i++) - layouts.Layouts[i] = vertexBuffers.Get()[i] ? vertexBuffers.Get()[i]->GetVertexLayout() : nullptr; + VertexBufferLayouts key; + for (int32 i = 0; i < vertexBuffers.Length() && i < GPU_MAX_VB_BINDED; i++) + key.Layouts[i] = vertexBuffers.Get()[i] ? vertexBuffers.Get()[i]->GetVertexLayout() : nullptr; for (int32 i = vertexBuffers.Length(); i < GPU_MAX_VB_BINDED; i++) - layouts.Layouts[i] = nullptr; + key.Layouts[i] = nullptr; // Lookup existing cache CacheLocker.Lock(); GPUVertexLayout* result; - if (!VertexBufferCache.TryGet(layouts, result)) - { - Elements elements; - bool anyValid = false; - for (int32 slot = 0; slot < vertexBuffers.Length(); slot++) - { - if (layouts.Layouts[slot]) - { - anyValid = true; - int32 start = elements.Count(); - elements.Add(layouts.Layouts[slot]->GetElements()); - for (int32 j = start; j < elements.Count(); j++) - elements.Get()[j].Slot = (byte)slot; - } - } - result = anyValid ? Get(elements) : nullptr; - VertexBufferCache.Add(layouts, result); - } + if (!VertexBufferCache.TryGet(key, result)) + result = AddCache(key, vertexBuffers.Length()); CacheLocker.Unlock(); return result; } +GPUVertexLayout* GPUVertexLayout::Get(const Span& layouts) +{ + if (layouts.Length() == 0) + return nullptr; + if (layouts.Length() == 1) + return layouts.Get()[0] ? layouts.Get()[0] : nullptr; + + // Build hash key for set of buffers (in case there is layout sharing by different sets of buffers) + VertexBufferLayouts key; + for (int32 i = 0; i < layouts.Length() && i < GPU_MAX_VB_BINDED; i++) + key.Layouts[i] = layouts.Get()[i]; + for (int32 i = layouts.Length(); i < GPU_MAX_VB_BINDED; i++) + key.Layouts[i] = nullptr; + + // Lookup existing cache + CacheLocker.Lock(); + GPUVertexLayout* result; + if (!VertexBufferCache.TryGet(key, result)) + result = AddCache(key, layouts.Length()); + CacheLocker.Unlock(); + + return result; +} + +GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, const GPUVertexLayout* reference) +{ + if (!reference || !base || base == reference) + return base; + GPUVertexLayout* result = base; + if (base && reference) + { + bool anyMissing = false; + const Elements& baseElements = base->GetElements(); + Elements newElements = baseElements; + for (const VertexElement& e : reference->GetElements()) + { + bool missing = true; + for (const VertexElement& ee : baseElements) + { + if (ee.Type == e.Type) + { + missing = false; + break; + } + } + if (missing) + { + // Insert any missing elements + VertexElement ne = { e.Type, e.Slot, 0, e.PerInstance, e.Format }; + if (e.Type == VertexElement::Types::TexCoord1 || e.Type == VertexElement::Types::TexCoord2 || e.Type == VertexElement::Types::TexCoord3) + { + // Alias missing texcoords with existing texcoords + for (const VertexElement& ee : newElements) + { + if (ee.Type == VertexElement::Types::TexCoord0) + { + ne = ee; + ne.Type = e.Type; + break; + } + } + } + newElements.Add(ne); + anyMissing = true; + } + } + if (anyMissing) + result = Get(newElements, true); + } + return result; +} + void ClearVertexLayoutCache() { for (const auto& e : LayoutCache) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index e234b32b1..56cf413bc 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -23,7 +23,7 @@ private: protected: GPUVertexLayout(); - void SetElements(const Elements& elements, uint32 offsets[GPU_MAX_VS_ELEMENTS]); + void SetElements(const Elements& elements, bool explicitOffsets); public: /// @@ -46,8 +46,9 @@ public: /// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list. /// /// The list of elements for the layout. + /// If set to true, input elements offsets will be used without automatic calculations (offsets with value 0). /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. - API_FUNCTION() static GPUVertexLayout* Get(const Array>& elements); + API_FUNCTION() static GPUVertexLayout* Get(const Array>& elements, bool explicitOffsets = false); /// /// Gets the vertex layout for a given list of vertex buffers (sequence of binding slots based on layouts set on those buffers). Uses internal cache to skip creating layout if it's already exists for a given list. @@ -56,6 +57,21 @@ public: /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. API_FUNCTION() static GPUVertexLayout* Get(const Span& vertexBuffers); + /// + /// Merges list of layouts in a single one. Uses internal cache to skip creating layout if it's already exists for a given list. + /// + /// The list of layouts to merge. + /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. + API_FUNCTION() static GPUVertexLayout* Get(const Span& layouts); + + /// + /// Merges reference vertex elements into the given set of elements to ensure the reference list is satisfied (vertex shader input requirement). Returns base layout if it's valid. + /// + /// The list of vertex buffers for the layout. + /// The list of reference inputs. + /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. + static GPUVertexLayout* Merge(GPUVertexLayout* base, const GPUVertexLayout* reference); + public: // [GPUResource] GPUResourceType GetResourceType() const override diff --git a/Source/Engine/Graphics/Shaders/VertexElement.h b/Source/Engine/Graphics/Shaders/VertexElement.h index f15bb86c7..4bc628fcc 100644 --- a/Source/Engine/Graphics/Shaders/VertexElement.h +++ b/Source/Engine/Graphics/Shaders/VertexElement.h @@ -66,7 +66,7 @@ PACK_BEGIN() struct FLAXENGINE_API VertexElement API_FIELD() Types Type; // Index of the input vertex buffer slot (as provided in GPUContext::BindVB). API_FIELD() byte Slot; - // Byte offset of this element relative to the start of a vertex buffer. Use value 0 to use auto-calculated offset based on previous elements in the layout (or for the first one). + // Byte offset of this element relative to the start of a vertex buffer. Use value 0 to use auto-calculated offset based on previous elements in the layout (except when explicitOffsets is false). API_FIELD() byte Offset; // Flag used to mark data using hardware-instancing (element will be repeated for every instance). Empty to step data per-vertex when reading input buffer stream (rather than per-instance step). API_FIELD() byte PerInstance; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 35656606a..ac11989d5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -148,27 +148,22 @@ static bool TryCreateDevice(IDXGIAdapter* adapter, D3D_FEATURE_LEVEL maxFeatureL return false; } -GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements) +GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements, bool explicitOffsets) : GPUResourceBase(device, StringView::Empty) , InputElementsCount(elements.Count()) { - uint32 offsets[GPU_MAX_VB_BINDED] = {}; + SetElements(elements, explicitOffsets); for (int32 i = 0; i < elements.Count(); i++) { - const VertexElement& src = elements.Get()[i]; + const VertexElement& src = GetElements().Get()[i]; D3D11_INPUT_ELEMENT_DESC& dst = InputElements[i]; - uint32& offset = offsets[src.Slot]; - if (src.Offset != 0) - offset = src.Offset; dst.SemanticName = RenderToolsDX::GetVertexInputSemantic(src.Type, dst.SemanticIndex); dst.Format = RenderToolsDX::ToDxgiFormat(src.Format); dst.InputSlot = src.Slot; - dst.AlignedByteOffset = offset; + dst.AlignedByteOffset = src.Offset; dst.InputSlotClass = src.PerInstance ? D3D11_INPUT_PER_INSTANCE_DATA : D3D11_INPUT_PER_VERTEX_DATA; dst.InstanceDataStepRate = src.PerInstance ? 1 : 0; - offset += PixelFormatExtensions::SizeInBytes(src.Format); } - SetElements(elements, offsets); } GPUDevice* GPUDeviceDX11::Create() @@ -832,9 +827,9 @@ GPUSampler* GPUDeviceDX11::CreateSampler() return New(this); } -GPUVertexLayout* GPUDeviceDX11::CreateVertexLayout(const VertexElements& elements) +GPUVertexLayout* GPUDeviceDX11::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) { - return New(this, elements); + return New(this, elements, explicitOffsets); } GPUSwapChain* GPUDeviceDX11::CreateSwapChain(Window* window) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h index 079d38967..066f02863 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h @@ -128,7 +128,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; - GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index faf50a927..7dcfb0d31 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -11,19 +11,23 @@ GPUShaderProgramVSDX11::~GPUShaderProgramVSDX11() { for (const auto& e : _cache) - e.Value->Release(); + { + if (e.Value) + e.Value->Release(); + } } ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout) { - if (!vertexLayout) - vertexLayout = (GPUVertexLayoutDX11*)Layout; ID3D11InputLayout* inputLayout = nullptr; if (!_cache.TryGet(vertexLayout, inputLayout)) { + if (!vertexLayout) + vertexLayout = (GPUVertexLayoutDX11*)Layout; if (vertexLayout && vertexLayout->InputElementsCount) { - VALIDATE_DIRECTX_CALL(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(vertexLayout->InputElements, vertexLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); + auto actualLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout); + LOG_DIRECTX_RESULT(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(actualLayout->InputElements, actualLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); } _cache.Add(vertexLayout, inputLayout); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h index 119ee2964..d5ff07a0c 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUVertexLayoutDX11.h @@ -13,7 +13,7 @@ class GPUVertexLayoutDX11 : public GPUResourceBase { public: - GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements); + GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements, bool explicitOffsets); uint32 InputElementsCount; D3D11_INPUT_ELEMENT_DESC InputElements[GPU_MAX_VS_ELEMENTS]; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index ab4a0ab81..65e3f6d83 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -36,27 +36,22 @@ static bool CheckDX12Support(IDXGIAdapter* adapter) return false; } -GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements) +GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets) : GPUResourceDX12(device, StringView::Empty) , InputElementsCount(elements.Count()) { - uint32 offsets[GPU_MAX_VB_BINDED] = {}; + SetElements(elements, explicitOffsets); for (int32 i = 0; i < elements.Count(); i++) { - const VertexElement& src = elements.Get()[i]; + const VertexElement& src = GetElements().Get()[i]; D3D12_INPUT_ELEMENT_DESC& dst = InputElements[i]; - uint32& offset = offsets[src.Slot]; - if (src.Offset != 0) - offset = src.Offset; dst.SemanticName = RenderToolsDX::GetVertexInputSemantic(src.Type, dst.SemanticIndex); dst.Format = RenderToolsDX::ToDxgiFormat(src.Format); dst.InputSlot = src.Slot; - dst.AlignedByteOffset = offset; + dst.AlignedByteOffset = src.Offset; dst.InputSlotClass = src.PerInstance ? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA : D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA; dst.InstanceDataStepRate = src.PerInstance ? 1 : 0; - offset += PixelFormatExtensions::SizeInBytes(src.Format); } - SetElements(elements, offsets); } GPUDevice* GPUDeviceDX12::Create() @@ -868,9 +863,9 @@ GPUSampler* GPUDeviceDX12::CreateSampler() return New(this); } -GPUVertexLayout* GPUDeviceDX12::CreateVertexLayout(const VertexElements& elements) +GPUVertexLayout* GPUDeviceDX12::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) { - return New(this, elements); + return New(this, elements, explicitOffsets); } GPUSwapChain* GPUDeviceDX12::CreateSwapChain(Window* window) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h index 82f59a8ee..81c42070f 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h @@ -196,7 +196,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; - GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h index 225c3367c..79b4aa9de 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUVertexLayoutDX12.h @@ -13,7 +13,7 @@ class GPUVertexLayoutDX12 : public GPUResourceDX12 { public: - GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements); + GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets); uint32 InputElementsCount; D3D12_INPUT_ELEMENT_DESC InputElements[GPU_MAX_VS_ELEMENTS]; diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp index d1fe23dd8..f7fdd1eba 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp @@ -173,7 +173,7 @@ GPUSampler* GPUDeviceNull::CreateSampler() return New(); } -GPUVertexLayout* GPUDeviceNull::CreateVertexLayout(const VertexElements& elements) +GPUVertexLayout* GPUDeviceNull::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) { return New(elements); } diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h index 80de34db3..b9d53d2f4 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.h @@ -47,7 +47,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; - GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h b/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h index f1a5f0abd..a483a881f 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUVertexLayoutNull.h @@ -15,7 +15,7 @@ public: GPUVertexLayoutNull(const Elements& elements) : GPUVertexLayout() { - SetElements(elements, {}); + SetElements(elements, false); } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index e5f7f2bcc..21e653a36 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -449,10 +449,10 @@ uint32 GetHash(const FramebufferVulkan::Key& key) return hash; } -GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements) +GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements, bool explicitOffsets) : GPUResourceVulkan(device, StringView::Empty) { - uint32 offsets[GPU_MAX_VB_BINDED] = {}; + SetElements(elements, explicitOffsets); for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) { VkVertexInputBindingDescription& binding = Bindings[i]; @@ -463,28 +463,23 @@ GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elem uint32 bindingsCount = 0; for (int32 i = 0; i < elements.Count(); i++) { - const VertexElement& src = elements.Get()[i]; - uint32& offset = offsets[src.Slot]; - if (src.Offset != 0) - offset = src.Offset; + const VertexElement& src = GetElements().Get()[i]; const int32 size = PixelFormatExtensions::SizeInBytes(src.Format); ASSERT_LOW_LAYER(src.Slot < GPU_MAX_VB_BINDED); VkVertexInputBindingDescription& binding = Bindings[src.Slot]; binding.binding = src.Slot; - binding.stride = Math::Max(binding.stride, (uint32_t)(offset + size)); + binding.stride = Math::Max(binding.stride, (uint32_t)(src.Offset + size)); binding.inputRate = src.PerInstance ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; VkVertexInputAttributeDescription& attribute = Attributes[i]; attribute.location = i; attribute.binding = src.Slot; attribute.format = RenderToolsVulkan::ToVulkanFormat(src.Format); - attribute.offset = offset; + attribute.offset = src.Offset; bindingsCount = Math::Max(bindingsCount, (uint32)src.Slot + 1); - offset += size; } - SetElements(elements, offsets); RenderToolsVulkan::ZeroStruct(CreateInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); CreateInfo.vertexBindingDescriptionCount = bindingsCount; @@ -2129,9 +2124,9 @@ GPUSampler* GPUDeviceVulkan::CreateSampler() return New(this); } -GPUVertexLayout* GPUDeviceVulkan::CreateVertexLayout(const VertexElements& elements) +GPUVertexLayout* GPUDeviceVulkan::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) { - return New(this, elements); + return New(this, elements, explicitOffsets); } GPUSwapChain* GPUDeviceVulkan::CreateSwapChain(Window* window) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 8b64eeb9c..10a6c1b77 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -610,7 +610,7 @@ public: GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSampler* CreateSampler() override; - GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override; + GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h index b2d0e4b75..67a05824f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h @@ -13,7 +13,7 @@ class GPUVertexLayoutVulkan : public GPUResourceVulkan { public: - GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements); + GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements, bool explicitOffsets); VkPipelineVertexInputStateCreateInfo CreateInfo; VkVertexInputBindingDescription Bindings[GPU_MAX_VB_BINDED]; From ea5cb5d83a42542d4a5ba370de7655eab1974f35 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 3 Jan 2025 01:10:31 +0100 Subject: [PATCH 102/215] Minor fixes and improvements --- Source/Engine/Content/BinaryAsset.cpp | 2 ++ .../Content/Upgraders/BinaryAssetUpgrader.h | 20 +++++-------------- .../Graphics/Materials/MaterialParams.cpp | 4 ++-- Source/Engine/Graphics/Models/Types.h | 2 ++ .../Graphics/Shaders/GPUVertexLayout.cpp | 14 ++++++++++++- .../Engine/Graphics/Shaders/GPUVertexLayout.h | 5 +++++ .../ModelTool/MeshAccelerationStructure.cpp | 4 ++-- .../ModelTool/MeshAccelerationStructure.h | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 6 +++--- Source/Engine/Tools/ModelTool/ModelTool.h | 2 +- 10 files changed, 36 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 692fc9329..228a93f29 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -191,6 +191,8 @@ bool BinaryAsset::HasDependenciesModified() const FlaxChunk* BinaryAsset::GetOrCreateChunk(int32 index) { + if (IsVirtual()) // Virtual assets don't own storage container + return nullptr; ASSERT(Math::IsInRange(index, 0, ASSET_FILE_DATA_CHUNKS - 1)); // Try get diff --git a/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h b/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h index bebb0cf88..9cf459bfb 100644 --- a/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h @@ -75,8 +75,7 @@ public: }; private: - Upgrader const* _upgraders; - int32 _upgradersCount; + Array> _upgraders; public: /// @@ -87,10 +86,8 @@ public: /// True if cannot upgrade or upgrade failed, otherwise false bool Upgrade(uint32 serializedVersion, AssetMigrationContext& context) const { - // Find upgrader - for (int32 i = 0; i < _upgradersCount; i++) + for (auto& upgrader : _upgraders) { - auto& upgrader = _upgraders[i]; if (upgrader.CurrentVersion == serializedVersion) { // Set target version and preserve metadata @@ -105,7 +102,6 @@ public: return upgrader.Handler(context); } } - return true; } @@ -127,7 +123,6 @@ public: context.Output.Header.Chunks[chunkIndex]->Data.Copy(srcChunk->Data); } } - return false; } @@ -176,27 +171,22 @@ public: protected: BinaryAssetUpgrader() { - _upgraders = nullptr; - _upgradersCount = 0; } void setup(Upgrader const* upgraders, int32 upgradersCount) { - _upgraders = upgraders; - _upgradersCount = upgradersCount; + _upgraders.Add(upgraders, upgradersCount); } public: // [IAssetUpgrader] bool ShouldUpgrade(uint32 serializedVersion) const override { - // Find upgrader - for (int32 i = 0; i < _upgradersCount; i++) + for (auto& upgrader : _upgraders) { - if (_upgraders[i].CurrentVersion == serializedVersion) + if (upgrader.CurrentVersion == serializedVersion) return true; } - return false; } }; diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 0e56c33a8..607d70473 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -663,7 +663,7 @@ bool MaterialParams::Load(ReadStream* stream) param->_type = static_cast(stream->ReadByte()); param->_isPublic = stream->ReadBool(); param->_override = param->_isPublic; - stream->ReadString(¶m->_name, 10421); + stream->Read(param->_name, 10421); param->_registerIndex = stream->ReadByte(); stream->ReadUint16(¶m->_offset); @@ -738,7 +738,7 @@ bool MaterialParams::Load(ReadStream* stream) stream->Read(param->_paramId); param->_isPublic = stream->ReadBool(); param->_override = param->_isPublic; - stream->ReadString(¶m->_name, 10421); + stream->Read(param->_name, 10421); param->_registerIndex = stream->ReadByte(); stream->ReadUint16(¶m->_offset); diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index 20d574029..b7f9a2151 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -67,6 +67,8 @@ enum class MeshBufferType /// The vertex buffer (third). /// Vertex2 = 3, + + MAX, }; // Vertex structure for all models (versioned) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 91f19c701..edf3b9921 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -70,7 +70,7 @@ namespace String VertexElement::ToString() const { #if GPU_ENABLE_RESOURCE_NAMING - return String::Format(TEXT("{}, format {}, offset {}, per-instance {}, slot {}"), ScriptingEnum::ToString(Type), ScriptingEnum::ToString(Format), Offset, PerInstance, Slot); + return String::Format(TEXT("{}, {}, offset {}, {}, slot {}"), ScriptingEnum::ToString(Type), ScriptingEnum::ToString(Format), Offset, PerInstance ? TEXT("per-instance") : TEXT("per-vertex"), Slot); #else return TEXT("VertexElement"); #endif @@ -116,6 +116,18 @@ void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets _stride += offset; } +String GPUVertexLayout::GetElementsString() const +{ + String result; + for (int32 i = 0; i < _elements.Count(); i++) + { + if (i != 0) + result += '\n'; + result += _elements[i].ToString(); + } + return result; +} + GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets) { // Hash input layout diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index 56cf413bc..9bf606ec1 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -34,6 +34,11 @@ public: return _elements; } + /// + /// Gets the list of elements used by this layout as a text (each element in a new line). + /// + API_PROPERTY() String GetElementsString() const; + /// /// Gets the size in bytes of all elements in the layout structure (including their offsets). /// diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index 5d4caeabd..25f90e235 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -331,11 +331,11 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) } } -void MeshAccelerationStructure::Add(ModelData* modelData, int32 lodIndex, bool copy) +void MeshAccelerationStructure::Add(const ModelData* modelData, int32 lodIndex, bool copy) { PROFILE_CPU(); lodIndex = Math::Clamp(lodIndex, 0, modelData->LODs.Count() - 1); - ModelLodData& lod = modelData->LODs[lodIndex]; + const ModelLodData& lod = modelData->LODs[lodIndex]; _meshes.EnsureCapacity(_meshes.Count() + lod.Meshes.Count()); for (int32 i = 0; i < lod.Meshes.Count(); i++) { diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h index 2c77d63af..edff32168 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h @@ -61,7 +61,7 @@ public: void Add(Model* model, int32 lodIndex); // Adds the model geometry for the build to the structure. - void Add(ModelData* modelData, int32 lodIndex, bool copy = false); + void Add(const ModelData* modelData, int32 lodIndex, bool copy = false); // Adds the triangles geometry for the build to the structure. void Add(Float3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy = false); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 641ab1990..9a1721569 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -81,7 +81,7 @@ class GPUModelSDFTask : public GPUTask ConditionVariable* _signal; AssetReference _shader; Model* _inputModel; - ModelData* _modelData; + const ModelData* _modelData; int32 _lodIndex; Int3 _resolution; ModelBase::SDFData* _sdf; @@ -104,7 +104,7 @@ class GPUModelSDFTask : public GPUTask }); public: - GPUModelSDFTask(ConditionVariable& signal, Model* inputModel, ModelData* modelData, int32 lodIndex, const Int3& resolution, ModelBase::SDFData* sdf, GPUTexture* sdfResult, const Float3& xyzToLocalMul, const Float3& xyzToLocalAdd) + GPUModelSDFTask(ConditionVariable& signal, Model* inputModel, const ModelData* modelData, int32 lodIndex, const Int3& resolution, ModelBase::SDFData* sdf, GPUTexture* sdfResult, const Float3& xyzToLocalMul, const Float3& xyzToLocalAdd) : GPUTask(Type::Custom) , _signal(&signal) , _shader(Content::LoadAsyncInternal(TEXT("Shaders/SDF"))) @@ -350,7 +350,7 @@ public: } }; -bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold, bool useGPU) +bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold, bool useGPU) { PROFILE_CPU(); auto startTime = Platform::GetTimeSeconds(); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 708b94342..db7131df1 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -98,7 +98,7 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API ModelTool // Optional: inputModel or modelData // Optional: outputSDF or null, outputStream or null - static bool GenerateModelSDF(class Model* inputModel, class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold = 0.6f, bool useGPU = true); + static bool GenerateModelSDF(class Model* inputModel, const class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold = 0.6f, bool useGPU = true); #if USE_EDITOR From 4f42fb230277b67ec8ce3493d7dcb43026412fc6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 4 Jan 2025 10:55:55 +0100 Subject: [PATCH 103/215] Update other graphics apis to match missing vertex shader inputs merging --- Source/Engine/Graphics/DynamicBuffer.cpp | 5 +++++ Source/Engine/Graphics/DynamicBuffer.h | 2 ++ Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp | 8 +++----- Source/Engine/Graphics/Shaders/GPUVertexLayout.h | 2 +- .../Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp | 4 ++-- .../GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp | 3 +-- .../GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp | 3 +-- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index 67192a821..dcd885a42 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -67,6 +67,11 @@ void DynamicBuffer::Dispose() Data.Resize(0); } +GPUVertexLayout* DynamicVertexBuffer::GetLayout() const +{ + return _layout ? _layout : (GetBuffer() ? GetBuffer()->GetVertexLayout() : nullptr); +} + void DynamicVertexBuffer::SetLayout(GPUVertexLayout* layout) { _layout = layout; diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index 6cc72d26b..fffb4bba7 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -144,6 +144,8 @@ public: { } + // Gets the vertex buffer layout. + GPUVertexLayout* GetLayout() const; // Sets the vertex buffer layout. void SetLayout(GPUVertexLayout* layout); diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index edf3b9921..524b0d4cd 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -208,12 +208,10 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span& layouts) return result; } -GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, const GPUVertexLayout* reference) +GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference) { - if (!reference || !base || base == reference) - return base; - GPUVertexLayout* result = base; - if (base && reference) + GPUVertexLayout* result = base ? base : reference; + if (base && reference && base != reference) { bool anyMissing = false; const Elements& baseElements = base->GetElements(); diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index 9bf606ec1..9212626a3 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -75,7 +75,7 @@ public: /// The list of vertex buffers for the layout. /// The list of reference inputs. /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. - static GPUVertexLayout* Merge(GPUVertexLayout* base, const GPUVertexLayout* reference); + static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference); public: // [GPUResource] diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index 7dcfb0d31..eb49171dc 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -26,8 +26,8 @@ ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* v vertexLayout = (GPUVertexLayoutDX11*)Layout; if (vertexLayout && vertexLayout->InputElementsCount) { - auto actualLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout); - LOG_DIRECTX_RESULT(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(actualLayout->InputElements, actualLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); + auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout); + LOG_DIRECTX_RESULT(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(mergedVertexLayout->InputElements, mergedVertexLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); } _cache.Add(vertexLayout, inputLayout); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index ecfce4eca..7653992ce 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -49,8 +49,6 @@ bool GPUPipelineStateDX12::IsValid() const ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, int32 rtCount, GPUTextureViewDX12** rtHandles, GPUVertexLayoutDX12* vertexLayout) { ASSERT(depth || rtCount); - if (!vertexLayout) - vertexLayout = VertexLayout; // Prepare key GPUPipelineStateKeyDX12 key; @@ -85,6 +83,7 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i _desc.SampleDesc.Quality = key.MSAA == MSAALevel::None ? 0 : GPUDeviceDX12::GetMaxMSAAQuality((int32)key.MSAA); _desc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK; _desc.DSVFormat = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindDepthStencilFormat(key.DepthFormat)); + vertexLayout = (GPUVertexLayoutDX12*)GPUVertexLayout::Merge(vertexLayout, VertexLayout); _desc.InputLayout.pInputElementDescs = vertexLayout ? vertexLayout->InputElements : nullptr; _desc.InputLayout.NumElements = vertexLayout ? vertexLayout->InputElementsCount : 0; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 2b50ef626..7f3896038 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -205,8 +205,6 @@ PipelineLayoutVulkan* GPUPipelineStateVulkan::GetLayout() VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVertexLayoutVulkan* vertexLayout) { ASSERT(renderPass); - if (!vertexLayout) - vertexLayout = VertexShaderLayout; // Try reuse cached version VkPipeline pipeline = VK_NULL_HANDLE; @@ -224,6 +222,7 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer PROFILE_CPU_NAMED("Create Pipeline"); // Bind vertex input + vertexLayout = (GPUVertexLayoutVulkan*)GPUVertexLayout::Merge(vertexLayout, VertexShaderLayout); _desc.pVertexInputState = vertexLayout ? &vertexLayout->CreateInfo : nullptr; // Update description to match the pipeline From 6f04231b317ea8e0606af1278fd7a913153b35be Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 4 Jan 2025 21:04:18 +0100 Subject: [PATCH 104/215] Rename Vertex Shader input semantic of`BLENDWEIGHT` to `BLENDWEIGHTS` --- .../Editor/MaterialTemplates/Surface.shader | 2 +- .../Engine/Graphics/Shaders/VertexElement.h | 40 +++++++++---------- .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 2 +- .../ShadersCompilation/Parser/ShaderMeta.h | 4 +- .../Parser/ShaderProcessing.Parse.cpp | 3 +- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index fd6cb31fa..1c142f463 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -459,7 +459,7 @@ META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(BLENDWEIGHTS, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) VertexOutput VS_Skinned(ModelInput_Skinned input) { VertexOutput output; diff --git a/Source/Engine/Graphics/Shaders/VertexElement.h b/Source/Engine/Graphics/Shaders/VertexElement.h index 4bc628fcc..3744c2f6a 100644 --- a/Source/Engine/Graphics/Shaders/VertexElement.h +++ b/Source/Engine/Graphics/Shaders/VertexElement.h @@ -19,45 +19,45 @@ PACK_BEGIN() struct FLAXENGINE_API VertexElement { // Undefined. Unknown = 0, - // Vertex position. + // Vertex position. Maps to 'POSITION' semantic in the shader. Position = 1, - // Vertex color. + // Vertex color. Maps to 'COLOR' semantic in the shader. Color = 2, - // Vertex normal vector. + // Vertex normal vector. Maps to 'NORMAL' semantic in the shader. Normal = 3, - // Vertex tangent vector. + // Vertex tangent vector. Maps to 'TANGENT' semantic in the shader. Tangent = 4, - // Skinned bone blend indices. + // Skinned bone blend indices. Maps to 'BLENDINDICES' semantic in the shader. BlendIndices = 5, - // Skinned bone blend weights. + // Skinned bone blend weights. Maps to 'BLENDWEIGHTS' semantic in the shader. BlendWeights = 6, - // Primary texture coordinate (UV). + // Primary texture coordinate (UV). Maps to 'TEXCOORD0' semantic in the shader. TexCoord0 = 7, - // Additional texture coordinate (UV1). + // Additional texture coordinate (UV1). Maps to 'TEXCOORD1' semantic in the shader. TexCoord1 = 8, - // Additional texture coordinate (UV2). + // Additional texture coordinate (UV2). Maps to 'TEXCOORD2' semantic in the shader. TexCoord2 = 9, - // Additional texture coordinate (UV3). + // Additional texture coordinate (UV3). Maps to 'TEXCOORD3' semantic in the shader. TexCoord3 = 10, - // Additional texture coordinate (UV4). + // Additional texture coordinate (UV4). Maps to 'TEXCOORD4' semantic in the shader. TexCoord4 = 11, - // Additional texture coordinate (UV5). + // Additional texture coordinate (UV5). Maps to 'TEXCOORD5' semantic in the shader. TexCoord5 = 12, - // Additional texture coordinate (UV6). + // Additional texture coordinate (UV6). Maps to 'TEXCOORD6' semantic in the shader. TexCoord6 = 13, - // Additional texture coordinate (UV7). + // Additional texture coordinate (UV7). Maps to 'TEXCOORD7' semantic in the shader. TexCoord7 = 14, - // General purpose attribute (at index 0). + // General purpose attribute (at index 0). Maps to 'ATTRIBUTE0' semantic in the shader. Attribute0 = 15, - // General purpose attribute (at index 1). + // General purpose attribute (at index 1). Maps to 'ATTRIBUTE1' semantic in the shader. Attribute1 = 16, - // General purpose attribute (at index 2). + // General purpose attribute (at index 2). Maps to 'ATTRIBUTE2' semantic in the shader. Attribute2 = 17, - // General purpose attribute (at index 3). + // General purpose attribute (at index 3). Maps to 'ATTRIBUTE3' semantic in the shader. Attribute3 = 18, - // Texture coordinate. + // Texture coordinate. Maps to 'TEXCOORD' semantic in the shader. TexCoord = TexCoord0, - // General purpose attribute. + // General purpose attribute. Maps to 'ATTRIBUTE0' semantic in the shader. Attribute = Attribute0, MAX }; diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index 550ac5b87..94e2513d5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -290,7 +290,7 @@ LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& se case VertexElement::Types::BlendIndices: return "BLENDINDICES"; case VertexElement::Types::BlendWeights: - return "BLENDWEIGHT"; + return "BLENDWEIGHTS"; case VertexElement::Types::TexCoord0: return "TEXCOORD"; case VertexElement::Types::TexCoord1: diff --git a/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h b/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h index a8c45d0eb..796206622 100644 --- a/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h +++ b/Source/Engine/ShadersCompilation/Parser/ShaderMeta.h @@ -141,7 +141,9 @@ public: BITANGENT = 6, ATTRIBUTE = 7, BLENDINDICES = 8, - BLENDWEIGHT = 9, + BLENDWEIGHTS = 9, + // [Deprecated in v1.10] + BLENDWEIGHT = BLENDWEIGHTS, }; /// diff --git a/Source/Engine/ShadersCompilation/Parser/ShaderProcessing.Parse.cpp b/Source/Engine/ShadersCompilation/Parser/ShaderProcessing.Parse.cpp index da5f373b3..ad7a73743 100644 --- a/Source/Engine/ShadersCompilation/Parser/ShaderProcessing.Parse.cpp +++ b/Source/Engine/ShadersCompilation/Parser/ShaderProcessing.Parse.cpp @@ -26,7 +26,8 @@ VertexShaderMeta::InputType ShaderProcessing::ParseInputType(const Token& token) _PARSE_ENTRY(BITANGENT), _PARSE_ENTRY(ATTRIBUTE), _PARSE_ENTRY(BLENDINDICES), - _PARSE_ENTRY(BLENDWEIGHT), + _PARSE_ENTRY(BLENDWEIGHTS), + _PARSE_ENTRY(BLENDWEIGHT), // [Deprecated in v1.10] }; #undef _PARSE_ENTRY From 792d32281876b9a6c3e3055f1580348423f65915 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 5 Jan 2025 00:17:52 +0100 Subject: [PATCH 105/215] Missing change for 6f04231b317ea8e0606af1278fd7a913153b35be --- Source/Engine/Graphics/Materials/MaterialShader.h | 2 +- Source/Shaders/MaterialCommon.hlsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index fce9b56e4..8170c8e5a 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 169 +#define MATERIAL_GRAPH_VERSION 170 class Material; class GPUShader; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 325f8d065..6b20e2d71 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -213,7 +213,7 @@ struct ModelInput_Skinned float4 Normal : NORMAL; float4 Tangent : TANGENT; uint4 BlendIndices : BLENDINDICES; - float4 BlendWeights : BLENDWEIGHT; + float4 BlendWeights : BLENDWEIGHTS; }; struct Model_VS2PS From 78cf1a49480034a1c06d18925c215bbfd2ef0ea0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 5 Jan 2025 16:45:30 +0100 Subject: [PATCH 106/215] Use `MeshDataCache` in vertex painting tool --- Source/Editor/Tools/VertexPainting.cs | 95 ++++-------------------- Source/Engine/Utilities/MeshDataCache.cs | 26 +++---- 2 files changed, 28 insertions(+), 93 deletions(-) diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index d8e1fed88..b6cce7b9c 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -2,18 +2,16 @@ using System; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph; -using FlaxEditor.Viewport; using FlaxEditor.Viewport.Modes; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Utilities; using Object = FlaxEngine.Object; namespace FlaxEditor.Tools @@ -335,11 +333,6 @@ namespace FlaxEditor.Tools sealed class VertexPaintingGizmo : GizmoBase { - private struct MeshData - { - public Mesh.Vertex[] VertexBuffer; - } - private MaterialInstance _vertexColorsPreviewMaterial; private Model _brushModel; private MaterialInstance _brushMaterial; @@ -351,9 +344,7 @@ namespace FlaxEditor.Tools private Vector3 _hitLocation; private Vector3 _hitNormal; private StaticModel _selectedModel; - private MeshData[][] _meshDatas; - private bool _meshDatasInProgress; - private bool _meshDatasCancel; + private MeshDataCache _meshDatas; private EditModelVertexColorsAction _undoAction; public bool IsPainting => _isPainting; @@ -369,74 +360,18 @@ namespace FlaxEditor.Tools if (_selectedModel == model) return; PaintEnd(); - WaitForMeshDataRequestEnd(); _selectedModel = model; - _meshDatas = null; - _meshDatasInProgress = false; - _meshDatasCancel = false; + if (model && model.Model) + { + if (_meshDatas == null) + _meshDatas = new MeshDataCache(); + _meshDatas.RequestMeshData(_selectedModel.Model); + } + else + { + _meshDatas?.Dispose(); + } _hasHit = false; - RequestMeshData(); - } - - private void RequestMeshData() - { - if (_meshDatasInProgress) - return; - if (_meshDatas != null) - return; - _meshDatasInProgress = true; - _meshDatasCancel = false; - Task.Run(DownloadMeshData); - } - - private void WaitForMeshDataRequestEnd() - { - if (_meshDatasInProgress) - { - _meshDatasCancel = true; - for (int i = 0; i < 500 && _meshDatasInProgress; i++) - Thread.Sleep(10); - } - } - - private void DownloadMeshData() - { - try - { - if (!_selectedModel) - { - _meshDatasInProgress = false; - return; - } - var model = _selectedModel.Model; - var lods = model.LODs; - _meshDatas = new MeshData[lods.Length][]; - - for (int lodIndex = 0; lodIndex < lods.Length && !_meshDatasCancel; lodIndex++) - { - var lod = lods[lodIndex]; - var meshes = lod.Meshes; - _meshDatas[lodIndex] = new MeshData[meshes.Length]; - - for (int meshIndex = 0; meshIndex < meshes.Length && !_meshDatasCancel; meshIndex++) - { - var mesh = meshes[meshIndex]; - _meshDatas[lodIndex][meshIndex] = new MeshData - { - VertexBuffer = mesh.DownloadVertexBuffer() - }; - } - } - } - catch (Exception ex) - { - Editor.LogWarning("Failed to get mesh data. " + ex.Message); - Editor.LogWarning(ex); - } - finally - { - _meshDatasInProgress = false; - } } private void PaintStart() @@ -460,10 +395,10 @@ namespace FlaxEditor.Tools Profiler.BeginEvent("Vertex Paint"); // Ensure to have vertex data ready - WaitForMeshDataRequestEnd(); + _meshDatas.WaitForMeshDataRequestEnd(); // Edit the model vertex colors - var meshDatas = _meshDatas; + var meshDatas = _meshDatas.MeshDatas; if (meshDatas == null) throw new Exception("Missing mesh data of the model to paint."); var instanceTransform = _selectedModel.Transform; @@ -637,7 +572,7 @@ namespace FlaxEditor.Tools } // Draw intersecting vertices - var meshDatas = _meshDatas; + var meshDatas = _meshDatas?.MeshDatas; if (meshDatas != null && _gizmoMode.PreviewVertexSize > Mathf.Epsilon) { if (!_verticesPreviewMaterial) diff --git a/Source/Engine/Utilities/MeshDataCache.cs b/Source/Engine/Utilities/MeshDataCache.cs index eda8059c7..9ea8e6db0 100644 --- a/Source/Engine/Utilities/MeshDataCache.cs +++ b/Source/Engine/Utilities/MeshDataCache.cs @@ -30,13 +30,13 @@ namespace FlaxEngine.Utilities private Model _model; private MeshData[][] _meshDatas; - private bool _meshDatasInProgress; - private bool _meshDatasCancel; + private bool _inProgress; + private bool _cancel; /// /// Gets the mesh datas (null if during downloading). /// - public MeshData[][] MeshDatas => _meshDatasInProgress ? null : _meshDatas; + public MeshData[][] MeshDatas => _inProgress ? null : _meshDatas; /// /// Occurs when mesh data gets downloaded (called on async thread). @@ -57,7 +57,7 @@ namespace FlaxEngine.Utilities // Mode changes so release previous cache Dispose(); } - if (_meshDatasInProgress) + if (_inProgress) { // Still downloading return false; @@ -70,8 +70,8 @@ namespace FlaxEngine.Utilities // Start downloading _model = model; - _meshDatasInProgress = true; - _meshDatasCancel = false; + _inProgress = true; + _cancel = false; Task.Run(new Action(DownloadMeshData)); return false; } @@ -83,7 +83,7 @@ namespace FlaxEngine.Utilities { WaitForMeshDataRequestEnd(); _meshDatas = null; - _meshDatasInProgress = false; + _inProgress = false; } /// @@ -91,10 +91,10 @@ namespace FlaxEngine.Utilities /// public void WaitForMeshDataRequestEnd() { - if (_meshDatasInProgress) + if (_inProgress) { - _meshDatasCancel = true; - for (int i = 0; i < 500 && _meshDatasInProgress; i++) + _cancel = true; + for (int i = 0; i < 500 && _inProgress; i++) Thread.Sleep(10); } } @@ -113,13 +113,13 @@ namespace FlaxEngine.Utilities var lods = _model.LODs; _meshDatas = new MeshData[lods.Length][]; - for (int lodIndex = 0; lodIndex < lods.Length && !_meshDatasCancel; lodIndex++) + for (int lodIndex = 0; lodIndex < lods.Length && !_cancel; lodIndex++) { var lod = lods[lodIndex]; var meshes = lod.Meshes; _meshDatas[lodIndex] = new MeshData[meshes.Length]; - for (int meshIndex = 0; meshIndex < meshes.Length && !_meshDatasCancel; meshIndex++) + for (int meshIndex = 0; meshIndex < meshes.Length && !_cancel; meshIndex++) { var mesh = meshes[meshIndex]; _meshDatas[lodIndex][meshIndex] = new MeshData @@ -139,7 +139,7 @@ namespace FlaxEngine.Utilities } finally { - _meshDatasInProgress = false; + _inProgress = false; if (success) { From 933fac6c1397cd551df19659acc6623eeb2d2c31 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 5 Jan 2025 17:41:27 +0100 Subject: [PATCH 107/215] Add `Slice` to `Span` and sue it to make code cleaner --- Source/Engine/Core/Types/Span.h | 24 +++++++++++++++++++ .../Materials/DecalMaterialShader.cpp | 2 +- .../Materials/DeformableMaterialShader.cpp | 2 +- .../Graphics/Materials/GUIMaterialShader.cpp | 2 +- .../Materials/MaterialShaderFeatures.cpp | 6 ++--- .../Materials/ParticleMaterialShader.cpp | 2 +- .../Materials/PostFxMaterialShader.cpp | 2 +- .../Materials/TerrainMaterialShader.cpp | 2 +- .../VolumeParticleMaterialShader.cpp | 2 +- .../DirectX/DX12/GPUShaderDX12.cpp | 2 +- .../GraphicsDevice/Vulkan/GPUShaderVulkan.cpp | 2 +- .../Particles/Graph/GPU/GPUParticles.cpp | 2 +- 12 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h index 8f16b8c7d..3120ae670 100644 --- a/Source/Engine/Core/Types/Span.h +++ b/Source/Engine/Core/Types/Span.h @@ -85,6 +85,30 @@ public: return (U*)_data; } +public: + /// + /// Constructs a slice out of the current span that begins at a specified index. + /// + /// The zero-based index at which to begin the slice. + /// A span that consists of all elements of the current span from to the end of the span. + Span Slice(int32 start) + { + ASSERT(start <= _length); + return Span(_data + start, _length - start); + } + + /// + /// Constructs a slice out of the current span starting at a specified index for a specified length. + /// + /// The zero-based index at which to begin this slice. + /// The length for the result slice. + /// A span that consists of elements from the current span starting at . + Span Slice(int32 start, int32 length) + { + ASSERT(start + length <= _length); + return Span(_data + start, length); + } + public: /// /// Gets or sets the element by index diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index 714805f45..ab38822c0 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -33,7 +33,7 @@ void DecalMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(DecalMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(DecalMaterialShaderData), cb.Length() - sizeof(DecalMaterialShaderData)); + cb = cb.Slice(sizeof(DecalMaterialShaderData)); const bool isCameraInside = OrientedBoundingBox(Vector3::Half, drawCall.World).Contains(view.Position) == ContainmentType::Contains; // Setup parameters diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp index 434d1ef05..333c9eb35 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -41,7 +41,7 @@ void DeformableMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(DeformableMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(DeformableMaterialShaderData), cb.Length() - sizeof(DeformableMaterialShaderData)); + cb = cb.Slice(sizeof(DeformableMaterialShaderData)); int32 srv = 1; // Setup features diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 69a097a2a..d6066adab 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -32,7 +32,7 @@ void GUIMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(GUIMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(GUIMaterialShaderData), cb.Length() - sizeof(GUIMaterialShaderData)); + cb = cb.Slice(sizeof(GUIMaterialShaderData)); int32 srv = 0; const auto ps = context->IsDepthBufferBinded() ? _cache.Depth : _cache.NoDepth; auto customData = (Render2D::CustomData*)params.CustomData; diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 2b2d079cd..a0b85092a 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -113,7 +113,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span(cb.Get() + sizeof(Data), cb.Length() - sizeof(Data)); + cb = cb.Slice(sizeof(Data)); srv += SRVs; } @@ -184,7 +184,7 @@ bool GlobalIlluminationFeature::Bind(MaterialShader::BindParameters& params, Spa params.GPUContext->UnBindSR(srv + 2); } - cb = Span(cb.Get() + sizeof(Data), cb.Length() - sizeof(Data)); + cb = cb.Slice(sizeof(Data)); srv += SRVs; return useGI; } @@ -236,7 +236,7 @@ bool SDFReflectionsFeature::Bind(MaterialShader::BindParameters& params, SpanUnBindSR(srv + 6); } - cb = Span(cb.Get() + sizeof(Data), cb.Length() - sizeof(Data)); + cb = cb.Slice(sizeof(Data)); srv += SRVs; return useSDFReflections; } diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 33e2bb454..1659e973e 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -53,7 +53,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(ParticleMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(ParticleMaterialShaderData), cb.Length() - sizeof(ParticleMaterialShaderData)); + cb = cb.Slice(sizeof(ParticleMaterialShaderData)); int32 srv = 2; // Setup features diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index c820fc613..edb293c65 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -31,7 +31,7 @@ void PostFxMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(PostFxMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(PostFxMaterialShaderData), cb.Length() - sizeof(PostFxMaterialShaderData)); + cb = cb.Slice(sizeof(PostFxMaterialShaderData)); int32 srv = 0; // Setup parameters diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 2a040b55a..2bb0b42ad 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -50,7 +50,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(TerrainMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(TerrainMaterialShaderData), cb.Length() - sizeof(TerrainMaterialShaderData)); + cb = cb.Slice(sizeof(TerrainMaterialShaderData)); int32 srv = 3; // Setup features diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp index cc04498df..853058226 100644 --- a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp @@ -41,7 +41,7 @@ void VolumeParticleMaterialShader::Bind(BindParameters& params) Span cb(_cbData.Get(), _cbData.Count()); ASSERT_LOW_LAYER(cb.Length() >= sizeof(VolumeParticleMaterialShaderData)); auto materialData = reinterpret_cast(cb.Get()); - cb = Span(cb.Get() + sizeof(VolumeParticleMaterialShaderData), cb.Length() - sizeof(VolumeParticleMaterialShaderData)); + cb = cb.Slice(sizeof(VolumeParticleMaterialShaderData)); int32 srv = 1; auto customData = (VolumetricFogPass::CustomData*)params.CustomData; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp index c4aea2172..0fe07a466 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp @@ -12,7 +12,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const { // Extract the DX shader header from the cache DxShaderHeader* header = (DxShaderHeader*)bytecode.Get(); - bytecode = Span(bytecode.Get() + sizeof(DxShaderHeader), bytecode.Length() - sizeof(DxShaderHeader)); + bytecode = bytecode.Slice(sizeof(DxShaderHeader)); GPUShaderProgram* shader = nullptr; switch (type) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp index d36919b5f..9608d5207 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp @@ -102,7 +102,7 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons { // Extract the SPIR-V shader header from the cache SpirvShaderHeader* header = (SpirvShaderHeader*)bytecode.Get(); - bytecode = Span(bytecode.Get() + sizeof(SpirvShaderHeader), bytecode.Length() - sizeof(SpirvShaderHeader)); + bytecode = bytecode.Slice(sizeof(SpirvShaderHeader)); // Extract the SPIR-V bytecode BytesContainer spirv; diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp index dbd7c0d02..43fde57db 100644 --- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp +++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp @@ -166,7 +166,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Constants = hasCB ? Span(_cbData.Get() + sizeof(GPUParticlesData), _cbData.Count() - sizeof(GPUParticlesData)) : Span(nullptr, 0); + bindMeta.Constants = hasCB ? ToSpan(_cbData).Slice(sizeof(GPUParticlesData)) : Span(nullptr, 0); bindMeta.Input = nullptr; if (viewTask) { From 29bfef677f24b0fe5e15335d973b61d5b77ce3cc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 5 Jan 2025 23:49:44 +0100 Subject: [PATCH 108/215] Add `PixelFormatSampler` utility to quick read/write operations on various data formats Moved from `TextureTool` to be used in runtime and with more generic use cases (including C# scripting). --- .../Platform/Windows/WindowsPlatformTools.cpp | 9 +- Source/Editor/Tools/Terrain/TerrainTools.cpp | 13 +- Source/Editor/Utilities/EditorUtilities.cpp | 17 +- .../Engine/Graphics/PixelFormatExtensions.cpp | 442 ++++++++++++++++++ .../Engine/Graphics/PixelFormatExtensions.h | 6 + Source/Engine/Graphics/PixelFormatSampler.cs | 46 ++ Source/Engine/Graphics/PixelFormatSampler.h | 82 ++++ .../Engine/Graphics/Textures/TextureBase.cpp | 17 +- .../Engine/Tools/TextureTool/TextureTool.cpp | 330 +------------ Source/Engine/Tools/TextureTool/TextureTool.h | 74 --- 10 files changed, 610 insertions(+), 426 deletions(-) create mode 100644 Source/Engine/Graphics/PixelFormatSampler.cs create mode 100644 Source/Engine/Graphics/PixelFormatSampler.h diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 5259ca2df..da1832d2e 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Content/Content.h" @@ -240,7 +241,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon) const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip); const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get(); const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip)); - const auto sampler = TextureTool::GetSampler(icon->Format); + const auto sampler = PixelFormatSampler::Get(icon->Format); ASSERT_LOW_LAYER(sampler); // Write colors @@ -252,7 +253,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon) for (uint32 x = 0; x < width; x++) { float u = (float)x / width; - const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + const Color c = sampler->SampleLinear(srcPixelsData, Float2(u, v), srcPixelsSize, srcPixels->RowPitch); colorData[idx++] = Color32(c).GetAsBGRA(); } } @@ -271,7 +272,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon) { uint32 x = packedX * 8 + pixelIdx; float u = (float)x / width; - const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + const Color c = sampler->SampleLinear(srcPixelsData, Float2(u, v), srcPixelsSize, srcPixels->RowPitch); if (c.A < 0.25f) mask |= 1 << (7 - pixelIdx); } @@ -322,7 +323,7 @@ bool UpdateExeIcon(const String& path, const TextureData& icon) const TextureData* iconRGBA8 = &icon; TextureData tmpData1; //if (icon.Format != PixelFormat::R8G8B8A8_UNorm) - if (TextureTool::GetSampler(icon.Format) == nullptr) + if (PixelFormatSampler::Get(icon.Format) == nullptr) { if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) { diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp index 3ba11cdf5..111152ad0 100644 --- a/Source/Editor/Tools/Terrain/TerrainTools.cpp +++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp @@ -8,6 +8,7 @@ #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/Terrain.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Graphics/Textures/TextureData.h" @@ -103,7 +104,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool h // Decompress or convert data if need to data.Mip0DataPtr = &data.Mip0Data; - if (PixelFormatExtensions::IsCompressed(data.Format) || TextureTool::GetSampler(data.Format) == nullptr) + if (PixelFormatExtensions::IsCompressed(data.Format) || PixelFormatSampler::Get(data.Format) == nullptr) { PROFILE_CPU_NAMED("Decompress"); @@ -136,7 +137,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool h } // Check if can even sample the given format - const auto sampler = TextureTool::GetSampler(data.Format); + const auto sampler = PixelFormatSampler::Get(data.Format); if (sampler == nullptr) { LOG(Warning, "Texture format {0} cannot be sampled.", (int32)data.Format); @@ -188,7 +189,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches TextureDataResult dataHeightmap; if (GetTextureDataForSampling(heightmap, dataHeightmap, true)) return true; - const auto sampler = TextureTool::GetSampler(dataHeightmap.Format); + const auto sampler = PixelFormatSampler::Get(dataHeightmap.Format); // Initialize with sub-range of the input heightmap const Vector2 uvPerPatch = Vector2::One / Vector2(numberOfPatches); @@ -204,7 +205,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches for (int32 x = 0; x < heightmapSize; x++) { const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch; - const Color color = TextureTool::SampleLinear(sampler, uv, dataHeightmap.Mip0DataPtr->Get(), dataHeightmap.Mip0Size, dataHeightmap.RowPitch); + const Color color = sampler->SampleLinear(dataHeightmap.Mip0DataPtr->Get(), uv, dataHeightmap.Mip0Size, dataHeightmap.RowPitch); heightmapData[z * heightmapSize + x] = color.R * heightmapScale; } } @@ -244,7 +245,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches // Get splatmap data if (GetTextureDataForSampling(splatmap, data1)) return true; - const auto sampler = TextureTool::GetSampler(data1.Format); + const auto sampler = PixelFormatSampler::Get(data1.Format); // Modify heightmap splatmaps with sub-range of the input splatmaps for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) @@ -260,7 +261,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches { const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch; - const Color color = TextureTool::SampleLinear(sampler, uv, data1.Mip0DataPtr->Get(), data1.Mip0Size, data1.RowPitch); + const Color color = sampler->SampleLinear(data1.Mip0DataPtr->Get(), uv, data1.Mip0Size, data1.RowPitch); Color32 layers; layers.R = (byte)(Math::Min(1.0f, color.R) * 255.0f); diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index e3f521437..f946f56d2 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -6,6 +6,7 @@ #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Core/Log.h" @@ -142,7 +143,7 @@ bool EditorUtilities::ExportApplicationImage(const Guid& iconId, int32 width, in bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 width, int32 height, PixelFormat format, const String& path) { // Change format if need to - const TextureData* iconData = &icon; + TextureData* iconData = (TextureData*)&icon; TextureData tmpData1, tmpData2; if (icon.Format != format) { @@ -150,7 +151,7 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt if (PixelFormatExtensions::HasAlpha(iconData->Format) && !PixelFormatExtensions::HasAlpha(format)) { // Pre-multiply alpha if can - auto sampler = TextureTool::GetSampler(iconData->Format); + auto sampler = PixelFormatSampler::Get(iconData->Format); if (!sampler) { if (TextureTool::Convert(tmpData2, *iconData, PixelFormat::R16G16B16A16_Float)) @@ -159,7 +160,7 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt return true; } iconData = &tmpData2; - sampler = TextureTool::GetSampler(iconData->Format); + sampler = PixelFormatSampler::Get(iconData->Format); } if (sampler) { @@ -168,10 +169,10 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt { for (int32 x = 0; x < iconData->Width; x++) { - Color color = TextureTool::SamplePoint(sampler, x, y, mipData->Data.Get(), mipData->RowPitch); + Color color = sampler->SamplePoint(mipData->Data.Get(), x, y, mipData->RowPitch); color *= color.A; color.A = 1.0f; - TextureTool::Store(sampler, x, y, mipData->Data.Get(), mipData->RowPitch, color); + sampler->Store(mipData->Data.Get(), x, y, mipData->RowPitch, color); } } } @@ -191,7 +192,7 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt if (PixelFormatExtensions::HasAlpha(icon.Format) && !PixelFormatExtensions::HasAlpha(format)) { // Pre-multiply alpha if can - auto sampler = TextureTool::GetSampler(icon.Format); + auto sampler = PixelFormatSampler::Get(icon.Format); if (sampler) { auto mipData = iconData->GetData(0, 0); @@ -199,10 +200,10 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt { for (int32 x = 0; x < iconData->Width; x++) { - Color color = TextureTool::SamplePoint(sampler, x, y, mipData->Data.Get(), mipData->RowPitch); + Color color = sampler->SamplePoint(mipData->Data.Get(), x, y, mipData->RowPitch); color *= color.A; color.A = 1.0f; - TextureTool::Store(sampler, x, y, mipData->Data.Get(), mipData->RowPitch, color); + sampler->Store(mipData->Data.Get(), x, y, mipData->RowPitch, color); } } } diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 2a341eec4..10a110d62 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1,7 +1,13 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "PixelFormatExtensions.h" +#include "PixelFormatSampler.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Math/Color.h" +#include "Engine/Core/Math/Color32.h" +#include "Engine/Core/Math/Half.h" +#include "Engine/Core/Math/Packed.h" +#include "Engine/Core/Math/Vector4.h" // ReSharper disable CppClangTidyClangDiagnosticSwitchEnum @@ -1060,3 +1066,439 @@ PixelFormat PixelFormatExtensions::FindUncompressedFormat(PixelFormat format) return format; } } + +static PixelFormatSampler PixelFormatSamplers[] = +{ + { + PixelFormat::R32G32B32A32_Float, + sizeof(Float4), + [](const void* ptr) + { + return *(Float4*)ptr; + }, + [](void* ptr, const Float4& value) + { + *(Float4*)ptr = value; + }, + }, + { + PixelFormat::R32G32B32_Float, + sizeof(Float3), + [](const void* ptr) + { + return Float4(*(Float3*)ptr, 1.0f); + }, + [](void* ptr, const Float4& value) + { + *(Float3*)ptr = Float3(value); + }, + }, + { + PixelFormat::R16G16B16A16_Float, + sizeof(Half4), + [](const void* ptr) + { + return ((Half4*)ptr)->ToFloat4(); + }, + [](void* ptr, const Float4& value) + { + *(Half4*)ptr = Half4(value.X, value.Y, value.Z, value.W); + }, + }, + { + PixelFormat::R16G16B16A16_UNorm, + sizeof(RGBA16UNorm), + [](const void* ptr) + { + return ((RGBA16UNorm*)ptr)->ToFloat4(); + }, + [](void* ptr, const Float4& value) + { + *(RGBA16UNorm*)ptr = RGBA16UNorm(value.X, value.Y, value.Z, value.W); + } + }, + { + PixelFormat::R32G32_Float, + sizeof(Float2), + [](const void* ptr) + { + return Float4(((Float2*)ptr)->X, ((Float2*)ptr)->Y, 0.0f, 0.0f); + }, + [](void* ptr, const Float4& value) + { + *(Float2*)ptr = Float2(value.X, value.Y); + }, + }, + { + PixelFormat::R8G8B8A8_UNorm, + sizeof(Color32), + [](const void* ptr) + { + return Float4(Color(*(Color32*)ptr)); + }, + [](void* ptr, const Float4& value) + { + *(Color32*)ptr = Color32(value); + }, + }, + { + PixelFormat::R8G8B8A8_UNorm_sRGB, + sizeof(Color32), + [](const void* ptr) + { + return Float4(Color::SrgbToLinear(Color(*(Color32*)ptr))); + }, + [](void* ptr, const Float4& value) + { + Color srgb = Color::LinearToSrgb((const Color&)value); + *(Color32*)ptr = Color32(srgb); + }, + }, + { + PixelFormat::R8G8_UNorm, + sizeof(uint16), + [](const void* ptr) + { + const uint8* rg = (const uint8*)ptr; + return Float4((float)rg[0] / MAX_uint8, (float)rg[1] / MAX_uint8, 0, 1); + }, + [](void* ptr, const Float4& value) + { + uint8* rg = (uint8*)ptr; + rg[0] = (uint8)(value.X * MAX_uint8); + rg[1] = (uint8)(value.Y * MAX_uint8); + }, + }, + { + PixelFormat::R16G16_Float, + sizeof(Half2), + [](const void* ptr) + { + const Float2 rg = ((Half2*)ptr)->ToFloat2(); + return Float4(rg.X, rg.Y, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(Half2*)ptr = Half2(value.X, value.Y); + }, + }, + { + PixelFormat::R16G16_UNorm, + sizeof(RG16UNorm), + [](const void* ptr) + { + const Float2 rg = ((RG16UNorm*)ptr)->ToFloat2(); + return Float4(rg.X, rg.Y, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(RG16UNorm*)ptr = RG16UNorm(value.X, value.Y); + }, + }, + { + PixelFormat::R32_Float, + sizeof(float), + [](const void* ptr) + { + return Float4(*(float*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(float*)ptr = value.X; + }, + }, + { + PixelFormat::R16_Float, + sizeof(Half), + [](const void* ptr) + { + return Float4(Float16Compressor::Decompress(*(Half*)ptr), 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(Half*)ptr = Float16Compressor::Compress(value.X); + }, + }, + { + PixelFormat::R16_UNorm, + sizeof(uint16), + [](const void* ptr) + { + return Float4((float)*(uint16*)ptr / MAX_uint16, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(uint16*)ptr = (uint16)(value.X * MAX_uint16); + }, + }, + { + PixelFormat::R8_UNorm, + sizeof(uint8), + [](const void* ptr) + { + return Float4((float)*(byte*)ptr / MAX_uint8, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(byte*)ptr = (byte)(value.X * MAX_uint8); + }, + }, + { + PixelFormat::A8_UNorm, + sizeof(uint8), + [](const void* ptr) + { + return Float4(0, 0, 0, (float)*(byte*)ptr / MAX_uint8); + }, + [](void* ptr, const Float4& value) + { + *(byte*)ptr = (byte)(value.W * MAX_uint8); + }, + }, + { + PixelFormat::R32_UInt, + sizeof(uint32), + [](const void* ptr) + { + return Float4((float)*(uint32*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(uint32*)ptr = (uint32)value.X; + }, + }, + { + PixelFormat::R32_SInt, + sizeof(int32), + [](const void* ptr) + { + return Float4((float)*(int32*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(int32*)ptr = (int32)value.X; + }, + }, + { + PixelFormat::R16_UInt, + sizeof(uint16), + [](const void* ptr) + { + return Float4((float)*(uint16*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(uint16*)ptr = (uint16)value.X; + }, + }, + { + PixelFormat::R16_SInt, + sizeof(int16), + [](const void* ptr) + { + return Float4((float)*(int16*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(int16*)ptr = (int16)value.X; + }, + }, + { + PixelFormat::R8_UInt, + sizeof(uint8), + [](const void* ptr) + { + return Float4((float)*(uint8*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(uint8*)ptr = (uint8)value.X; + }, + }, + { + PixelFormat::R8_SInt, + sizeof(int8), + [](const void* ptr) + { + return Float4((float)*(int8*)ptr, 0, 0, 1); + }, + [](void* ptr, const Float4& value) + { + *(int8*)ptr = (int8)value.X; + }, + }, + { + PixelFormat::B8G8R8A8_UNorm, + sizeof(Color32), + [](const void* ptr) + { + const Color32 bgra = *(Color32*)ptr; + return Float4(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A))); + }, + [](void* ptr, const Float4& value) + { + *(Color32*)ptr = Color32(byte(value.Z * MAX_uint8), byte(value.Y * MAX_uint8), byte(value.X * MAX_uint8), byte(value.W * MAX_uint8)); + }, + }, + { + PixelFormat::B8G8R8A8_UNorm_sRGB, + sizeof(Color32), + [](const void* ptr) + { + const Color32 bgra = *(Color32*)ptr; + return Float4(Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A)))); + }, + [](void* ptr, const Float4& value) + { + Color srgb = Color::LinearToSrgb((const Color&)value); + *(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), byte(srgb.A * MAX_uint8)); + }, + }, + { + PixelFormat::B8G8R8X8_UNorm, + sizeof(Color32), + [](const void* ptr) + { + const Color32 bgra = *(Color32*)ptr; + return Float4(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8))); + }, + [](void* ptr, const Float4& value) + { + *(Color32*)ptr = Color32(byte(value.Z * MAX_uint8), byte(value.Y * MAX_uint8), byte(value.X * MAX_uint8), MAX_uint8); + }, + }, + { + PixelFormat::B8G8R8X8_UNorm_sRGB, + sizeof(Color32), + [](const void* ptr) + { + const Color32 bgra = *(Color32*)ptr; + return Float4(Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8)))); + }, + [](void* ptr, const Float4& value) + { + Color srgb = Color::LinearToSrgb((const Color&)value); + *(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), MAX_uint8); + }, + }, + { + PixelFormat::R11G11B10_Float, + sizeof(FloatR11G11B10), + [](const void* ptr) + { + const Float3 rgb = ((FloatR11G11B10*)ptr)->ToFloat3(); + return Float4(rgb.X, rgb.Y, rgb.Z, 0.0f); + }, + [](void* ptr, const Float4& value) + { + *(FloatR11G11B10*)ptr = FloatR11G11B10(value.X, value.Y, value.Z); + }, + }, + { + PixelFormat::R10G10B10A2_UNorm, + sizeof(Float1010102), + [](const void* ptr) + { + return ((Float1010102*)ptr)->ToFloat4(); + }, + [](void* ptr, const Float4& value) + { + *(Float1010102*)ptr = Float1010102(value.X, value.Y, value.Z, value.W); + }, + }, + { + PixelFormat::R8G8B8A8_UInt, + sizeof(Color32), + [](const void* ptr) + { + uint8 data[4]; + Platform::MemoryCopy(data, ptr, sizeof(data)); + return Float4(data[0], data[1], data[2], data[3]); + }, + [](void* ptr, const Float4& value) + { + uint8 data[4] = { (uint8)value.X, (uint8)value.Y, (uint8)value.Z, (uint8)value.W}; + Platform::MemoryCopy(ptr, data, sizeof(data)); + }, + }, + { + PixelFormat::R8G8B8A8_SInt, + sizeof(Color32), + [](const void* ptr) + { + int8 data[4]; + Platform::MemoryCopy(data, ptr, sizeof(data)); + return Float4(data[0], data[1], data[2], data[3]); + }, + [](void* ptr, const Float4& value) + { + int8 data[4] = { (int8)value.X, (int8)value.Y, (int8)value.Z, (int8)value.W}; + Platform::MemoryCopy(ptr, data, sizeof(data)); + }, + }, +}; + +void PixelFormatSampler::Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const +{ + Write((byte*)data + rowPitch * y + PixelSize * x, (Float4&)color); +} + +Float4 PixelFormatSampler::Sample(const void* data, int32 x) const +{ + return Read((const byte*)data + x * PixelSize); +} + +Color PixelFormatSampler::SamplePoint(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const +{ + const Int2 end = size - 1; + const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y)); + Float4 result = Read((const byte*)data + rowPitch * uvFloor.Y + PixelSize * uvFloor.X); + return *(Color*)&result; +} + +Color PixelFormatSampler::SamplePoint(const void* data, int32 x, int32 y, int32 rowPitch) const +{ + Float4 result = Read((const byte*)data + rowPitch * y + PixelSize * x); + return *(Color*)&result; +} + +Color PixelFormatSampler::SampleLinear(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const +{ + const Int2 end = size - 1; + const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y)); + const Int2 uvNext(Math::Min(uvFloor.X + 1, end.X), Math::Min(uvFloor.Y + 1, end.Y)); + const Float2 uvFraction(uv.X * size.Y - uvFloor.X, uv.Y * size.Y - uvFloor.Y); + + const Float4 v00 = Read((const byte*)data + rowPitch * uvFloor.Y + PixelSize * uvFloor.X); + const Float4 v01 = Read((const byte*)data + rowPitch * uvFloor.Y + PixelSize * uvNext.X); + const Float4 v10 = Read((const byte*)data + rowPitch * uvNext.Y + PixelSize * uvFloor.X); + const Float4 v11 = Read((const byte*)data + rowPitch * uvNext.Y + PixelSize * uvNext.X); + + Float4 result = Float4::Lerp(Float4::Lerp(v00, v01, uvFraction.X), Float4::Lerp(v10, v11, uvFraction.X), uvFraction.Y); + return *(Color*)&result; +} + +const PixelFormatSampler* PixelFormatSampler::Get(PixelFormat format) +{ + format = PixelFormatExtensions::MakeTypelessFloat(format); + for (const auto& sampler : PixelFormatSamplers) + { + if (sampler.Format == format) + return &sampler; + } + return nullptr; +} + +#if !COMPILE_WITHOUT_CSHARP + +void PixelFormatExtensions::GetSamplerInternal(PixelFormat format, int32& pixelSize, void** read, void** write) +{ + if (const PixelFormatSampler* sampler = PixelFormatSampler::Get(format)) + { + pixelSize = sampler->PixelSize; + *read = sampler->Read; + *write = sampler->Write; + } +} + +#endif diff --git a/Source/Engine/Graphics/PixelFormatExtensions.h b/Source/Engine/Graphics/PixelFormatExtensions.h index d5296ea65..f3274d9ea 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.h +++ b/Source/Engine/Graphics/PixelFormatExtensions.h @@ -205,4 +205,10 @@ public: static PixelFormat FindUnorderedAccessFormat(PixelFormat format); static PixelFormat FindDepthStencilFormat(PixelFormat format); static PixelFormat FindUncompressedFormat(PixelFormat format); + +private: + // Internal bindings +#if !COMPILE_WITHOUT_CSHARP + API_FUNCTION(NoProxy) static void GetSamplerInternal(PixelFormat format, API_PARAM(Out) int32& pixelSize, API_PARAM(Out) void** read, API_PARAM(Out) void** write); +#endif }; diff --git a/Source/Engine/Graphics/PixelFormatSampler.cs b/Source/Engine/Graphics/PixelFormatSampler.cs new file mode 100644 index 000000000..6a8127b4a --- /dev/null +++ b/Source/Engine/Graphics/PixelFormatSampler.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +namespace FlaxEngine +{ + unsafe partial struct PixelFormatSampler + { + /// + /// Element format. + /// + public PixelFormat Format; + + /// + /// Element size in bytes. + /// + public int PixelSize; + + /// + /// Read data function. + /// + public delegate* unmanaged Read; + + /// + /// Write data function. + /// + public delegate* unmanaged Write; + + /// + /// Tries to get a sampler tool for the specified format to read pixels. + /// + /// The format. + /// The sampler object or empty when cannot sample the given format. + /// True if got sampler, otherwise false. + public static bool Get(PixelFormat format, out PixelFormatSampler sampler) + { + PixelFormatExtensions.Internal_GetSamplerInternal(format, out var pixelSize, out var read, out var write); + sampler = new PixelFormatSampler + { + Format = format, + PixelSize = pixelSize, + Read = (delegate* unmanaged)read.ToPointer(), + Write = (delegate* unmanaged)write.ToPointer(), + }; + return pixelSize != 0; + } + } +} diff --git a/Source/Engine/Graphics/PixelFormatSampler.h b/Source/Engine/Graphics/PixelFormatSampler.h new file mode 100644 index 000000000..c0e0cb6d1 --- /dev/null +++ b/Source/Engine/Graphics/PixelFormatSampler.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "PixelFormat.h" + +/// +/// Utility for writing and reading from different pixels formats within a single code path. +/// +API_STRUCT(NoDefault) struct FLAXENGINE_API PixelFormatSampler +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(PixelFormatSampler); + typedef Float4 (*ReadPixel)(const void* data); + typedef void (*WritePixel)(void* data, const Float4& value); + +public: + // Element format. + PixelFormat Format; + // Element size in bytes. + int32 PixelSize; + // Read data function. + ReadPixel Read; + // Write data function. + WritePixel Write; + +public: + /// + /// Stores the color into the specified texture data (uses no interpolation). + /// + /// The data pointer for the texture slice (1D or 2D image). + /// The X texture coordinates (normalized to range 0-width). + /// The Y texture coordinates (normalized to range 0-height). + /// The row pitch (in bytes). The offset between each image rows. + /// The color to store (linear). + void Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const; + + /// + /// Samples the specified linear data (uses no interpolation). + /// + /// The data pointer for the data slice (linear buffer or 1D image). + /// Index of the element. + /// The sampled value. + Float4 Sample(const void* data, int32 x) const; + + /// + /// Samples the specified texture data (uses no interpolation). + /// + /// The data pointer for the texture slice (1D or 2D image). + /// The texture coordinates (normalized to range 0-1). + /// The size of the input texture (in pixels). + /// The row pitch (in bytes). The offset between each image rows. + /// The sampled color (linear). + Color SamplePoint(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const; + + /// + /// Samples the specified texture data (uses no interpolation). + /// + /// The data pointer for the texture slice (1D or 2D image). + /// The X texture coordinates (normalized to range 0-width). + /// The Y texture coordinates (normalized to range 0-height). + /// The row pitch (in bytes). The offset between each image rows. + /// The sampled color (linear). + Color SamplePoint(const void* data, int32 x, int32 y, int32 rowPitch) const; + + /// + /// Samples the specified texture data (uses linear interpolation). + /// + /// The data pointer for the texture slice (1D or 2D image). + /// The texture coordinates (normalized to range 0-1). + /// The size of the input texture (in pixels). + /// The row pitch (in bytes). The offset between each image rows. + /// The sampled color (linear). + Color SampleLinear(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const; + +public: + /// + /// Tries to get a sampler tool for the specified format to read pixels. + /// + /// The format. + /// The pointer to the sampler object or null when cannot sample the given format. + static const PixelFormatSampler* Get(PixelFormat format); +}; diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index 61b8945bb..d1a78a922 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -11,6 +11,7 @@ #include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Scripting/Enums.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Threading/Threading.h" @@ -100,14 +101,14 @@ bool TextureMipData::GetPixels(Array& pixels, int32 width, int32 height default: { // Try to use texture sampler utility - auto sampler = TextureTool::GetSampler(format); + auto sampler = PixelFormatSampler::Get(format); if (sampler) { for (int32 y = 0; y < height; y++) { for (int32 x = 0; x < width; x++) { - Color c = TextureTool::SamplePoint(sampler, x, y, src, RowPitch); + Color c = sampler->SamplePoint(src, x, y, RowPitch); *(Color32*)(dst + dstRowSize * y + x * sizeof(Color32)) = Color32(c); } } @@ -149,14 +150,14 @@ bool TextureMipData::GetPixels(Array& pixels, int32 width, int32 height, default: { // Try to use texture sampler utility - auto sampler = TextureTool::GetSampler(format); + auto sampler = PixelFormatSampler::Get(format); if (sampler) { for (int32 y = 0; y < height; y++) { for (int32 x = 0; x < width; x++) { - Color c = TextureTool::SamplePoint(sampler, x, y, src, RowPitch); + Color c = sampler->SamplePoint(src, x, y, RowPitch); *(Color*)(dst + dstRowSize * y + x * sizeof(Color)) = c; } } @@ -475,7 +476,7 @@ bool TextureBase::SetPixels(const Span& pixels, int32 mipIndex, int32 a if (error) { // Try to use texture sampler utility - auto sampler = TextureTool::GetSampler(format); + auto sampler = PixelFormatSampler::Get(format); if (sampler) { for (int32 y = 0; y < height; y++) @@ -483,7 +484,7 @@ bool TextureBase::SetPixels(const Span& pixels, int32 mipIndex, int32 a for (int32 x = 0; x < width; x++) { Color c(pixels.Get()[x + y * width]); - TextureTool::Store(sampler, x, y, dst, rowPitch, c); + sampler->Store(dst, x, y, rowPitch, c); } } error = false; @@ -553,7 +554,7 @@ bool TextureBase::SetPixels(const Span& pixels, int32 mipIndex, int32 arr if (error) { // Try to use texture sampler utility - auto sampler = TextureTool::GetSampler(format); + auto sampler = PixelFormatSampler::Get(format); if (sampler) { for (int32 y = 0; y < height; y++) @@ -561,7 +562,7 @@ bool TextureBase::SetPixels(const Span& pixels, int32 mipIndex, int32 arr for (int32 x = 0; x < width; x++) { Color c(pixels.Get()[x + y * width]); - TextureTool::Store(sampler, x, y, dst, rowPitch, c); + sampler->Store(dst, x, y, rowPitch, c); } } error = false; diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index 550e26699..1818c7246 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -6,15 +6,13 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" -#include "Engine/Core/Math/Packed.h" -#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Math/Vector2.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Serialization/JsonWriter.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Scripting/Enums.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/Textures/TextureData.h" -#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Profiler/ProfilerCPU.h" #if USE_EDITOR @@ -372,326 +370,6 @@ bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidt #endif } -TextureTool::PixelFormatSampler PixelFormatSamplers[] = -{ - { - PixelFormat::R32G32B32A32_Float, - sizeof(Float4), - [](const void* ptr) - { - return Color(*(Float4*)ptr); - }, - [](const void* ptr, const Color& color) - { - *(Float4*)ptr = color.ToFloat4(); - }, - }, - { - PixelFormat::R32G32B32_Float, - sizeof(Float3), - [](const void* ptr) - { - return Color(*(Float3*)ptr, 1.0f); - }, - [](const void* ptr, const Color& color) - { - *(Float3*)ptr = color.ToFloat3(); - }, - }, - { - PixelFormat::R16G16B16A16_Float, - sizeof(Half4), - [](const void* ptr) - { - return Color(((Half4*)ptr)->ToFloat4()); - }, - [](const void* ptr, const Color& color) - { - *(Half4*)ptr = Half4(color.R, color.G, color.B, color.A); - }, - }, - { - PixelFormat::R16G16B16A16_UNorm, - sizeof(RGBA16UNorm), - [](const void* ptr) - { - return Color(((RGBA16UNorm*)ptr)->ToFloat4()); - }, - [](const void* ptr, const Color& color) - { - *(RGBA16UNorm*)ptr = RGBA16UNorm(color.R, color.G, color.B, color.A); - } - }, - { - PixelFormat::R32G32_Float, - sizeof(Float2), - [](const void* ptr) - { - return Color(((Float2*)ptr)->X, ((Float2*)ptr)->Y, 1.0f); - }, - [](const void* ptr, const Color& color) - { - *(Float2*)ptr = Float2(color.R, color.G); - }, - }, - { - PixelFormat::R8G8B8A8_UNorm, - sizeof(Color32), - [](const void* ptr) - { - return Color(*(Color32*)ptr); - }, - [](const void* ptr, const Color& color) - { - *(Color32*)ptr = Color32(color); - }, - }, - { - PixelFormat::R8G8B8A8_UNorm_sRGB, - sizeof(Color32), - [](const void* ptr) - { - return Color::SrgbToLinear(Color(*(Color32*)ptr)); - }, - [](const void* ptr, const Color& color) - { - Color srgb = Color::LinearToSrgb(color); - *(Color32*)ptr = Color32(srgb); - }, - }, - { - PixelFormat::R8G8_UNorm, - sizeof(uint16), - [](const void* ptr) - { - const uint8* rg = (const uint8*)ptr; - return Color((float)rg[0] / MAX_uint8, (float)rg[1] / MAX_uint8, 0, 1); - }, - [](const void* ptr, const Color& color) - { - uint8* rg = (uint8*)ptr; - rg[0] = (uint8)(color.R * MAX_uint8); - rg[1] = (uint8)(color.G * MAX_uint8); - }, - }, - { - PixelFormat::R16G16_Float, - sizeof(Half2), - [](const void* ptr) - { - const Float2 rg = ((Half2*)ptr)->ToFloat2(); - return Color(rg.X, rg.Y, 0, 1); - }, - [](const void* ptr, const Color& color) - { - *(Half2*)ptr = Half2(color.R, color.G); - }, - }, - { - PixelFormat::R16G16_UNorm, - sizeof(RG16UNorm), - [](const void* ptr) - { - const Float2 rg = ((RG16UNorm*)ptr)->ToFloat2(); - return Color(rg.X, rg.Y, 0, 1); - }, - [](const void* ptr, const Color& color) - { - *(RG16UNorm*)ptr = RG16UNorm(color.R, color.G); - }, - }, - { - PixelFormat::R32_Float, - sizeof(float), - [](const void* ptr) - { - return Color(*(float*)ptr, 0, 0, 1); - }, - [](const void* ptr, const Color& color) - { - *(float*)ptr = color.R; - }, - }, - { - PixelFormat::R16_Float, - sizeof(Half), - [](const void* ptr) - { - return Color(Float16Compressor::Decompress(*(Half*)ptr), 0, 0, 1); - }, - [](const void* ptr, const Color& color) - { - *(Half*)ptr = Float16Compressor::Compress(color.R); - }, - }, - { - PixelFormat::R16_UNorm, - sizeof(uint16), - [](const void* ptr) - { - return Color((float)*(uint16*)ptr / MAX_uint16, 0, 0, 1); - }, - [](const void* ptr, const Color& color) - { - *(uint16*)ptr = (uint16)(color.R * MAX_uint16); - }, - }, - { - PixelFormat::R8_UNorm, - sizeof(uint8), - [](const void* ptr) - { - return Color((float)*(byte*)ptr / MAX_uint8, 0, 0, 1); - }, - [](const void* ptr, const Color& color) - { - *(byte*)ptr = (byte)(color.R * MAX_uint8); - }, - }, - { - PixelFormat::A8_UNorm, - sizeof(uint8), - [](const void* ptr) - { - return Color(0, 0, 0, (float)*(byte*)ptr / MAX_uint8); - }, - [](const void* ptr, const Color& color) - { - *(byte*)ptr = (byte)(color.A * MAX_uint8); - }, - }, - { - PixelFormat::B8G8R8A8_UNorm, - sizeof(Color32), - [](const void* ptr) - { - const Color32 bgra = *(Color32*)ptr; - return Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A)); - }, - [](const void* ptr, const Color& color) - { - *(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), byte(color.A * MAX_uint8)); - }, - }, - { - PixelFormat::B8G8R8A8_UNorm_sRGB, - sizeof(Color32), - [](const void* ptr) - { - const Color32 bgra = *(Color32*)ptr; - return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A))); - }, - [](const void* ptr, const Color& color) - { - Color srgb = Color::LinearToSrgb(color); - *(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), byte(srgb.A * MAX_uint8)); - }, - }, - { - PixelFormat::B8G8R8X8_UNorm, - sizeof(Color32), - [](const void* ptr) - { - const Color32 bgra = *(Color32*)ptr; - return Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8)); - }, - [](const void* ptr, const Color& color) - { - *(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), MAX_uint8); - }, - }, - { - PixelFormat::B8G8R8X8_UNorm_sRGB, - sizeof(Color32), - [](const void* ptr) - { - const Color32 bgra = *(Color32*)ptr; - return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8))); - }, - [](const void* ptr, const Color& color) - { - Color srgb = Color::LinearToSrgb(color); - *(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), MAX_uint8); - }, - }, - { - PixelFormat::R11G11B10_Float, - sizeof(FloatR11G11B10), - [](const void* ptr) - { - const Float3 rgb = ((FloatR11G11B10*)ptr)->ToFloat3(); - return Color(rgb.X, rgb.Y, rgb.Z); - }, - [](const void* ptr, const Color& color) - { - *(FloatR11G11B10*)ptr = FloatR11G11B10(color.R, color.G, color.B); - }, - }, - { - PixelFormat::R10G10B10A2_UNorm, - sizeof(Float1010102), - [](const void* ptr) - { - const Float3 rgb = ((Float1010102*)ptr)->ToFloat3(); - return Color(rgb.X, rgb.Y, rgb.Z); - }, - [](const void* ptr, const Color& color) - { - *(Float1010102*)ptr = Float1010102(color.R, color.G, color.B, color.A); - }, - }, -}; - -const TextureTool::PixelFormatSampler* TextureTool::GetSampler(PixelFormat format) -{ - format = PixelFormatExtensions::MakeTypelessFloat(format); - for (auto& sampler : PixelFormatSamplers) - { - if (sampler.Format == format) - return &sampler; - } - return nullptr; -} - -void TextureTool::Store(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch, const Color& color) -{ - ASSERT_LOW_LAYER(sampler); - sampler->Store((byte*)data + rowPitch * y + sampler->PixelSize * x, color); -} - -Color TextureTool::SamplePoint(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch) -{ - ASSERT_LOW_LAYER(sampler); - - const Int2 end = size - 1; - const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y)); - - return sampler->Sample((byte*)data + rowPitch * uvFloor.Y + sampler->PixelSize * uvFloor.X); -} - -Color TextureTool::SamplePoint(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch) -{ - ASSERT_LOW_LAYER(sampler); - return sampler->Sample((byte*)data + rowPitch * y + sampler->PixelSize * x); -} - -Color TextureTool::SampleLinear(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch) -{ - ASSERT_LOW_LAYER(sampler); - - const Int2 end = size - 1; - const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y)); - const Int2 uvNext(Math::Min(uvFloor.X + 1, end.X), Math::Min(uvFloor.Y + 1, end.Y)); - const Float2 uvFraction(uv.X * size.Y - uvFloor.X, uv.Y * size.Y - uvFloor.Y); - - const Color v00 = sampler->Sample((byte*)data + rowPitch * uvFloor.Y + sampler->PixelSize * uvFloor.X); - const Color v01 = sampler->Sample((byte*)data + rowPitch * uvFloor.Y + sampler->PixelSize * uvNext.X); - const Color v10 = sampler->Sample((byte*)data + rowPitch * uvNext.Y + sampler->PixelSize * uvFloor.X); - const Color v11 = sampler->Sample((byte*)data + rowPitch * uvNext.Y + sampler->PixelSize * uvNext.X); - - return Color::Lerp(Color::Lerp(v00, v01, uvFraction.X), Color::Lerp(v10, v11, uvFraction.X), uvFraction.Y); -} - PixelFormat TextureTool::ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress) { const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0; @@ -795,7 +473,7 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type) bool TextureTool::Transform(TextureData& texture, const Function& transformation) { PROFILE_CPU(); - auto sampler = TextureTool::GetSampler(texture.Format); + auto sampler = PixelFormatSampler::Get(texture.Format); if (!sampler) return true; for (auto& slice : texture.Items) @@ -809,9 +487,9 @@ bool TextureTool::Transform(TextureData& texture, const Function& { for (int32 x = 0; x < mipWidth; x++) { - Color color = TextureTool::SamplePoint(sampler, x, y, mip.Data.Get(), mip.RowPitch); + Color color = sampler->SamplePoint(mip.Data.Get(), x, y, mip.RowPitch); transformation(color); - TextureTool::Store(sampler, x, y, mip.Data.Get(), mip.RowPitch, color); + sampler->Store(mip.Data.Get(), x, y, mip.RowPitch, color); } } } diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 13bac0dc5..cdbd9ee8f 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -176,80 +176,6 @@ public: static bool Resize(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight); public: - typedef Color (*ReadPixel)(const void*); - typedef void (*WritePixel)(const void*, const Color&); - - struct PixelFormatSampler - { - PixelFormat Format; - int32 PixelSize; - ReadPixel Sample; - WritePixel Store; - }; - - /// - /// Determines whether this tool can sample the specified format to read texture samples and returns the sampler object. - /// - /// The format. - /// The pointer to the sampler object or null if cannot sample the given format. - static const PixelFormatSampler* GetSampler(PixelFormat format); - - /// - /// Stores the color into the specified texture data (uses no interpolation). - /// - /// - /// Use GetSampler for the texture sampler. - /// - /// The texture data sampler. - /// The X texture coordinates (normalized to range 0-width). - /// The Y texture coordinates (normalized to range 0-height). - /// The data pointer for the texture slice (1D or 2D image). - /// The row pitch (in bytes). The offset between each image rows. - /// The color to store (linear). - static void Store(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch, const Color& color); - - /// - /// Samples the specified texture data (uses no interpolation). - /// - /// - /// Use GetSampler for the texture sampler. - /// - /// The texture data sampler. - /// The texture coordinates (normalized to range 0-1). - /// The data pointer for the texture slice (1D or 2D image). - /// The size of the input texture (in pixels). - /// The row pitch (in bytes). The offset between each image rows. - /// The sampled color (linear). - static Color SamplePoint(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch); - - /// - /// Samples the specified texture data (uses no interpolation). - /// - /// - /// Use GetSampler for the texture sampler. - /// - /// The texture data sampler. - /// The X texture coordinates (normalized to range 0-width). - /// The Y texture coordinates (normalized to range 0-height). - /// The data pointer for the texture slice (1D or 2D image). - /// The row pitch (in bytes). The offset between each image rows. - /// The sampled color (linear). - static Color SamplePoint(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch); - - /// - /// Samples the specified texture data (uses linear interpolation). - /// - /// - /// Use GetSampler for the texture sampler. - /// - /// The texture data sampler. - /// The texture coordinates (normalized to range 0-1). - /// The data pointer for the texture slice (1D or 2D image). - /// The size of the input texture (in pixels). - /// The row pitch (in bytes). The offset between each image rows. - /// The sampled color (linear). - static Color SampleLinear(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch); - static PixelFormat ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress); private: From db4d7d2a0590fa9a7af63e542dd28e1305bc78ec Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 6 Jan 2025 22:47:19 +0100 Subject: [PATCH 109/215] **Refactor meshes format to support custom vertex layouts and new flexible api to access mesh data** #3044 #2667 --- .../SceneGraph/Actors/AnimatedModelNode.cs | 65 +- .../SceneGraph/Actors/StaticModelNode.cs | 9 +- Source/Editor/Tools/VertexPainting.cs | 27 +- .../Editor/Viewport/Previews/ModelPreview.cs | 43 +- .../Editor/Windows/Assets/ModelBaseWindow.cs | 181 ++++ Source/Editor/Windows/Assets/ModelWindow.cs | 223 +---- .../Windows/Assets/SkinnedModelWindow.cs | 271 +----- Source/Engine/CSG/CSGBuilder.cpp | 2 +- Source/Engine/CSG/CSGData.cpp | 4 +- Source/Engine/CSG/CSGData.h | 6 +- Source/Engine/CSG/CSGMesh.Triangulate.cpp | 7 +- Source/Engine/CSG/CSGMesh.h | 2 +- Source/Engine/CSG/Types.h | 10 + Source/Engine/Content/Assets/Animation.cpp | 60 ++ Source/Engine/Content/Assets/Animation.h | 6 + Source/Engine/Content/Assets/Model.cpp | 604 +++++-------- Source/Engine/Content/Assets/Model.h | 27 +- Source/Engine/Content/Assets/ModelBase.cpp | 729 ++++++++++++++-- Source/Engine/Content/Assets/ModelBase.h | 57 +- Source/Engine/Content/Assets/SkinnedModel.cpp | 796 ++++++++---------- Source/Engine/Content/Assets/SkinnedModel.h | 27 +- .../Content/Upgraders/ModelAssetUpgrader.h | 141 +++- .../Upgraders/SkinnedModelAssetUpgrader.h | 215 ++++- .../Engine/ContentExporters/ExportModel.cpp | 207 +---- .../Engine/ContentImporters/ImportModel.cpp | 57 +- Source/Engine/ContentImporters/ImportModel.h | 8 +- Source/Engine/Core/Math/BoundingBox.cs | 22 +- Source/Engine/Core/Math/Float4.cs | 10 + Source/Engine/Engine/Base/GameBase.cpp | 2 +- .../Engine/Graphics/GPUBufferDescription.cs | 6 + Source/Engine/Graphics/Models/BlendShape.h | 7 + .../Engine/Graphics/Models/CollisionProxy.h | 14 +- Source/Engine/Graphics/Models/Config.h | 5 +- Source/Engine/Graphics/Models/Mesh.cpp | 410 ++++----- Source/Engine/Graphics/Models/Mesh.cs | 51 +- Source/Engine/Graphics/Models/Mesh.h | 19 +- Source/Engine/Graphics/Models/MeshAccessor.cs | 728 ++++++++++++++++ Source/Engine/Graphics/Models/MeshAccessor.h | 185 ++++ Source/Engine/Graphics/Models/MeshBase.cpp | 515 ++++++++++- Source/Engine/Graphics/Models/MeshBase.cs | 79 ++ Source/Engine/Graphics/Models/MeshBase.h | 49 +- .../Graphics/Models/MeshDeformation.cpp | 6 + .../Engine/Graphics/Models/MeshDeformation.h | 2 + Source/Engine/Graphics/Models/ModelData.cpp | 535 +----------- Source/Engine/Graphics/Models/ModelData.h | 95 +-- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 459 +++++----- Source/Engine/Graphics/Models/SkinnedMesh.cs | 65 +- Source/Engine/Graphics/Models/SkinnedMesh.h | 64 +- Source/Engine/Graphics/Models/Types.h | 28 +- .../Engine/Graphics/PixelFormatExtensions.cpp | 4 +- Source/Engine/Graphics/RenderTools.cpp | 30 +- Source/Engine/Graphics/RenderTools.h | 10 +- .../Graphics/Shaders/GPUVertexLayout.cpp | 2 - .../DirectX/DX11/GPUShaderDX11.cpp | 4 +- Source/Engine/Level/Actors/AnimatedModel.cpp | 72 +- Source/Engine/Level/Actors/AnimatedModel.h | 2 +- .../Engine/Level/Actors/ModelInstanceActor.h | 5 +- Source/Engine/Level/Actors/StaticModel.cpp | 13 +- Source/Engine/Level/Actors/StaticModel.h | 2 +- Source/Engine/Particles/ParticleSystem.cpp | 2 + Source/Engine/Physics/Actors/Cloth.cpp | 124 ++- .../Tools/TextureTool/TextureTool.stb.cpp | 21 +- Source/Engine/UI/TextRender.cpp | 66 +- Source/Engine/UI/TextRender.h | 4 +- Source/Engine/Utilities/MeshDataCache.cs | 33 +- 65 files changed, 4428 insertions(+), 3106 deletions(-) create mode 100644 Source/Engine/Graphics/Models/MeshAccessor.cs create mode 100644 Source/Engine/Graphics/Models/MeshAccessor.h diff --git a/Source/Editor/SceneGraph/Actors/AnimatedModelNode.cs b/Source/Editor/SceneGraph/Actors/AnimatedModelNode.cs index 80063d038..e9660080d 100644 --- a/Source/Editor/SceneGraph/Actors/AnimatedModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/AnimatedModelNode.cs @@ -80,19 +80,23 @@ namespace FlaxEditor.SceneGraph.Actors // Get vertex data for each mesh var meshes = model.LODs[0].Meshes; - var meshesData = new SkinnedMesh.Vertex0[meshes.Length][]; - var bonesVertices = new List[bones.Length]; + var bonesVertices = new List[bones.Length]; var indicesLimit = new Int4(bones.Length - 1); for (int i = 0; i < meshes.Length; i++) { - meshesData[i] = meshes[i].DownloadVertexBuffer0(); - - var meshData = meshes[i].DownloadVertexBuffer0(); - for (int j = 0; j < meshData.Length; j++) + var assessor = new MeshAccessor(); + if (assessor.LoadMesh(meshes[i])) + return; + var positionStream = assessor.Position(); + var blendIndicesStream = assessor.BlendIndices(); + var blendWeightsStream = assessor.BlendWeights(); + if (!positionStream.IsValid || !blendIndicesStream.IsValid || !blendWeightsStream.IsValid) + continue; + var count = positionStream.Count; + for (int j = 0; j < count; j++) { - ref var v = ref meshData[j]; - var weights = (Float4)v.BlendWeights; - var indices = Int4.Min((Int4)v.BlendIndices, indicesLimit); + var weights = blendWeightsStream.GetFloat4(j); + var indices = Int4.Min((Int4)blendIndicesStream.GetFloat4(j), indicesLimit); // Find the bone with the highest influence on the vertex var maxWeightIndex = 0; @@ -104,17 +108,18 @@ namespace FlaxEditor.SceneGraph.Actors var maxWeightBone = indices[maxWeightIndex]; // Skin vertex position with the current pose - Float3.Transform(ref v.Position, ref skinningMatrices[indices[0]], out Float3 pos0); - Float3.Transform(ref v.Position, ref skinningMatrices[indices[1]], out Float3 pos1); - Float3.Transform(ref v.Position, ref skinningMatrices[indices[2]], out Float3 pos2); - Float3.Transform(ref v.Position, ref skinningMatrices[indices[3]], out Float3 pos3); - v.Position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3]; + var position = positionStream.GetFloat3(j); + Float3.Transform(ref position, ref skinningMatrices[indices[0]], out Float3 pos0); + Float3.Transform(ref position, ref skinningMatrices[indices[1]], out Float3 pos1); + Float3.Transform(ref position, ref skinningMatrices[indices[2]], out Float3 pos2); + Float3.Transform(ref position, ref skinningMatrices[indices[3]], out Float3 pos3); + position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3]; // Add vertex to the bone list ref var boneVertices = ref bonesVertices[maxWeightBone]; if (boneVertices == null) - boneVertices = new List(); - boneVertices.Add(v); + boneVertices = new List(); + boneVertices.Add(position); } } @@ -128,10 +133,10 @@ namespace FlaxEditor.SceneGraph.Actors continue; // Skip not used bones // Compute bounds of the vertices using this bone (in local space of the actor) - Float3 boneBoundsMin = boneVertices[0].Position, boneBoundsMax = boneVertices[0].Position; + Float3 boneBoundsMin = boneVertices[0], boneBoundsMax = boneVertices[0]; for (int i = 1; i < boneVertices.Count; i++) { - var pos = boneVertices[i].Position; + var pos = boneVertices[i]; boneBoundsMin = Float3.Min(boneBoundsMin, pos); boneBoundsMax = Float3.Max(boneBoundsMax, pos); } @@ -165,10 +170,10 @@ namespace FlaxEditor.SceneGraph.Actors var boneBounds = BoundingBox.Zero; if (boneVertices != null) { - boneBounds = new BoundingBox(boneVertices[0].Position, boneVertices[0].Position); + boneBounds = new BoundingBox(boneVertices[0], boneVertices[0]); for (int i = 1; i < boneVertices.Count; i++) { - var pos = boneVertices[i].Position; + var pos = boneVertices[i]; boneBounds.Minimum = Float3.Min(boneBounds.Minimum, pos); boneBounds.Minimum = Float3.Max(boneBounds.Maximum, pos); } @@ -263,7 +268,7 @@ namespace FlaxEditor.SceneGraph.Actors var boneLocalBounds = BoundingBox.Zero; for (int i = 0; i < boneVertices.Count; i++) { - var pos = boneTransform.WorldToLocal(boneVertices[i].Position); + var pos = boneTransform.WorldToLocal(boneVertices[i]); Vector3.Min(ref boneLocalBounds.Minimum, ref pos, out boneLocalBounds.Minimum); Vector3.Max(ref boneLocalBounds.Maximum, ref pos, out boneLocalBounds.Maximum); } @@ -360,20 +365,20 @@ namespace FlaxEditor.SceneGraph.Actors Editor.Instance.Scene.MarkSceneEdited(actor.Scene); } - private static unsafe Matrix CalculateCovarianceMatrix(List vertices) + private static unsafe Matrix CalculateCovarianceMatrix(List vertices) { // [Reference: https://en.wikipedia.org/wiki/Covariance_matrix] // Calculate average point var avg = Float3.Zero; for (int i = 0; i < vertices.Count; i++) - avg += vertices[i].Position; + avg += vertices[i]; avg /= vertices.Count; // Calculate distance to average for every point var errors = new Float3[vertices.Count]; for (int i = 0; i < vertices.Count; i++) - errors[i] = vertices[i].Position - avg; + errors[i] = vertices[i] - avg; var covariance = Matrix.Identity; var cj = stackalloc float[3]; @@ -393,15 +398,9 @@ namespace FlaxEditor.SceneGraph.Actors var row = new Float4(cj[0], cj[1], cj[2], 0.0f); switch (j) { - case 0: - covariance.Row1 = row; - break; - case 1: - covariance.Row2 = row; - break; - case 2: - covariance.Row3 = row; - break; + case 0: covariance.Row1 = row; break; + case 1: covariance.Row2 = row; break; + case 2: covariance.Row3 = row; break; } } return covariance; diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 885cbd5b5..ee208f448 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors [HideInEditor] public sealed class StaticModelNode : ActorNode { - private Dictionary _vertices; + private Dictionary _vertices; /// public StaticModelNode(Actor actor) @@ -53,14 +53,17 @@ namespace FlaxEditor.SceneGraph.Actors var key = FlaxEngine.Object.GetUnmanagedPtr(mesh); if (!_vertices.TryGetValue(key, out var verts)) { - verts = mesh.DownloadVertexBuffer(); + var accessor = new MeshAccessor(); + if (accessor.LoadMesh(mesh)) + continue; + verts = accessor.Positions; if (verts == null) continue; _vertices.Add(key, verts); } for (int i = 0; i < verts.Length; i++) { - var v = verts[i].Position; + ref var v = ref verts[i]; var distance = Float3.DistanceSquared(ref pointLocal, ref v); if (distance <= minDistance) { diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index b6cce7b9c..75d42ba93 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -414,10 +414,13 @@ namespace FlaxEditor.Tools for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++) { var meshData = lodData[meshIndex]; - for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++) + var colors = meshData.VertexAccessor.Colors; + if (colors == null) + continue; + var vertexCount = colors.Length; + for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) { - ref var v = ref meshData.VertexBuffer[vertexIndex]; - _selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, v.Color); + _selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, colors[vertexIndex]); } } } @@ -430,10 +433,13 @@ namespace FlaxEditor.Tools for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++) { var meshData = lodData[meshIndex]; - for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++) + var positionStream = meshData.VertexAccessor.Position(); + if (!positionStream.IsValid) + continue; + var vertexCount = positionStream.Count; + for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) { - ref var v = ref meshData.VertexBuffer[vertexIndex]; - var pos = instanceTransform.LocalToWorld(v.Position); + var pos = instanceTransform.LocalToWorld(positionStream.GetFloat3(vertexIndex)); var dst = Vector3.Distance(ref pos, ref brushSphere.Center); if (dst > brushSphere.Radius) continue; @@ -590,12 +596,13 @@ namespace FlaxEditor.Tools for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++) { var meshData = lodData[meshIndex]; - if (meshData.VertexBuffer == null) + var positionStream = meshData.VertexAccessor.Position(); + if (!positionStream.IsValid) continue; - for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++) + var vertexCount = positionStream.Count; + for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) { - ref var v = ref meshData.VertexBuffer[vertexIndex]; - var pos = instanceTransform.LocalToWorld(v.Position); + var pos = instanceTransform.LocalToWorld(positionStream.GetFloat3(vertexIndex)); if (brushSphere.Contains(ref pos) == ContainmentType.Disjoint) continue; Matrix transform = modelScaleMatrix * Matrix.Translation(pos - viewOrigin); diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index b75c9863b..b8687d848 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -296,10 +296,17 @@ namespace FlaxEditor.Viewport.Previews for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) { var meshData = lod[meshIndex]; - for (int i = 0; i < meshData.VertexBuffer.Length; i++) + var positionStream = meshData.VertexAccessor.Position(); + var normalStream = meshData.VertexAccessor.Normal(); + if (positionStream.IsValid && normalStream.IsValid) { - ref var v = ref meshData.VertexBuffer[i]; - DebugDraw.DrawLine(v.Position, v.Position + v.Normal * 4.0f, Color.Blue); + var count = positionStream.Count; + for (int i = 0; i < count; i++) + { + var position = positionStream.GetFloat3(i); + var normal = normalStream.GetFloat3(i) * 2.0f - 1.0f; + DebugDraw.DrawLine(position, position + normal * 4.0f, Color.Blue); + } } } } @@ -313,10 +320,17 @@ namespace FlaxEditor.Viewport.Previews for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) { var meshData = lod[meshIndex]; - for (int i = 0; i < meshData.VertexBuffer.Length; i++) + var positionStream = meshData.VertexAccessor.Position(); + var tangentStream = meshData.VertexAccessor.Tangent(); + if (positionStream.IsValid && tangentStream.IsValid) { - ref var v = ref meshData.VertexBuffer[i]; - DebugDraw.DrawLine(v.Position, v.Position + v.Tangent * 4.0f, Color.Red); + var count = positionStream.Count; + for (int i = 0; i < count; i++) + { + var position = positionStream.GetFloat3(i); + var tangent = tangentStream.GetFloat3(i) * 2.0f - 1.0f; + DebugDraw.DrawLine(position, position + tangent * 4.0f, Color.Red); + } } } } @@ -330,10 +344,21 @@ namespace FlaxEditor.Viewport.Previews for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) { var meshData = lod[meshIndex]; - for (int i = 0; i < meshData.VertexBuffer.Length; i++) + var positionStream = meshData.VertexAccessor.Position(); + var normalStream = meshData.VertexAccessor.Normal(); + var tangentStream = meshData.VertexAccessor.Tangent(); + if (positionStream.IsValid && normalStream.IsValid && tangentStream.IsValid) { - ref var v = ref meshData.VertexBuffer[i]; - DebugDraw.DrawLine(v.Position, v.Position + v.Bitangent * 4.0f, Color.Green); + var count = positionStream.Count; + for (int i = 0; i < count; i++) + { + var position = positionStream.GetFloat3(i); + var normal = normalStream.GetFloat3(i) * 2.0f - 1.0f; + var tangent = tangentStream.GetFloat4(i); + var bitangentSign = tangent.W > Mathf.Epsilon ? -1.0f : +1.0f; + var bitangent = Float3.Cross(normal, new Float3(tangent) * 2.0f - 1.0f) * bitangentSign; + DebugDraw.DrawLine(position, position + bitangent * 4.0f, Color.Green); + } } } } diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index ee355c9de..ead4b7bd9 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -8,6 +8,7 @@ using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Utilities; #pragma warning disable 1591 @@ -92,6 +93,170 @@ namespace FlaxEditor.Windows.Assets } } + protected sealed class UVsLayoutPreviewControl : RenderToTextureControl + { + private int _channel = -1; + private int _lod, _mesh = -1; + private int _highlightIndex = -1; + private int _isolateIndex = -1; + public ModelBaseWindow Window; + + public UVsLayoutPreviewControl() + { + Offsets = new Margin(4); + AutomaticInvalidate = false; + } + + public int Channel + { + set + { + if (_channel == value) + return; + _channel = value; + Visible = _channel != -1; + if (Visible) + Invalidate(); + } + } + + public int LOD + { + set + { + if (_lod != value) + { + _lod = value; + Invalidate(); + } + } + } + + public int Mesh + { + set + { + if (_mesh != value) + { + _mesh = value; + Invalidate(); + } + } + } + + public int HighlightIndex + { + set + { + if (_highlightIndex != value) + { + _highlightIndex = value; + Invalidate(); + } + } + } + + public int IsolateIndex + { + set + { + if (_isolateIndex != value) + { + _isolateIndex = value; + Invalidate(); + } + } + } + + private void DrawMeshUVs(int meshIndex, ref MeshDataCache.MeshData meshData) + { + var uvScale = Size; + if (meshData.IndexBuffer == null || meshData.VertexAccessor == null) + return; + var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White; + var texCoordStream = meshData.VertexAccessor.TexCoord(_channel); + if (!texCoordStream.IsValid) + return; + for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) + { + // Cache triangle indices + uint i0 = meshData.IndexBuffer[i + 0]; + uint i1 = meshData.IndexBuffer[i + 1]; + uint i2 = meshData.IndexBuffer[i + 2]; + + // Cache triangle uvs positions and transform positions to output target + Float2 uv0 = texCoordStream.GetFloat2((int)i0) * uvScale; + Float2 uv1 = texCoordStream.GetFloat2((int)i1) * uvScale; + Float2 uv2 = texCoordStream.GetFloat2((int)i2) * uvScale; + + // Don't draw too small triangles + float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2); + if (area > 10.0f) + { + // Draw triangle + Render2D.DrawLine(uv0, uv1, linesColor); + Render2D.DrawLine(uv1, uv2, linesColor); + Render2D.DrawLine(uv2, uv0, linesColor); + } + } + } + + /// + public override void DrawSelf() + { + base.DrawSelf(); + + var size = Size; + if (_channel < 0 || size.MaxValue < 5.0f) + return; + if (Window._meshData == null) + Window._meshData = new MeshDataCache(); + if (!Window._meshData.RequestMeshData(Window._asset)) + { + Invalidate(); + Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); + return; + } + + Render2D.PushClip(new Rectangle(Float2.Zero, size)); + + var meshDatas = Window._meshData.MeshDatas; + var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1); + var lod = meshDatas[lodIndex]; + var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1); + if (mesh == -1) + { + for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) + { + if (_isolateIndex != -1 && _isolateIndex != meshIndex) + continue; + DrawMeshUVs(meshIndex, ref lod[meshIndex]); + } + } + else + { + DrawMeshUVs(mesh, ref lod[mesh]); + } + + Render2D.PopClip(); + } + + protected override void OnSizeChanged() + { + Height = Width; + + base.OnSizeChanged(); + } + + protected override void OnVisibleChanged() + { + base.OnVisibleChanged(); + + Parent.PerformLayout(); + Height = Width; + } + } + protected readonly SplitPanel _split; protected readonly Tabs _tabs; protected readonly ToolStripButton _saveButton; @@ -101,6 +266,8 @@ namespace FlaxEditor.Windows.Assets protected int _isolateIndex = -1; protected int _highlightIndex = -1; + protected MeshDataCache _meshData; + /// protected ModelBaseWindow(Editor editor, AssetItem item) : base(editor, item) @@ -142,6 +309,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { + _meshData?.WaitForMeshDataRequestEnd(); foreach (var child in _tabs.Children) { if (child is Tab tab && tab.Proxy.Window != null) @@ -170,6 +338,9 @@ namespace FlaxEditor.Windows.Assets /// public override void OnItemReimported(ContentItem item) { + // Discard any old mesh data cache + _meshData?.Dispose(); + // Refresh the properties (will get new data in OnAssetLoaded) foreach (var child in _tabs.Children) { @@ -185,6 +356,16 @@ namespace FlaxEditor.Windows.Assets base.OnItemReimported(item); } + /// + public override void OnDestroy() + { + // Free mesh memory + _meshData?.Dispose(); + _meshData = null; + + base.OnDestroy(); + } + /// public override bool UseLayoutData => true; diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 55446dc0a..16826b508 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -15,7 +15,6 @@ using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Json; using FlaxEngine.Tools; -using FlaxEngine.Utilities; using Object = FlaxEngine.Object; namespace FlaxEditor.Windows.Assets @@ -495,7 +494,7 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); _uvsPreview = layout.Custom().CustomControl; - _uvsPreview.Proxy = proxy; + _uvsPreview.Window = proxy.Window; } /// @@ -505,11 +504,17 @@ namespace FlaxEditor.Windows.Assets if (_uvsPreview != null) { - _uvsPreview.Channel = _uvsPreview.Proxy._uvChannel; - _uvsPreview.LOD = _uvsPreview.Proxy.LOD; - _uvsPreview.Mesh = _uvsPreview.Proxy.Mesh; - _uvsPreview.HighlightIndex = _uvsPreview.Proxy.Window._highlightIndex; - _uvsPreview.IsolateIndex = _uvsPreview.Proxy.Window._isolateIndex; + var proxy = (UVsPropertiesProxy)Values[0]; + switch (proxy._uvChannel) + { + case UVChannel.TexCoord: _uvsPreview.Channel = 0; break; + case UVChannel.LightmapUVs: _uvsPreview.Channel = 1; break; + default: _uvsPreview.Channel = -1; break; + } + _uvsPreview.LOD = proxy.LOD; + _uvsPreview.Mesh = proxy.Mesh; + _uvsPreview.HighlightIndex = proxy.Window._highlightIndex; + _uvsPreview.IsolateIndex = proxy.Window._isolateIndex; } } @@ -520,196 +525,6 @@ namespace FlaxEditor.Windows.Assets base.Deinitialize(); } } - - private sealed class UVsLayoutPreviewControl : RenderToTextureControl - { - private UVChannel _channel; - private int _lod, _mesh = -1; - private int _highlightIndex = -1; - private int _isolateIndex = -1; - public UVsPropertiesProxy Proxy; - - public UVsLayoutPreviewControl() - { - Offsets = new Margin(4); - AutomaticInvalidate = false; - } - - public UVChannel Channel - { - set - { - if (_channel == value) - return; - _channel = value; - Visible = _channel != UVChannel.None; - if (Visible) - Invalidate(); - } - } - - public int LOD - { - set - { - if (_lod != value) - { - _lod = value; - Invalidate(); - } - } - } - - public int Mesh - { - set - { - if (_mesh != value) - { - _mesh = value; - Invalidate(); - } - } - } - - public int HighlightIndex - { - set - { - if (_highlightIndex != value) - { - _highlightIndex = value; - Invalidate(); - } - } - } - - public int IsolateIndex - { - set - { - if (_isolateIndex != value) - { - _isolateIndex = value; - Invalidate(); - } - } - } - - private void DrawMeshUVs(int meshIndex, MeshDataCache.MeshData meshData) - { - var uvScale = Size; - if (meshData.IndexBuffer == null || meshData.VertexBuffer == null) - return; - var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White; - switch (_channel) - { - case UVChannel.TexCoord: - for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) - { - // Cache triangle indices - uint i0 = meshData.IndexBuffer[i + 0]; - uint i1 = meshData.IndexBuffer[i + 1]; - uint i2 = meshData.IndexBuffer[i + 2]; - - // Cache triangle uvs positions and transform positions to output target - Float2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale; - Float2 uv1 = meshData.VertexBuffer[i1].TexCoord * uvScale; - Float2 uv2 = meshData.VertexBuffer[i2].TexCoord * uvScale; - - // Don't draw too small triangles - float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2); - if (area > 10.0f) - { - // Draw triangle - Render2D.DrawLine(uv0, uv1, linesColor); - Render2D.DrawLine(uv1, uv2, linesColor); - Render2D.DrawLine(uv2, uv0, linesColor); - } - } - break; - case UVChannel.LightmapUVs: - for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) - { - // Cache triangle indices - uint i0 = meshData.IndexBuffer[i + 0]; - uint i1 = meshData.IndexBuffer[i + 1]; - uint i2 = meshData.IndexBuffer[i + 2]; - - // Cache triangle uvs positions and transform positions to output target - Float2 uv0 = meshData.VertexBuffer[i0].LightmapUVs * uvScale; - Float2 uv1 = meshData.VertexBuffer[i1].LightmapUVs * uvScale; - Float2 uv2 = meshData.VertexBuffer[i2].LightmapUVs * uvScale; - - // Don't draw too small triangles - float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2); - if (area > 3.0f) - { - // Draw triangle - Render2D.DrawLine(uv0, uv1, linesColor); - Render2D.DrawLine(uv1, uv2, linesColor); - Render2D.DrawLine(uv2, uv0, linesColor); - } - } - break; - } - } - - /// - public override void DrawSelf() - { - base.DrawSelf(); - - var size = Size; - if (_channel == UVChannel.None || size.MaxValue < 5.0f) - return; - if (Proxy.Window._meshData == null) - Proxy.Window._meshData = new MeshDataCache(); - if (!Proxy.Window._meshData.RequestMeshData(Proxy.Window._asset)) - { - Invalidate(); - Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); - return; - } - - Render2D.PushClip(new Rectangle(Float2.Zero, size)); - - var meshDatas = Proxy.Window._meshData.MeshDatas; - var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1); - var lod = meshDatas[lodIndex]; - var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1); - if (mesh == -1) - { - for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) - { - if (_isolateIndex != -1 && _isolateIndex != meshIndex) - continue; - DrawMeshUVs(meshIndex, lod[meshIndex]); - } - } - else - { - DrawMeshUVs(mesh, lod[mesh]); - } - - Render2D.PopClip(); - } - - protected override void OnSizeChanged() - { - Height = Width; - - base.OnSizeChanged(); - } - - protected override void OnVisibleChanged() - { - base.OnVisibleChanged(); - - Parent.PerformLayout(); - Height = Width; - } - } } [CustomEditor(typeof(ProxyEditor))] @@ -802,7 +617,6 @@ namespace FlaxEditor.Windows.Assets private readonly ModelPreview _preview; private StaticModel _highlightActor; - private MeshDataCache _meshData; private ModelImportSettings _importSettings = new ModelImportSettings(); private ModelSdfOptions _sdfOptions; private ToolStripButton _showCurrentLODButton; @@ -941,7 +755,6 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { - _meshData?.WaitForMeshDataRequestEnd(); _preview.Model = null; _highlightActor.Model = null; @@ -970,21 +783,9 @@ namespace FlaxEditor.Windows.Assets base.OnAssetLoaded(); } - /// - public override void OnItemReimported(ContentItem item) - { - // Discard any old mesh data cache - _meshData?.Dispose(); - - base.OnItemReimported(item); - } - /// public override void OnDestroy() { - _meshData?.Dispose(); - _meshData = null; - base.OnDestroy(); Object.Destroy(ref _highlightActor); diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index f2ea2ac87..d26a2153f 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -5,8 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using FlaxEditor.Content; using FlaxEditor.Content.Import; using FlaxEditor.CustomEditors; @@ -182,8 +180,6 @@ namespace FlaxEditor.Windows.Assets proxy._highlightCheckBoxes.Clear(); var lods = proxy.Asset.LODs; var loadedLODs = proxy.Asset.LoadedLODs; - var nodes = proxy.Asset.Nodes; - var bones = proxy.Asset.Bones; // General properties { @@ -286,8 +282,6 @@ namespace FlaxEditor.Windows.Assets var proxy = (SkeletonPropertiesProxy)Values[0]; if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) return; - var lods = proxy.Asset.LODs; - var loadedLODs = proxy.Asset.LoadedLODs; var nodes = proxy.Asset.Nodes; var bones = proxy.Asset.Bones; @@ -523,7 +517,7 @@ namespace FlaxEditor.Windows.Assets if (_uvChannel == value) return; _uvChannel = value; - Window.RequestMeshData(); + Window._meshData?.RequestMeshData(Window._asset); } } @@ -558,7 +552,7 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); _uvsPreview = layout.Custom().CustomControl; - _uvsPreview.Proxy = proxy; + _uvsPreview.Window = proxy.Window; } /// @@ -568,11 +562,12 @@ namespace FlaxEditor.Windows.Assets if (_uvsPreview != null) { - _uvsPreview.Channel = _uvsPreview.Proxy._uvChannel; - _uvsPreview.LOD = _uvsPreview.Proxy.LOD; - _uvsPreview.Mesh = _uvsPreview.Proxy.Mesh; - _uvsPreview.HighlightIndex = _uvsPreview.Proxy.Window._highlightIndex; - _uvsPreview.IsolateIndex = _uvsPreview.Proxy.Window._isolateIndex; + var proxy = (UVsPropertiesProxy)Values[0]; + _uvsPreview.Channel = proxy._uvChannel == UVChannel.TexCoord ? 0 : -1; + _uvsPreview.LOD = proxy.LOD; + _uvsPreview.Mesh = proxy.Mesh; + _uvsPreview.HighlightIndex = proxy.Window._highlightIndex; + _uvsPreview.IsolateIndex = proxy.Window._isolateIndex; } } @@ -583,171 +578,6 @@ namespace FlaxEditor.Windows.Assets base.Deinitialize(); } } - - private sealed class UVsLayoutPreviewControl : RenderToTextureControl - { - private UVChannel _channel; - private int _lod, _mesh = -1; - private int _highlightIndex = -1; - private int _isolateIndex = -1; - public UVsPropertiesProxy Proxy; - - public UVsLayoutPreviewControl() - { - Offsets = new Margin(4); - AutomaticInvalidate = false; - } - - public UVChannel Channel - { - set - { - if (_channel == value) - return; - _channel = value; - Visible = _channel != UVChannel.None; - if (Visible) - Invalidate(); - } - } - - public int LOD - { - set - { - if (_lod != value) - { - _lod = value; - Invalidate(); - } - } - } - - public int Mesh - { - set - { - if (_mesh != value) - { - _mesh = value; - Invalidate(); - } - } - } - - public int HighlightIndex - { - set - { - if (_highlightIndex != value) - { - _highlightIndex = value; - Invalidate(); - } - } - } - - public int IsolateIndex - { - set - { - if (_isolateIndex != value) - { - _isolateIndex = value; - Invalidate(); - } - } - } - - private void DrawMeshUVs(int meshIndex, MeshData meshData) - { - var uvScale = Size; - var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White; - switch (_channel) - { - case UVChannel.TexCoord: - for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) - { - // Cache triangle indices - uint i0 = meshData.IndexBuffer[i + 0]; - uint i1 = meshData.IndexBuffer[i + 1]; - uint i2 = meshData.IndexBuffer[i + 2]; - - // Cache triangle uvs positions and transform positions to output target - Float2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale; - Float2 uv1 = meshData.VertexBuffer[i1].TexCoord * uvScale; - Float2 uv2 = meshData.VertexBuffer[i2].TexCoord * uvScale; - - // Don't draw too small triangles - float area = Float2.TriangleArea(ref uv0, ref uv1, ref uv2); - if (area > 3.0f) - { - // Draw triangle - Render2D.DrawLine(uv0, uv1, linesColor); - Render2D.DrawLine(uv1, uv2, linesColor); - Render2D.DrawLine(uv2, uv0, linesColor); - } - } - break; - } - } - - /// - protected override void DrawChildren() - { - base.DrawChildren(); - - var size = Size; - if (_channel == UVChannel.None || size.MaxValue < 5.0f) - return; - if (!Proxy.Window.RequestMeshData()) - { - Invalidate(); - Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); - return; - } - - Render2D.PushClip(new Rectangle(Float2.Zero, size)); - - var meshDatas = Proxy.Window._meshDatas; - if (meshDatas.Length != 0) - { - var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1); - var lod = meshDatas[lodIndex]; - var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1); - if (mesh == -1) - { - for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++) - { - if (_isolateIndex != -1 && _isolateIndex != meshIndex) - continue; - DrawMeshUVs(meshIndex, lod[meshIndex]); - } - } - else - { - DrawMeshUVs(mesh, lod[mesh]); - } - } - - Render2D.PopClip(); - } - - protected override void OnSizeChanged() - { - Height = Width; - - base.OnSizeChanged(); - } - - protected override void OnVisibleChanged() - { - base.OnVisibleChanged(); - - Parent.PerformLayout(); - Height = Width; - } - } } [CustomEditor(typeof(ProxyEditor))] @@ -1159,21 +989,11 @@ namespace FlaxEditor.Windows.Assets } } - private struct MeshData - { - public uint[] IndexBuffer; - public SkinnedMesh.Vertex[] VertexBuffer; - } - private Preview _preview; private AnimatedModel _highlightActor; private ToolStripButton _showNodesButton; private ToolStripButton _showCurrentLODButton; - private MeshData[][] _meshDatas; - private bool _meshDatasInProgress; - private bool _meshDatasCancel; - /// public SkinnedModelWindow(Editor editor, AssetItem item) : base(editor, item) @@ -1256,67 +1076,6 @@ namespace FlaxEditor.Windows.Assets } } - private bool RequestMeshData() - { - if (_meshDatasInProgress) - return false; - if (_meshDatas != null) - return true; - _meshDatasInProgress = true; - _meshDatasCancel = false; - Task.Run(new Action(DownloadMeshData)); - return false; - } - - private void WaitForMeshDataRequestEnd() - { - if (_meshDatasInProgress) - { - _meshDatasCancel = true; - for (int i = 0; i < 500 && _meshDatasInProgress; i++) - Thread.Sleep(10); - } - } - - private void DownloadMeshData() - { - try - { - if (!_asset) - { - _meshDatasInProgress = false; - return; - } - var lods = _asset.LODs; - _meshDatas = new MeshData[lods.Length][]; - - for (int lodIndex = 0; lodIndex < lods.Length && !_meshDatasCancel; lodIndex++) - { - var lod = lods[lodIndex]; - var meshes = lod.Meshes; - _meshDatas[lodIndex] = new MeshData[meshes.Length]; - - for (int meshIndex = 0; meshIndex < meshes.Length && !_meshDatasCancel; meshIndex++) - { - var mesh = meshes[meshIndex]; - _meshDatas[lodIndex][meshIndex] = new MeshData - { - IndexBuffer = mesh.DownloadIndexBuffer(), - VertexBuffer = mesh.DownloadVertexBuffer() - }; - } - } - } - catch (Exception ex) - { - Editor.LogWarning("Failed to get mesh data. " + ex.Message); - Editor.LogWarning(ex); - } - finally - { - _meshDatasInProgress = false; - } - } /// public override void Update(float deltaTime) @@ -1373,7 +1132,6 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { - WaitForMeshDataRequestEnd(); _preview.SkinnedModel = null; _highlightActor.SkinnedModel = null; @@ -1404,22 +1162,9 @@ namespace FlaxEditor.Windows.Assets base.OnAssetLoaded(); } - /// - public override void OnItemReimported(ContentItem item) - { - // Discard any old mesh data cache - WaitForMeshDataRequestEnd(); - _meshDatas = null; - _meshDatasInProgress = false; - - base.OnItemReimported(item); - } - /// public override void OnDestroy() { - WaitForMeshDataRequestEnd(); - base.OnDestroy(); Object.Destroy(ref _highlightActor); diff --git a/Source/Engine/CSG/CSGBuilder.cpp b/Source/Engine/CSG/CSGBuilder.cpp index c1c2ea78b..fa8ce6291 100644 --- a/Source/Engine/CSG/CSGBuilder.cpp +++ b/Source/Engine/CSG/CSGBuilder.cpp @@ -273,7 +273,7 @@ bool CSGBuilderImpl::buildInner(Scene* scene, BuildData& data) // Convert CSG meshes into raw triangles data RawData meshData; - Array vertexBuffer; + Array vertexBuffer; combinedMesh->Triangulate(meshData, vertexBuffer); meshData.RemoveEmptySlots(); if (meshData.Slots.HasItems()) diff --git a/Source/Engine/CSG/CSGData.cpp b/Source/Engine/CSG/CSGData.cpp index a5da3fce1..96c768d0c 100644 --- a/Source/Engine/CSG/CSGData.cpp +++ b/Source/Engine/CSG/CSGData.cpp @@ -55,7 +55,7 @@ namespace CSG }; } -void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount) +void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount) { auto& surface = Surfaces.AddOne(); surface.ScaleInLightmap = scaleInLightmap; @@ -65,7 +65,7 @@ void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapU surface.Vertices.Add(firstVertex, vertexCount); } -void RawData::AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount) +void RawData::AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount) { // Add surface to slot auto slot = GetOrAddSlot(surfaceMaterial); diff --git a/Source/Engine/CSG/CSGData.h b/Source/Engine/CSG/CSGData.h index 5136b298d..c1e2a8278 100644 --- a/Source/Engine/CSG/CSGData.h +++ b/Source/Engine/CSG/CSGData.h @@ -28,7 +28,7 @@ namespace CSG Rectangle LightmapUVsBox; Float2 Size; Rectangle UVsArea; - Array Vertices; + Array Vertices; }; struct SurfaceTriangle @@ -68,7 +68,7 @@ namespace CSG return Surfaces.IsEmpty(); } - void AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount); + void AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount); }; public: @@ -118,7 +118,7 @@ namespace CSG } public: - void AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount); + void AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const MeshVertex* firstVertex, int32 vertexCount); /// /// Removes the empty slots. diff --git a/Source/Engine/CSG/CSGMesh.Triangulate.cpp b/Source/Engine/CSG/CSGMesh.Triangulate.cpp index 29d22ca3a..c17eed682 100644 --- a/Source/Engine/CSG/CSGMesh.Triangulate.cpp +++ b/Source/Engine/CSG/CSGMesh.Triangulate.cpp @@ -9,7 +9,7 @@ #include "Engine/Core/Collections/Dictionary.h" #include "CSGData.h" -bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const +bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const { // Reject empty meshes int32 verticesCount = _vertices.Count(); @@ -19,7 +19,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const // Mesh triangles data cacheVB.Clear(); cacheVB.EnsureCapacity(_polygons.Count() * 3); - Array surfaceCacheVB(32); + Array surfaceCacheVB(32); // Cache submeshes by material to lay them down // key- brush index, value- direcotry for surfaces (key: surface index, value: list with start vertex per triangle) @@ -29,7 +29,7 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const int32 firstI, secondI, thirdI; int32 triangleIndices[3]; Vector2 uvs[3]; - RawModelVertex rawVertex; + MeshVertex rawVertex; for (int32 i = 0; i < _polygons.Count(); i++) { const Polygon& polygon = _polygons[i]; @@ -142,7 +142,6 @@ bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const rawVertex.Tangent = tangent; rawVertex.Bitangent = bitangent; rawVertex.LightmapUVs = Vector2::Zero; - rawVertex.Color = Color::Black; cacheVB.Add(rawVertex); } diff --git a/Source/Engine/CSG/CSGMesh.h b/Source/Engine/CSG/CSGMesh.h index b71b3f5f0..cc2effd4e 100644 --- a/Source/Engine/CSG/CSGMesh.h +++ b/Source/Engine/CSG/CSGMesh.h @@ -121,7 +121,7 @@ namespace CSG /// Result data /// Cache data /// True if cannot generate valid mesh data (due to missing triangles etc.) - bool Triangulate(RawData& data, Array& cacheVB) const; + bool Triangulate(RawData& data, Array& cacheVB) const; /// /// Perform CSG operation with another mesh diff --git a/Source/Engine/CSG/Types.h b/Source/Engine/CSG/Types.h index 2e578d868..7100ef694 100644 --- a/Source/Engine/CSG/Types.h +++ b/Source/Engine/CSG/Types.h @@ -8,4 +8,14 @@ namespace CSG { typedef ::BrushMode Mode; + + struct MeshVertex + { + Float3 Position; + Float2 TexCoord; + Float3 Normal; + Float3 Tangent; + Float3 Bitangent; + Float2 LightmapUVs; + }; }; diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 88ee5a3bd..8d4a03abb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -13,8 +13,10 @@ #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Serialization/JsonWriters.h" +#include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/JsonAsset.h" #include "Engine/Level/Level.h" +#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #endif REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false); @@ -490,6 +492,64 @@ bool Animation::Save(const StringView& path) return false; } +bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int32 animIndex) +{ + // Validate input + if (animIndex < 0 || animIndex >= modelData.Animations.Count()) + { + Log::ArgumentOutOfRangeException(TEXT("animIndex")); + return true; + } + auto& anim = modelData.Animations.Get()[animIndex]; + if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance) + { + Log::ArgumentOutOfRangeException(TEXT("Invalid animation duration.")); + return true; + } + if (anim.Channels.IsEmpty()) + { + Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty.")); + return true; + } + + // Info + stream.Write(103); // Header version (for fast version upgrades without serialization format change) + stream.Write(anim.Duration); + stream.Write(anim.FramesPerSecond); + stream.Write((byte)anim.RootMotionFlags); + stream.Write(anim.RootNodeName, 13); + + // Animation channels + stream.WriteInt32(anim.Channels.Count()); + for (const auto& channel : anim.Channels) + { + stream.Write(channel.NodeName, 172); + Serialization::Serialize(stream, channel.Position); + Serialization::Serialize(stream, channel.Rotation); + Serialization::Serialize(stream, channel.Scale); + } + + // Animation events + stream.WriteInt32(anim.Events.Count()); + for (auto& e : anim.Events) + { + stream.Write(e.First, 172); + stream.Write(e.Second.GetKeyframes().Count()); + for (const auto& k : e.Second.GetKeyframes()) + { + stream.Write(k.Time); + stream.Write(k.Value.Duration); + stream.Write(k.Value.TypeName, 17); + stream.WriteJson(k.Value.JsonData); + } + } + + // Nested animations + stream.WriteInt32(0); // Empty list + + return false; +} + void Animation::GetReferences(Array& assets, Array& files) const { BinaryAsset::GetReferences(assets, files); diff --git a/Source/Engine/Content/Assets/Animation.h b/Source/Engine/Content/Assets/Animation.h index f287fbd72..68e1f9c5e 100644 --- a/Source/Engine/Content/Assets/Animation.h +++ b/Source/Engine/Content/Assets/Animation.h @@ -151,6 +151,12 @@ public: bool Save(const StringView& path = StringView::Empty); #endif +private: +#if USE_EDITOR + friend class ImportModel; + static bool SaveHeader(const class ModelData& modelData, WriteStream& stream, int32 animIndex); +#endif + public: // [BinaryAsset] #if USE_EDITOR diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index bf58c8782..a97c82a33 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -7,7 +7,7 @@ #include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Upgraders/ModelAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" -#include "Engine/Debug/DebugDraw.h" +#include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" @@ -18,13 +18,13 @@ #include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Textures/GPUTexture.h" -#include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Threading/Threading.h" #include "Engine/Tools/ModelTool/ModelTool.h" #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Graphics/Textures/TextureData.h" #endif REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase"); @@ -246,330 +246,6 @@ bool Model::SetupLODs(const Span& meshesCountPerLod) return Init(meshesCountPerLod); } -#if USE_EDITOR - -bool Model::Save(bool withMeshDataFromGpu, const StringView& path) -{ - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); - return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - if (withMeshDataFromGpu && IsInMainThread()) - { - LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread)."); - return true; - } - if (IsVirtual() && !withMeshDataFromGpu) - { - LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data)."); - return true; - } - - ScopeLock lock(Locker); - - // Create model data header - MemoryWriteStream headerStream(1024); - MemoryWriteStream* stream = &headerStream; - { - // Min Screen Size - stream->WriteFloat(MinScreenSize); - - // Amount of material slots - stream->WriteInt32(MaterialSlots.Count()); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < MaterialSlots.Count(); materialSlotIndex++) - { - auto& slot = MaterialSlots[materialSlotIndex]; - - const auto id = slot.Material.GetID(); - stream->Write(id); - stream->WriteByte(static_cast(slot.ShadowsMode)); - stream->WriteString(slot.Name, 11); - } - - // Amount of LODs - const int32 lods = LODs.Count(); - stream->WriteByte(lods); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) - { - auto& lod = LODs[lodIndex]; - - // Screen Size - stream->WriteFloat(lod.ScreenSize); - - // Amount of meshes - const int32 meshes = lod.Meshes.Count(); - stream->WriteUint16(meshes); - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++) - { - const auto& mesh = lod.Meshes[meshIndex]; - - // Material Slot index - stream->WriteInt32(mesh.GetMaterialSlotIndex()); - - // Box - const auto box = mesh.GetBox(); - stream->WriteBoundingBox(box); - - // Sphere - const auto sphere = mesh.GetSphere(); - stream->WriteBoundingSphere(sphere); - - // Has Lightmap UVs - stream->WriteBool(mesh.HasLightmapUVs()); - } - } - } - - // Use a temporary chunks for data storage for virtual assets - FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS]; - Platform::MemoryClear(tmpChunks, sizeof(tmpChunks)); - Array chunks; - if (IsVirtual()) - chunks.Resize(ASSET_FILE_DATA_CHUNKS); -#define GET_CHUNK(index) (IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index)) - - // Check if use data from drive or from GPU - if (withMeshDataFromGpu) - { - // Download all meshes buffers - Array tasks; - for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { - auto& lod = LODs[lodIndex]; - - const int32 meshesCount = lod.Meshes.Count(); - struct MeshData - { - BytesContainer VB0; - BytesContainer VB1; - BytesContainer VB2; - BytesContainer IB; - - uint32 DataSize() const - { - return VB0.Length() + VB1.Length() + VB2.Length() + IB.Length(); - } - }; - Array meshesData; - meshesData.Resize(meshesCount); - tasks.EnsureCapacity(meshesCount * 4); - - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - const auto& mesh = lod.Meshes[meshIndex]; - auto& meshData = meshesData[meshIndex]; - - // Vertex Buffer 0 (required) - auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0); - if (task == nullptr) - return true; - task->Start(); - tasks.Add(task); - - // Vertex Buffer 1 (required) - task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB1); - if (task == nullptr) - return true; - task->Start(); - tasks.Add(task); - - // Vertex Buffer 2 (optional) - task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB2); - if (task) - { - task->Start(); - tasks.Add(task); - } - - // Index Buffer (required) - task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB); - if (task == nullptr) - return true; - task->Start(); - tasks.Add(task); - } - - // Wait for all - if (Task::WaitAll(tasks)) - return true; - tasks.Clear(); - - // Create meshes data - { - int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool)); - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - dataSize += meshesData[meshIndex].DataSize(); - } - - MemoryWriteStream meshesStream(dataSize); - - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - const auto& mesh = lod.Meshes[meshIndex]; - const auto& meshData = meshesData[meshIndex]; - - uint32 vertices = mesh.GetVertexCount(); - uint32 triangles = mesh.GetTriangleCount(); - bool hasColors = meshData.VB2.IsValid(); - uint32 vb0Size = vertices * sizeof(VB0ElementType); - uint32 vb1Size = vertices * sizeof(VB1ElementType); - uint32 vb2Size = vertices * sizeof(VB2ElementType); - uint32 indicesCount = triangles * 3; - bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16; - bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); - uint32 ibSize = indicesCount * (use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32)); - - if (vertices == 0 || triangles == 0) - { - LOG(Warning, "Cannot save model with empty meshes."); - return true; - } - if ((uint32)meshData.VB0.Length() < vb0Size) - { - LOG(Warning, "Invalid vertex buffer 0 size."); - return true; - } - if ((uint32)meshData.VB1.Length() < vb1Size) - { - LOG(Warning, "Invalid vertex buffer 1 size."); - return true; - } - if (hasColors && (uint32)meshData.VB2.Length() < vb2Size) - { - LOG(Warning, "Invalid vertex buffer 2 size."); - return true; - } - if ((uint32)meshData.IB.Length() < ibSize) - { - LOG(Warning, "Invalid index buffer size."); - return true; - } - - // #MODEL_DATA_FORMAT_USAGE - meshesStream.WriteUint32(vertices); - meshesStream.WriteUint32(triangles); - - meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size); - meshesStream.WriteBytes(meshData.VB1.Get(), vb1Size); - - meshesStream.WriteBool(hasColors); - - if (hasColors) - { - meshesStream.WriteBytes(meshData.VB2.Get(), vb2Size); - } - - if (shouldUse16BitIndexBuffer == use16BitIndexBuffer) - { - meshesStream.WriteBytes(meshData.IB.Get(), ibSize); - } - else if (shouldUse16BitIndexBuffer) - { - auto ib = (const int32*)meshData.IB.Get(); - for (uint32 i = 0; i < indicesCount; i++) - { - meshesStream.WriteUint16(ib[i]); - } - } - else - { - CRASH; - } - } - - // Override LOD data chunk with the fetched GPU meshes memory - auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); - if (lodChunk == nullptr) - return true; - lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); - } - } - - // Download SDF data - if (SDF.Texture) - { - auto sdfChunk = GET_CHUNK(15); - if (sdfChunk == nullptr) - return true; - MemoryWriteStream sdfStream; - sdfStream.WriteInt32(1); // Version - ModelSDFHeader data(SDF, SDF.Texture->GetDescription()); - sdfStream.WriteBytes(&data, sizeof(data)); - TextureData sdfTextureData; - if (SDF.Texture->DownloadData(sdfTextureData)) - return true; - for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++) - { - auto& mip = sdfTextureData.Items[0].Mips[mipLevel]; - ModelSDFMip mipData(mipLevel, mip); - sdfStream.WriteBytes(&mipData, sizeof(mipData)); - sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length()); - } - sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); - } - } - else - { - // Load all chunks with a mesh data - for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { - if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) - return true; - } - - if (SDF.Texture) - { - // SDF data from file (only if has no cached texture data) - if (LoadChunk(15)) - return true; - } - else - { - // No SDF texture - ReleaseChunk(15); - } - } - - // Set mesh header data - auto headerChunk = GET_CHUNK(0); - ASSERT(headerChunk != nullptr); - headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition()); - -#undef GET_CHUNK - - // Save - AssetInitData data; - data.SerializedVersion = SerializedVersion; - if (IsVirtual()) - Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks)); - const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true); - if (IsVirtual()) - Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks)); - if (saveResult) - { - LOG(Error, "Cannot save \'{0}\'", ToString()); - return true; - } - - return false; -} - -#endif - bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold, bool useGPU) { if (EnableModelSDF == 2) @@ -666,6 +342,172 @@ bool Model::Init(const Span& meshesCountPerLod) return false; } +bool Model::LoadHeader(ReadStream& stream, byte& headerVersion) +{ + if (ModelBase::LoadHeader(stream, headerVersion)) + return true; + + // LODs + byte lods = stream.ReadByte(); + if (lods == 0 || lods > MODEL_MAX_LODS) + return true; + LODs.Resize(lods); + _initialized = true; + for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) + { + auto& lod = LODs[lodIndex]; + lod._model = this; + lod._lodIndex = lodIndex; + stream.ReadFloat(&lod.ScreenSize); + + // Meshes + uint16 meshesCount; + stream.ReadUint16(&meshesCount); + if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES) + return true; + ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount); + lod.Meshes.Resize(meshesCount, false); + for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + Mesh& mesh = lod.Meshes[meshIndex]; + mesh.Link(this, lodIndex, meshIndex); + + // Material Slot index + int32 materialSlotIndex; + stream.ReadInt32(&materialSlotIndex); + if (materialSlotIndex < 0 || materialSlotIndex >= MaterialSlots.Count()) + { + LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, MaterialSlots.Count()); + return true; + } + mesh.SetMaterialSlotIndex(materialSlotIndex); + + // Bounds + BoundingBox box; + stream.Read(box); + BoundingSphere sphere; + stream.Read(sphere); + mesh.SetBounds(box, sphere); + + // Lightmap UVs channel + int8 lightmapUVs; + stream.ReadInt8(&lightmapUVs); + mesh.LightmapUVsIndex = (int32)lightmapUVs; + } + } + + return false; +} + +#if USE_EDITOR + +bool Model::SaveHeader(WriteStream& stream) +{ + if (ModelBase::SaveHeader(stream)) + return true; + static_assert(HeaderVersion == 2, "Update code"); + + // LODs + stream.Write((byte)LODs.Count()); + for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) + { + auto& lod = LODs[lodIndex]; + stream.Write(lod.ScreenSize); + + // Meshes + stream.Write((uint16)lod.Meshes.Count()); + for (const auto& mesh : lod.Meshes) + { + stream.Write(mesh.GetMaterialSlotIndex()); + stream.Write(mesh.GetBox()); + stream.Write(mesh.GetSphere()); + stream.Write((int8)mesh.LightmapUVsIndex); + } + } + + return false; +} + +bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData) +{ + if (ModelBase::SaveHeader(stream, modelData)) + return true; + static_assert(HeaderVersion == 2, "Update code"); + + // LODs + stream.Write((byte)modelData.LODs.Count()); + for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++) + { + auto& lod = modelData.LODs[lodIndex]; + stream.Write(lod.ScreenSize); + + // Meshes + stream.Write((uint16)lod.Meshes.Count()); + for (const auto& mesh : lod.Meshes) + { + BoundingBox box; + BoundingSphere sphere; + mesh->CalculateBounds(box, sphere); + stream.Write(mesh->MaterialSlotIndex); + stream.Write(box); + stream.Write(sphere); + stream.Write((int8)mesh->LightmapUVsIndex); + } + } + + return false; +} + +bool Model::Save(bool withMeshDataFromGpu, Function& getChunk) +{ + if (ModelBase::Save(withMeshDataFromGpu, getChunk)) + return true; + + if (withMeshDataFromGpu) + { + // Download SDF data + if (SDF.Texture) + { + auto sdfChunk = getChunk(15); + if (sdfChunk == nullptr) + return true; + MemoryWriteStream sdfStream; + sdfStream.WriteInt32(1); // Version + ModelSDFHeader data(SDF, SDF.Texture->GetDescription()); + sdfStream.WriteBytes(&data, sizeof(data)); + TextureData sdfTextureData; + if (SDF.Texture->DownloadData(sdfTextureData)) + return true; + for (int32 mipLevel = 0; mipLevel < sdfTextureData.Items[0].Mips.Count(); mipLevel++) + { + auto& mip = sdfTextureData.Items[0].Mips[mipLevel]; + ModelSDFMip mipData(mipLevel, mip); + sdfStream.WriteBytes(&mipData, sizeof(mipData)); + sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length()); + } + sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); + } + } + else + { + if (SDF.Texture) + { + // SDF data from file (only if has no cached texture data) + if (LoadChunk(15)) + return true; + } + else + { + // No SDF texture + ReleaseChunk(15); + } + } + + return false; +} + +#endif + void Model::SetupMaterialSlots(int32 slotsCount) { ModelBase::SetupMaterialSlots(slotsCount); @@ -687,6 +529,26 @@ int32 Model::GetLODsCount() const return LODs.Count(); } +const MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) const +{ + auto& lod = LODs[lodIndex]; + return &lod.Meshes[meshIndex]; +} + +MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) +{ + auto& lod = LODs[lodIndex]; + return &lod.Meshes[meshIndex]; +} + +void Model::GetMeshes(Array& meshes, int32 lodIndex) const +{ + auto& lod = LODs[lodIndex]; + meshes.Resize(lod.Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + meshes[meshIndex] = &lod.Meshes[meshIndex]; +} + void Model::GetMeshes(Array& meshes, int32 lodIndex) { auto& lod = LODs[lodIndex]; @@ -722,91 +584,11 @@ Asset::LoadResult Model::load() if (chunk0 == nullptr || chunk0->IsMissing()) return LoadResult::MissingDataChunk; MemoryReadStream headerStream(chunk0->Get(), chunk0->Size()); - ReadStream* stream = &headerStream; - - // Min Screen Size - stream->ReadFloat(&MinScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - stream->ReadInt32(&materialSlotsCount); - if (materialSlotsCount <= 0 || materialSlotsCount > 4096) + + // Load asset data (anything but mesh contents that use streaming) + byte headerVersion; + if (LoadHeader(headerStream, headerVersion)) return LoadResult::InvalidData; - MaterialSlots.Resize(materialSlotsCount, false); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) - { - auto& slot = MaterialSlots[materialSlotIndex]; - - // Material - Guid materialId; - stream->Read(materialId); - slot.Material = materialId; - - // Shadows Mode - slot.ShadowsMode = static_cast(stream->ReadByte()); - - // Name - stream->ReadString(&slot.Name, 11); - } - - // Amount of LODs - byte lods; - stream->ReadByte(&lods); - if (lods == 0 || lods > MODEL_MAX_LODS) - return LoadResult::InvalidData; - LODs.Resize(lods); - _initialized = true; - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) - { - auto& lod = LODs[lodIndex]; - lod._model = this; - lod._lodIndex = lodIndex; - - // Screen Size - stream->ReadFloat(&lod.ScreenSize); - - // Amount of meshes - uint16 meshesCount; - stream->ReadUint16(&meshesCount); - if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES) - return LoadResult::InvalidData; - ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount); - - // Allocate memory - lod.Meshes.Resize(meshesCount, false); - - // For each mesh - for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - Mesh& mesh = lod.Meshes[meshIndex]; - mesh.Link(this, lodIndex, meshIndex); - - // Material Slot index - int32 materialSlotIndex; - stream->ReadInt32(&materialSlotIndex); - if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount) - { - LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); - return LoadResult::InvalidData; - } - mesh.SetMaterialSlotIndex(materialSlotIndex); - - // Bounds - BoundingBox box; - stream->ReadBoundingBox(&box); - BoundingSphere sphere; - stream->ReadBoundingSphere(&sphere); - mesh.SetBounds(box, sphere); - - // Has Lightmap UVs - bool hasLightmapUVs = stream->ReadBool(); - mesh.LightmapUVsIndex = hasLightmapUVs ? 1 : -1; - } - } // Load SDF auto chunk15 = GetChunk(15); @@ -815,7 +597,7 @@ Asset::LoadResult Model::load() PROFILE_CPU_NAMED("SDF"); MemoryReadStream sdfStream(chunk15->Get(), chunk15->Size()); int32 version; - sdfStream.ReadInt32(&version); + sdfStream.Read(version); switch (version) { case 1: diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index de17f37e1..3907e2703 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -159,7 +159,7 @@ public: /// API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase { - DECLARE_BINARY_ASSET_HEADER(Model, 25); + DECLARE_BINARY_ASSET_HEADER(Model, 30); friend Mesh; public: @@ -274,19 +274,6 @@ public: /// True if failed, otherwise false. API_FUNCTION() bool SetupLODs(const Span& meshesCountPerLod); -#if USE_EDITOR - - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread. - /// True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded. - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty); - -#endif - /// /// Generates the Sign Distant Field for this model. /// @@ -312,10 +299,22 @@ private: /// True if failed, otherwise false. bool Init(const Span& meshesCountPerLod); + // [ModelBase] + bool LoadHeader(ReadStream& stream, byte& headerVersion); +#if USE_EDITOR + friend class ImportModel; + bool SaveHeader(WriteStream& stream) override; + static bool SaveHeader(WriteStream& stream, const ModelData& modelData); + bool Save(bool withMeshDataFromGpu, Function& getChunk) override; +#endif + public: // [ModelBase] void SetupMaterialSlots(int32 slotsCount) override; int32 GetLODsCount() const override; + const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override; + MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override; + void GetMeshes(Array& meshes, int32 lodIndex = 0) const override; void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 220c1b4f7..bee811a72 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -5,6 +5,9 @@ #include "Engine/Content/WeakAssetReference.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Graphics/Config.h" +#include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/Models/MeshBase.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" #define STREAM_TASK_BASE ThreadPoolTask @@ -12,8 +15,11 @@ #include "Engine/Threading/MainThreadTask.h" #define STREAM_TASK_BASE MainThreadTask #endif -#include "SkinnedModel.h" // TODO: remove this -#include "Model.h" // TODO: remove this +#if USE_EDITOR +#include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Graphics/Models/ModelData.h" +#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#endif /// /// Model LOD streaming task. @@ -54,93 +60,26 @@ public: } MemoryReadStream stream(data.Get(), data.Length()); - // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering) - - // Load model LOD (initialize vertex and index buffers) - // TODO: reformat model data storage to be the same for both assets (only custom per-mesh type data like BlendShapes) + // Load meshes data and pass data to the GPU buffers Array meshes; model->GetMeshes(meshes, _lodIndex); - if (model->Is()) + byte meshVersion = stream.ReadByte(); + if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion) { - byte version = stream.ReadByte(); - for (int32 i = 0; i < meshes.Count(); i++) - { - auto& mesh = (SkinnedMesh&)*meshes[i]; - - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint16 blendShapesCount; - stream.ReadUint16(&blendShapesCount); - if (blendShapesCount != mesh.BlendShapes.Count()) - { - LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Incorrect blend shapes amount: {} (expected: {})", i, _lodIndex, model->ToString(), blendShapesCount, mesh.BlendShapes.Count()); - return true; - } - for (auto& blendShape : mesh.BlendShapes) - { - blendShape.UseNormals = stream.ReadBool(); - stream.ReadUint32(&blendShape.MinVertexIndex); - stream.ReadUint32(&blendShape.MaxVertexIndex); - uint32 blendShapeVertices; - stream.ReadUint32(&blendShapeVertices); - blendShape.Vertices.Resize(blendShapeVertices); - stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex)); - } - const uint32 indicesCount = triangles * 3; - const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - const auto vb0 = stream.Move(vertices); - const auto ib = stream.Move(indicesCount * ibStride); - - // Setup GPU resources - if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer)) - { - LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles); - return true; - } - } + LOG(Warning, "Unsupported mesh version {}", meshVersion); + return true; } - else + for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) { - for (int32 i = 0; i < meshes.Count(); i++) + if (model->LoadMesh(stream, meshVersion, meshes[meshIndex])) { - auto& mesh = (Mesh&)*meshes[i]; - - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return true; - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Move(vertices); - } - auto ib = stream.Move(indicesCount * ibStride); - - // Setup GPU resources - if (mesh.Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer)) - { - LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles); - return true; - } + LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'", meshIndex, _lodIndex, model->ToString()); + return true; } } // Update residency level + // Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering) model->_loadedLODs++; model->ResidencyChanged(); @@ -209,6 +148,638 @@ void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const GetChunkData(chunkIndex, data); } +#if USE_EDITOR + +bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path) +{ + // Validate state + if (WaitForLoaded()) + { + LOG(Error, "Asset loading failed. Cannot save it."); + return true; + } + if (IsVirtual() && path.IsEmpty()) + { + LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); + return true; + } + if (withMeshDataFromGpu && IsInMainThread()) + { + LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread)."); + return true; + } + if (IsVirtual() && !withMeshDataFromGpu) + { + LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data)."); + return true; + } + + ScopeLock lock(Locker); + + // Use a temporary chunks for data storage for virtual assets + FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS]; + Platform::MemoryClear(tmpChunks, sizeof(tmpChunks)); + Array chunks; + if (IsVirtual()) + chunks.Resize(ASSET_FILE_DATA_CHUNKS); + Function getChunk = [&](int32 index) -> FlaxChunk* { + return IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index); + }; + + // Save LODs data + const int32 lodsCount = GetLODsCount(); + if (withMeshDataFromGpu) + { + // Fetch runtime mesh data (from GPU) + MemoryWriteStream meshesStream; + for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) + { + meshesStream.SetPosition(0); + if (SaveLOD(meshesStream, lodIndex)) + return true; + auto lodChunk = getChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); + if (lodChunk == nullptr) + return true; + lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); + } + } + else if (!IsVirtual()) + { + // Load all chunks with a mesh data + for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) + { + if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) + return true; + } + } + + // Save custom data + if (Save(withMeshDataFromGpu, getChunk)) + return true; + + // Save header data + { + MemoryWriteStream headerStream(1024); + if (SaveHeader(headerStream)) + return true; + auto headerChunk = getChunk(0); + ASSERT(headerChunk != nullptr); + headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition()); + } + + // Save file + AssetInitData data; + data.SerializedVersion = GetSerializedVersion(); + if (IsVirtual()) + Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks)); + const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true); + if (IsVirtual()) + Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks)); + if (saveResult) + { + LOG(Error, "Cannot save \'{0}\'", ToString()); + return true; + } + + return false; +} + +#endif + +bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion) +{ + // Basic info + stream.Read(headerVersion); + if (headerVersion < 2 || headerVersion > HeaderVersion) + { + LOG(Warning, "Unsupported model asset header version {}", headerVersion); + return true; + } + static_assert(HeaderVersion == 2, "Update code"); + stream.Read(MinScreenSize); + + // Materials + int32 materialsCount; + stream.Read(materialsCount); + if (materialsCount < 0 || materialsCount > 4096) + return true; + MaterialSlots.Resize(materialsCount, false); + Guid materialId; + for (auto& slot : MaterialSlots) + { + stream.Read(materialId); + slot.Material = materialId; + slot.ShadowsMode = (ShadowsCastingMode)stream.ReadByte(); + stream.Read(slot.Name, 11); + } + + return false; +} + +bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) +{ + // Load descriptor + static_assert(MeshVersion == 2, "Update code"); + uint32 vertices, triangles; + stream.Read(vertices); + stream.Read(triangles); + const uint32 indicesCount = triangles * 3; + const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + if (vertices == 0 || triangles == 0) + return true; + Array> vbData; + Array> vbLayout; + byte vbCount; + stream.Read(vbCount); + if (vbCount > MODEL_MAX_VB) + return true; + vbData.Resize(vbCount); + vbLayout.Resize(vbCount); + for (int32 i = 0; i < vbCount; i++) + { + GPUVertexLayout::Elements elements; + stream.Read(elements); + vbLayout[i] = GPUVertexLayout::Get(elements); + } + + // Move over actual mesh data + for (int32 i = 0; i < vbCount; i++) + { + auto layout = vbLayout[i]; + if (!layout) + { + LOG(Warning, "Failed to get vertex layout for buffer {}", i); + return true; + } + vbData[i] = stream.Move(vertices * layout->GetStride()); + } + const auto ibData = stream.Move(indicesCount * ibStride); + + // Pass results if read-only + if (dataIfReadOnly) + { + dataIfReadOnly->Vertices = vertices; + dataIfReadOnly->Triangles = triangles; + dataIfReadOnly->IBStride = ibStride; + dataIfReadOnly->VBData = vbData; + dataIfReadOnly->VBLayout = vbLayout; + dataIfReadOnly->IBData = ibData; + return false; + } + + // Setup GPU resources + return mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout); +} + +#if USE_EDITOR + +bool ModelBase::SaveHeader(WriteStream& stream) +{ + // Basic info + static_assert(HeaderVersion == 2, "Update code"); + stream.Write(HeaderVersion); + stream.Write(MinScreenSize); + + // Materials + stream.Write(MaterialSlots.Count()); + for (const auto& slot : MaterialSlots) + { + stream.Write(slot.Material.GetID()); + stream.Write((byte)slot.ShadowsMode); + stream.Write(slot.Name, 11); + } + + return false; +} + +bool ModelBase::SaveHeader(WriteStream& stream, const ModelData& modelData) +{ + // Validate data + if (modelData.LODs.Count() > MODEL_MAX_LODS) + { + Log::ArgumentOutOfRangeException(TEXT("LODs"), TEXT("Too many LODs.")); + return true; + } + for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++) + { + auto& lod = modelData.LODs[lodIndex]; + if (lod.Meshes.Count() > MODEL_MAX_MESHES) + { + Log::ArgumentOutOfRangeException(TEXT("LOD.Meshes"), TEXT("Too many meshes.")); + return true; + } + for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + { + auto& mesh = *lod.Meshes[meshIndex]; + if (mesh.MaterialSlotIndex < 0 || mesh.MaterialSlotIndex >= modelData.Materials.Count()) + { + Log::ArgumentOutOfRangeException(TEXT("MaterialSlotIndex"), TEXT("Incorrect material index used by the mesh.")); + return true; + } + } + } + + // Basic info + static_assert(HeaderVersion == 2, "Update code"); + stream.Write(HeaderVersion); + stream.Write(modelData.MinScreenSize); + + // Materials + stream.WriteInt32(modelData.Materials.Count()); + for (const auto& slot : modelData.Materials) + { + stream.Write(slot.AssetID); + stream.Write((byte)slot.ShadowsMode); + stream.Write(slot.Name, 11); + } + + return false; +} + +bool ModelBase::SaveLOD(WriteStream& stream, int32 lodIndex) const +{ + // Download all meshes buffers from the GPU + Array tasks; + Array meshes; + GetMeshes(meshes, lodIndex); + const int32 meshesCount = meshes.Count(); + struct MeshData + { + BytesContainer VB[3]; + BytesContainer IB; + }; + Array meshesData; + meshesData.Resize(meshesCount); + tasks.EnsureCapacity(meshesCount * 4); + for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + const auto& mesh = *meshes[meshIndex]; + auto& meshData = meshesData[meshIndex]; + + // Vertex Buffer 0 (required) + auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB[0]); + if (task == nullptr) + return true; + task->Start(); + tasks.Add(task); + + // Vertex Buffer 1 (optional) + task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB[1]); + if (task) + { + task->Start(); + tasks.Add(task); + } + + // Vertex Buffer 2 (optional) + task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB[2]); + if (task) + { + task->Start(); + tasks.Add(task); + } + + // Index Buffer (required) + task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB); + if (task == nullptr) + return true; + task->Start(); + tasks.Add(task); + } + + // Wait for async tasks + if (Task::WaitAll(tasks)) + return true; + + // Create meshes data + static_assert(MeshVersion == 2, "Update code"); + stream.Write(MeshVersion); + for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + const auto& mesh = *meshes[meshIndex]; + const auto& meshData = meshesData[meshIndex]; + uint32 vertices = mesh.GetVertexCount(); + uint32 triangles = mesh.GetTriangleCount(); + uint32 indicesCount = triangles * 3; + bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16; + bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); + uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + uint32 ibSize = indicesCount * ibStride; + + // Validate data + if (vertices == 0 || triangles == 0) + { + LOG(Warning, "Cannot save model with empty meshes."); + return true; + } + Array> vbLayout; + for (int32 vbIndex = 0; vbIndex < MODEL_MAX_VB; vbIndex++) + { + if (vbIndex != 0 && meshData.VB[vbIndex].IsInvalid()) // VB0 is always required + continue; + const GPUBuffer* vb = mesh.GetVertexBuffer(vbIndex); + if (!vb) + break; + const GPUVertexLayout* layout = vb->GetVertexLayout(); + if (!layout) + { + LOG(Warning, "Invalid vertex buffer {}. Missing vertex layout.", vbIndex); + return true; + } + const uint32 vbSize = vb->GetSize(); + vbLayout.Add(layout); + if ((uint32)meshData.VB[vbIndex].Length() != vbSize) + { + LOG(Warning, "Invalid vertex buffer {} size. Got {} bytes but expected {} bytes. Stride: {}. Layout:\n{}", vbIndex, meshData.VB[vbIndex].Length(), vbSize, vb->GetStride(), layout->GetElementsString()); + return true; + } + } + if ((uint32)meshData.IB.Length() < ibSize) + { + LOG(Warning, "Invalid index buffer size. Got {} bytes bytes expected {} bytes. Stride: {}", meshData.IB.Length(), ibSize, ibStride); + return true; + } + + // Write descriptor + stream.Write(vertices); + stream.Write(triangles); + byte vbCount = (byte)vbLayout.Count(); + stream.Write(vbCount); + for (const GPUVertexLayout* layout : vbLayout) + stream.Write(layout->GetElements()); + + // Write actual mesh data + for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++) + { + const auto& vb = meshData.VB[vbIndex]; + stream.WriteBytes(vb.Get(), vb.Length()); + } + if (shouldUse16BitIndexBuffer == use16BitIndexBuffer) + { + stream.WriteBytes(meshData.IB.Get(), ibSize); + } + else if (shouldUse16BitIndexBuffer) + { + // Convert 32-bit indices to 16-bit + auto ib = (const int32*)meshData.IB.Get(); + for (uint32 i = 0; i < indicesCount; i++) + { + stream.WriteUint16(ib[i]); + } + } + else + { + CRASH; + } + + // Write custom data + if (SaveMesh(stream, &mesh)) + return true; + } + + return false; +} + +bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool saveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex)) +{ + // Create meshes data + static_assert(MeshVersion == 2, "Update code"); + stream.Write(MeshVersion); + const auto& lod = modelData.LODs[lodIndex]; + for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + { + const auto& mesh = *lod.Meshes[meshIndex]; + uint32 vertices = (uint32)mesh.Positions.Count(); + uint32 indicesCount = (uint32)mesh.Indices.Count(); + uint32 triangles = indicesCount / 3; + bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + const bool isSkinned = mesh.BlendIndices.HasItems() && mesh.BlendWeights.HasItems(); + + // Validate data + if (vertices == 0 || triangles == 0 || indicesCount % 3 != 0) + { + LOG(Warning, "Cannot save model with empty meshes."); + return true; + } + bool hasUVs = mesh.UVs.HasItems(); + if (hasUVs && (uint32)mesh.UVs.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("UVs")); + return true; + } + bool hasNormals = mesh.Normals.HasItems(); + if (hasNormals && (uint32)mesh.Normals.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("Normals")); + return true; + } + bool hasTangents = mesh.Tangents.HasItems(); + if (hasTangents && (uint32)mesh.Tangents.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents")); + return true; + } + bool hasBitangentSigns = mesh.BitangentSigns.HasItems(); + if (hasBitangentSigns && (uint32)mesh.BitangentSigns.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns")); + return true; + } + bool hasLightmapUVs = mesh.LightmapUVs.HasItems(); + if (hasLightmapUVs && (uint32)mesh.LightmapUVs.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs")); + return true; + } + bool hasColors = mesh.Colors.HasItems(); + if (hasColors && (uint32)mesh.Colors.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("Colors")); + return true; + } + if (isSkinned && (uint32)mesh.BlendIndices.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices")); + return true; + } + if (isSkinned && (uint32)mesh.BlendWeights.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights")); + return true; + } + + // Define vertex buffers layout and packing + Array> vbElements; + const bool useSeparatePositions = !isSkinned; + const bool useSeparateColors = !isSkinned; + { + byte vbIndex = 0; + // TODO: add option to quantize vertex positions (eg. 16-bit) + // TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords) + // TODO: add support for 16-bit blend indices (up to 65535 bones) + + // Position + if (useSeparatePositions) + { + auto& vb0 = vbElements.AddOne(); + vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float }); + vbIndex++; + } + + // General purpose components + { + auto& vb = vbElements.AddOne(); + if (!useSeparatePositions) + vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float }); + if (hasUVs) + vb.Add({ VertexElement::Types::TexCoord, vbIndex, 0, 0, PixelFormat::R16G16_Float }); + if (hasLightmapUVs) + vb.Add({ VertexElement::Types::TexCoord1, vbIndex, 0, 0, PixelFormat::R16G16_Float }); + vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm }); + vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm }); + if (isSkinned) + { + vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UInt }); + vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float }); + } + if (!useSeparateColors && hasColors) + vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm }); + vbIndex++; + } + + // Colors + if (useSeparateColors && hasColors) + { + auto& vb = vbElements.AddOne(); + vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm }); + vbIndex++; + } + } + + // Write descriptor + stream.Write(vertices); + stream.Write(triangles); + byte vbCount = (byte)vbElements.Count(); + stream.Write(vbCount); + for (const auto& elements : vbElements) + stream.Write(elements); + + // Write vertex buffers + for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++) + { + if (useSeparatePositions && vbIndex == 0) + { + // Fast path for vertex positions of static models using the first buffer + stream.WriteBytes(mesh.Positions.Get(), sizeof(Float3) * vertices); + continue; + } + + // Write vertex components interleaved + const GPUVertexLayout::Elements& layout = vbElements[vbIndex]; + for (uint32 vertex = 0; vertex < vertices; vertex++) + { + for (const VertexElement& element : layout) + { + switch (element.Type) + { + case VertexElement::Types::Position: + { + const Float3 position = mesh.Positions.Get()[vertex]; + stream.Write(position); + break; + } + case VertexElement::Types::Color: + { + const Color32 color(mesh.Colors.Get()[vertex]); + stream.Write(color); + break; + } + case VertexElement::Types::Normal: + { + const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ; + const FloatR10G10B10A2 normalEnc(normal * 0.5f + 0.5f, 0); + stream.Write(normalEnc.Value); + break; + } + case VertexElement::Types::Tangent: + { + const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ; + const Float3 tangent = hasTangents ? mesh.Tangents.Get()[vertex] : Float3::UnitX; + const float bitangentSign = hasBitangentSigns ? mesh.BitangentSigns.Get()[vertex] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent); + const Float1010102 tangentEnc(tangent * 0.5f + 0.5f, (byte)(bitangentSign < 0 ? 1 : 0)); + stream.Write(tangentEnc.Value); + break; + } + case VertexElement::Types::BlendIndices: + { + const Int4 blendIndices = mesh.BlendIndices.Get()[vertex]; + const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W); + stream.Write(blendIndicesEnc); + break; + } + case VertexElement::Types::BlendWeights: + { + const Float4 blendWeights = mesh.BlendWeights.Get()[vertex]; + const Half4 blendWeightsEnc(blendWeights); + stream.Write(blendWeightsEnc); + break; + } + case VertexElement::Types::TexCoord0: + { + const Float2 uv = hasUVs ? mesh.UVs.Get()[vertex] : Float2::Zero; + const Half2 uvEnc(uv); + stream.Write(uvEnc); + break; + } + case VertexElement::Types::TexCoord1: + { + // TODO: refactor LightmapUVs into a generic TexCoord channel and support up to 4 UVs + const Float2 lightmapUV = hasLightmapUVs ? mesh.LightmapUVs.Get()[vertex] : Float2::Zero; + const Half2 lightmapUVEnc(lightmapUV); + stream.Write(lightmapUVEnc); + break; + } + default: + LOG(Error, "Unsupported vertex element: {}", element.ToString()); + return true; + } + } + } + } + + // Write index buffer + const uint32* meshIndices = mesh.Indices.Get(); + if (use16BitIndexBuffer) + { + for (uint32 i = 0; i < indicesCount; i++) + stream.Write((uint16)meshIndices[i]); + } + else + { + stream.WriteBytes(meshIndices, sizeof(uint32) * indicesCount); + } + + // Write custom data + if (saveMesh && saveMesh(stream, modelData, lodIndex, meshIndex)) + return true; + } + + return false; +} + +bool ModelBase::SaveMesh(WriteStream& stream, const MeshBase* mesh) const +{ + return false; +} + +bool ModelBase::Save(bool withMeshDataFromGpu, Function& getChunk) +{ + return false; +} + +#endif + void ModelBase::CancelStreaming() { BinaryAsset::CancelStreaming(); diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 34d7ee23b..22d502f25 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -19,6 +19,7 @@ #define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) class MeshBase; +class StreamModelLODTask; struct RenderContextBatch; /// @@ -28,9 +29,12 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset { DECLARE_ASSET_HEADER(ModelBase); friend MeshBase; - friend class StreamModelLODTask; + friend StreamModelLODTask; protected: + static constexpr byte HeaderVersion = 2; + static constexpr byte MeshVersion = 2; + bool _initialized = false; int32 _loadedLODs = 0; StreamModelLODTask* _streamingTask = nullptr; @@ -174,12 +178,27 @@ public: /// /// Gets amount of the level of details in the model. /// - virtual int32 GetLODsCount() const = 0; + API_PROPERTY(Sealed) virtual int32 GetLODsCount() const = 0; + + /// + /// Gets the mesh for a particular LOD index. + /// + virtual const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const = 0; + + /// + /// Gets the mesh for a particular LOD index. + /// + API_FUNCTION(Sealed) virtual MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) = 0; /// /// Gets the meshes for a particular LOD index. /// - virtual void GetMeshes(Array& meshes, int32 lodIndex = 0) = 0; + virtual void GetMeshes(Array& meshes, int32 lodIndex = 0) const = 0; + + /// + /// Gets the meshes for a particular LOD index. + /// + API_FUNCTION(Sealed) virtual void GetMeshes(API_PARAM(Out) Array& meshes, int32 lodIndex = 0) = 0; public: /// @@ -196,6 +215,38 @@ public: /// The data (it may be missing if failed to get it). void GetLODData(int32 lodIndex, BytesContainer& data) const; +#if USE_EDITOR + /// + /// Saves this asset to the file. Supported only in Editor. + /// + /// If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread. + /// True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location, and it's loaded. + /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. + /// True when cannot save data, otherwise false. + API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty); +#endif + +protected: + friend class AssetExporters; + friend class MeshAccessor; + struct MeshData + { + uint32 Triangles, Vertices, IBStride; + Array> VBData; + Array> VBLayout; + const void* IBData; + }; + virtual bool LoadMesh(class MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly = nullptr); + bool LoadHeader(ReadStream& stream, byte& headerVersion); +#if USE_EDITOR + virtual bool SaveHeader(WriteStream& stream); + static bool SaveHeader(WriteStream& stream, const class ModelData& modelData); + bool SaveLOD(WriteStream& stream, int32 lodIndex) const; + static bool SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool(saveMesh)(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex) = nullptr); + virtual bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const; + virtual bool Save(bool withMeshDataFromGpu, Function& getChunk); +#endif + public: // [BinaryAsset] void CancelStreaming() override; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 9450ddf64..ff3a5036a 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -20,6 +20,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #if USE_EDITOR +#include "Engine/Graphics/Models/ModelData.h" #include "Engine/Serialization/MemoryWriteStream.h" #endif @@ -121,9 +122,9 @@ SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bo } else { - #if !BUILD_RELEASE +#if !BUILD_RELEASE LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString()); - #endif +#endif return mapping; } } @@ -231,7 +232,7 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren if (!model->CanBeRendered()) return; if (!info.Buffer->IsValidFor(model)) - info.Buffer->Setup(model); + info.Buffer->Setup(model); const auto frame = Engine::FrameCount; const auto modelFrame = info.DrawState->PrevFrame + 1; @@ -395,7 +396,7 @@ bool SkinnedModel::SetupSkeleton(const Array& nodes, const Array MAX_uint16) return true; - if (bones.Count() <= 0 || bones.Count() > MAX_BONES_PER_MODEL) + if (bones.Count() <= 0 || bones.Count() > MODEL_MAX_BONES_PER_MODEL) return true; auto model = this; if (!model->IsVirtual()) @@ -431,310 +432,6 @@ bool SkinnedModel::SetupSkeleton(const Array& nodes, const ArrayWriteByte(1); - - // Min Screen Size - stream->WriteFloat(MinScreenSize); - - // Amount of material slots - stream->WriteInt32(MaterialSlots.Count()); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < MaterialSlots.Count(); materialSlotIndex++) - { - auto& slot = MaterialSlots[materialSlotIndex]; - - const auto id = slot.Material.GetID(); - stream->Write(id); - stream->WriteByte(static_cast(slot.ShadowsMode)); - stream->WriteString(slot.Name, 11); - } - - // Amount of LODs - const int32 lods = LODs.Count(); - stream->WriteByte(lods); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) - { - auto& lod = LODs[lodIndex]; - - // Screen Size - stream->WriteFloat(lod.ScreenSize); - - // Amount of meshes - const int32 meshes = lod.Meshes.Count(); - stream->WriteUint16(meshes); - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++) - { - const auto& mesh = lod.Meshes[meshIndex]; - - // Material Slot index - stream->WriteInt32(mesh.GetMaterialSlotIndex()); - - // Box - const auto box = mesh.GetBox(); - stream->WriteBoundingBox(box); - - // Sphere - const auto sphere = mesh.GetSphere(); - stream->WriteBoundingSphere(sphere); - - // Blend Shapes - stream->WriteUint16(mesh.BlendShapes.Count()); - for (int32 blendShapeIndex = 0; blendShapeIndex < mesh.BlendShapes.Count(); blendShapeIndex++) - { - auto& blendShape = mesh.BlendShapes[blendShapeIndex]; - stream->WriteString(blendShape.Name, 13); - stream->WriteFloat(blendShape.Weight); - } - } - } - - // Skeleton - { - stream->WriteInt32(Skeleton.Nodes.Count()); - - // For each node - for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++) - { - auto& node = Skeleton.Nodes[nodeIndex]; - - stream->Write(node.ParentIndex); - stream->WriteTransform(node.LocalTransform); - stream->WriteString(node.Name, 71); - } - - stream->WriteInt32(Skeleton.Bones.Count()); - - // For each bone - for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++) - { - auto& bone = Skeleton.Bones[boneIndex]; - - stream->Write(bone.ParentIndex); - stream->Write(bone.NodeIndex); - stream->WriteTransform(bone.LocalTransform); - stream->Write(bone.OffsetMatrix); - } - } - - // Retargeting - { - stream->WriteInt32(_skeletonRetargets.Count()); - for (const auto& retarget : _skeletonRetargets) - { - stream->Write(retarget.SourceAsset); - stream->Write(retarget.SkeletonAsset); - stream->Write(retarget.NodesMapping); - } - } - } - - // Use a temporary chunks for data storage for virtual assets - FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS]; - Platform::MemoryClear(tmpChunks, sizeof(tmpChunks)); - Array chunks; - if (IsVirtual()) - chunks.Resize(ASSET_FILE_DATA_CHUNKS); -#define GET_CHUNK(index) (IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index)) - - // Check if use data from drive or from GPU - if (withMeshDataFromGpu) - { - // Download all meshes buffers - Array tasks; - for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { - auto& lod = LODs[lodIndex]; - - const int32 meshesCount = lod.Meshes.Count(); - struct MeshData - { - BytesContainer VB0; - BytesContainer IB; - - uint32 DataSize() const - { - return VB0.Length() + IB.Length(); - } - }; - Array meshesData; - meshesData.Resize(meshesCount); - tasks.EnsureCapacity(meshesCount * 4); - - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - const auto& mesh = lod.Meshes[meshIndex]; - auto& meshData = meshesData[meshIndex]; - - // Vertex Buffer 0 (required) - auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0); - if (task == nullptr) - return true; - task->Start(); - tasks.Add(task); - - // Index Buffer (required) - task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB); - if (task == nullptr) - return true; - task->Start(); - tasks.Add(task); - } - if (Task::WaitAll(tasks)) - return true; - tasks.Clear(); - - // Create meshes data - { - int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool)); - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - dataSize += meshesData[meshIndex].DataSize(); - MemoryWriteStream meshesStream(Math::RoundUpToPowerOf2(dataSize)); - - meshesStream.WriteByte(1); - for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - const auto& mesh = lod.Meshes[meshIndex]; - const auto& meshData = meshesData[meshIndex]; - - const uint32 vertices = mesh.GetVertexCount(); - const uint32 triangles = mesh.GetTriangleCount(); - const uint32 vb0Size = vertices * sizeof(VB0SkinnedElementType); - const uint32 indicesCount = triangles * 3; - const bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16; - const bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); - const uint32 ibSize = indicesCount * (use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32)); - - if (vertices == 0 || triangles == 0) - { - LOG(Warning, "Cannot save model with empty meshes."); - return true; - } - if ((uint32)meshData.VB0.Length() < vb0Size) - { - LOG(Warning, "Invalid vertex buffer 0 size."); - return true; - } - if ((uint32)meshData.IB.Length() < ibSize) - { - LOG(Warning, "Invalid index buffer size."); - return true; - } - - // #MODEL_DATA_FORMAT_USAGE - meshesStream.WriteUint32(vertices); - meshesStream.WriteUint32(triangles); - meshesStream.WriteUint16(mesh.BlendShapes.Count()); - for (const auto& blendShape : mesh.BlendShapes) - { - meshesStream.WriteBool(blendShape.UseNormals); - meshesStream.WriteUint32(blendShape.MinVertexIndex); - meshesStream.WriteUint32(blendShape.MaxVertexIndex); - meshesStream.WriteUint32(blendShape.Vertices.Count()); - meshesStream.WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex)); - } - meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size); - if (shouldUse16BitIndexBuffer == use16BitIndexBuffer) - { - meshesStream.WriteBytes(meshData.IB.Get(), ibSize); - } - else if (shouldUse16BitIndexBuffer) - { - const auto ib = reinterpret_cast(meshData.IB.Get()); - for (uint32 i = 0; i < indicesCount; i++) - { - meshesStream.WriteUint16(ib[i]); - } - } - else - { - CRASH; - } - } - - // Override meshes data chunk with the fetched GPU meshes memory - auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); - if (lodChunk == nullptr) - return true; - lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); - } - } - } - else - { - ASSERT(!IsVirtual()); - - // Load all chunks with a mesh data - for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) - { - if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) - return true; - } - } - - // Set mesh header data - auto headerChunk = GET_CHUNK(0); - ASSERT(headerChunk != nullptr); - headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition()); - -#undef GET_CHUNK - - // Save - AssetInitData data; - data.SerializedVersion = SerializedVersion; - if (IsVirtual()) - Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks)); - const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true); - if (IsVirtual()) - Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks)); - if (saveResult) - { - LOG(Error, "Cannot save \'{0}\'", ToString()); - return true; - } - - return false; -} - -#endif - bool SkinnedModel::Init(const Span& meshesCountPerLod) { if (meshesCountPerLod.IsInvalid() || meshesCountPerLod.Length() > MODEL_MAX_LODS) @@ -777,6 +474,329 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) return false; } +void BlendShape::LoadHeader(ReadStream& stream, byte headerVersion) +{ + stream.Read(Name, 13); + stream.Read(Weight); +} + +void BlendShape::Load(ReadStream& stream, byte meshVersion) +{ + UseNormals = stream.ReadBool(); + stream.ReadUint32(&MinVertexIndex); + stream.ReadUint32(&MaxVertexIndex); + uint32 blendShapeVertices; + stream.ReadUint32(&blendShapeVertices); + Vertices.Resize(blendShapeVertices); + stream.ReadBytes(Vertices.Get(), Vertices.Count() * sizeof(BlendShapeVertex)); +} + +#if USE_EDITOR + +void BlendShape::SaveHeader(WriteStream& stream) const +{ + stream.Write(Name, 13); + stream.Write(Weight); +} + +void BlendShape::Save(WriteStream& stream) const +{ + stream.WriteBool(UseNormals); + stream.WriteUint32(MinVertexIndex); + stream.WriteUint32(MaxVertexIndex); + stream.WriteUint32(Vertices.Count()); + stream.WriteBytes(Vertices.Get(), Vertices.Count() * sizeof(BlendShapeVertex)); +} + +#endif + +bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) +{ + if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly)) + return true; + static_assert(MeshVersion == 2, "Update code"); + auto skinnedMesh = (SkinnedMesh*)mesh; + + // Blend Shapes + uint16 blendShapesCount; + stream.Read(blendShapesCount); + if (dataIfReadOnly) + { + // Skip blend shapes + BlendShape tmp; + while (blendShapesCount-- != 0) + tmp.Load(stream, meshVersion); + return false; + } + if (blendShapesCount != skinnedMesh->BlendShapes.Count()) + { + LOG(Warning, "Incorrect blend shapes amount: {} (expected: {})", blendShapesCount, skinnedMesh->BlendShapes.Count()); + return true; + } + for (auto& blendShape : skinnedMesh->BlendShapes) + blendShape.Load(stream, meshVersion); + + return false; +} + +bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) +{ + if (ModelBase::LoadHeader(stream, headerVersion)) + return true; + static_assert(HeaderVersion == 2, "Update code"); + + // LODs + byte lods = stream.ReadByte(); + if (lods > MODEL_MAX_LODS) + return true; + LODs.Resize(lods); + _initialized = true; + for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) + { + auto& lod = LODs[lodIndex]; + lod._model = this; + lod._lodIndex = lodIndex; + stream.Read(lod.ScreenSize); + + // Meshes + uint16 meshesCount; + stream.Read(meshesCount); + if (meshesCount > MODEL_MAX_MESHES) + return true; + ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount); + lod.Meshes.Resize(meshesCount, false); + for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + SkinnedMesh& mesh = lod.Meshes[meshIndex]; + mesh.Link(this, lodIndex, meshIndex); + + // Material Slot index + int32 materialSlotIndex; + stream.Read(materialSlotIndex); + if (materialSlotIndex < 0 || materialSlotIndex >= MaterialSlots.Count()) + { + LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, MaterialSlots.Count()); + return true; + } + mesh.SetMaterialSlotIndex(materialSlotIndex); + + // Bounds + BoundingBox box; + stream.Read(box); + BoundingSphere sphere; + stream.Read(sphere); + mesh.SetBounds(box, sphere); + + // Blend Shapes + uint16 blendShapes; + stream.Read(blendShapes); + mesh.BlendShapes.Resize(blendShapes); + for (auto& blendShape : mesh.BlendShapes) + blendShape.LoadHeader(stream, headerVersion); + } + } + + // Skeleton + { + int32 nodesCount; + stream.Read(nodesCount); + if (nodesCount < 0) + return true; + Skeleton.Nodes.Resize(nodesCount, false); + for (auto& node : Skeleton.Nodes) + { + stream.Read(node.ParentIndex); + stream.Read(node.LocalTransform); + stream.Read(node.Name, 71); + } + + int32 bonesCount; + stream.Read(bonesCount); + if (bonesCount < 0) + return true; + Skeleton.Bones.Resize(bonesCount, false); + for (auto& bone : Skeleton.Bones) + { + stream.Read(bone.ParentIndex); + stream.Read(bone.NodeIndex); + stream.Read(bone.LocalTransform); + stream.Read(bone.OffsetMatrix); + } + } + + // Retargeting + { + int32 entriesCount; + stream.Read(entriesCount); + _skeletonRetargets.Resize(entriesCount); + for (auto& retarget : _skeletonRetargets) + { + stream.Read(retarget.SourceAsset); + stream.Read(retarget.SkeletonAsset); + stream.Read(retarget.NodesMapping); + } + } + + return false; +} + +#if USE_EDITOR + +bool SkinnedModel::SaveHeader(WriteStream& stream) +{ + if (ModelBase::SaveHeader(stream)) + return true; + static_assert(HeaderVersion == 2, "Update code"); + + // LODs + stream.Write((byte)LODs.Count()); + for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) + { + auto& lod = LODs[lodIndex]; + stream.Write(lod.ScreenSize); + + // Meshes + stream.Write((uint16)lod.Meshes.Count()); + for (const auto& mesh : lod.Meshes) + { + stream.Write(mesh.GetMaterialSlotIndex()); + stream.Write(mesh.GetBox()); + stream.Write(mesh.GetSphere()); + + // Blend Shapes + const int32 blendShapes = mesh.BlendShapes.Count(); + stream.Write((uint16)blendShapes); + for (const auto& blendShape : mesh.BlendShapes) + blendShape.Save(stream); + } + } + + // Skeleton nodes + const auto& skeletonNodes = Skeleton.Nodes; + stream.Write(skeletonNodes.Count()); + for (const auto& node : skeletonNodes) + { + stream.Write(node.ParentIndex); + stream.Write(node.LocalTransform); + stream.Write(node.Name, 71); + } + + // Skeleton bones + const auto& skeletonBones = Skeleton.Bones; + stream.WriteInt32(skeletonBones.Count()); + for (const auto& bone : skeletonBones) + { + stream.Write(bone.ParentIndex); + stream.Write(bone.NodeIndex); + stream.Write(bone.LocalTransform); + stream.Write(bone.OffsetMatrix); + } + + // Retargeting + stream.WriteInt32(_skeletonRetargets.Count()); + for (const auto& retarget : _skeletonRetargets) + { + stream.Write(retarget.SourceAsset); + stream.Write(retarget.SkeletonAsset); + stream.Write(retarget.NodesMapping); + } + + return false; +} + +bool SkinnedModel::SaveHeader(WriteStream& stream, const ModelData& modelData) +{ + if (ModelBase::SaveHeader(stream, modelData)) + return true; + static_assert(HeaderVersion == 2, "Update code"); + + // LODs + stream.Write((byte)modelData.LODs.Count()); + for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++) + { + auto& lod = modelData.LODs[lodIndex]; + + // Screen Size + stream.Write(lod.ScreenSize); + + // Meshes + stream.Write((uint16)lod.Meshes.Count()); + for (const auto& mesh : lod.Meshes) + { + BoundingBox box; + BoundingSphere sphere; + mesh->CalculateBounds(box, sphere); + stream.Write(mesh->MaterialSlotIndex); + stream.Write(box); + stream.Write(sphere); + + // Blend Shapes + const int32 blendShapes = mesh->BlendShapes.Count(); + stream.WriteUint16((uint16)blendShapes); + for (const auto& blendShape : mesh->BlendShapes) + blendShape.SaveHeader(stream); + } + } + + // Skeleton nodes + const auto& skeletonNodes = modelData.Skeleton.Nodes; + stream.WriteInt32(skeletonNodes.Count()); + for (const auto& node : skeletonNodes) + { + stream.Write(node.ParentIndex); + stream.Write(node.LocalTransform); + stream.Write(node.Name, 71); + } + + // Skeleton bones + const auto& skeletonBones = modelData.Skeleton.Bones; + stream.WriteInt32(skeletonBones.Count()); + for (const auto& bone : skeletonBones) + { + stream.Write(bone.ParentIndex); + stream.Write(bone.NodeIndex); + stream.Write(bone.LocalTransform); + stream.Write(bone.OffsetMatrix); + } + + // Retargeting + stream.Write(0); // Empty list + + return false; +} + +bool SkinnedModel::SaveMesh(WriteStream& stream, const MeshBase* mesh) const +{ + if (ModelBase::SaveMesh(stream, mesh)) + return true; + static_assert(MeshVersion == 2, "Update code"); + auto skinnedMesh = (const SkinnedMesh*)mesh; + + // Blend Shapes + uint16 blendShapesCount = skinnedMesh->BlendShapes.Count(); + stream.Write(blendShapesCount); + for (auto& blendShape : skinnedMesh->BlendShapes) + blendShape.Save(stream); + + return false; +} + +bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex) +{ + static_assert(MeshVersion == 2, "Update code"); + auto& mesh = modelData.LODs[lodIndex].Meshes[meshIndex]; + + // Blend Shapes + uint16 blendShapesCount = (uint16)mesh->BlendShapes.Count(); + stream.Write(blendShapesCount); + for (auto& blendShape : mesh->BlendShapes) + blendShape.Save(stream); + + return false; +} + +#endif + void SkinnedModel::ClearSkeletonMapping() { for (auto& e : _skeletonMappingCache) @@ -841,6 +861,26 @@ int32 SkinnedModel::GetLODsCount() const return LODs.Count(); } +const MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) const +{ + auto& lod = LODs[lodIndex]; + return &lod.Meshes[meshIndex]; +} + +MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) +{ + auto& lod = LODs[lodIndex]; + return &lod.Meshes[meshIndex]; +} + +void SkinnedModel::GetMeshes(Array& meshes, int32 lodIndex) const +{ + auto& lod = LODs[lodIndex]; + meshes.Resize(lod.Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + meshes[meshIndex] = &lod.Meshes[meshIndex]; +} + void SkinnedModel::GetMeshes(Array& meshes, int32 lodIndex) { auto& lod = LODs[lodIndex]; @@ -889,145 +929,11 @@ Asset::LoadResult SkinnedModel::load() if (chunk0 == nullptr || chunk0->IsMissing()) return LoadResult::MissingDataChunk; MemoryReadStream headerStream(chunk0->Get(), chunk0->Size()); - ReadStream* stream = &headerStream; - // Header Version - byte version = stream->ReadByte(); - - // Min Screen Size - stream->ReadFloat(&MinScreenSize); - - // Amount of material slots - int32 materialSlotsCount; - stream->ReadInt32(&materialSlotsCount); - if (materialSlotsCount < 0 || materialSlotsCount > 4096) + // Load asset data (anything but mesh contents that use streaming) + byte headerVersion; + if (LoadHeader(headerStream, headerVersion)) return LoadResult::InvalidData; - MaterialSlots.Resize(materialSlotsCount, false); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) - { - auto& slot = MaterialSlots[materialSlotIndex]; - - // Material - Guid materialId; - stream->Read(materialId); - slot.Material = materialId; - - // Shadows Mode - slot.ShadowsMode = static_cast(stream->ReadByte()); - - // Name - stream->ReadString(&slot.Name, 11); - } - - // Amount of LODs - byte lods; - stream->ReadByte(&lods); - if (lods > MODEL_MAX_LODS) - return LoadResult::InvalidData; - LODs.Resize(lods); - _initialized = true; - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lods; lodIndex++) - { - auto& lod = LODs[lodIndex]; - lod._model = this; - lod._lodIndex = lodIndex; - - // Screen Size - stream->ReadFloat(&lod.ScreenSize); - - // Amount of meshes - uint16 meshesCount; - stream->ReadUint16(&meshesCount); - if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES) - return LoadResult::InvalidData; - ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount); - - // Allocate memory - lod.Meshes.Resize(meshesCount, false); - - // For each mesh - for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) - { - SkinnedMesh& mesh = lod.Meshes[meshIndex]; - mesh.Link(this, lodIndex, meshIndex); - - // Material Slot index - int32 materialSlotIndex; - stream->ReadInt32(&materialSlotIndex); - if (materialSlotIndex < 0 || materialSlotIndex >= materialSlotsCount) - { - LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount); - return LoadResult::InvalidData; - } - mesh.SetMaterialSlotIndex(materialSlotIndex); - - // Bounds - BoundingBox box; - stream->ReadBoundingBox(&box); - BoundingSphere sphere; - stream->ReadBoundingSphere(&sphere); - mesh.SetBounds(box, sphere); - - // Blend Shapes - uint16 blendShapes; - stream->ReadUint16(&blendShapes); - mesh.BlendShapes.Resize(blendShapes); - for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++) - { - auto& blendShape = mesh.BlendShapes[blendShapeIndex]; - stream->ReadString(&blendShape.Name, 13); - stream->ReadFloat(&blendShape.Weight); - } - } - } - - // Skeleton - { - int32 nodesCount; - stream->ReadInt32(&nodesCount); - if (nodesCount <= 0) - return LoadResult::InvalidData; - Skeleton.Nodes.Resize(nodesCount, false); - for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) - { - auto& node = Skeleton.Nodes.Get()[nodeIndex]; - stream->Read(node.ParentIndex); - stream->ReadTransform(&node.LocalTransform); - stream->ReadString(&node.Name, 71); - } - - int32 bonesCount; - stream->ReadInt32(&bonesCount); - if (bonesCount <= 0) - return LoadResult::InvalidData; - Skeleton.Bones.Resize(bonesCount, false); - for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) - { - auto& bone = Skeleton.Bones.Get()[boneIndex]; - stream->Read(bone.ParentIndex); - stream->Read(bone.NodeIndex); - stream->ReadTransform(&bone.LocalTransform); - stream->Read(bone.OffsetMatrix); - } - } - - // Retargeting - { - int32 entriesCount; - stream->ReadInt32(&entriesCount); - _skeletonRetargets.Resize(entriesCount); - for (int32 entryIndex = 0; entryIndex < entriesCount; entryIndex++) - { - auto& retarget = _skeletonRetargets[entryIndex]; - stream->Read(retarget.SourceAsset); - stream->Read(retarget.SkeletonAsset); - stream->Read(retarget.NodesMapping); - } - } // Request resource streaming StartStreaming(true); diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index 78350285c..c996e987c 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -139,7 +139,7 @@ public: /// API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase { - DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5); + DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 30); friend SkinnedMesh; public: // Skeleton mapping descriptor. @@ -331,17 +331,6 @@ public: /// True if failed, otherwise false. API_FUNCTION() bool SetupSkeleton(const Array& nodes, const Array& bones, bool autoCalculateOffsetMatrix); -#if USE_EDITOR - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread. - /// True if save also GPU mesh buffers, otherwise will keep data in storage unmodified. Valid only if saving the same asset to the same location and it's loaded. - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty); -#endif - private: /// /// Initializes this skinned model to an empty collection of meshes. Ensure to init SkeletonData manually after the call. @@ -350,6 +339,17 @@ private: /// True if failed, otherwise false. bool Init(const Span& meshesCountPerLod); + // [ModelBase] + bool LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) override; + bool LoadHeader(ReadStream& stream, byte& headerVersion); +#if USE_EDITOR + friend class ImportModel; + bool SaveHeader(WriteStream& stream) override; + static bool SaveHeader(WriteStream& stream, const ModelData& modelData); + bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const override; + static bool SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex); +#endif + void ClearSkeletonMapping(); void OnSkeletonMappingSourceAssetUnloaded(Asset* obj); @@ -384,6 +384,9 @@ public: uint64 GetMemoryUsage() const override; void SetupMaterialSlots(int32 slotsCount) override; int32 GetLODsCount() const override; + const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override; + MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override; + void GetMeshes(Array& meshes, int32 lodIndex = 0) const override; void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; diff --git a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h index 8b7d70084..a608585ab 100644 --- a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h @@ -5,6 +5,9 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" +#include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" /// /// Model Asset Upgrader @@ -13,17 +16,145 @@ class ModelAssetUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// ModelAssetUpgrader() { - static const Upgrader upgraders[] = + Upgrader upgraders[] = { - {}, + { 25, 30, Upgrade_25_To_30 }, // [Deprecated in v1.10] }; setup(upgraders, ARRAY_COUNT(upgraders)); } + +PRAGMA_DISABLE_DEPRECATION_WARNINGS + static bool Upgrade_25_To_30(AssetMigrationContext& context) + { + // [Deprecated in v1.10] + ASSERT(context.Input.SerializedVersion == 25 && context.Output.SerializedVersion == 30); + MemoryWriteStream output; + + // Upgrade header + byte lodsCount; + Array> meshesCountPerLod; + { + if (context.AllocateChunk(0)) + return true; + auto& data = context.Input.Header.Chunks[0]->Data; + MemoryReadStream stream(data.Get(), data.Length()); + + constexpr byte headerVersion = 2; + output.Write(headerVersion); + + float minScreenSize; + stream.Read(minScreenSize); + output.Write(minScreenSize); + + // Materials + int32 materialSlotsCount; + stream.Read(materialSlotsCount); + output.Write(materialSlotsCount); + CHECK_RETURN(materialSlotsCount >= 0 && materialSlotsCount < 4096, true); + for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) + { + Guid materialId; + stream.Read(materialId); + output.Write(materialId); + byte shadowsCastingMode; + stream.Read(shadowsCastingMode); + output.Write(shadowsCastingMode); + String name; + stream.Read(name, 11); + output.Write(name, 11); + } + + // LODs + stream.Read(lodsCount); + output.Write(lodsCount); + CHECK_RETURN(lodsCount <= 6, true); + meshesCountPerLod.Resize(lodsCount); + for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) + { + float screenSize; + stream.Read(screenSize); + output.Write(screenSize); + + // Amount of meshes + uint16 meshesCount; + stream.Read(meshesCount); + output.Write(meshesCount); + CHECK_RETURN(meshesCount > 0 && meshesCount < 4096, true); + meshesCountPerLod[lodIndex] = meshesCount; + for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + int32 materialSlotIndex; + stream.Read(materialSlotIndex); + output.Write(materialSlotIndex); + BoundingBox box; + stream.Read(box); + output.Write(box); + BoundingSphere sphere; + stream.Read(sphere); + output.Write(sphere); + bool hasLightmapUVs = stream.ReadBool(); + int8 lightmapUVsIndex = hasLightmapUVs ? 1 : -1; + output.Write(lightmapUVsIndex); + } + } + + context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); + } + + // Upgrade meshes data + for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) + { + output.SetPosition(0); + const int32 chunkIndex = lodIndex + 1; + const FlaxChunk* lodData = context.Input.Header.Chunks[chunkIndex]; + MemoryReadStream stream(lodData->Get(), lodData->Size()); + + constexpr byte meshVersion = 2; + output.Write(meshVersion); + for (int32 meshIndex = 0; meshIndex < meshesCountPerLod[lodIndex]; meshIndex++) + { + // Descriptor + uint32 vertices, triangles; + stream.Read(vertices); + stream.Read(triangles); + output.Write(vertices); + output.Write(triangles); + auto vb0 = stream.Move(vertices); + auto vb1 = stream.Move(vertices); + bool hasColors = stream.ReadBool(); + VB2ElementType18* vb2 = nullptr; + if (hasColors) + vb2 = stream.Move(vertices); + uint32 indicesCount = triangles * 3; + bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + auto ibData = stream.Move(indicesCount * ibStride); + byte vbCount = hasColors ? 3 : 2; + output.Write(vbCount); + output.Write(VB0ElementType18::GetLayout()->GetElements()); + output.Write(VB1ElementType18::GetLayout()->GetElements()); + if (hasColors) + output.Write(VB2ElementType18::GetLayout()->GetElements()); + + // Buffers + output.WriteBytes(vb0, vertices * sizeof(VB0ElementType18)); + output.WriteBytes(vb1, vertices * sizeof(VB1ElementType18)); + if (hasColors) + output.WriteBytes(vb2, vertices * sizeof(VB2ElementType18)); + output.WriteBytes(ibData, indicesCount * ibStride); + } + + if (context.AllocateChunk(chunkIndex)) + return true; + context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); + } + + // Copy SDF data + return CopyChunk(context, 15); + } +PRAGMA_ENABLE_DEPRECATION_WARNINGS }; #endif diff --git a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h index fdd9e66a5..bf339a3ea 100644 --- a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h @@ -5,6 +5,10 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" +#include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Graphics/Models/BlendShape.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" /// /// Skinned Model Asset Upgrader @@ -13,17 +17,218 @@ class SkinnedModelAssetUpgrader : public BinaryAssetUpgrader { public: - /// - /// Initializes a new instance of the class. - /// SkinnedModelAssetUpgrader() { - static const Upgrader upgraders[] = + const Upgrader upgraders[] = { - {}, + { 5, 30, Upgrade_5_To_30 }, // [Deprecated in v1.10] }; setup(upgraders, ARRAY_COUNT(upgraders)); } + +PRAGMA_DISABLE_DEPRECATION_WARNINGS + static bool Upgrade_5_To_30(AssetMigrationContext& context) + { + // [Deprecated in v1.10] + ASSERT(context.Input.SerializedVersion == 5 && context.Output.SerializedVersion == 30); + MemoryWriteStream output; + + // Upgrade header + byte lodsCount; + Array> meshesCountPerLod; + { + if (context.AllocateChunk(0)) + return true; + auto& data = context.Input.Header.Chunks[0]->Data; + MemoryReadStream stream(data.Get(), data.Length()); + + constexpr byte headerVersion = 2; + byte headerVersionOld; + stream.Read(headerVersionOld); + CHECK_RETURN(headerVersionOld == 1, true); + output.Write(headerVersion); + + float minScreenSize; + stream.Read(minScreenSize); + output.Write(minScreenSize); + + // Materials + int32 materialSlotsCount; + stream.Read(materialSlotsCount); + output.Write(materialSlotsCount); + CHECK_RETURN(materialSlotsCount >= 0 && materialSlotsCount < 4096, true); + for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) + { + Guid materialId; + stream.Read(materialId); + output.Write(materialId); + byte shadowsCastingMode; + stream.Read(shadowsCastingMode); + output.Write(shadowsCastingMode); + String name; + stream.Read(name, 11); + output.Write(name, 11); + } + + // LODs + stream.Read(lodsCount); + output.Write(lodsCount); + CHECK_RETURN(materialSlotsCount <= 6, true); + meshesCountPerLod.Resize(lodsCount); + for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) + { + float screenSize; + stream.Read(screenSize); + output.Write(screenSize); + + // Amount of meshes + uint16 meshesCount; + stream.Read(meshesCount); + output.Write(meshesCount); + CHECK_RETURN(meshesCount > 0 && meshesCount < 4096, true); + meshesCountPerLod[lodIndex] = meshesCount; + for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + int32 materialSlotIndex; + stream.Read(materialSlotIndex); + output.Write(materialSlotIndex); + BoundingBox box; + stream.Read(box); + output.Write(box); + BoundingSphere sphere; + stream.Read(sphere); + output.Write(sphere); + uint16 blendShapesCount; + stream.Read(blendShapesCount); + output.Write(blendShapesCount); + for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++) + { + String name; + stream.Read(name, 13); + output.Write(name, 13); + float weight; + stream.Read(weight); + output.Write(weight); + } + } + } + + // Skeleton + { + int32 nodesCount; + stream.Read(nodesCount); + output.Write(nodesCount); + if (nodesCount < 0) + return true; + for (int32 i = 0 ; i < nodesCount; i++) + { + int32 parentIndex; + Transform localTransform; + String name; + stream.Read(parentIndex); + output.Write(parentIndex); + stream.Read(localTransform); + output.Write(localTransform); + stream.Read(name, 71); + output.Write(name, 71); + } + + int32 bonesCount; + stream.Read(bonesCount); + output.Write(bonesCount); + if (bonesCount < 0) + return true; + for (int32 i = 0 ; i < bonesCount; i++) + { + int32 parentIndex, nodeIndex; + Transform localTransform; + String name; + Matrix offsetMatrix; + stream.Read(parentIndex); + output.Write(parentIndex); + stream.Read(nodeIndex); + output.Write(nodeIndex); + stream.Read(localTransform); + output.Write(localTransform); + stream.Read(offsetMatrix); + output.Write(offsetMatrix); + } + } + + // Retargeting + { + int32 entriesCount; + stream.Read(entriesCount); + output.Write(entriesCount); + for (int32 i = 0 ; i < entriesCount; i++) + { + Guid sourceAsset, skeletonAsset; + Dictionary nodesMapping; + stream.Read(sourceAsset); + output.Write(sourceAsset); + stream.Read(skeletonAsset); + output.Write(skeletonAsset); + stream.Read(nodesMapping); + output.Write(nodesMapping); + } + } + + context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); + } + + // Upgrade meshes data + for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) + { + output.SetPosition(0); + const int32 chunkIndex = lodIndex + 1; + const FlaxChunk* lodData = context.Input.Header.Chunks[chunkIndex]; + MemoryReadStream stream(lodData->Get(), lodData->Size()); + + constexpr byte meshVersion = 2; + byte headerVersionOld; + stream.Read(headerVersionOld); + CHECK_RETURN(headerVersionOld == 1, true); + output.Write(meshVersion); + for (int32 meshIndex = 0; meshIndex < meshesCountPerLod[lodIndex]; meshIndex++) + { + // Descriptor + uint32 vertices, triangles; + stream.Read(vertices); + stream.Read(triangles); + output.Write(vertices); + output.Write(triangles); + uint16 blendShapesCount; + stream.Read(blendShapesCount); + Array blendShapes; + blendShapes.Resize(blendShapesCount); + for (auto& blendShape : blendShapes) + blendShape.Load(stream, meshVersion); + auto vb0 = stream.Move(vertices); + uint32 indicesCount = triangles * 3; + bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + auto ibData = stream.Move(indicesCount * ibStride); + output.Write((byte)1); + output.Write(VB0SkinnedElementType2::GetLayout()->GetElements()); + + // Buffers + output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType2)); + output.WriteBytes(ibData, indicesCount * ibStride); + + // Blend Shapes + output.Write(blendShapesCount); + for (auto& blendShape : blendShapes) + blendShape.Save(output); + } + + if (context.AllocateChunk(chunkIndex)) + return true; + context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); + } + + return false; + } +PRAGMA_ENABLE_DEPRECATION_WARNINGS }; #endif diff --git a/Source/Engine/ContentExporters/ExportModel.cpp b/Source/Engine/ContentExporters/ExportModel.cpp index 642886c7b..74e5a140a 100644 --- a/Source/Engine/ContentExporters/ExportModel.cpp +++ b/Source/Engine/ContentExporters/ExportModel.cpp @@ -9,12 +9,12 @@ #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Core/DeleteMe.h" ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) { - // Prepare - auto asset = (Model*)context.Asset.Get(); + auto asset = (ModelBase*)context.Asset.Get(); auto lock = asset->Storage->LockSafe(); auto path = GET_OUTPUT_PATH(context, "obj"); const int32 lodIndex = 0; @@ -26,7 +26,6 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) const auto chunk = asset->GetChunk(chunkIndex); if (!chunk) return ExportAssetResult::CannotLoadData; - MemoryReadStream stream(chunk->Get(), chunk->Size()); FileWriteStream* output = FileWriteStream::Open(path); if (output == nullptr) @@ -37,62 +36,61 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get())); // Extract all meshes - const auto& lod = asset->LODs[lodIndex]; - int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0 - for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) + if (asset->GetLODsCount() <= lodIndex) + return ExportAssetResult::Error; + Array meshes; + asset->GetMeshes(meshes, lodIndex); + uint32 vertexStart = 1; // OBJ counts vertices from 1 not from 0 + ModelBase::MeshData meshData; + byte meshVersion = stream.ReadByte(); + for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) { - auto& mesh = lod.Meshes[meshIndex]; - - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) + auto mesh = meshes[meshIndex]; + if (asset->LoadMesh(stream, meshVersion, mesh, &meshData)) + return ExportAssetResult::CannotLoadData; + if (meshData.Vertices == 0 || meshData.Triangles == 0) return ExportAssetResult::Error; - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Move(vertices); - } - auto ib = stream.Move(indicesCount * ibStride); - + MeshAccessor accessor; + if (accessor.LoadFromMeshData(&meshData)) + return ExportAssetResult::CannotLoadAsset; output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex)); - for (uint32 i = 0; i < vertices; i++) + auto positionStream = accessor.Position(); + if (!positionStream.IsValid()) + return ExportAssetResult::Error; + for (uint32 i = 0; i < meshData.Vertices; i++) { - auto v = vb0[i].Position; + auto v = positionStream.GetFloat3(i); output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z)); } - output->WriteChar('\n'); - for (uint32 i = 0; i < vertices; i++) + auto texCoordStream = accessor.TexCoord(); + if (texCoordStream.IsValid()) { - auto v = vb1[i].TexCoord.ToFloat2(); - output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y)); + for (uint32 i = 0; i < meshData.Vertices; i++) + { + auto v = texCoordStream.GetFloat2(i); + output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y)); + } + output->WriteChar('\n'); } - output->WriteChar('\n'); - - for (uint32 i = 0; i < vertices; i++) + auto normalStream = accessor.Normal(); + if (normalStream.IsValid()) { - auto v = vb1[i].Normal.ToFloat3() * 2.0f - 1.0f; - output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z)); + for (uint32 i = 0; i < meshData.Vertices; i++) + { + auto v = normalStream.GetFloat3(i) * 2.0f - 1.0f; + output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z)); + } + output->WriteChar('\n'); } - output->WriteChar('\n'); - - if (use16BitIndexBuffer) + if (meshData.IBStride == sizeof(uint16)) { - auto t = (uint16*)ib; - for (uint32 i = 0; i < triangles; i++) + auto t = (const uint16*)meshData.IBData; + for (uint32 i = 0; i < meshData.Triangles; i++) { auto i0 = vertexStart + *t++; auto i1 = vertexStart + *t++; @@ -102,8 +100,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) } else { - auto t = (uint32*)ib; - for (uint32 i = 0; i < triangles; i++) + auto t = (const uint32*)meshData.IBData; + for (uint32 i = 0; i < meshData.Triangles; i++) { auto i0 = vertexStart + *t++; auto i1 = vertexStart + *t++; @@ -111,10 +109,9 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); } } - output->WriteChar('\n'); - vertexStart += vertices; + vertexStart += meshData.Vertices; } if (output->HasError()) @@ -125,120 +122,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context) { - // Prepare - auto asset = (SkinnedModel*)context.Asset.Get(); - auto lock = asset->Storage->LockSafe(); - auto path = GET_OUTPUT_PATH(context, "obj"); - const int32 lodIndex = 0; - - // Fetch chunk with data - const auto chunkIndex = 1; - if (asset->LoadChunk(chunkIndex)) - return ExportAssetResult::CannotLoadData; - const auto chunk = asset->GetChunk(chunkIndex); - if (!chunk) - return ExportAssetResult::CannotLoadData; - - MemoryReadStream stream(chunk->Get(), chunk->Size()); - FileWriteStream* output = FileWriteStream::Open(path); - if (output == nullptr) - return ExportAssetResult::Error; - DeleteMe outputDeleteMe(output); - - const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi(); - output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get())); - - // Extract all meshes - const auto& lod = asset->LODs[lodIndex]; - int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0 - byte version = stream.ReadByte(); - for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) - { - auto& mesh = lod.Meshes[meshIndex]; - - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint16 blendShapesCount; - stream.ReadUint16(&blendShapesCount); - for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++) - { - stream.ReadBool(); - uint32 tmp; - stream.ReadUint32(&tmp); - stream.ReadUint32(&tmp); - uint32 blendShapeVertices; - stream.ReadUint32(&blendShapeVertices); - stream.Move(blendShapeVertices * sizeof(BlendShapeVertex)); - } - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - return ExportAssetResult::Error; - auto vb0 = stream.Move(vertices); - auto ib = stream.Move(indicesCount * ibStride); - - output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex)); - - for (uint32 i = 0; i < vertices; i++) - { - auto v = vb0[i].Position; - output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z)); - } - - output->WriteChar('\n'); - - for (uint32 i = 0; i < vertices; i++) - { - auto v = vb0[i].TexCoord.ToFloat2(); - output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y)); - } - - output->WriteChar('\n'); - - for (uint32 i = 0; i < vertices; i++) - { - auto v = vb0[i].Normal.ToFloat3() * 2.0f - 1.0f; - output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z)); - } - - output->WriteChar('\n'); - - if (use16BitIndexBuffer) - { - auto t = (uint16*)ib; - for (uint32 i = 0; i < triangles; i++) - { - auto i0 = vertexStart + *t++; - auto i1 = vertexStart + *t++; - auto i2 = vertexStart + *t++; - output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); - } - } - else - { - auto t = (uint32*)ib; - for (uint32 i = 0; i < triangles; i++) - { - auto i0 = vertexStart + *t++; - auto i1 = vertexStart + *t++; - auto i2 = vertexStart + *t++; - output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); - } - } - - output->WriteChar('\n'); - - vertexStart += vertices; - } - - if (output->HasError()) - return ExportAssetResult::Error; - - return ExportAssetResult::Ok; + // The same code, except SkinnedModel::LoadMesh will be used to read Blend Shapes data + return ExportModel(context); } #endif diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index 77cac2f03..c33ab3853 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -132,7 +132,6 @@ void RepackMeshLightmapUVs(ModelData& data) { Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv); Float2 uvScale(entry.Slot->Width * atlasSizeInv, entry.Slot->Height * atlasSizeInv); - // TODO: SIMD for (auto& uv : entry.Mesh->LightmapUVs) { uv = uv * uvScale + uvOffset; @@ -540,37 +539,28 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context) return CreateModel(context, modelData); } -CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelData& modelData, const Options* options) +CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); IMPORT_SETUP(Model, Model::SerializedVersion); + static_assert(Model::SerializedVersion == 30, "Update code."); // Save model header MemoryWriteStream stream(4096); - if (modelData.Pack2ModelHeader(&stream)) + if (Model::SaveHeader(stream, modelData)) return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); // Pack model LODs data - const auto lodCount = modelData.GetLODsCount(); + const auto lodCount = modelData.LODs.Count(); for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { stream.SetPosition(0); - - // Pack meshes - auto& meshes = modelData.LODs[lodIndex].Meshes; - for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) - { - if (meshes[meshIndex]->Pack2Model(&stream)) - { - LOG(Warning, "Cannot pack mesh."); - return CreateAssetResult::Error; - } - } - - const int32 chunkIndex = lodIndex + 1; + if (Model::SaveLOD(stream, modelData, lodIndex)) + return CreateAssetResult::Error; + const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); @@ -591,40 +581,28 @@ CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelDat return CreateAssetResult::Ok; } -CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options) +CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion); + static_assert(SkinnedModel::SerializedVersion == 30, "Update code."); // Save skinned model header MemoryWriteStream stream(4096); - if (modelData.Pack2SkinnedModelHeader(&stream)) + if (SkinnedModel::SaveHeader(stream, modelData)) return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); // Pack model LODs data - const auto lodCount = modelData.GetLODsCount(); + const auto lodCount = modelData.LODs.Count(); for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { stream.SetPosition(0); - - // Mesh Data Version - stream.WriteByte(1); - - // Pack meshes - auto& meshes = modelData.LODs[lodIndex].Meshes; - for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) - { - if (meshes[meshIndex]->Pack2SkinnedModel(&stream)) - { - LOG(Warning, "Cannot pack mesh."); - return CreateAssetResult::Error; - } - } - - const int32 chunkIndex = lodIndex + 1; + if (SkinnedModel::SaveLOD(stream, modelData, lodIndex, SkinnedModel::SaveMesh)) + return CreateAssetResult::Error; + const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); @@ -633,10 +611,11 @@ CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, M return CreateAssetResult::Ok; } -CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options) +CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); IMPORT_SETUP(Animation, Animation::SerializedVersion); + static_assert(Animation::SerializedVersion == 1, "Update code."); // Save animation data MemoryWriteStream stream(8182); @@ -651,7 +630,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode animIndex = i; } } - if (modelData.Pack2AnimationHeader(&stream, animIndex)) + if (Animation::SaveHeader(modelData, stream, animIndex)) return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; @@ -660,7 +639,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, Mode return CreateAssetResult::Ok; } -CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array& prefabObjects) +CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const ModelData& data, const Options& options, const Array& prefabObjects) { PROFILE_CPU(); if (data.Nodes.Count() == 0) diff --git a/Source/Engine/ContentImporters/ImportModel.h b/Source/Engine/ContentImporters/ImportModel.h index b6c934cf4..c9d1cb4b5 100644 --- a/Source/Engine/ContentImporters/ImportModel.h +++ b/Source/Engine/ContentImporters/ImportModel.h @@ -40,10 +40,10 @@ public: static CreateAssetResult Create(CreateAssetContext& context); private: - static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr); - static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr); - static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr); - static CreateAssetResult CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array& prefabObjects); + static CreateAssetResult CreateModel(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr); + static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr); + static CreateAssetResult CreateAnimation(CreateAssetContext& context, const ModelData& data, const Options* options = nullptr); + static CreateAssetResult CreatePrefab(CreateAssetContext& context, const ModelData& data, const Options& options, const Array& prefabObjects); }; #endif diff --git a/Source/Engine/Core/Math/BoundingBox.cs b/Source/Engine/Core/Math/BoundingBox.cs index e161ed300..2809f98b4 100644 --- a/Source/Engine/Core/Math/BoundingBox.cs +++ b/Source/Engine/Core/Math/BoundingBox.cs @@ -356,17 +356,29 @@ namespace FlaxEngine /// The newly constructed bounding box. /// Thrown when is null. public static BoundingBox FromPoints(Vector3[] points) + { + FromPoints(points, out var result); + return result; + } + + /// + /// Constructs a that fully contains the given points. + /// + /// The points that will be contained by the box. + /// When the method completes, contains the newly constructed bounding box. + /// Thrown when is null. + public static void FromPoints(Span points, out BoundingBox result) { if (points == null) throw new ArgumentNullException(nameof(points)); - var min = Vector3.Maximum; - var max = Vector3.Minimum; + var min = Float3.Maximum; + var max = Float3.Minimum; for (var i = 0; i < points.Length; ++i) { - Vector3.Min(ref min, ref points[i], out min); - Vector3.Max(ref max, ref points[i], out max); + Float3.Min(ref min, ref points[i], out min); + Float3.Max(ref max, ref points[i], out max); } - return new BoundingBox(min, max); + result = new BoundingBox(min, max); } /// diff --git a/Source/Engine/Core/Math/Float4.cs b/Source/Engine/Core/Math/Float4.cs index 7b9a81b84..34d561aa2 100644 --- a/Source/Engine/Core/Math/Float4.cs +++ b/Source/Engine/Core/Math/Float4.cs @@ -1343,6 +1343,16 @@ namespace FlaxEngine return new Float3(value.X, value.Y, value.Z); } + /// + /// Performs an explicit conversion from to . + /// + /// The value. + /// The result of the conversion. + public static implicit operator Int4(Float4 value) + { + return new Int4((int)value.X, (int)value.Y, (int)value.Z, (int)value.W); + } + /// /// Returns a that represents this instance. /// diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index ea4201142..2ac8d213c 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -73,7 +73,7 @@ int32 GameBase::LoadProduct() goto LOAD_GAME_HEAD_FAILED; // Load game primary data - stream->ReadArray(&data); + stream->Read(data); if (data.Count() != 808 + sizeof(Guid)) goto LOAD_GAME_HEAD_FAILED; Encryption::DecryptBytes(data.Get(), data.Count()); diff --git a/Source/Engine/Graphics/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs index ee64a86c5..9dafd8aef 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.cs +++ b/Source/Engine/Graphics/GPUBufferDescription.cs @@ -400,6 +400,12 @@ namespace FlaxEngine Format = format; } + /// + public override string ToString() + { + return string.Format("{0}, {1}, offset {2}, {3}, slot {3}", Type, Format, Offset, PerInstance != 0 ? "per-instance" : "per-vertex", Slot); + } + /// public bool Equals(VertexElement other) { diff --git a/Source/Engine/Graphics/Models/BlendShape.h b/Source/Engine/Graphics/Models/BlendShape.h index d7dd2294e..224cb572b 100644 --- a/Source/Engine/Graphics/Models/BlendShape.h +++ b/Source/Engine/Graphics/Models/BlendShape.h @@ -68,4 +68,11 @@ public: /// The list of shape vertices. /// Array Vertices; + + void LoadHeader(class ReadStream& stream, byte headerVersion); + void Load(ReadStream& stream, byte meshVersion); +#if USE_EDITOR + void SaveHeader(class WriteStream& stream) const; + void Save(WriteStream& stream) const; +#endif }; diff --git a/Source/Engine/Graphics/Models/CollisionProxy.h b/Source/Engine/Graphics/Models/CollisionProxy.h index 00a6a6d51..f511c103a 100644 --- a/Source/Engine/Graphics/Models/CollisionProxy.h +++ b/Source/Engine/Graphics/Models/CollisionProxy.h @@ -31,21 +31,21 @@ public: } template - void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices) + void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3)) { Triangles.Clear(); Triangles.EnsureCapacity(triangles, false); - const IndexType* it = indices; for (uint32 i = 0; i < triangles; i++) { - auto i0 = *(it++); - auto i1 = *(it++); - auto i2 = *(it++); - + const IndexType i0 = *(it++); + const IndexType i1 = *(it++); + const IndexType i2 = *(it++); if (i0 < vertices && i1 < vertices && i2 < vertices) { - Triangles.Add({ positions[i0], positions[i1], positions[i2] }); +#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx) + Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) }); +#undef GET_POS } } } diff --git a/Source/Engine/Graphics/Models/Config.h b/Source/Engine/Graphics/Models/Config.h index c9488ad9c..3a543ec52 100644 --- a/Source/Engine/Graphics/Models/Config.h +++ b/Source/Engine/Graphics/Models/Config.h @@ -16,7 +16,10 @@ #define MODEL_MAX_MESHES 4096 // Maximum amount of texture channels (UVs) per vertex -#define MODEL_MAX_UVS 4 +#define MODEL_MAX_UV 4 + +// Maximum amount of vertex buffers (VBs) per mesh +#define MODEL_MAX_VB 3 // Enable/disable precise mesh collision testing (with in-build vertex buffer caching, this will increase memory usage) #define MODEL_USE_PRECISE_MESH_INTERSECTS (USE_EDITOR) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index c6dcf8979..370d56931 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "Mesh.h" +#include "MeshAccessor.h" #include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" @@ -11,16 +12,14 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" -#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/ManagedCLR/MCore.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" #if USE_EDITOR #include "Engine/Renderer/GBufferPass.h" #endif +PRAGMA_DISABLE_DEPRECATION_WARNINGS GPUVertexLayout* VB0ElementType18::GetLayout() { return GPUVertexLayout::Get({ @@ -44,77 +43,100 @@ GPUVertexLayout* VB2ElementType18::GetLayout() { VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }, }); } +PRAGMA_ENABLE_DEPRECATION_WARNINGS namespace { - template - bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const IndexType* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) + bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { - auto model = mesh->GetModel(); + auto model = mesh->GetModelBase(); CHECK_RETURN(model && model->IsVirtual(), true); CHECK_RETURN(triangles && vertices, true); + MeshAccessor accessor; + + // Index Buffer + { + if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat)) + return true; + auto indexStream = accessor.Index(); + ASSERT(indexStream.IsLinear(indexFormat)); + indexStream.SetLinear(triangles); + } - // Pack mesh data into vertex buffers - Array vb1; - Array vb2; - vb1.Resize(vertexCount); + // Vertex Buffer 0 (position-only) + { + GPUVertexLayout* vb0layout = GPUVertexLayout::Get({ { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float } }); + if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout)) + return true; + auto positionStream = accessor.Position(); + ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float)); + positionStream.SetLinear(vertices); + } + + // Vertex Buffer 1 (general purpose components) + GPUVertexLayout::Elements vb1elements; if (normals) { + vb1elements.Add({ VertexElement::Types::Normal, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm }); if (tangents) - { - for (uint32 i = 0; i < vertexCount; i++) - { - const Float3 normal = normals[i]; - const Float3 tangent = tangents[i]; - auto& v = vb1.Get()[i]; - RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent); - } - } - else - { - for (uint32 i = 0; i < vertexCount; i++) - { - const Float3 normal = normals[i]; - auto& v = vb1.Get()[i]; - RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal); - } - } - } - else - { - // Set default tangent frame - const auto n = Float1010102(Float3::UnitZ); - const auto t = Float1010102(Float3::UnitX); - for (uint32 i = 0; i < vertexCount; i++) - { - vb1.Get()[i].Normal = n; - vb1.Get()[i].Tangent = t; - } + vb1elements.Add({ VertexElement::Types::Tangent, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm }); } if (uvs) + vb1elements.Add({ VertexElement::Types::TexCoord, 1, 0, 0, PixelFormat::R16G16_Float }); + if (vb1elements.HasItems()) { - for (uint32 i = 0; i < vertexCount; i++) - vb1.Get()[i].TexCoord = Half2(uvs[i]); - } - else - { - auto v = Half2::Zero; - for (uint32 i = 0; i < vertexCount; i++) - vb1.Get()[i].TexCoord = v; - } - { - auto v = Half2::Zero; - for (uint32 i = 0; i < vertexCount; i++) - vb1.Get()[i].LightmapUVs = v; - } - if (colors) - { - vb2.Resize(vertexCount); - for (uint32 i = 0; i < vertexCount; i++) - vb2.Get()[i].Color = colors[i]; + GPUVertexLayout* vb1layout = GPUVertexLayout::Get(vb1elements); + if (accessor.AllocateBuffer(MeshBufferType::Vertex1, vertexCount, vb1layout)) + return true; + if (normals) + { + auto normalStream = accessor.Normal(); + if (tangents) + { + auto tangentStream = accessor.Tangent(); + for (uint32 i = 0; i < vertexCount; i++) + { + const Float3 normal = normals[i]; + const Float3 tangent = tangents[i]; + Float3 n; + Float4 t; + RenderTools::CalculateTangentFrame(n, t, normal, tangent); + normalStream.SetFloat3(i, n); + tangentStream.SetFloat4(i, t); + } + } + else + { + for (uint32 i = 0; i < vertexCount; i++) + { + const Float3 normal = normals[i]; + Float3 n; + Float4 t; + RenderTools::CalculateTangentFrame(n, t, normal); + normalStream.SetFloat3(i, n); + } + } + } + if (uvs) + { + auto uvsStream = accessor.TexCoord(); + for (uint32 i = 0; i < vertexCount; i++) + uvsStream.SetFloat2(i, uvs[i]); + } } - return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles); + // Vertex Buffer 2 (color-only) + if (colors) + { + GPUVertexLayout* vb2layout = GPUVertexLayout::Get({{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }}); + if (accessor.AllocateBuffer(MeshBufferType::Vertex2, vertexCount, vb2layout)) + return true; + auto colorStream = accessor.Color(); + ASSERT(colorStream.IsLinear(PixelFormat::R8G8B8A8_UNorm)); + colorStream.SetLinear(colors); + } + + return accessor.UpdateMesh(mesh); } #if !COMPILE_WITHOUT_CSHARP @@ -125,21 +147,38 @@ namespace ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount); auto vertices = MCore::Array::GetAddress(verticesObj); auto triangles = MCore::Array::GetAddress(trianglesObj); + const PixelFormat indexFormat = sizeof(IndexType) == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt; const auto normals = normalsObj ? MCore::Array::GetAddress(normalsObj) : nullptr; const auto tangents = tangentsObj ? MCore::Array::GetAddress(tangentsObj) : nullptr; const auto uvs = uvObj ? MCore::Array::GetAddress(uvObj) : nullptr; const auto colors = colorsObj ? MCore::Array::GetAddress(colorsObj) : nullptr; - return UpdateMesh(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); + return UpdateMesh(mesh, vertexCount, triangleCount, indexFormat, vertices, triangles, normals, tangents, uvs, colors); } #endif } +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib) +{ + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false); + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib) +{ + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true); + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices) { Release(); // Setup GPU resources + PRAGMA_DISABLE_DEPRECATION_WARNINGS const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices); + PRAGMA_ENABLE_DEPRECATION_WARNINGS if (!failed) { // Calculate mesh bounds @@ -152,12 +191,12 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0Element bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { - return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); + return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors); } bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { - return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); + return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors); } bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer) @@ -169,9 +208,11 @@ bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* if (vb2) vbData.Add(vb2); Array> vbLayout; + PRAGMA_DISABLE_DEPRECATION_WARNINGS vbLayout.Add(VB0ElementType::GetLayout()); vbLayout.Add(VB1ElementType::GetLayout()); vbLayout.Add(VB2ElementType::GetLayout()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout); } @@ -251,7 +292,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float for (int32 meshIndex = 0; meshIndex < _index; meshIndex++) vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount(); drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; - drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); + drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(Color32); } drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; @@ -314,7 +355,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in for (int32 meshIndex = 0; meshIndex < _index; meshIndex++) vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount(); drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; - drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); + drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(Color32); } drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; @@ -364,96 +405,6 @@ void Mesh::Release() MeshBase::Release(); } -bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const -{ - if (_cachedVertexBuffers[0].IsEmpty()) - { - PROFILE_CPU(); - auto model = GetModel(); - ScopeLock lock(model->Locker); - if (model->IsVirtual()) - { - LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download."); - return true; - } - - // Fetch chunk with data from drive/memory - const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex()); - if (model->LoadChunk(chunkIndex)) - return true; - const auto chunk = model->GetChunk(chunkIndex); - if (!chunk) - { - LOG(Error, "Missing chunk."); - return true; - } - - MemoryReadStream stream(chunk->Get(), chunk->Size()); - - // Seek to find mesh location - for (int32 i = 0; i <= _index; i++) - { - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - { - LOG(Error, "Invalid mesh data."); - return true; - } - auto vb0 = stream.Move(vertices); - auto vb1 = stream.Move(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Move(vertices); - } - auto ib = stream.Move(indicesCount * ibStride); - - if (i != _index) - continue; - - // Cache mesh data - _cachedIndexBufferCount = indicesCount; - _cachedIndexBuffer.Set(ib, indicesCount * ibStride); - _cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType)); - _cachedVertexBuffers[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType)); - if (hasColors) - _cachedVertexBuffers[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType)); - break; - } - } - - switch (type) - { - case MeshBufferType::Index: - result.Link(_cachedIndexBuffer); - count = _cachedIndexBufferCount; - break; - case MeshBufferType::Vertex0: - result.Link(_cachedVertexBuffers[0]); - count = _cachedVertexBuffers[0].Count() / sizeof(VB0ElementType); - break; - case MeshBufferType::Vertex1: - result.Link(_cachedVertexBuffers[1]); - count = _cachedVertexBuffers[1].Count() / sizeof(VB1ElementType); - break; - case MeshBufferType::Vertex2: - result.Link(_cachedVertexBuffers[2]); - count = _cachedVertexBuffers[2].Count() / sizeof(VB2ElementType); - break; - default: - return true; - } - return false; -} - #if !COMPILE_WITHOUT_CSHARP bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) @@ -466,135 +417,80 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } +// [Deprecated in v1.10] enum class InternalBufferType { VB0 = 0, VB1 = 1, VB2 = 2, - IB16 = 3, - IB32 = 4, }; MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI) { - auto mesh = this; - auto type = (InternalBufferType)typeI; - auto model = mesh->GetModel(); - ScopeLock lock(model->Locker); + // [Deprecated in v1.10] + ScopeLock lock(GetModelBase()->Locker); - // Virtual assets always fetch from GPU memory - forceGpu |= model->IsVirtual(); - - if (!mesh->IsInitialized() && forceGpu) + // Get vertex buffers data from the mesh (CPU or GPU) + MeshAccessor accessor; + MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 }; + switch ((InternalBufferType)typeI) { - LOG(Error, "Cannot load mesh data from GPU if it's not loaded."); - return nullptr; - } - - MeshBufferType bufferType; - switch (type) - { - case InternalBufferType::VB0: - bufferType = MeshBufferType::Vertex0; - break; case InternalBufferType::VB1: - bufferType = MeshBufferType::Vertex1; + bufferTypes[0] = MeshBufferType::Vertex1; break; case InternalBufferType::VB2: - bufferType = MeshBufferType::Vertex2; + bufferTypes[0] = MeshBufferType::Vertex2; break; - case InternalBufferType::IB16: - case InternalBufferType::IB32: - bufferType = MeshBufferType::Index; - break; - default: + } + if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1))) return nullptr; - } - BytesContainer data; - int32 dataCount; - if (forceGpu) - { - // Get data from GPU - // TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer - auto task = mesh->DownloadDataGPUAsync(bufferType, data); - if (task == nullptr) - return nullptr; - task->Start(); - model->Locker.Unlock(); - if (task->Wait()) - { - LOG(Error, "Task failed."); - return nullptr; - } - model->Locker.Lock(); - - // Extract elements count from result data - switch (bufferType) - { - case MeshBufferType::Index: - dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32)); - break; - case MeshBufferType::Vertex0: - dataCount = data.Length() / sizeof(VB0ElementType); - break; - case MeshBufferType::Vertex1: - dataCount = data.Length() / sizeof(VB1ElementType); - break; - case MeshBufferType::Vertex2: - dataCount = data.Length() / sizeof(VB2ElementType); - break; - } - } - else - { - // Get data from CPU - if (DownloadDataCPU(bufferType, data, dataCount)) - return nullptr; - } + auto positionStream = accessor.Position(); + auto texCoordStream = accessor.TexCoord(); + auto lightmapUVsStream = accessor.TexCoord(1); + auto normalStream = accessor.Normal(); + auto tangentStream = accessor.Tangent(); + auto colorStream = accessor.Color(); + auto count = GetVertexCount(); // Convert into managed array - MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount); + MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), count); void* managedArrayPtr = MCore::Array::GetAddress(result); - const int32 elementSize = data.Length() / dataCount; - switch (type) + switch ((InternalBufferType)typeI) { +PRAGMA_DISABLE_DEPRECATION_WARNINGS case InternalBufferType::VB0: + CHECK_RETURN(positionStream.IsValid(), nullptr); + for (int32 i = 0; i < count; i++) + { + auto& dst = ((VB0ElementType*)managedArrayPtr)[i]; + dst.Position = positionStream.GetFloat3(i); + } + break; case InternalBufferType::VB1: + for (int32 i = 0; i < count; i++) + { + auto& dst = ((VB1ElementType*)managedArrayPtr)[i]; + if (texCoordStream.IsValid()) + dst.TexCoord = texCoordStream.GetFloat2(i); + if (normalStream.IsValid()) + dst.Normal = normalStream.GetFloat3(i); + if (tangentStream.IsValid()) + dst.Tangent = tangentStream.GetFloat4(i); + if (lightmapUVsStream.IsValid()) + dst.LightmapUVs = lightmapUVsStream.GetFloat2(i); + } + break; case InternalBufferType::VB2: - { - Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); - break; - } - case InternalBufferType::IB16: - { - if (elementSize == sizeof(uint16)) + if (colorStream.IsValid()) { - Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); - } - else - { - auto dst = (uint16*)managedArrayPtr; - auto src = (uint32*)data.Get(); - for (int32 i = 0; i < dataCount; i++) - dst[i] = src[i]; + for (int32 i = 0; i < count; i++) + { + auto& dst = ((VB2ElementType*)managedArrayPtr)[i]; + dst.Color = Color32(colorStream.GetFloat4(i)); + } } break; - } - case InternalBufferType::IB32: - { - if (elementSize == sizeof(uint16)) - { - auto dst = (uint32*)managedArrayPtr; - auto src = (uint16*)data.Get(); - for (int32 i = 0; i < dataCount; i++) - dst[i] = src[i]; - } - else - { - Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); - } - break; - } +PRAGMA_ENABLE_DEPRECATION_WARNINGS } return result; diff --git a/Source/Engine/Graphics/Models/Mesh.cs b/Source/Engine/Graphics/Models/Mesh.cs index 8870c14f6..e04348e54 100644 --- a/Source/Engine/Graphics/Models/Mesh.cs +++ b/Source/Engine/Graphics/Models/Mesh.cs @@ -9,7 +9,9 @@ namespace FlaxEngine { /// /// The Vertex Buffer 0 structure format. + /// [Deprecated in v1.10] /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public struct Vertex0 { /// @@ -20,7 +22,9 @@ namespace FlaxEngine /// /// The Vertex Buffer 1 structure format. + /// [Deprecated in v1.10] /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public struct Vertex1 { /// @@ -46,7 +50,9 @@ namespace FlaxEngine /// /// The Vertex Buffer 2 structure format. + /// [Deprecated in v1.10] /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public struct Vertex2 { /// @@ -57,7 +63,9 @@ namespace FlaxEngine /// /// The raw Vertex Buffer structure format. + /// [Deprecated in v1.10] /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public struct Vertex { /// @@ -425,6 +433,10 @@ namespace FlaxEngine UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors); } + /// + /// [Deprecated in v1.10] + /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] internal enum InternalBufferType { VB0 = 0, @@ -436,9 +448,11 @@ namespace FlaxEngine /// /// Downloads the first vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// [Deprecated in v1.10] /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false) { var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0); @@ -449,9 +463,11 @@ namespace FlaxEngine /// /// Downloads the second vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// [Deprecated in v1.10] /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public Vertex1[] DownloadVertexBuffer1(bool forceGpu = false) { var result = (Vertex1[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex1), (int)InternalBufferType.VB1); @@ -462,12 +478,14 @@ namespace FlaxEngine /// /// Downloads the third vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// [Deprecated in v1.10] /// /// /// If mesh has no vertex colors (stored in vertex buffer 2) the returned value is null. /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data or null if mesh has no vertex colors. + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public Vertex2[] DownloadVertexBuffer2(bool forceGpu = false) { if (!HasVertexColors) @@ -480,14 +498,13 @@ namespace FlaxEngine /// /// Downloads the raw vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// [Deprecated in v1.10] /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public Vertex[] DownloadVertexBuffer(bool forceGpu = false) { - // TODO: perform data conversion on C++ side to make it faster - // TODO: implement batched data download (3 buffers at once) to reduce stall - var vb0 = DownloadVertexBuffer0(forceGpu); var vb1 = DownloadVertexBuffer1(forceGpu); var vb2 = DownloadVertexBuffer2(forceGpu); @@ -519,33 +536,5 @@ namespace FlaxEngine return result; } - - /// - /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). - /// - /// If mesh index buffer format (see ) is then it's faster to call . - /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. - /// The gathered data. - public uint[] DownloadIndexBuffer(bool forceGpu = false) - { - var result = (uint[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(uint), (int)InternalBufferType.IB32); - if (result == null) - throw new Exception("Failed to download mesh data."); - return result; - } - - /// - /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). - /// - /// If mesh index buffer format (see ) is then data won't be downloaded. - /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. - /// The gathered data. - public ushort[] DownloadIndexBufferUShort(bool forceGpu = false) - { - var result = (ushort[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(ushort), (int)InternalBufferType.IB16); - if (result == null) - throw new Exception("Failed to download mesh data."); - return result; - } } } diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 8dba159a2..9d8f532c1 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -43,13 +43,14 @@ public: } /// - /// Lightmap texture coordinates channel index. + /// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available. /// API_FIELD() int32 LightmapUVsIndex = -1; public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. @@ -58,13 +59,12 @@ public: /// The third vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib) - { - return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false); - } + DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.") + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. @@ -73,15 +73,14 @@ public: /// The third vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib) - { - return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true); - } + DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.") + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). /// Can be used only for virtual assets (see and ). /// Mesh data will be cached and uploaded to the GPU with a delay. + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. @@ -91,6 +90,7 @@ public: /// The index buffer in clockwise order. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. + DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.") bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices); /// @@ -174,7 +174,6 @@ public: // [MeshBase] bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) override; void Release() override; - bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: // Internal bindings diff --git a/Source/Engine/Graphics/Models/MeshAccessor.cs b/Source/Engine/Graphics/Models/MeshAccessor.cs new file mode 100644 index 000000000..2eef70ff0 --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshAccessor.cs @@ -0,0 +1,728 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; +using System.Runtime.InteropServices; + +namespace FlaxEngine +{ + /// + /// General purpose utility for accessing mesh data (both read and write). + /// + public class MeshAccessor + { + /// + /// Mesh data stream. + /// + public unsafe ref struct Stream + { + private Span _data; + private PixelFormat _format; + private int _stride; + private readonly PixelFormatSampler _sampler; + + internal Stream(Span data, PixelFormat format, int stride) + { + _data = data; + _stride = stride; + if (PixelFormatSampler.Get(format, out _sampler)) + { + _format = format; + } + else if (format != PixelFormat.Unknown) + { + Debug.LogError($"Unsupported pixel format '{format}' to sample vertex attribute."); + } + } + + /// + /// Gets the raw data block. + /// + public Span Data => _data; + + /// + /// Gets the format of the data. + /// + public PixelFormat Format => _format; + + /// + /// Gets the stride (in bytes) of the data. + /// + public int Stride => _stride; + + /// + /// Gets the count of the items in the stride. + /// + public int Count => _data.Length / _stride; + + /// + /// Returns true if stream is valid. + /// + public bool IsValid => _format != PixelFormat.Unknown; + + /// + /// Checks if the stream can use via a single memory copy. + /// + /// Source data format. + /// True if stream is linear and format matches expected input data. + public bool IsLinear(PixelFormat expectedFormat) + { + return _format == expectedFormat && _stride == PixelFormatExtensions.SizeInBytes(_format); + } + + /// + /// Reads a float value from a given item. + /// + /// Zero-based index of the item. + /// Loaded value. + public float GetFloat(int index) + { + fixed (byte* data = _data) + return _sampler.Read(data + index * _stride).X; + } + + /// + /// Reads a Float2 value from a given item. + /// + /// Zero-based index of the item. + /// Loaded value. + public Float2 GetFloat2(int index) + { + fixed (byte* data = _data) + return new Float2(_sampler.Read(data + index * _stride)); + } + + /// + /// Reads a Float3 value from a given item. + /// + /// Zero-based index of the item. + /// Loaded value. + public Float3 GetFloat3(int index) + { + fixed (byte* data = _data) + return new Float3(_sampler.Read(data + index * _stride)); + } + + /// + /// Reads a Float4 value from a given item. + /// + /// Zero-based index of the item. + /// Loaded value. + public Float4 GetFloat4(int index) + { + fixed (byte* data = _data) + return _sampler.Read(data + index * _stride); + } + + /// + /// Writes a float value to a given item. + /// + /// Zero-based index of the item. + /// Value to assign. + public void SetFloat(int index, float value) + { + var v = new Float4(value); + fixed (byte* data = _data) + _sampler.Write(data + index * _stride, ref v); + } + + /// + /// Writes a Float2 value to a given item. + /// + /// Zero-based index of the item. + /// Value to assign. + public void SetFloat2(int index, Float2 value) + { + var v = new Float4(value, 0.0f, 0.0f); + fixed (byte* data = _data) + _sampler.Write(data + index * _stride, ref v); + } + + /// + /// Writes a Float3 value to a given item. + /// + /// Zero-based index of the item. + /// Value to assign. + public void SetFloat3(int index, Float3 value) + { + var v = new Float4(value, 0.0f); + fixed (byte* data = _data) + _sampler.Write(data + index * _stride, ref v); + } + + /// + /// Writes a Float4 value to a given item. + /// + /// Zero-based index of the item. + /// Value to assign. + public void SetFloat4(int index, Float4 value) + { + fixed (byte* data = _data) + _sampler.Write(data + index * _stride, ref value); + } + + /// + /// Copies a linear block of data into the stream. Assumes returned true for the format of the input data. + /// + /// Check input data and stream type with IsLinear before calling. + /// Pointer to the source data. + public void SetLinear(IntPtr data) + { + new Span(data.ToPointer(), _data.Length).CopyTo(_data); + } + + /// + /// Copies the contents of the input into the elements of this stream. + /// + /// The source . + public void Set(Span src) + { + if (IsLinear(PixelFormat.R32G32_Float)) + { + src.CopyTo(MemoryMarshal.Cast(_data)); + } + else + { + var count = Count; + fixed (byte* data = _data) + { + for (int i = 0; i < count; i++) + { + var v = new Float4(src[i], 0.0f, 0.0f); + _sampler.Write(data + i * _stride, ref v); + } + } + } + } + + /// + /// Copies the contents of the input into the elements of this stream. + /// + /// The source . + public void Set(Span src) + { + if (IsLinear(PixelFormat.R32G32B32_Float)) + { + src.CopyTo(MemoryMarshal.Cast(_data)); + } + else + { + var count = Count; + fixed (byte* data = _data) + { + for (int i = 0; i < count; i++) + { + var v = new Float4(src[i], 0.0f); + _sampler.Write(data + i * _stride, ref v); + } + } + } + } + + /// + /// Copies the contents of the input into the elements of this stream. + /// + /// The source . + public void Set(Span src) + { + if (IsLinear(PixelFormat.R32G32B32A32_Float)) + { + src.CopyTo(MemoryMarshal.Cast(_data)); + } + else + { + var count = Count; + fixed (byte* data = _data) + { + for (int i = 0; i < count; i++) + { + var v = (Float4)src[i]; + _sampler.Write(data + i * _stride, ref v); + } + } + } + } + + /// + /// Copies the contents of this stream into a destination . + /// + /// The destination . + public void CopyTo(Span dst) + { + if (IsLinear(PixelFormat.R32G32_Float)) + { + _data.CopyTo(MemoryMarshal.Cast(dst)); + } + else + { + var count = Count; + fixed (byte* data = _data) + { + for (int i = 0; i < count; i++) + { + dst[i] = new Float2(_sampler.Read(data + i * _stride)); + } + } + } + } + + /// + /// Copies the contents of this stream into a destination . + /// + /// The destination . + public void CopyTo(Span dst) + { + if (IsLinear(PixelFormat.R32G32B32_Float)) + { + _data.CopyTo(MemoryMarshal.Cast(dst)); + } + else + { + var count = Count; + fixed (byte* data = _data) + { + for (int i = 0; i < count; i++) + { + dst[i] = new Float3(_sampler.Read(data + i * _stride)); + } + } + } + } + + /// + /// Copies the contents of this stream into a destination . + /// + /// The destination . + public void CopyTo(Span dst) + { + if (IsLinear(PixelFormat.R32G32B32A32_Float)) + { + _data.CopyTo(MemoryMarshal.Cast(dst)); + } + else + { + var count = Count; + fixed (byte* data = _data) + { + for (int i = 0; i < count; i++) + { + dst[i] = (Color)_sampler.Read(data + i * _stride); + } + } + } + } + } + + private byte[][] _data = new byte[(int)MeshBufferType.MAX][]; + private PixelFormat[] _formats = new PixelFormat[(int)MeshBufferType.MAX]; + private GPUVertexLayout[] _layouts = new GPUVertexLayout[(int)MeshBufferType.MAX]; + + /// + /// Loads the data from the mesh. + /// + /// The source mesh object to access. + /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. + /// Custom list of mesh buffers to load. Use empty value to access all of them. + /// True if failed, otherwise false. + public bool LoadMesh(MeshBase mesh, bool forceGpu = false, Span buffers = new()) + { + if (mesh == null) + return true; + Span allBuffers = stackalloc MeshBufferType[(int)MeshBufferType.MAX] { MeshBufferType.Index, MeshBufferType.Vertex0, MeshBufferType.Vertex1, MeshBufferType.Vertex2 }; + var buffersLocal = buffers.IsEmpty ? allBuffers : buffers; + if (mesh.DownloadData(buffersLocal.ToArray(), out var meshBuffers, out var meshLayouts, forceGpu)) + return true; + for (int i = 0; i < buffersLocal.Length; i++) + { + _data[(int)buffersLocal[i]] = meshBuffers[i]; + _layouts[(int)buffersLocal[i]] = meshLayouts[i]; + } + _formats[(int)MeshBufferType.Index] = mesh.Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt; + return false; + } + + /// + /// Loads the data from the provided mesh buffer. + /// + /// Type of the mesh buffer to load. + /// Data used by that buffer. + /// Layout of the elements in the buffer. + /// True if failed, otherwise false. + public bool LoadBuffer(MeshBufferType bufferType, Span bufferData, GPUVertexLayout layout) + { + if (bufferData.IsEmpty) + return true; + if (layout == null) + return true; + int idx = (int)bufferType; + _data[idx] = bufferData.ToArray(); + _layouts[idx] = layout; + return false; + } + + /// + /// Allocates the data for the mesh vertex buffer. + /// + /// Type of the mesh buffer to initialize. + /// Amount of items in the buffer. + /// Layout of the elements in the buffer. + /// True if failed, otherwise false. + public bool AllocateBuffer(MeshBufferType bufferType, int count, GPUVertexLayout layout) + { + if (count <= 0) + return true; + if (layout == null) + return true; + int idx = (int)bufferType; + _data[idx] = new byte[count * layout.Stride]; + _layouts[idx] = layout; + return false; + } + + /// + /// Allocates the data for the mesh buffer. + /// + /// Type of the mesh buffer to initialize. + /// Amount of items in the buffer. + /// Format of the elements in the buffer. + /// True if failed, otherwise false. + public bool AllocateBuffer(MeshBufferType bufferType, int count, PixelFormat format) + { + if (count <= 0) + return true; + int stride = PixelFormatExtensions.SizeInBytes(format); + if (stride <= 0) + return true; + int idx = (int)bufferType; + _data[idx] = new byte[count * stride]; + _formats[idx] = format; + return false; + } + + /// + /// Updates the mesh vertex and index buffers with data assigned to the accessor (eg. via AllocateBuffer). + /// + /// The target mesh to update. + /// True if auto-calculate mesh local bounding box based on input positions stream. Otherwise, mesh bound swill stay unchanged. + /// True if failed, otherwise false. + public unsafe bool UpdateMesh(MeshBase mesh, bool calculateBounds = true) + { + if (mesh == null) + return true; + int IB = (int)MeshBufferType.Index; + int VB0 = (int)MeshBufferType.Vertex0; + int VB1 = (int)MeshBufferType.Vertex1; + int VB2 = (int)MeshBufferType.Vertex2; + + uint vertices = 0, triangles = 0; + fixed (byte* data0Ptr = _data[0]) + fixed (byte* data1Ptr = _data[1]) + fixed (byte* data2Ptr = _data[2]) + fixed (byte* data3Ptr = _data[3]) + { + Span dataPtr = stackalloc IntPtr[(int)MeshBufferType.MAX] { new IntPtr(data0Ptr), new IntPtr(data1Ptr), new IntPtr(data2Ptr), new IntPtr(data3Ptr) }; + IntPtr ibData = IntPtr.Zero; + bool use16BitIndexBuffer = false; + IntPtr[] vbData = new IntPtr[3]; + GPUVertexLayout[] vbLayout = new GPUVertexLayout[3]; + if (_data[VB0] != null) + { + vbData[0] = dataPtr[VB0]; + vbLayout[0] = _layouts[VB0]; + vertices = (uint)_data[VB0].Length / _layouts[VB0].Stride; + } + if (_data[VB1] != null) + { + vbData[1] = dataPtr[VB1]; + vbLayout[1] = _layouts[VB1]; + vertices = (uint)_data[VB1].Length / _layouts[VB1].Stride; + } + if (_data[VB2] != null) + { + vbData[2] = dataPtr[VB2]; + vbLayout[2] = _layouts[VB2]; + vertices = (uint)_data[VB2].Length / _layouts[VB2].Stride; + } + if (_data[IB] != null && _formats[IB] != PixelFormat.Unknown) + { + ibData = dataPtr[IB]; + use16BitIndexBuffer = _formats[IB] == PixelFormat.R16_UInt; + triangles = (uint)(_data[IB].Length / PixelFormatExtensions.SizeInBytes(_formats[IB])); + } + + if (mesh.Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) + return true; + } + + if (calculateBounds) + { + // Calculate mesh bounds + BoundingBox bounds; + var positionStream = Position(); + if (!positionStream.IsValid) + return true; + if (positionStream.IsLinear(PixelFormat.R32G32B32_Float)) + { + Span positionData = positionStream.Data; + BoundingBox.FromPoints(MemoryMarshal.Cast(positionData), out bounds); + } + else + { + Float3 min = Float3.Maximum, max = Float3.Minimum; + for (int i = 0; i < vertices; i++) + { + Float3 pos = positionStream.GetFloat3(i); + Float3.Min(ref min, ref pos, out min); + Float3.Max(ref max, ref pos, out max); + } + bounds = new BoundingBox(min, max); + } + mesh.SetBounds(ref bounds); + } + + return false; + } + + /// + /// Access stream with index buffer. + /// + /// Mesh data stream (might be invalid if data is not provided). + public Stream Index() + { + Span data = new Span(); + PixelFormat format = PixelFormat.Unknown; + int stride = 0; + var ib = _data[(int)MeshBufferType.Index]; + if (ib != null) + { + data = ib; + format = _formats[(int)MeshBufferType.Index]; + stride = PixelFormatExtensions.SizeInBytes(format); + } + return new Stream(data, format, stride); + } + + /// + /// Access stream with a specific vertex attribute. + /// + /// Type of the attribute. + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream Attribute(VertexElement.Types attribute) + { + Span data = new Span(); + PixelFormat format = PixelFormat.Unknown; + int stride = 0; + for (int vbIndex = 0; vbIndex < 3 && format == PixelFormat.Unknown; vbIndex++) + { + int idx = vbIndex + 1; + var layout = _layouts[idx]; + if (!layout) + continue; + foreach (VertexElement e in layout.Elements) + { + var vb = _data[idx]; + if (e.Type == attribute && vb != null) + { + data = new Span(vb).Slice(e.Offset); + format = e.Format; + stride = (int)layout.Stride; + break; + } + } + } + return new Stream(data, format, stride); + } + + /// + /// Access stream with vertex position attribute. + /// + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream Position() + { + return Attribute(VertexElement.Types.Position); + } + + /// + /// Access stream with vertex color attribute. + /// + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream Color() + { + return Attribute(VertexElement.Types.Color); + } + + /// + /// Access stream with vertex normal vector attribute. + /// + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream Normal() + { + return Attribute(VertexElement.Types.Normal); + } + + /// + /// Access stream with vertex tangent vector attribute. + /// + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream Tangent() + { + return Attribute(VertexElement.Types.Tangent); + } + + /// + /// Access stream with vertex skeleton bones blend indices attribute. + /// + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream BlendIndices() + { + return Attribute(VertexElement.Types.BlendIndices); + } + + /// + /// Access stream with vertex skeleton bones blend weights attribute. + /// + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream BlendWeights() + { + return Attribute(VertexElement.Types.BlendWeights); + } + + /// + /// Access stream with vertex texture coordinates attribute (specific UV channel). + /// + /// UV channel index (zero-based). + /// Mesh data stream (might be invalid if attribute is not provided). + public Stream TexCoord(int channel = 0) + { + return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel)); + } + + /// + /// Gets or sets the vertex positions. Null if does not exist in vertex buffers of the mesh. + /// + /// Uses stream to read or write data to the vertex buffer. + public Float3[] Positions + { + get => GetStreamFloat3(VertexElement.Types.Position); + set => SetStreamFloat3(VertexElement.Types.Position, value); + } + + /// + /// Gets or sets the vertex colors. Null if does not exist in vertex buffers of the mesh. + /// + /// Uses stream to read or write data to the vertex buffer. + public Color[] Colors + { + get => GetStreamColor(VertexElement.Types.Color); + set => SetStreamColor(VertexElement.Types.Color, value); + } + + /// + /// Gets or sets the vertex normal vectors (unpacked, normalized). Null if does not exist in vertex buffers of the mesh. + /// + /// Uses stream to read or write data to the vertex buffer. + public Float3[] Normals + { + get => GetStreamFloat3(VertexElement.Types.Normal, UnpackNormal); + set => SetStreamFloat3(VertexElement.Types.Normal, value, PackNormal); + } + + /// + /// Gets or sets the vertex UVs (texcoord channel 0). Null if does not exist in vertex buffers of the mesh. + /// + /// Uses stream to read or write data to the vertex buffer. + public Float2[] TexCoords + { + get => GetStreamFloat2(VertexElement.Types.TexCoord); + set => SetStreamFloat2(VertexElement.Types.TexCoord, value); + } + + private delegate void TransformDelegate3(ref Float3 value); + + private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null) + { + Float3[] result = null; + var stream = Attribute(attribute); + if (stream.IsValid) + { + result = new Float3[stream.Count]; + stream.CopyTo(result); + if (transform != null) + { + for (int i = 0; i < result.Length; i++) + transform(ref result[i]); + } + } + return result; + } + + private void SetStreamFloat3(VertexElement.Types attribute, Float3[] value, TransformDelegate3 transform = null) + { + var stream = Attribute(attribute); + if (stream.IsValid) + { + if (transform != null) + { + // TODO: transform in-place? + value = (Float3[])value.Clone(); + for (int i = 0; i < value.Length; i++) + transform(ref value[i]); + } + stream.Set(value); + } + } + + private Float2[] GetStreamFloat2(VertexElement.Types attribute) + { + Float2[] result = null; + var stream = Attribute(attribute); + if (stream.IsValid) + { + result = new Float2[stream.Count]; + stream.CopyTo(result); + } + return result; + } + + private void SetStreamFloat2(VertexElement.Types attribute, Float2[] value) + { + var stream = Attribute(attribute); + if (stream.IsValid) + { + stream.Set(value); + } + } + + private Color[] GetStreamColor(VertexElement.Types attribute) + { + Color[] result = null; + var stream = Attribute(attribute); + if (stream.IsValid) + { + result = new Color[stream.Count]; + stream.CopyTo(result); + } + return result; + } + + private void SetStreamColor(VertexElement.Types attribute, Color[] value) + { + var stream = Attribute(attribute); + if (stream.IsValid) + { + stream.Set(value); + } + } + + private static void UnpackNormal(ref Float3 value) + { + // [0; 1] -> [-1; 1] + value = value * 2.0f - 1.0f; + } + + private static void PackNormal(ref Float3 value) + { + // [-1; 1] -> [0; 1] + value = value * 0.5f + 0.5f; + } + } +} diff --git a/Source/Engine/Graphics/Models/MeshAccessor.h b/Source/Engine/Graphics/Models/MeshAccessor.h new file mode 100644 index 000000000..cd17ccd15 --- /dev/null +++ b/Source/Engine/Graphics/Models/MeshAccessor.h @@ -0,0 +1,185 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Types.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Graphics/PixelFormat.h" +#include "Engine/Graphics/PixelFormatSampler.h" +#include "Engine/Graphics/Shaders/VertexElement.h" + +/// +/// General purpose utility for accessing mesh data (both read and write). +/// +class FLAXENGINE_API MeshAccessor +{ +public: + /// + /// Mesh data stream. + /// + struct Stream + { + friend MeshAccessor; + + private: + Span _data; + PixelFormat _format; + int32 _stride; + PixelFormatSampler _sampler; + + Stream(Span data, PixelFormat format, int32 stride); + + public: + Span GetData() const; + PixelFormat GetFormat() const; + int32 GetStride() const; + int32 GetCount() const; + bool IsValid() const; + bool IsLinear(PixelFormat expectedFormat) const; + + FORCE_INLINE float GetFloat(int32 index) const + { + return _sampler.Read(_data.Get() + index * _stride).X; + } + + FORCE_INLINE Float2 GetFloat2(int32 index) const + { + return Float2(_sampler.Read(_data.Get() + index * _stride)); + } + + FORCE_INLINE Float3 GetFloat3(int32 index) const + { + return Float3(_sampler.Read(_data.Get() + index * _stride)); + } + + FORCE_INLINE Float4 GetFloat4(int32 index) const + { + return _sampler.Read(_data.Get() + index * _stride); + } + + FORCE_INLINE void SetFloat(int32 index, const float& value) + { + _sampler.Write(_data.Get() + index * _stride, Float4(value)); + } + + FORCE_INLINE void SetFloat2(int32 index, const Float2& value) + { + _sampler.Write(_data.Get() + index * _stride, Float4(value)); + } + + FORCE_INLINE void SetFloat3(int32 index, const Float3& value) + { + _sampler.Write(_data.Get() + index * _stride, Float4(value)); + } + + FORCE_INLINE void SetFloat4(int32 index, const Float4& value) + { + _sampler.Write(_data.Get() + index * _stride, value); + } + + // Check input data and stream type with IsLinear before calling. + void SetLinear(const void* data); + }; + +private: + BytesContainer _data[(int32)MeshBufferType::MAX]; + PixelFormat _formats[(int32)MeshBufferType::MAX] = {}; + GPUVertexLayout* _layouts[(int32)MeshBufferType::MAX] = {}; + +public: + /// + /// Loads the data from the mesh. + /// + /// The source mesh object to access. + /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. + /// Custom list of mesh buffers to load. Use empty value to access all of them. + /// True if failed, otherwise false. + bool LoadMesh(const MeshBase* mesh, bool forceGpu = false, Span buffers = Span()); + + /// + /// Loads the data from the provided mesh buffer. + /// + /// Type of the mesh buffer to load. + /// Data used by that buffer. + /// Layout of the elements in the buffer. + /// True if failed, otherwise false. + bool LoadBuffer(MeshBufferType bufferType, Span bufferData, GPUVertexLayout* layout); + + // Used internally via ModelBase::LoadMesh. + bool LoadFromMeshData(const void* meshDataPtr); + + /// + /// Allocates the data for the mesh vertex buffer. + /// + /// Type of the mesh buffer to initialize. + /// Amount of items in the buffer. + /// Layout of the elements in the buffer. + /// True if failed, otherwise false. + bool AllocateBuffer(MeshBufferType bufferType, int32 count, GPUVertexLayout* layout); + + /// + /// Allocates the data for the mesh buffer. + /// + /// Type of the mesh buffer to initialize. + /// Amount of items in the buffer. + /// Format of the elements in the buffer. + /// True if failed, otherwise false. + bool AllocateBuffer(MeshBufferType bufferType, int32 count, PixelFormat format); + + /// + /// Updates the mesh vertex and index buffers with data assigned to the accessor (eg. via AllocateBuffer). + /// + /// The target mesh to update. + /// True if auto-calculate mesh local bounding box based on input positions stream. Otherwise, mesh bound swill stay unchanged. + /// True if failed, otherwise false. + bool UpdateMesh(MeshBase* mesh, bool calculateBounds = true); + +public: + // Access stream with index buffer. + Stream Index(); + + // Access stream with a specific vertex attribute. + Stream Attribute(VertexElement::Types attribute); + + // Access stream with vertex position attribute. + FORCE_INLINE Stream Position() + { + return Attribute(VertexElement::Types::Position); + } + + // Access stream with vertex color attribute. + FORCE_INLINE Stream Color() + { + return Attribute(VertexElement::Types::Color); + } + + // Access stream with vertex normal vector attribute. + FORCE_INLINE Stream Normal() + { + return Attribute(VertexElement::Types::Normal); + } + + // Access stream with vertex tangent vector attribute. + FORCE_INLINE Stream Tangent() + { + return Attribute(VertexElement::Types::Tangent); + } + + // Access stream with vertex skeleton bones blend indices attribute. + FORCE_INLINE Stream BlendIndices() + { + return Attribute(VertexElement::Types::BlendIndices); + } + + // Access stream with vertex skeleton bones blend weights attribute. + FORCE_INLINE Stream BlendWeights() + { + return Attribute(VertexElement::Types::BlendWeights); + } + + // Access stream with vertex texture coordinates attribute (specific UV channel). + FORCE_INLINE Stream TexCoord(int32 channel = 0) + { + return Attribute((VertexElement::Types)((byte)VertexElement::Types::TexCoord0 + channel)); + } +}; diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index e9b4b1493..a845bdf4b 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -1,14 +1,23 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "MeshBase.h" +#include "MeshAccessor.h" #include "Engine/Core/Log.h" #include "Engine/Content/Assets/ModelBase.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/PixelFormatExtensions.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Scripting/Enums.h" #include "Engine/Scripting/ManagedCLR/MCore.h" +#include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Threading/Task.h" + +static_assert(MODEL_MAX_VB == 3, "Update code in mesh to match amount of vertex buffers."); namespace { @@ -28,6 +37,252 @@ namespace #endif } +MeshAccessor::Stream::Stream(Span data, PixelFormat format, int32 stride) + : _data(data) + , _format(PixelFormat::Unknown) + , _stride(stride) +{ + auto sampler = PixelFormatSampler::Get(format); + if (sampler) + { + _format = format; + _sampler = *sampler; + } + else if (format != PixelFormat::Unknown) + { + LOG(Error, "Unsupported pixel format '{}' to sample vertex attribute.", ScriptingEnum::ToString(format)); + } +} + +Span MeshAccessor::Stream::GetData() const +{ + return _data; +} + +PixelFormat MeshAccessor::Stream::GetFormat() const +{ + return _format; +} + +int32 MeshAccessor::Stream::GetStride() const +{ + return _stride; +} + +int32 MeshAccessor::Stream::GetCount() const +{ + return _data.Length() / _stride; +} + +bool MeshAccessor::Stream::IsValid() const +{ + return _format != PixelFormat::Unknown; +} + +bool MeshAccessor::Stream::IsLinear(PixelFormat expectedFormat) const +{ + return _format == expectedFormat && _stride == PixelFormatExtensions::SizeInBytes(_format); +} + +void MeshAccessor::Stream::SetLinear(const void* data) +{ + Platform::MemoryCopy(_data.Get(), data, _data.Length()); +} + +bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span buffers) +{ + CHECK_RETURN(mesh, true); + constexpr MeshBufferType allBuffers[(int32)MeshBufferType::MAX] = { MeshBufferType::Index, MeshBufferType::Vertex0, MeshBufferType::Vertex1, MeshBufferType::Vertex2 }; + if (buffers.IsInvalid()) + buffers = Span(allBuffers, ARRAY_COUNT(allBuffers)); + Array> meshBuffers; + Array> meshLayouts; + if (mesh->DownloadData(buffers, meshBuffers, meshLayouts, forceGpu)) + return true; + for (int32 i = 0; i < buffers.Length(); i++) + { + _data[(int32)buffers[i]] = MoveTemp(meshBuffers[i]); + _layouts[(int32)buffers[i]] = meshLayouts[i]; + } + _formats[(int32)MeshBufferType::Index] = mesh->Use16BitIndexBuffer() ? PixelFormat::R16_UInt : PixelFormat::R32_UInt; + return false; +} + +bool MeshAccessor::LoadBuffer(MeshBufferType bufferType, Span bufferData, GPUVertexLayout* layout) +{ + CHECK_RETURN(layout, true); + CHECK_RETURN(bufferData.IsValid(), true); + const int32 idx = (int32)bufferType; + _data[idx].Link(bufferData); + _layouts[idx] = layout; + return false; +} + +bool MeshAccessor::LoadFromMeshData(const void* meshDataPtr) +{ + if (!meshDataPtr) + return true; + const auto& meshData = *(const ModelBase::MeshData*)meshDataPtr; + if (meshData.VBData.Count() != meshData.VBLayout.Count()) + return true; + _data[(int32)MeshBufferType::Index].Link((const byte*)meshData.IBData, meshData.IBStride * meshData.Triangles * 3); + if (meshData.VBData.Count() > 0 && meshData.VBLayout[0]) + { + constexpr int32 idx = (int32)MeshBufferType::Vertex0; + _data[idx].Link((const byte*)meshData.VBData[0], meshData.VBLayout[0]->GetStride() * meshData.Vertices); + _layouts[idx] = meshData.VBLayout[0]; + } + if (meshData.VBData.Count() > 1 && meshData.VBLayout[1]) + { + constexpr int32 idx = (int32)MeshBufferType::Vertex1; + _data[idx].Link((const byte*)meshData.VBData[1], meshData.VBLayout[1]->GetStride() * meshData.Vertices); + _layouts[idx] = meshData.VBLayout[1]; + } + if (meshData.VBData.Count() > 2 && meshData.VBLayout[2]) + { + constexpr int32 idx = (int32)MeshBufferType::Vertex2; + _data[idx].Link((const byte*)meshData.VBData[2], meshData.VBLayout[2]->GetStride() * meshData.Vertices); + _layouts[idx] = meshData.VBLayout[2]; + } + return false; +} + +bool MeshAccessor::AllocateBuffer(MeshBufferType bufferType, int32 count, GPUVertexLayout* layout) +{ + CHECK_RETURN(count, true); + CHECK_RETURN(layout, true); + const int32 idx = (int32)bufferType; + _data[idx].Allocate(count * layout->GetStride()); + _layouts[idx] = layout; + return false; +} + +bool MeshAccessor::AllocateBuffer(MeshBufferType bufferType, int32 count, PixelFormat format) +{ + CHECK_RETURN(count, true); + const int32 stride = PixelFormatExtensions::SizeInBytes(format); + CHECK_RETURN(stride, true); + const int32 idx = (int32)bufferType; + _data[idx].Allocate(count * stride); + _formats[idx] = format; + return false; +} + +bool MeshAccessor::UpdateMesh(MeshBase* mesh, bool calculateBounds) +{ + CHECK_RETURN(mesh, true); + constexpr int32 IB = (int32)MeshBufferType::Index; + constexpr int32 VB0 = (int32)MeshBufferType::Vertex0; + constexpr int32 VB1 = (int32)MeshBufferType::Vertex1; + constexpr int32 VB2 = (int32)MeshBufferType::Vertex2; + + uint32 vertices = 0, triangles = 0; + const void* ibData = nullptr; + bool use16BitIndexBuffer = false; + Array> vbData; + Array> vbLayout; + vbData.Resize(3); + vbLayout.Resize(3); + vbData.SetAll(nullptr); + vbLayout.SetAll(nullptr); + if (_data[VB0].IsValid()) + { + vbData[0] = _data[VB0].Get(); + vbLayout[0] = _layouts[VB0]; + vertices = _data[VB0].Length() / _layouts[VB0]->GetStride(); + } + if (_data[VB1].IsValid()) + { + vbData[1] = _data[VB1].Get(); + vbLayout[1] = _layouts[VB1]; + vertices = _data[VB1].Length() / _layouts[VB1]->GetStride(); + } + if (_data[VB2].IsValid()) + { + vbData[2] = _data[VB2].Get(); + vbLayout[2] = _layouts[VB2]; + vertices = _data[VB2].Length() / _layouts[VB2]->GetStride(); + } + if (_data[IB].IsValid() && _formats[IB] != PixelFormat::Unknown) + { + ibData = _data[IB].Get(); + use16BitIndexBuffer = _formats[IB] == PixelFormat::R16_UInt; + triangles = _data[IB].Length() / PixelFormatExtensions::SizeInBytes(_formats[IB]); + } + + if (mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) + return true; + + if (calculateBounds) + { + // Calculate mesh bounds + BoundingBox bounds; + auto positionStream = Position(); + CHECK_RETURN(positionStream.IsValid(), true); + if (positionStream.IsLinear(PixelFormat::R32G32B32_Float)) + { + Span positionData = positionStream.GetData(); + BoundingBox::FromPoints((const Float3*)positionData.Get(), positionData.Length() / sizeof(Float3), bounds); + } + else + { + Float3 min = Float3::Maximum, max = Float3::Minimum; + for (uint32 i = 0; i < vertices; i++) + { + Float3 pos = positionStream.GetFloat3(i); + Float3::Min(min, pos, min); + Float3::Max(max, pos, max); + } + bounds = BoundingBox(min, max); + } + mesh->SetBounds(bounds); + } + + return false; +} + +MeshAccessor::Stream MeshAccessor::Index() +{ + Span data; + PixelFormat format = PixelFormat::Unknown; + int32 stride = 0; + auto& ib = _data[(int32)MeshBufferType::Index]; + if (ib.IsValid()) + { + data = ib; + format = _formats[(int32)MeshBufferType::Index]; + stride = PixelFormatExtensions::SizeInBytes(format); + } + return Stream(data, format, stride); +} + +MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute) +{ + Span data; + PixelFormat format = PixelFormat::Unknown; + int32 stride = 0; + for (int32 vbIndex = 0; vbIndex < 3 && format == PixelFormat::Unknown; vbIndex++) + { + static_assert((int32)MeshBufferType::Vertex0 == 1, "Update code."); + const int32 idx = vbIndex + 1; + auto layout = _layouts[idx]; + if (!layout) + continue; + for (const VertexElement& e : layout->GetElements()) + { + auto& vb = _data[idx]; + if (e.Type == attribute && vb.IsValid()) + { + data = vb.Slice(e.Offset); + format = e.Format; + stride = layout->GetStride(); + break; + } + } + } + return Stream(data, format, stride); +} + MeshBase::~MeshBase() { SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]); @@ -70,7 +325,12 @@ void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere) } } -bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) +GPUVertexLayout* MeshBase::GetVertexLayout() const +{ + return GPUVertexLayout::Get(Span(_vertexBuffers, MODEL_MAX_VB)); +} + +bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) { CHECK_RETURN(vbData.HasItems() && vertices, true); CHECK_RETURN(ibData, true); @@ -126,9 +386,10 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const ArrayGetVertexLayout(); break; case MeshBufferType::Vertex1: buffer = _vertexBuffers[1]; + if (layout && buffer) + *layout = buffer->GetVertexLayout(); break; case MeshBufferType::Vertex2: buffer = _vertexBuffers[2]; + if (layout && buffer) + *layout = buffer->GetVertexLayout(); break; } return buffer && buffer->DownloadData(result); } -Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const +Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout) const { GPUBuffer* buffer = nullptr; switch (type) @@ -274,17 +542,163 @@ Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result break; case MeshBufferType::Vertex0: buffer = _vertexBuffers[0]; + if (layout && buffer) + *layout = buffer->GetVertexLayout(); break; case MeshBufferType::Vertex1: buffer = _vertexBuffers[1]; + if (layout && buffer) + *layout = buffer->GetVertexLayout(); break; case MeshBufferType::Vertex2: buffer = _vertexBuffers[2]; + if (layout && buffer) + *layout = buffer->GetVertexLayout(); break; } return buffer ? buffer->DownloadDataAsync(result) : nullptr; } +bool MeshBase::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const +{ + if (_cachedVertexBuffers[0].IsInvalid()) + { + PROFILE_CPU(); + auto model = GetModelBase(); + ScopeLock lock(model->Locker); + if (model->IsVirtual()) + { + LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download."); + return true; + } + + // Fetch chunk with data from drive/memory + const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex()); + if (model->LoadChunk(chunkIndex)) + return true; + const auto chunk = model->GetChunk(chunkIndex); + if (!chunk) + { + LOG(Error, "Missing chunk."); + return true; + } + MemoryReadStream stream(chunk->Get(), chunk->Size()); + ModelBase::MeshData meshData; + + // Seek to find mesh location + byte meshVersion = stream.ReadByte(); + for (int32 meshIndex = 0; meshIndex <= _index; meshIndex++) + { + if (model->LoadMesh(stream, meshVersion, model->GetMesh(meshIndex, _lodIndex), &meshData)) + return true; + if (meshIndex != _index) + continue; + + // Cache mesh data + _cachedVertexBufferCount = meshData.Vertices; + _cachedIndexBufferCount = (int32)meshData.Triangles * 3; + _cachedIndexBuffer.Copy((const byte*)meshData.IBData, _cachedIndexBufferCount * (int32)meshData.IBStride); + for (int32 vb = 0; vb < meshData.VBData.Count(); vb++) + { + _cachedVertexBuffers[vb].Copy((const byte*)meshData.VBData[vb], (int32)(meshData.VBLayout[vb]->GetStride() * meshData.Vertices)); + _cachedVertexLayouts[vb] = meshData.VBLayout[vb]; + } + break; + } + } + + switch (type) + { + case MeshBufferType::Index: + result.Link(_cachedIndexBuffer); + count = _cachedIndexBufferCount; + break; + case MeshBufferType::Vertex0: + result.Link(_cachedVertexBuffers[0]); + count = _cachedVertexBufferCount; + if (layout) + *layout = _cachedVertexLayouts[0]; + break; + case MeshBufferType::Vertex1: + result.Link(_cachedVertexBuffers[1]); + count = _cachedVertexBufferCount; + if (layout) + *layout = _cachedVertexLayouts[1]; + break; + case MeshBufferType::Vertex2: + result.Link(_cachedVertexBuffers[2]); + count = _cachedVertexBufferCount; + if (layout) + *layout = _cachedVertexLayouts[2]; + break; + default: + return true; + } + return false; +} + +bool MeshBase::DownloadData(Span types, Array>& buffers, Array>& layouts, bool forceGpu) const +{ + PROFILE_CPU(); + buffers.Resize(types.Length()); + layouts.Resize(types.Length()); + layouts.SetAll(nullptr); + auto model = _model; + model->Locker.Lock(); + + // Virtual assets always fetch from GPU memory + forceGpu |= model->IsVirtual(); + + if (forceGpu) + { + if (!IsInitialized()) + { + model->Locker.Unlock(); + LOG(Error, "Cannot load mesh data from GPU if it's not loaded."); + return true; + } + + // Get data from GPU (start of series of async tasks that copy GPU-read data into staging buffers) + Array> tasks; + for (int32 i = 0; i < types.Length(); i++) + { + auto task = DownloadDataGPUAsync(types[i], buffers[i], &layouts[i]); + if (!task) + { + model->Locker.Unlock(); + return true; + } + task->Start(); + tasks.Add(task); + } + + // Wait for async tasks + model->Locker.Unlock(); + if (Task::WaitAll(tasks)) + { + LOG(Error, "Failed to download mesh data from GPU."); + return true; + } + model->Locker.Lock(); + } + else + { + // Get data from CPU + for (int32 i = 0; i < types.Length(); i++) + { + int32 count = 0; + if (DownloadDataCPU(types[i], buffers[i], count, &layouts[i])) + { + model->Locker.Unlock(); + return true; + } + } + } + + model->Locker.Unlock(); + return false; +} + void MeshBase::GetDrawCallGeometry(DrawCall& drawCall) const { drawCall.Geometry.IndexBuffer = _indexBuffer; @@ -324,4 +738,85 @@ bool MeshBase::UpdateTrianglesUShort(int32 triangleCount, const MArray* triangle return ::UpdateTriangles(this, triangleCount, trianglesObj); } +MArray* MeshBase::DownloadIndexBuffer(bool forceGpu, MTypeObject* resultType, bool use16Bit) +{ + ScopeLock lock(GetModelBase()->Locker); + + // Get index buffer data from the mesh (CPU or GPU) + MeshAccessor accessor; + MeshBufferType bufferTypes[1] = { MeshBufferType::Index }; + if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1))) + return nullptr; + auto indexStream = accessor.Index(); + if (!indexStream.IsValid()) + return nullptr; + auto indexData = indexStream.GetData(); + auto indexCount = indexStream.GetCount(); + auto indexStride = indexStream.GetStride(); + + // Convert into managed array + MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), indexCount); + void* managedArrayPtr = MCore::Array::GetAddress(result); + if (use16Bit) + { + if (indexStride == sizeof(uint16)) + { + Platform::MemoryCopy(managedArrayPtr, indexData.Get(), indexData.Length()); + } + else + { + auto dst = (uint16*)managedArrayPtr; + auto src = (const uint32*)indexData.Get(); + for (int32 i = 0; i < indexCount; i++) + dst[i] = src[i]; + } + } + else + { + if (indexStride == sizeof(uint16)) + { + auto dst = (uint32*)managedArrayPtr; + auto src = (const uint16*)indexData.Get(); + for (int32 i = 0; i < indexCount; i++) + dst[i] = src[i]; + } + else + { + Platform::MemoryCopy(managedArrayPtr, indexData.Get(), indexData.Length()); + } + } + + return result; +} + +bool MeshBase::DownloadData(int32 count, MeshBufferType* types, BytesContainer& buffer0, BytesContainer& buffer1, BytesContainer& buffer2, BytesContainer& buffer3, GPUVertexLayout*& layout0, GPUVertexLayout*& layout1, GPUVertexLayout*& layout2, GPUVertexLayout*& layout3, bool forceGpu) const +{ + layout0 = layout1 = layout2 = layout3 = nullptr; + Array> meshBuffers; + Array> meshLayouts; + if (DownloadData(Span(types, count), meshBuffers, meshLayouts, forceGpu)) + return true; + if (count > 0) + { + buffer0 = meshBuffers[0]; + layout0 = meshLayouts[0]; + if (count > 1) + { + buffer1 = meshBuffers[1]; + layout1 = meshLayouts[1]; + if (count > 2) + { + buffer2 = meshBuffers[2]; + layout2 = meshLayouts[2]; + if (count > 3) + { + buffer3 = meshBuffers[3]; + layout3 = meshLayouts[3]; + } + } + } + } + return false; +} + #endif diff --git a/Source/Engine/Graphics/Models/MeshBase.cs b/Source/Engine/Graphics/Models/MeshBase.cs index 5156c20a6..db1cf388a 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cs +++ b/Source/Engine/Graphics/Models/MeshBase.cs @@ -92,5 +92,84 @@ namespace FlaxEngine if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles))) throw new Exception("Failed to update mesh data."); } + + /// + /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// + /// If mesh index buffer format (see ) is then it's faster to call . + /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. + /// The gathered data. + public uint[] DownloadIndexBuffer(bool forceGpu = false) + { + var result = (uint[])Internal_DownloadIndexBuffer(__unmanagedPtr, forceGpu, typeof(uint), false); + if (result == null) + throw new Exception("Failed to download mesh data."); + return result; + } + + /// + /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// + /// If mesh index buffer format (see ) is then data won't be downloaded. + /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. + /// The gathered data. + public ushort[] DownloadIndexBufferUShort(bool forceGpu = false) + { + var result = (ushort[])Internal_DownloadIndexBuffer(__unmanagedPtr, forceGpu, typeof(ushort), true); + if (result == null) + throw new Exception("Failed to download mesh data."); + return result; + } + + /// + /// Extracts mesh buffers data. + /// + /// List of buffers to load. + /// The result mesh buffers. + /// The result layouts of the vertex buffers. + /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. + /// True if failed, otherwise false + public unsafe bool DownloadData(Span types, out byte[][] buffers, out GPUVertexLayout[] layouts, bool forceGpu = false) + { + if (types == null) + throw new ArgumentNullException(nameof(types)); + buffers = null; + layouts = null; + var count = types.Length; + fixed (MeshBufferType* typesPtr = types) + { + if (Internal_DownloadData(__unmanagedPtr, + count, typesPtr, + out byte[] buffer0, out byte[] buffer1, out byte[] buffer2, out byte[] buffer3, + out GPUVertexLayout layout0, out GPUVertexLayout layout1, out GPUVertexLayout layout2, out GPUVertexLayout layout3, + forceGpu, + out int __buffer0Count, out int __buffer1Count, out int __buffer2Count, out int __buffer3Count + )) + return true; + buffers = new byte[count][]; + layouts = new GPUVertexLayout[count]; + if (count > 0) + { + buffers[0] = buffer0; + layouts[0] = layout0; + if (count > 1) + { + buffers[1] = buffer1; + layouts[1] = layout1; + if (count > 2) + { + buffers[2] = buffer2; + layouts[2] = layout2; + if (count > 3) + { + buffers[3] = buffer3; + layouts[3] = layout3; + } + } + } + } + } + return false; + } } } diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index ff5e07acd..e256b4826 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -5,7 +5,6 @@ #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Types/DataContainer.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Graphics/Enums.h" #include "Engine/Graphics/Models/Types.h" #include "Engine/Level/Types.h" @@ -47,12 +46,13 @@ protected: bool _use16BitIndexBuffer = false; bool _hasBounds = false; - GPUBuffer* _vertexBuffers[3] = {}; + GPUBuffer* _vertexBuffers[MODEL_MAX_VB] = {}; GPUBuffer* _indexBuffer = nullptr; - mutable Array _cachedVertexBuffers[3]; - mutable Array _cachedIndexBuffer; - mutable int32 _cachedIndexBufferCount = 0; + mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB]; + mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB] = {}; + mutable BytesContainer _cachedIndexBuffer; + mutable int32 _cachedIndexBufferCount = 0, _cachedVertexBufferCount = 0; #if MODEL_USE_PRECISE_MESH_INTERSECTS CollisionProxy _collisionProxy; @@ -177,19 +177,19 @@ public: /// Sets the mesh bounds. /// /// The bounding box. - void SetBounds(const BoundingBox& box); + API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box); /// /// Sets the mesh bounds. /// /// The bounding box. /// The bounding sphere. - void SetBounds(const BoundingBox& box, const BoundingSphere& sphere); + API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box, API_PARAM(ref) const BoundingSphere& sphere); /// /// Gets the index buffer. /// - FORCE_INLINE GPUBuffer* GetIndexBuffer() const + API_FUNCTION() FORCE_INLINE GPUBuffer* GetIndexBuffer() const { return _indexBuffer; } @@ -199,23 +199,29 @@ public: /// /// The bind slot index. /// The buffer or null if not used. - FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const + API_FUNCTION() FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const { return _vertexBuffers[index]; } + /// + /// Gets the vertex buffers layout. Made out of all buffers used by this mesh. + /// + /// The vertex layout. + API_PROPERTY() GPUVertexLayout* GetVertexLayout() const; + public: /// /// Initializes the mesh buffers. /// /// Amount of vertices in the vertex buffer. /// Amount of triangles in the index buffer. - /// Array with pointers to vertex buffers initial data (layout defined by ). + /// Array with pointers to vertex buffers initial data (layout defined by ). /// Pointer to index buffer data. Data is uint16 or uint32 depending on value. /// True to use 16-bit indices for the index buffer (true: uint16, false: uint32). /// Layout descriptors for the vertex buffers attributes (one for each vertex buffer). /// True if failed, otherwise false. - virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout); + API_FUNCTION(Sealed) virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout); /// /// Releases the mesh data (GPU buffers and local cache). @@ -290,16 +296,18 @@ public: /// /// Buffer type /// The result data + /// The result layout of the vertex buffer (optional). /// True if failed, otherwise false - bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const; + bool DownloadDataGPU(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout = nullptr) const; /// /// Extracts mesh buffer data from GPU in the async task. /// /// Buffer type /// The result data + /// The result layout of the vertex buffer (optional). /// Created async task used to gather the buffer data. - Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const; + Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout = nullptr) const; /// /// Extract mesh buffer data from CPU. Cached internally. @@ -307,8 +315,19 @@ public: /// Buffer type /// The result data /// The amount of items inside the result buffer. + /// The result layout of the vertex buffer (optional). /// True if failed, otherwise false - virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const = 0; + bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const; + + /// + /// Extracts mesh buffers data. + /// + /// List of buffers to load. + /// The result mesh buffers. + /// The result layouts of the vertex buffers. + /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. + /// True if failed, otherwise false + bool DownloadData(Span types, Array>& buffers, Array>& layouts, bool forceGpu = false) const; public: /// @@ -423,5 +442,7 @@ private: #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj); API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj); + API_FUNCTION(NoProxy) MArray* DownloadIndexBuffer(bool forceGpu, MTypeObject* resultType, bool use16Bit); + API_FUNCTION(NoProxy) bool DownloadData(int32 count, MeshBufferType* types, API_PARAM(Out) BytesContainer& buffer0, API_PARAM(Out) BytesContainer& buffer1, API_PARAM(Out) BytesContainer& buffer2, API_PARAM(Out) BytesContainer& buffer3, API_PARAM(Out) GPUVertexLayout*& layout0, API_PARAM(Out) GPUVertexLayout*& layout1, API_PARAM(Out) GPUVertexLayout*& layout2, API_PARAM(Out) GPUVertexLayout*& layout3, bool forceGpu) const; #endif }; diff --git a/Source/Engine/Graphics/Models/MeshDeformation.cpp b/Source/Engine/Graphics/Models/MeshDeformation.cpp index 2183fd7ea..e183371c0 100644 --- a/Source/Engine/Graphics/Models/MeshDeformation.cpp +++ b/Source/Engine/Graphics/Models/MeshDeformation.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "MeshDeformation.h" +#include "MeshAccessor.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -29,6 +30,11 @@ FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferTyp return key.Value; } +bool MeshDeformationData::LoadMeshAccessor(MeshAccessor& accessor) const +{ + return accessor.LoadBuffer(Type, ToSpan(VertexBuffer.Data), VertexBuffer.GetLayout()); +} + void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const { const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0); diff --git a/Source/Engine/Graphics/Models/MeshDeformation.h b/Source/Engine/Graphics/Models/MeshDeformation.h index dbc7db87c..441b41c36 100644 --- a/Source/Engine/Graphics/Models/MeshDeformation.h +++ b/Source/Engine/Graphics/Models/MeshDeformation.h @@ -32,6 +32,8 @@ struct MeshDeformationData ~MeshDeformationData() { } + + bool LoadMeshAccessor(class MeshAccessor& accessor) const; }; /// diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index ff80fadfd..fddde25ce 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -6,9 +6,6 @@ #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/WriteStream.h" -#include "Engine/Debug/Exceptions/ArgumentNullException.h" -#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" -#include "Engine/Debug/Exceptions/InvalidOperationException.h" void MeshData::Clear() { @@ -71,6 +68,7 @@ void MeshData::Release() BlendShapes.Resize(0); } +PRAGMA_DISABLE_DEPRECATION_WARNINGS void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount) { Positions.Resize(verticesCount, false); @@ -160,6 +158,7 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb vb1++; } } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void MeshData::SetIndexBuffer(void* data, uint32 indicesCount) { @@ -181,237 +180,52 @@ void MeshData::SetIndexBuffer(void* data, uint32 indicesCount) } } -bool MeshData::Pack2Model(WriteStream* stream) const -{ - // Validate input - if (stream == nullptr) - { - LOG(Error, "Invalid input."); - return true; - } - - // Cache size - uint32 verticiecCount = Positions.Count(); - uint32 indicesCount = Indices.Count(); - uint32 trianglesCount = indicesCount / 3; - bool use16Bit = indicesCount <= MAX_uint16; - if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0) - { - LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount); - return true; - } - - // Validate data structure - bool hasUVs = UVs.HasItems(); - if (hasUVs && UVs.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("UVs")); - return true; - } - bool hasNormals = Normals.HasItems(); - if (hasNormals && Normals.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("Normals")); - return true; - } - bool hasTangents = Tangents.HasItems(); - if (hasTangents && Tangents.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents")); - return true; - } - bool hasBitangentSigns = BitangentSigns.HasItems(); - if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns")); - return true; - } - bool hasLightmapUVs = LightmapUVs.HasItems(); - if (hasLightmapUVs && LightmapUVs.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs")); - return true; - } - bool hasVertexColors = Colors.HasItems(); - if (hasVertexColors && Colors.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("Colors")); - return true; - } - - // Vertices - stream->WriteUint32(verticiecCount); - - // Triangles - stream->WriteUint32(trianglesCount); - - // Vertex Buffer 0 - stream->WriteBytes(Positions.Get(), sizeof(Float3) * verticiecCount); - - // Vertex Buffer 1 - VB1ElementType vb1; - for (uint32 i = 0; i < verticiecCount; i++) - { - // Get vertex components - Float2 uv = hasUVs ? UVs[i] : Float2::Zero; - Float3 normal = hasNormals ? Normals[i] : Float3::UnitZ; - Float3 tangent = hasTangents ? Tangents[i] : Float3::UnitX; - Float2 lightmapUV = hasLightmapUVs ? LightmapUVs[i] : Float2::Zero; - Float3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent); - - // Write vertex - vb1.TexCoord = Half2(uv); - vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0); - vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast(bitangentSign < 0 ? 1 : 0)); - vb1.LightmapUVs = Half2(lightmapUV); - stream->WriteBytes(&vb1, sizeof(vb1)); - } - - // Vertex Buffer 2 - stream->WriteBool(hasVertexColors); - if (hasVertexColors) - { - VB2ElementType vb2; - for (uint32 i = 0; i < verticiecCount; i++) - { - vb2.Color = Color32(Colors[i]); - stream->WriteBytes(&vb2, sizeof(vb2)); - } - } - - // Index Buffer - if (use16Bit) - { - for (uint32 i = 0; i < indicesCount; i++) - stream->WriteUint16(Indices[i]); - } - else - { - stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount); - } - - return false; -} - -bool MeshData::Pack2SkinnedModel(WriteStream* stream) const -{ - // Validate input - if (stream == nullptr) - { - LOG(Error, "Invalid input."); - return true; - } - - // Cache size - uint32 verticiecCount = Positions.Count(); - uint32 indicesCount = Indices.Count(); - uint32 trianglesCount = indicesCount / 3; - bool use16Bit = indicesCount <= MAX_uint16; - if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0) - { - LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount); - return true; - } - - // Validate data structure - bool hasUVs = UVs.HasItems(); - if (hasUVs && UVs.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT( "UVs")); - return true; - } - bool hasNormals = Normals.HasItems(); - if (hasNormals && Normals.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("Normals")); - return true; - } - bool hasTangents = Tangents.HasItems(); - if (hasTangents && Tangents.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents")); - return true; - } - bool hasBitangentSigns = BitangentSigns.HasItems(); - if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns")); - return true; - } - if (BlendIndices.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices")); - return true; - } - if (BlendWeights.Count() != verticiecCount) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights")); - return true; - } - - // Vertices - stream->WriteUint32(verticiecCount); - - // Triangles - stream->WriteUint32(trianglesCount); - - // Blend Shapes - stream->WriteUint16(BlendShapes.Count()); - for (const auto& blendShape : BlendShapes) - { - stream->WriteBool(blendShape.UseNormals); - stream->WriteUint32(blendShape.MinVertexIndex); - stream->WriteUint32(blendShape.MaxVertexIndex); - stream->WriteUint32(blendShape.Vertices.Count()); - stream->WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex)); - } - - // Vertex Buffer - VB0SkinnedElementType vb; - for (uint32 i = 0; i < verticiecCount; i++) - { - // Get vertex components - Float2 uv = hasUVs ? UVs[i] : Float2::Zero; - Float3 normal = hasNormals ? Normals[i] : Float3::UnitZ; - Float3 tangent = hasTangents ? Tangents[i] : Float3::UnitX; - Float3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent); - Int4 blendIndices = BlendIndices[i]; - Float4 blendWeights = BlendWeights[i]; - - // Write vertex - vb.Position = Positions[i]; - vb.TexCoord = Half2(uv); - vb.Normal = Float1010102(normal * 0.5f + 0.5f, 0); - vb.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast(bitangentSign < 0 ? 1 : 0)); - vb.BlendIndices = Color32(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W); - vb.BlendWeights = Half4(blendWeights); - stream->WriteBytes(&vb, sizeof(vb)); - } - - // Index Buffer - if (use16Bit) - { - for (uint32 i = 0; i < indicesCount; i++) - stream->WriteUint16(Indices[i]); - } - else - { - stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount); - } - - return false; -} - void MeshData::CalculateBox(BoundingBox& result) const { if (Positions.HasItems()) BoundingBox::FromPoints(Positions.Get(), Positions.Count(), result); + else + result = BoundingBox::Zero; } void MeshData::CalculateSphere(BoundingSphere& result) const { if (Positions.HasItems()) BoundingSphere::FromPoints(Positions.Get(), Positions.Count(), result); + else + result = BoundingSphere::Empty; +} + +void MeshData::CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const +{ + if (Positions.HasItems()) + { + // Merged code of BoundingBox::FromPoints and BoundingSphere::FromPoints within a single loop + const Float3* points = Positions.Get(); + const int32 pointsCount = Positions.Count(); + Float3 min = points[0]; + Float3 max = min; + Float3 center = min; + for (int32 i = 1; i < pointsCount; i++) + Float3::Add(points[i], center, center); + center /= (float)pointsCount; + float radiusSq = Float3::DistanceSquared(center, min); + for (int32 i = 1; i < pointsCount; i++) + { + Float3::Min(min, points[i], min); + Float3::Max(max, points[i], max); + const float distance = Float3::DistanceSquared(center, points[i]); + if (distance > radiusSq) + radiusSq = distance; + } + box = BoundingBox(min, max); + sphere = BoundingSphere(center, Math::Sqrt(radiusSq)); + } + else + { + box = BoundingBox::Zero; + sphere = BoundingSphere::Empty; + } } void MeshData::TransformBuffer(const Matrix& matrix) @@ -616,276 +430,3 @@ void ModelData::TransformBuffer(const Matrix& matrix) } } } - -#if USE_EDITOR - -bool ModelData::Pack2ModelHeader(WriteStream* stream) const -{ - // Validate input - if (stream == nullptr) - { - Log::ArgumentNullException(); - return true; - } - const int32 lodCount = GetLODsCount(); - if (lodCount == 0 || lodCount > MODEL_MAX_LODS) - { - Log::ArgumentOutOfRangeException(); - return true; - } - if (Materials.IsEmpty()) - { - Log::ArgumentOutOfRangeException(TEXT("MaterialSlots"), TEXT("Material slots collection cannot be empty.")); - return true; - } - - // Min Screen Size - stream->WriteFloat(MinScreenSize); - - // Amount of material slots - stream->WriteInt32(Materials.Count()); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++) - { - auto& slot = Materials[materialSlotIndex]; - - stream->Write(slot.AssetID); - stream->WriteByte(static_cast(slot.ShadowsMode)); - stream->WriteString(slot.Name, 11); - } - - // Amount of LODs - stream->WriteByte(lodCount); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) - { - auto& lod = LODs[lodIndex]; - - // Screen Size - stream->WriteFloat(lod.ScreenSize); - - // Amount of meshes - const int32 meshes = lod.Meshes.Count(); - if (meshes == 0) - { - LOG(Warning, "Empty LOD."); - return true; - } - if (meshes > MODEL_MAX_MESHES) - { - LOG(Warning, "Too many meshes per LOD."); - return true; - } - stream->WriteUint16(meshes); - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++) - { - auto& mesh = *lod.Meshes[meshIndex]; - - // Material Slot - stream->WriteInt32(mesh.MaterialSlotIndex); - - // Box - BoundingBox box; - mesh.CalculateBox(box); - stream->WriteBoundingBox(box); - - // Sphere - BoundingSphere sphere; - mesh.CalculateSphere(sphere); - stream->WriteBoundingSphere(sphere); - - // TODO: calculate Sphere and Box at once - make it faster using SSE - - // Has Lightmap UVs - stream->WriteBool(mesh.LightmapUVs.HasItems()); - } - } - - return false; -} - -bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const -{ - // Validate input - if (stream == nullptr) - { - Log::ArgumentNullException(); - return true; - } - const int32 lodCount = GetLODsCount(); - if (lodCount > MODEL_MAX_LODS) - { - Log::ArgumentOutOfRangeException(); - return true; - } - - // Version - stream->WriteByte(1); - - // Min Screen Size - stream->WriteFloat(MinScreenSize); - - // Amount of material slots - stream->WriteInt32(Materials.Count()); - - // For each material slot - for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++) - { - auto& slot = Materials[materialSlotIndex]; - stream->Write(slot.AssetID); - stream->WriteByte(static_cast(slot.ShadowsMode)); - stream->WriteString(slot.Name, 11); - } - - // Amount of LODs - stream->WriteByte(lodCount); - - // For each LOD - for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) - { - auto& lod = LODs[lodIndex]; - - // Screen Size - stream->WriteFloat(lod.ScreenSize); - - // Amount of meshes - const int32 meshes = lod.Meshes.Count(); - if (meshes > MODEL_MAX_MESHES) - { - LOG(Warning, "Too many meshes per LOD."); - return true; - } - stream->WriteUint16(meshes); - - // For each mesh - for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++) - { - auto& mesh = *lod.Meshes[meshIndex]; - - // Material Slot - stream->WriteInt32(mesh.MaterialSlotIndex); - - // Box - BoundingBox box; - mesh.CalculateBox(box); - stream->WriteBoundingBox(box); - - // Sphere - BoundingSphere sphere; - mesh.CalculateSphere(sphere); - stream->WriteBoundingSphere(sphere); - - // TODO: calculate Sphere and Box at once - make it faster using SSE - - // Blend Shapes - const int32 blendShapes = mesh.BlendShapes.Count(); - stream->WriteUint16(blendShapes); - for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++) - { - auto& blendShape = mesh.BlendShapes[blendShapeIndex]; - stream->WriteString(blendShape.Name, 13); - stream->WriteFloat(blendShape.Weight); - } - } - } - - // Skeleton - { - stream->WriteInt32(Skeleton.Nodes.Count()); - - // For each node - for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++) - { - auto& node = Skeleton.Nodes[nodeIndex]; - - stream->Write(node.ParentIndex); - stream->WriteTransform(node.LocalTransform); - stream->WriteString(node.Name, 71); - } - - stream->WriteInt32(Skeleton.Bones.Count()); - - // For each bone - for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++) - { - auto& bone = Skeleton.Bones[boneIndex]; - - stream->Write(bone.ParentIndex); - stream->Write(bone.NodeIndex); - stream->WriteTransform(bone.LocalTransform); - stream->Write(bone.OffsetMatrix); - } - } - - // Retargeting - { - stream->WriteInt32(0); - } - - return false; -} - -bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const -{ - // Validate input - if (stream == nullptr || animIndex < 0 || animIndex >= Animations.Count()) - { - Log::ArgumentNullException(); - return true; - } - auto& anim = Animations.Get()[animIndex]; - if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance) - { - Log::InvalidOperationException(TEXT("Invalid animation duration.")); - return true; - } - if (anim.Channels.IsEmpty()) - { - Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty.")); - return true; - } - - // Info - stream->WriteInt32(103); // Header version (for fast version upgrades without serialization format change) - stream->WriteDouble(anim.Duration); - stream->WriteDouble(anim.FramesPerSecond); - stream->WriteByte((byte)anim.RootMotionFlags); - stream->WriteString(anim.RootNodeName, 13); - - // Animation channels - stream->WriteInt32(anim.Channels.Count()); - for (int32 i = 0; i < anim.Channels.Count(); i++) - { - auto& channel = anim.Channels[i]; - stream->WriteString(channel.NodeName, 172); - Serialization::Serialize(*stream, channel.Position); - Serialization::Serialize(*stream, channel.Rotation); - Serialization::Serialize(*stream, channel.Scale); - } - - // Animation events - stream->WriteInt32(anim.Events.Count()); - for (auto& e : anim.Events) - { - stream->WriteString(e.First, 172); - stream->WriteInt32(e.Second.GetKeyframes().Count()); - for (const auto& k : e.Second.GetKeyframes()) - { - stream->WriteFloat(k.Time); - stream->WriteFloat(k.Value.Duration); - stream->WriteStringAnsi(k.Value.TypeName, 17); - stream->WriteJson(k.Value.JsonData); - } - } - - // Nested animations - stream->WriteInt32(0); - - return false; -} - -#endif diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index ace5709bb..615a3d31a 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -39,9 +39,11 @@ public: Array Positions; /// - /// Texture coordinates + /// Texture coordinates (list of channels) /// + // TODO: multiple UVs Array UVs; + Array LightmapUVs; // TODO: remove this and move to UVs /// /// Normals vector @@ -65,11 +67,6 @@ public: /// Array Indices; - /// - /// Lightmap UVs - /// - Array LightmapUVs; - /// /// Vertex colors /// @@ -91,12 +88,17 @@ public: Array BlendShapes; /// - /// Global translation for this mesh to be at it's local origin. + /// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available. + /// + int32 LightmapUVsIndex = -1; + + /// + /// Global translation for this mesh to be at its local origin. /// Vector3 OriginTranslation = Vector3::Zero; /// - /// Orientation for this mesh at it's local origin. + /// Orientation for this mesh at its local origin. /// Quaternion OriginOrientation = Quaternion::Identity; @@ -105,15 +107,6 @@ public: /// Vector3 Scaling = Vector3::One; -public: - /// - /// Determines whether this instance has any mesh data. - /// - FORCE_INLINE bool HasData() const - { - return Indices.HasItems(); - } - public: /// /// Clear arrays @@ -142,6 +135,7 @@ public: void Release(); public: +PRAGMA_DISABLE_DEPRECATION_WARNINGS /// /// Init from model vertices array /// @@ -165,6 +159,7 @@ public: /// Array of data for vertex buffer 2 /// Amount of vertices void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount); +PRAGMA_ENABLE_DEPRECATION_WARNINGS /// /// Sets the index buffer data. @@ -174,20 +169,6 @@ public: void SetIndexBuffer(void* data, uint32 indicesCount); public: - /// - /// Pack mesh data to the stream - /// - /// Output stream - /// True if cannot save data, otherwise false - bool Pack2Model(WriteStream* stream) const; - - /// - /// Pack skinned mesh data to the stream - /// - /// Output stream - /// True if cannot save data, otherwise false - bool Pack2SkinnedModel(WriteStream* stream) const; - /// /// Calculate bounding box for the mesh /// @@ -200,9 +181,14 @@ public: /// Output sphere void CalculateSphere(BoundingSphere& result) const; -public: -#if COMPILE_WITH_MODEL_TOOL + /// + /// Calculates bounding box and sphere for the mesh. + /// + /// Output box. + /// Output sphere. + void CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const; +#if COMPILE_WITH_MODEL_TOOL /// /// Generate lightmap uvs for the mesh entry /// @@ -246,7 +232,6 @@ public: /// /// The area sum of all mesh triangles. float CalculateTrianglesArea() const; - #endif /// @@ -439,23 +424,6 @@ public: /// Array Animations; -public: - /// - /// Gets the valid level of details count. - /// - FORCE_INLINE int32 GetLODsCount() const - { - return LODs.Count(); - } - - /// - /// Determines whether this instance has valid skeleton structure. - /// - FORCE_INLINE bool HasSkeleton() const - { - return Skeleton.Bones.HasItems(); - } - public: /// /// Automatically calculates the screen size for every model LOD for a proper transitions. @@ -467,29 +435,4 @@ public: /// /// The matrix to use for the transformation. void TransformBuffer(const Matrix& matrix); - -#if USE_EDITOR -public: - /// - /// Pack mesh data to the header stream - /// - /// Output stream - /// True if cannot save data, otherwise false - bool Pack2ModelHeader(WriteStream* stream) const; - - /// - /// Pack skinned mesh data to the header stream - /// - /// Output stream - /// True if cannot save data, otherwise false - bool Pack2SkinnedModelHeader(WriteStream* stream) const; - - /// - /// Pack animation data to the header stream - /// - /// Output stream - /// Index of animation. - /// True if cannot save data, otherwise false - bool Pack2AnimationHeader(WriteStream* stream, int32 animIndex = 0) const; -#endif }; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 56bfad9ba..94d0b0f4f 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "SkinnedMesh.h" +#include "MeshAccessor.h" #include "MeshDeformation.h" #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" @@ -13,12 +14,11 @@ #include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Renderer/RenderList.h" -#include "Engine/Serialization/MemoryReadStream.h" -#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ManagedCLR/MCore.h" -#include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" +PRAGMA_DISABLE_DEPRECATION_WARNINGS + GPUVertexLayout* VB0SkinnedElementType2::GetLayout() { return GPUVertexLayout::Get({ @@ -31,6 +31,125 @@ GPUVertexLayout* VB0SkinnedElementType2::GetLayout() }); } +PRAGMA_ENABLE_DEPRECATION_WARNINGS + +namespace +{ + bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) + { + auto model = mesh->GetModelBase(); + CHECK_RETURN(model && model->IsVirtual(), true); + CHECK_RETURN(triangles && vertices, true); + MeshAccessor accessor; + + // Index Buffer + { + if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat)) + return true; + auto indexStream = accessor.Index(); + ASSERT(indexStream.IsLinear(indexFormat)); + indexStream.SetLinear(triangles); + } + + // Vertex Buffer + { + GPUVertexLayout::Elements vb0elements; + vb0elements.Add({ VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float }); + if (normals) + { + vb0elements.Add({ VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }); + if (tangents) + vb0elements.Add({ VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }); + } + vb0elements.Add({ VertexElement::Types::BlendIndices, 0, 0, 0, PixelFormat::R8G8B8A8_UInt }); + vb0elements.Add({ VertexElement::Types::BlendWeights, 0, 0, 0, PixelFormat::R16G16B16A16_Float }); + if (uvs) + vb0elements.Add({ VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float }); + if (colors) + vb0elements.Add({ VertexElement::Types::Color, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm }); + + GPUVertexLayout* vb0layout = GPUVertexLayout::Get(vb0elements); + if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout)) + return true; + + auto positionStream = accessor.Position(); + ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float)); + positionStream.SetLinear(vertices); + if (normals) + { + auto normalStream = accessor.Normal(); + if (tangents) + { + auto tangentStream = accessor.Tangent(); + for (uint32 i = 0; i < vertexCount; i++) + { + const Float3 normal = normals[i]; + const Float3 tangent = tangents[i]; + Float3 n; + Float4 t; + RenderTools::CalculateTangentFrame(n, t, normal, tangent); + normalStream.SetFloat3(i, n); + tangentStream.SetFloat4(i, t); + } + } + else + { + for (uint32 i = 0; i < vertexCount; i++) + { + const Float3 normal = normals[i]; + Float3 n; + Float4 t; + RenderTools::CalculateTangentFrame(n, t, normal); + normalStream.SetFloat3(i, n); + } + } + } + { + auto blendIndicesStream = accessor.BlendIndices(); + auto blendWeightsStream = accessor.BlendWeights(); + for (uint32 i = 0; i < vertexCount; i++) + { + blendIndicesStream.SetFloat4(i, blendIndices[i]); + blendWeightsStream.SetFloat4(i, blendWeights[i]); + } + } + if (uvs) + { + auto uvsStream = accessor.TexCoord(); + for (uint32 i = 0; i < vertexCount; i++) + uvsStream.SetFloat2(i, uvs[i]); + } + if (colors) + { + auto colorStream = accessor.Color(); + for (uint32 i = 0; i < vertexCount; i++) + colorStream.SetFloat4(i, Float4(Color(colors[i]))); // TODO: optimize with direct memory copy + } + } + + return accessor.UpdateMesh(mesh); + } + +#if !COMPILE_WITHOUT_CSHARP + template + bool UpdateMesh(SkinnedMesh* mesh, uint32 vertexCount, uint32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) + { + ASSERT((uint32)MCore::Array::GetLength(verticesObj) >= vertexCount); + ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount); + auto vertices = MCore::Array::GetAddress(verticesObj); + auto triangles = MCore::Array::GetAddress(trianglesObj); + const PixelFormat indexFormat = sizeof(IndexType) == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt; + const auto blendIndices = MCore::Array::GetAddress(blendIndicesObj); + const auto blendWeights = MCore::Array::GetAddress(blendWeightsObj); + const auto normals = normalsObj ? MCore::Array::GetAddress(normalsObj) : nullptr; + const auto tangents = tangentsObj ? MCore::Array::GetAddress(tangentsObj) : nullptr; + const auto uvs = uvObj ? MCore::Array::GetAddress(uvObj) : nullptr; + const auto colors = colorsObj ? MCore::Array::GetAddress(colorsObj) : nullptr; + return UpdateMesh(mesh, vertexCount, triangleCount, indexFormat, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors); + } +#endif +} + void SkeletonData::Swap(SkeletonData& other) { Nodes.Swap(other.Nodes); @@ -99,14 +218,39 @@ bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const Array> vbData; vbData.Add(vb0); Array> vbLayout; + PRAGMA_DISABLE_DEPRECATION_WARNINGS vbLayout.Add(VB0SkinnedElementType::GetLayout()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout); } +bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib) +{ + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return UpdateMesh(vertexCount, triangleCount, vb, ib, false); + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + +bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib) +{ + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return UpdateMesh(vertexCount, triangleCount, vb, ib, false); + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + +bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib) +{ + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return UpdateMesh(vertexCount, triangleCount, vb, ib, true); + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices) { // Setup GPU resources + PRAGMA_DISABLE_DEPRECATION_WARNINGS const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices); + PRAGMA_ENABLE_DEPRECATION_WARNINGS if (!failed) { // Calculate mesh bounds @@ -117,6 +261,16 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0 return failed; } +bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) +{ + return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors); +} + +bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) +{ + return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors); +} + void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const { const auto& entry = info.Buffer->At(_materialSlotIndex); @@ -212,297 +366,70 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI void SkinnedMesh::Release() { MeshBase::Release(); - + BlendShapes.Clear(); } -bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const -{ - if (_cachedVertexBuffers[0].IsEmpty()) - { - PROFILE_CPU(); - auto model = GetSkinnedModel(); - ScopeLock lock(model->Locker); - if (model->IsVirtual()) - { - LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download"); - return true; - } - - // Fetch chunk with data from drive/memory - const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(_lodIndex); - if (model->LoadChunk(chunkIndex)) - return true; - const auto chunk = model->GetChunk(chunkIndex); - if (!chunk) - { - LOG(Error, "Missing chunk."); - return true; - } - - MemoryReadStream stream(chunk->Get(), chunk->Size()); - - // Seek to find mesh location - byte version = stream.ReadByte(); - for (int32 i = 0; i <= _index; i++) - { - // #MODEL_DATA_FORMAT_USAGE - uint32 vertices; - stream.ReadUint32(&vertices); - uint32 triangles; - stream.ReadUint32(&triangles); - uint16 blendShapesCount; - stream.ReadUint16(&blendShapesCount); - for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++) - { - uint32 minVertexIndex, maxVertexIndex; - bool useNormals = stream.ReadBool(); - stream.ReadUint32(&minVertexIndex); - stream.ReadUint32(&maxVertexIndex); - uint32 blendShapeVertices; - stream.ReadUint32(&blendShapeVertices); - auto blendShapeVerticesData = stream.Move(blendShapeVertices * sizeof(BlendShapeVertex)); - } - uint32 indicesCount = triangles * 3; - bool use16BitIndexBuffer = indicesCount <= MAX_uint16; - uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); - if (vertices == 0 || triangles == 0) - { - LOG(Error, "Invalid mesh data."); - return true; - } - auto vb0 = stream.Move(vertices); - auto ib = stream.Move(indicesCount * ibStride); - - if (i != _index) - continue; - - // Cache mesh data - _cachedIndexBufferCount = indicesCount; - _cachedIndexBuffer.Set(ib, indicesCount * ibStride); - _cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType)); - break; - } - } - - switch (type) - { - case MeshBufferType::Index: - result.Link(_cachedIndexBuffer); - count = _cachedIndexBufferCount; - break; - case MeshBufferType::Vertex0: - result.Link(_cachedVertexBuffers[0]); - count = _cachedVertexBuffers[0].Count() / sizeof(VB0SkinnedElementType); - break; - default: - return true; - } - return false; -} - #if !COMPILE_WITHOUT_CSHARP -template -bool UpdateMesh(SkinnedMesh* mesh, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj) +bool SkinnedMesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { - auto model = mesh->GetSkinnedModel(); - ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj && blendIndicesObj && blendWeightsObj); - - // Get buffers data - const auto vertexCount = (uint32)MCore::Array::GetLength(verticesObj); - const auto triangleCount = (uint32)MCore::Array::GetLength(trianglesObj) / 3; - auto vertices = MCore::Array::GetAddress(verticesObj); - auto ib = MCore::Array::GetAddress(trianglesObj); - auto blendIndices = MCore::Array::GetAddress(blendIndicesObj); - auto blendWeights = MCore::Array::GetAddress(blendWeightsObj); - Array vb; - vb.Resize(vertexCount); - for (uint32 i = 0; i < vertexCount; i++) - vb.Get()[i].Position = vertices[i]; - if (normalsObj) - { - const auto normals = MCore::Array::GetAddress(normalsObj); - if (tangentsObj) - { - const auto tangents = MCore::Array::GetAddress(tangentsObj); - for (uint32 i = 0; i < vertexCount; i++) - { - const Float3 normal = normals[i]; - const Float3 tangent = tangents[i]; - auto& v = vb.Get()[i]; - RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent); - } - } - else - { - for (uint32 i = 0; i < vertexCount; i++) - { - const Float3 normal = normals[i]; - auto& v = vb.Get()[i]; - RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal); - } - } - } - else - { - const auto n = Float1010102(Float3::UnitZ); - const auto t = Float1010102(Float3::UnitX); - for (uint32 i = 0; i < vertexCount; i++) - { - vb[i].Normal = n; - vb[i].Tangent = t; - } - } - if (uvObj) - { - const auto uvs = MCore::Array::GetAddress(uvObj); - for (uint32 i = 0; i < vertexCount; i++) - vb[i].TexCoord = Half2(uvs[i]); - } - else - { - auto v = Half2::Zero; - for (uint32 i = 0; i < vertexCount; i++) - vb[i].TexCoord = v; - } - for (uint32 i = 0; i < vertexCount; i++) - { - auto v = blendIndices[i]; - vb[i].BlendIndices = Color32(v.X, v.Y, v.Z, v.W); - } - for (uint32 i = 0; i < vertexCount; i++) - { - auto v = blendWeights[i]; - vb[i].BlendWeights = Half4(v); - } - - return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib); + return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj); } -bool SkinnedMesh::UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj) +bool SkinnedMesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { - return ::UpdateMesh(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj); -} - -bool SkinnedMesh::UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj) -{ - return ::UpdateMesh(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj); + return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj); } +// [Deprecated in v1.10] enum class InternalBufferType { VB0 = 0, - IB16 = 3, - IB32 = 4, }; MArray* SkinnedMesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI) { - SkinnedMesh* mesh = this; - InternalBufferType type = (InternalBufferType)typeI; - auto model = mesh->GetSkinnedModel(); - ScopeLock lock(model->Locker); + // [Deprecated in v1.10] + ScopeLock lock(GetModelBase()->Locker); - // Virtual assets always fetch from GPU memory - forceGpu |= model->IsVirtual(); - - if (!mesh->IsInitialized() && forceGpu) - { - LOG(Error, "Cannot load mesh data from GPU if it's not loaded."); + // Get vertex buffers data from the mesh (CPU or GPU) + MeshAccessor accessor; + MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 }; + if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1))) return nullptr; - } - - MeshBufferType bufferType; - switch (type) - { - case InternalBufferType::VB0: - bufferType = MeshBufferType::Vertex0; - break; - case InternalBufferType::IB16: - case InternalBufferType::IB32: - bufferType = MeshBufferType::Index; - break; - default: - return nullptr; - } - BytesContainer data; - int32 dataCount; - if (forceGpu) - { - // Get data from GPU - // TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer - auto task = mesh->DownloadDataGPUAsync(bufferType, data); - if (task == nullptr) - return nullptr; - task->Start(); - model->Locker.Unlock(); - if (task->Wait()) - { - LOG(Error, "Task failed."); - return nullptr; - } - model->Locker.Lock(); - - // Extract elements count from result data - switch (bufferType) - { - case MeshBufferType::Index: - dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32)); - break; - case MeshBufferType::Vertex0: - dataCount = data.Length() / sizeof(VB0SkinnedElementType); - break; - } - } - else - { - // Get data from CPU - if (DownloadDataCPU(bufferType, data, dataCount)) - return nullptr; - } + auto positionStream = accessor.Position(); + auto texCoordStream = accessor.TexCoord(); + auto normalStream = accessor.Normal(); + auto tangentStream = accessor.Tangent(); + auto blendIndicesStream = accessor.BlendIndices(); + auto BlendWeightsStream = accessor.BlendWeights(); + auto count = GetVertexCount(); // Convert into managed array - MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount); + MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), count); void* managedArrayPtr = MCore::Array::GetAddress(result); - const int32 elementSize = data.Length() / dataCount; - switch (type) + switch ((InternalBufferType)typeI) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS case InternalBufferType::VB0: - { - Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); - break; - } - case InternalBufferType::IB16: - { - if (elementSize == sizeof(uint16)) + for (int32 i = 0; i < count; i++) { - Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); - } - else - { - auto dst = (uint16*)managedArrayPtr; - auto src = (uint32*)data.Get(); - for (int32 i = 0; i < dataCount; i++) - dst[i] = src[i]; + auto& dst = ((VB0SkinnedElementType*)managedArrayPtr)[i]; + dst.Position = positionStream.GetFloat3(i); + if (texCoordStream.IsValid()) + dst.TexCoord = texCoordStream.GetFloat2(i); + if (normalStream.IsValid()) + dst.Normal = normalStream.GetFloat3(i); + if (tangentStream.IsValid()) + dst.Tangent = tangentStream.GetFloat4(i); + if (blendIndicesStream.IsValid()) + dst.BlendIndices = Color32(blendIndicesStream.GetFloat4(i)); + if (BlendWeightsStream.IsValid()) + dst.BlendWeights = Half4(BlendWeightsStream.GetFloat4(i)); } break; - } - case InternalBufferType::IB32: - { - if (elementSize == sizeof(uint16)) - { - auto dst = (uint32*)managedArrayPtr; - auto src = (uint16*)data.Get(); - for (int32 i = 0; i < dataCount; i++) - dst[i] = src[i]; - } - else - { - Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length()); - } - break; - } + PRAGMA_ENABLE_DEPRECATION_WARNINGS } return result; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cs b/Source/Engine/Graphics/Models/SkinnedMesh.cs index f60305e3c..26a2809c7 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cs +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cs @@ -8,7 +8,9 @@ namespace FlaxEngine { /// /// The Vertex Buffer 0 structure format. + /// [Deprecated in v1.10] /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public struct Vertex0 { /// @@ -44,7 +46,9 @@ namespace FlaxEngine /// /// The raw Vertex Buffer structure format. + /// [Deprecated in v1.10] /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public struct Vertex { /// @@ -100,7 +104,8 @@ namespace FlaxEngine /// The normal vectors (per vertex). /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). - public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null) + /// The vertex colors (per vertex). + public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) { if (!ParentSkinnedModel.IsVirtual) throw new InvalidOperationException("Only virtual skinned models can be updated at runtime."); @@ -118,8 +123,10 @@ namespace FlaxEngine throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh."); if (uv != null && uv.Length != vertices.Length) throw new ArgumentOutOfRangeException(nameof(uv)); + if (colors != null && colors.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(colors)); - if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv)) + if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors)) throw new Exception("Failed to update mesh data."); } @@ -135,7 +142,8 @@ namespace FlaxEngine /// The normal vectors (per vertex). /// The normal vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). - public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null) + /// The vertex colors (per vertex). + public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) { if (!ParentSkinnedModel.IsVirtual) throw new InvalidOperationException("Only virtual skinned models can be updated at runtime."); @@ -153,8 +161,10 @@ namespace FlaxEngine throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh."); if (uv != null && uv.Length != vertices.Length) throw new ArgumentOutOfRangeException(nameof(uv)); + if (colors != null && colors.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(colors)); - if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv)) + if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors)) throw new Exception("Failed to update mesh data."); } @@ -170,7 +180,8 @@ namespace FlaxEngine /// The normal vectors (per vertex). /// The tangent vectors (per vertex). Use null to compute them from normal vectors. /// The texture coordinates (per vertex). - public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null) + /// The vertex colors (per vertex). + public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null) { if (!ParentSkinnedModel.IsVirtual) throw new InvalidOperationException("Only virtual skinned models can be updated at runtime."); @@ -188,8 +199,10 @@ namespace FlaxEngine throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh."); if (uv != null && uv.Length != vertices.Length) throw new ArgumentOutOfRangeException(nameof(uv)); + if (colors != null && colors.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(colors)); - if (Internal_UpdateMeshUShort(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv)) + if (Internal_UpdateMeshUShort(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors)) throw new Exception("Failed to update mesh data."); } @@ -250,18 +263,22 @@ namespace FlaxEngine UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv)); } + /// + /// [Deprecated in v1.10] + /// + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] internal enum InternalBufferType { VB0 = 0, - IB16 = 3, - IB32 = 4, } /// /// Downloads the first vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// [Deprecated in v1.10] /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false) { var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0); @@ -272,13 +289,13 @@ namespace FlaxEngine /// /// Downloads the raw vertex buffer that contains mesh vertices data. To download data from GPU set to true and call this method from the thread other than main thread (see ). + /// [Deprecated in v1.10] /// /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. + [Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")] public Vertex[] DownloadVertexBuffer(bool forceGpu = false) { - // TODO: perform data conversion on C++ side to make it faster - var vb0 = DownloadVertexBuffer0(forceGpu); var vertices = vb0.Length; @@ -299,33 +316,5 @@ namespace FlaxEngine return result; } - - /// - /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). - /// - /// If mesh index buffer format (see ) is then it's faster to call . - /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. - /// The gathered data. - public uint[] DownloadIndexBuffer(bool forceGpu = false) - { - var result = (uint[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(uint), (int)InternalBufferType.IB32); - if (result == null) - throw new Exception("Failed to download mesh data."); - return result; - } - - /// - /// Downloads the index buffer that contains mesh triangles data. To download data from GPU set to true and call this method from the thread other than main thread (see ). - /// - /// If mesh index buffer format (see ) is then data won't be downloaded. - /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. - /// The gathered data. - public ushort[] DownloadIndexBufferUShort(bool forceGpu = false) - { - var result = (ushort[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(ushort), (int)InternalBufferType.IB16); - if (result == null) - throw new Exception("Failed to download mesh data."); - return result; - } } } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index f28ea7fcd..5080024f9 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -51,45 +51,43 @@ public: public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib) - { - return UpdateMesh(vertexCount, triangleCount, vb, ib, false); - } + DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.") + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib) - { - return UpdateMesh(vertexCount, triangleCount, vb, ib, false); - } + DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.") + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. /// The vertex buffer data. /// The index buffer, clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib) - { - return UpdateMesh(vertexCount, triangleCount, vb, ib, true); - } + DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.") + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// [Deprecated in v1.10] /// /// The amount of vertices in the vertex buffer. /// The amount of triangles in the index buffer. @@ -97,8 +95,45 @@ public: /// The index buffer in clockwise order. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. + DEPRECATED("Use MeshAccessor or Load with separate vertex attribute arrays instead.") bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices); + /// + /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The amount of vertices in the vertex buffer. + /// The amount of triangles in the index buffer. + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The skeletal bones indices to use for skinning. + /// The skeletal bones weights to use for skinning (matches blendIndices). + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + /// The vertex colors (per vertex). + /// True if failed, otherwise false. + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr); + + /// + /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The amount of vertices in the vertex buffer. + /// The amount of triangles in the index buffer. + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The skeletal bones indices to use for skinning. + /// The skeletal bones weights to use for skinning (matches blendIndices). + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + /// The vertex colors (per vertex). + /// True if failed, otherwise false. + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr); + public: /// /// Draws the mesh. @@ -119,13 +154,12 @@ public: public: // [MeshBase] void Release() override; - bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: // Internal bindings #if !COMPILE_WITHOUT_CSHARP - API_FUNCTION(NoProxy) bool UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj); - API_FUNCTION(NoProxy) bool UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj); + API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj); + API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj); API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI); #endif }; diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index b7f9a2151..3acde27d9 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -46,7 +46,7 @@ API_ENUM(Attributes="HideInEditor") enum class ModelLightmapUVsSource /// /// The mesh buffer types. /// -enum class MeshBufferType +API_ENUM(Attributes="HideInEditor") enum class MeshBufferType { /// /// The index buffer. @@ -73,7 +73,7 @@ enum class MeshBufferType // Vertex structure for all models (versioned) // [Deprecated in v1.10] -PACK_STRUCT(struct ModelVertex19 +PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") ModelVertex19 { Float3 Position; Half2 TexCoord; @@ -83,11 +83,13 @@ PACK_STRUCT(struct ModelVertex19 Color32 Color; }); +PRAGMA_DISABLE_DEPRECATION_WARNINGS // [Deprecated in v1.10] typedef ModelVertex19 ModelVertex; +PRAGMA_ENABLE_DEPRECATION_WARNINGS // [Deprecated in v1.10] -struct RawModelVertex +struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawModelVertex { Float3 Position; Float2 TexCoord; @@ -100,7 +102,7 @@ struct RawModelVertex // For vertex data we use three buffers: one with positions, one with other attributes, and one with colors // [Deprecated in v1.10] -PACK_STRUCT(struct VB0ElementType18 +PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB0ElementType18 { Float3 Position; @@ -108,7 +110,7 @@ PACK_STRUCT(struct VB0ElementType18 }); // [Deprecated in v1.10] -PACK_STRUCT(struct VB1ElementType18 +PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB1ElementType18 { Half2 TexCoord; Float1010102 Normal; @@ -119,23 +121,25 @@ PACK_STRUCT(struct VB1ElementType18 }); // [Deprecated in v1.10] -PACK_STRUCT(struct VB2ElementType18 +PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB2ElementType18 { Color32 Color; static GPUVertexLayout* GetLayout(); }); +PRAGMA_DISABLE_DEPRECATION_WARNINGS // [Deprecated in v1.10] typedef VB0ElementType18 VB0ElementType; // [Deprecated in v1.10] typedef VB1ElementType18 VB1ElementType; // [Deprecated in v1.10] typedef VB2ElementType18 VB2ElementType; +PRAGMA_ENABLE_DEPRECATION_WARNINGS // Vertex structure for all skinned models (versioned) // [Deprecated in v1.10] -PACK_STRUCT(struct SkinnedModelVertex1 +PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") SkinnedModelVertex1 { Float3 Position; Half2 TexCoord; @@ -145,11 +149,13 @@ PACK_STRUCT(struct SkinnedModelVertex1 Color32 BlendWeights; }); +PRAGMA_DISABLE_DEPRECATION_WARNINGS // [Deprecated in v1.10] typedef SkinnedModelVertex1 SkinnedModelVertex; +PRAGMA_ENABLE_DEPRECATION_WARNINGS // [Deprecated in v1.10] -struct RawSkinnedModelVertex +struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawSkinnedModelVertex { Float3 Position; Float2 TexCoord; @@ -161,7 +167,7 @@ struct RawSkinnedModelVertex }; // [Deprecated on 28.04.2023, expires on 01.01.2024] -PACK_STRUCT(struct VB0SkinnedElementType1 +PACK_STRUCT(struct DEPRECATED("Use newer format.") VB0SkinnedElementType1 { Float3 Position; Half2 TexCoord; @@ -172,7 +178,7 @@ PACK_STRUCT(struct VB0SkinnedElementType1 }); // [Deprecated in v1.10] -PACK_STRUCT(struct VB0SkinnedElementType2 +PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB0SkinnedElementType2 { Float3 Position; Half2 TexCoord; @@ -184,5 +190,7 @@ PACK_STRUCT(struct VB0SkinnedElementType2 static GPUVertexLayout* GetLayout(); }); +PRAGMA_DISABLE_DEPRECATION_WARNINGS // [Deprecated in v1.10] typedef VB0SkinnedElementType2 VB0SkinnedElementType; +PRAGMA_ENABLE_DEPRECATION_WARNINGS diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 10a110d62..8b9a50fe5 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1496,8 +1496,8 @@ void PixelFormatExtensions::GetSamplerInternal(PixelFormat format, int32& pixelS if (const PixelFormatSampler* sampler = PixelFormatSampler::Get(format)) { pixelSize = sampler->PixelSize; - *read = sampler->Read; - *write = sampler->Write; + *read = (void*)sampler->Read; + *write = (void*)sampler->Write; } } diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index f2a2c565e..27e23a2b6 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -557,6 +557,26 @@ float RenderTools::ComputeTemporalTime() } void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal) +{ + // [Deprecated in v1.10] + Float3 n; + Float4 t; + CalculateTangentFrame(n, t, normal); + resultNormal = Float1010102(n, 0); + resultTangent = Float1010102(t); +} + +void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent) +{ + // [Deprecated in v1.10] + Float3 n; + Float4 t; + CalculateTangentFrame(n, t, normal, tangent); + resultNormal = Float1010102(n, 0); + resultTangent = Float1010102(t); +} + +void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal) { // Calculate tangent const Float3 c1 = Float3::Cross(normal, Float3::UnitZ); @@ -568,19 +588,19 @@ void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10 const byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); // Set tangent frame - resultNormal = Float1010102(normal * 0.5f + 0.5f, 0); - resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign); + resultNormal = normal * 0.5f + 0.5f; + resultTangent = Float4(tangent * 0.5f + 0.5f, sign); } -void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent) +void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent) { // Calculate bitangent sign const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent)); const byte sign = static_cast(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0); // Set tangent frame - resultNormal = Float1010102(normal * 0.5f + 0.5f, 0); - resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign); + resultNormal = normal * 0.5f + 0.5f; + resultTangent = Float4(tangent * 0.5f + 0.5f, sign); } void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside) diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index de0d318c6..15a934424 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -130,8 +130,14 @@ public: // Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing. static float ComputeTemporalTime(); - static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal); - static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent); + // [Deprecated in v1.10] + DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal); + // [Deprecated in v1.10] + DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent); + + // Result normal/tangent are already packed into [0;1] range. + static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal); + static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent); static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside); }; diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 524b0d4cd..59e40483b 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -1,9 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "GPUVertexLayout.h" -#if GPU_ENABLE_ASSERTION_LOW_LAYERS #include "Engine/Core/Log.h" -#endif #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Types/Span.h" diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index eb49171dc..7bb23de41 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -20,10 +20,10 @@ GPUShaderProgramVSDX11::~GPUShaderProgramVSDX11() ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout) { ID3D11InputLayout* inputLayout = nullptr; + if (!vertexLayout) + vertexLayout = (GPUVertexLayoutDX11*)Layout; if (!_cache.TryGet(vertexLayout, inputLayout)) { - if (!vertexLayout) - vertexLayout = (GPUVertexLayoutDX11*)Layout; if (vertexLayout && vertexLayout->InputElementsCount) { auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index b09b76394..818c35689 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -14,6 +14,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" @@ -628,30 +629,34 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD // Blend all blend shapes auto vertexCount = (uint32)mesh->GetVertexCount(); - auto data = (VB0SkinnedElementType*)deformation.VertexBuffer.Data.Get(); + MeshAccessor accessor; + if (deformation.LoadMeshAccessor(accessor)) + return; + auto positionStream = accessor.Position(); + auto normalStream = accessor.Normal(); + CHECK(positionStream.IsValid()); + useNormals &= normalStream.IsValid(); for (const auto& q : blendShapes) { - // TODO: use SIMD + for (int32 i = 0; i < q.First.Vertices.Count(); i++) + { + const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; + ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); + + Float3 position = positionStream.GetFloat3(blendShapeVertex.VertexIndex); + position = position + blendShapeVertex.PositionDelta * q.Second; + positionStream.SetFloat3(blendShapeVertex.VertexIndex, position); + } if (useNormals) { for (int32 i = 0; i < q.First.Vertices.Count(); i++) { const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); - VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); - vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; - Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta * q.Second; - vertex.Normal = normal * 0.5f + 0.5f; - } - } - else - { - for (int32 i = 0; i < q.First.Vertices.Count(); i++) - { - const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); - VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); - vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; + + Float3 normal = normalStream.GetFloat3(blendShapeVertex.VertexIndex) * 2.0f - 1.0f; + normal = normal + blendShapeVertex.NormalDelta * q.Second; + normal = normal * 0.5f + 0.5f; // TODO: optimize unpacking and packing to just apply it to the normal delta + normalStream.SetFloat3(blendShapeVertex.VertexIndex, normal); } } } @@ -659,21 +664,23 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD if (useNormals) { // Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range) - // TODO: use SIMD + auto tangentStream = accessor.Tangent(); for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++) { - VB0SkinnedElementType& vertex = *(data + vertexIndex); - - Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f; + Float3 normal = normalStream.GetFloat3(vertexIndex) * 2.0f - 1.0f; normal.Normalize(); - vertex.Normal = normal * 0.5f + 0.5f; + normal = normal * 0.5f + 0.5f; + normalStream.SetFloat3(vertexIndex, normal); - Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f; - tangent = tangent - ((tangent | normal) * normal); - tangent.Normalize(); - const auto tangentSign = vertex.Tangent.W; - vertex.Tangent = tangent * 0.5f + 0.5f; - vertex.Tangent.W = tangentSign; + if (tangentStream.IsValid()) + { + Float4 tangentRaw = normalStream.GetFloat4(vertexIndex); + Float3 tangent = Float3(tangentRaw) * 2.0f - 1.0f; + tangent = tangent - ((tangent | normal) * normal); + tangent.Normalize(); + tangentRaw = Float4(tangent * 0.5f + 0.5f, tangentRaw.W); + tangentStream.SetFloat4(vertexIndex, tangentRaw); + } } } @@ -1221,16 +1228,17 @@ bool AnimatedModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& nor return result; } -bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const +bool AnimatedModel::GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const { count = 0; - if (mesh.LODIndex < 0 || mesh.MeshIndex < 0) + if (ref.LODIndex < 0 || ref.MeshIndex < 0) return true; const auto model = SkinnedModel.Get(); if (!model || model->WaitForLoaded()) return true; - auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)]; - return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); + auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)]; + auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)]; + return mesh.DownloadDataCPU(type, result, count, layout); } MeshDeformation* AnimatedModel::GetMeshDeformation() const diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 57224f83e..b6d4aeaaf 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -433,7 +433,7 @@ public: MaterialBase* GetMaterial(int32 entryIndex) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; - bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; + bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override; void UpdateBounds() override; MeshDeformation* GetMeshDeformation() const override; void OnDeleteObject() override; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index f3c6cd5e1..c3c118f7c 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -112,12 +112,13 @@ public: /// /// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel). /// - /// Mesh reference. + /// Mesh reference. /// Buffer type /// The result data /// The amount of items inside the result buffer. + /// The result layout of the result buffer (for vertex buffers). Optional, pass null to ignore it. /// True if failed, otherwise false. - virtual bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const + virtual bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const { return true; } diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 83abc6198..054345785 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -8,6 +8,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/MeshDeformation.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scene/Scene.h" @@ -300,7 +301,8 @@ void StaticModel::FlushVertexColors() vertexColorsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("VertexColors")); if (vertexColorsBuffer->GetSize() != size) { - if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(Color32), vertexColorsData.Count(), nullptr))) + auto layout = GPUVertexLayout::Get({{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }}); + if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(layout, sizeof(Color32), vertexColorsData.Count(), nullptr))) break; } GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); @@ -627,16 +629,17 @@ bool StaticModel::IntersectsEntry(const Ray& ray, Real& distance, Vector3& norma return result; } -bool StaticModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const +bool StaticModel::GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const { count = 0; - if (mesh.LODIndex < 0 || mesh.MeshIndex < 0) + if (ref.LODIndex < 0 || ref.MeshIndex < 0) return true; const auto model = Model.Get(); if (!model || model->WaitForLoaded()) return true; - auto& lod = model->LODs[Math::Min(mesh.LODIndex, model->LODs.Count() - 1)]; - return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count); + auto& lod = model->LODs[Math::Min(ref.LODIndex, model->LODs.Count() - 1)]; + auto& mesh = lod.Meshes[Math::Min(ref.MeshIndex, lod.Meshes.Count() - 1)]; + return mesh.DownloadDataCPU(type, result, count, layout); } MeshDeformation* StaticModel::GetMeshDeformation() const diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index a8171d6ad..b3b730496 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -171,7 +171,7 @@ public: MaterialBase* GetMaterial(int32 entryIndex) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override; - bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override; + bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override; MeshDeformation* GetMeshDeformation() const override; void UpdateBounds() override; diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 0af499ed0..6f001bf44 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -221,6 +221,7 @@ Asset::LoadResult ParticleSystem::load() #endif switch (version) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS case 1: { // [Deprecated on 23.07.2019, expires on 27.04.2021] @@ -369,6 +370,7 @@ Asset::LoadResult ParticleSystem::load() break; } + PRAGMA_ENABLE_DEPRECATION_WARNINGS case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023] case 4: { diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 4dd1f56f5..fb8402cf6 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Math/Ray.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Physics/PhysicsBackend.h" @@ -50,6 +51,7 @@ void Cloth::SetMesh(const ModelInstanceActor::MeshReference& value) Function deformer; deformer.Bind(this); _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0, deformer); + _meshDeformation->RemoveDeformer(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex1, deformer); _meshDeformation = nullptr; } @@ -556,6 +558,7 @@ bool Cloth::CreateCloth() int32 count; if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, data, count)) return true; + // TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0 desc.VerticesData = data.Get(); desc.VerticesCount = count; desc.VerticesStride = data.Length() / count; @@ -656,6 +659,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) const int32 i0 = indicesData.Get()[index]; const int32 i1 = indicesData.Get()[index + 1]; const int32 i2 = indicesData.Get()[index + 2]; + // TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0 #define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) const Float3 v0(GET_POS(i0)); const Float3 v1(GET_POS(i1)); @@ -675,6 +679,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) const int32 i0 = indicesData.Get()[index]; const int32 i1 = indicesData.Get()[index + 1]; const int32 i2 = indicesData.Get()[index + 2]; + // TODO: use MeshAccessor vertex data layout descriptor instead hardcoded position data at the beginning of VB0 #define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride) const Float3 v0(GET_POS(i0)); const Float3 v1(GET_POS(i1)); @@ -770,11 +775,18 @@ bool Cloth::OnPreUpdate() return false; BytesContainer verticesData; int32 verticesCount; - if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) + GPUVertexLayout* layout; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount, &layout)) + return false; + MeshAccessor accessor; + if (accessor.LoadBuffer(MeshBufferType::Vertex0, verticesData, layout)) + return false; + auto positionStream = accessor.Position(); + auto blendIndicesStream = accessor.BlendIndices(); + auto blendWeightsStream = accessor.BlendWeights(); + if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid()) return false; PROFILE_CPU_NAMED("Skinned Pose"); - auto vbStride = (uint32)verticesData.Length() / verticesCount; - ASSERT(vbStride == sizeof(VB0SkinnedElementType)); PhysicsBackend::LockClothParticles(_cloth); const Span particles = PhysicsBackend::GetClothParticles(_cloth); // TODO: optimize memory allocs (eg. write directly to nvCloth mapped range or use shared allocator) @@ -794,43 +806,46 @@ bool Cloth::OnPreUpdate() { if (paint[i] > ZeroTolerance) continue; - VB0SkinnedElementType& vb0 = verticesData.Get()[i]; + + // Load vertex + Float3 position = positionStream.GetFloat3(i); + const Int4 blendIndices = blendIndicesStream.GetFloat4(i); + const Float4 blendWeights = blendWeightsStream.GetFloat4(i); // Calculate skinned vertex matrix from bones blending - const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly Matrix matrix; - const SkeletonBone& bone0 = bones[vb0.BlendIndices.R]; + const SkeletonBone& bone0 = bones[blendIndices.X]; Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); Matrix boneMatrix = matrix * blendWeights.X; if (blendWeights.Y > 0.0f) { - const SkeletonBone& bone1 = bones[vb0.BlendIndices.G]; + const SkeletonBone& bone1 = bones[blendIndices.Y]; Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Y; } if (blendWeights.Z > 0.0f) { - const SkeletonBone& bone2 = bones[vb0.BlendIndices.B]; + const SkeletonBone& bone2 = bones[blendIndices.Z]; Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Z; } if (blendWeights.W > 0.0f) { - const SkeletonBone& bone3 = bones[vb0.BlendIndices.A]; + const SkeletonBone& bone3 = bones[blendIndices.W]; Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); boneMatrix += matrix * blendWeights.W; } // Skin vertex position (similar to GPU vertex shader) - Float3 pos = Float3::Transform(vb0.Position, boneMatrix); + position = Float3::Transform(position, boneMatrix); // Transform back to the cloth space // TODO: skip when using identity? - pos = _localTransform.WorldToLocal(pos); + position = _localTransform.WorldToLocal(position); // Override fixed particle position - particlesSkinned[i] = Float4(pos, 0.0f); + particlesSkinned[i] = Float4(position, 0.0f); anyFixed = true; } @@ -891,11 +906,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat PROFILE_CPU_NAMED("Cloth"); PhysicsBackend::LockClothParticles(_cloth); const Span particles = PhysicsBackend::GetClothParticles(_cloth); - - auto vbData = deformation.VertexBuffer.Data.Get(); auto vbCount = (uint32)mesh->GetVertexCount(); - auto vbStride = (uint32)deformation.VertexBuffer.Data.Count() / vbCount; - // TODO: add support for mesh vertex data layout descriptor instead hardcoded position data at the beginning of VB0 ASSERT((uint32)particles.Length() >= vbCount); // Calculate normals @@ -949,6 +960,12 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat } // Update mesh vertices based on the cloth particles positions + MeshAccessor accessor; + if (deformation.LoadMeshAccessor(accessor)) + { + PhysicsBackend::UnlockClothParticles(_cloth); + return; + } if (auto* animatedModel = Cast(GetParent())) { if (animatedModel->GraphInstance.NodesPose.IsEmpty()) @@ -965,39 +982,44 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones - ASSERT(vbStride == sizeof(VB0SkinnedElementType)); + auto positionStream = accessor.Position(); + auto blendIndicesStream = accessor.BlendIndices(); + auto blendWeightsStream = accessor.BlendWeights(); + if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid()) + { + PhysicsBackend::UnlockClothParticles(_cloth); + return; + } const float* paint = _paint.Count() >= particles.Length() ? _paint.Get() : nullptr; for (uint32 i = 0; i < vbCount; i++) { - VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData; - vbData += vbStride; - // Skip fixed vertices if (paint && paint[i] < ZeroTolerance) continue; // Calculate skinned vertex matrix from bones blending - const Float4 blendWeights = vb.BlendWeights.ToFloat4(); + const Int4 blendIndices = blendIndicesStream.GetFloat4(i); + const Float4 blendWeights = blendWeightsStream.GetFloat4(i); // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly Matrix matrix; - const SkeletonBone& bone0 = skeleton.Bones[vb.BlendIndices.R]; + const SkeletonBone& bone0 = skeleton.Bones[blendIndices.X]; Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); Matrix boneMatrix = matrix * blendWeights.X; if (blendWeights.Y > 0.0f) { - const SkeletonBone& bone1 = skeleton.Bones[vb.BlendIndices.G]; + const SkeletonBone& bone1 = skeleton.Bones[blendIndices.Y]; Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Y; } if (blendWeights.Z > 0.0f) { - const SkeletonBone& bone2 = skeleton.Bones[vb.BlendIndices.B]; + const SkeletonBone& bone2 = skeleton.Bones[blendIndices.Z]; Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); boneMatrix += matrix * blendWeights.Z; } if (blendWeights.W > 0.0f) { - const SkeletonBone& bone3 = skeleton.Bones[vb.BlendIndices.A]; + const SkeletonBone& bone3 = skeleton.Bones[blendIndices.W]; Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); boneMatrix += matrix * blendWeights.W; } @@ -1006,43 +1028,59 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat Matrix boneMatrixInv; Matrix::Invert(boneMatrix, boneMatrixInv); Float3 pos = *(Float3*)&particles.Get()[i]; - vb.Position = Float3::Transform(pos, boneMatrixInv); + pos = Float3::Transform(pos, boneMatrixInv); + positionStream.SetFloat3(i, pos); } if (_simulationSettings.ComputeNormals) { // Write normals - for (uint32 i = 0; i < vbCount; i++) + auto normalStream = accessor.Normal(); + auto tangentStream = accessor.Tangent(); + if (normalStream.IsValid() && tangentStream.IsValid()) { - Float3 normal = normals.Get()[i]; - normal.Normalize(); - VB0SkinnedElementType& vb = *(VB0SkinnedElementType*)vbData; - vbData += vbStride; - RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal); + for (uint32 i = 0; i < vbCount; i++) + { + Float3 normal = normals.Get()[i]; + normal.Normalize(); + Float3 n; + Float4 t; + RenderTools::CalculateTangentFrame(n, t, normal); + normalStream.SetFloat3(i, n); + tangentStream.SetFloat4(i, t); + } } } } else if (deformation.Type == MeshBufferType::Vertex0) { // Copy particle positions to the mesh data - ASSERT(vbStride == sizeof(VB0ElementType)); - for (uint32 i = 0; i < vbCount; i++) + auto positionStream = accessor.Position(); + if (positionStream.IsValid()) { - *(Float3*)vbData = *(Float3*)&particles.Get()[i]; - vbData += vbStride; + for (uint32 i = 0; i < vbCount; i++) + { + positionStream.SetFloat3(i, *(const Float3*)&particles.Get()[i]); + } } } else { // Write normals for the modified vertices by the cloth - ASSERT(vbStride == sizeof(VB1ElementType)); - for (uint32 i = 0; i < vbCount; i++) + auto normalStream = accessor.Normal(); + auto tangentStream = accessor.Tangent(); + if (normalStream.IsValid() && tangentStream.IsValid()) { - Float3 normal = normals.Get()[i]; - normal.Normalize(); - VB1ElementType& vb = *(VB1ElementType*)vbData; - vbData += vbStride; - RenderTools::CalculateTangentFrame(vb.Normal, vb.Tangent, normal); + for (uint32 i = 0; i < vbCount; i++) + { + Float3 normal = normals.Get()[i]; + normal.Normalize(); + Float3 n; + Float4 t; + RenderTools::CalculateTangentFrame(n, t, normal); + normalStream.SetFloat3(i, n); + tangentStream.SetFloat4(i, t); + } } } diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 8ca585c32..c52425fd3 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -9,6 +9,7 @@ #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Utilities/AnsiPathTempFile.h" #include "Engine/Platform/File.h" @@ -199,7 +200,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const #endif // Convert into RGBA8 - const auto sampler = GetSampler(texture->Format); + const auto sampler = PixelFormatSampler::Get(texture->Format); if (sampler == nullptr) { LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format); @@ -218,7 +219,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const { for (int32 x = 0; x < texture->Width; x++) { - Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch); + Color color = sampler->SamplePoint(srcData->Data.Get(), x, y, srcData->RowPitch); if (sRGB) color = Color::SrgbToLinear(color); *(ptr + x + y * texture->Width) = color.ToFloat4(); @@ -234,7 +235,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const { for (int32 x = 0; x < texture->Width; x++) { - Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch); + Color color = sampler->SamplePoint(srcData->Data.Get(), x, y, srcData->RowPitch); if (sRGB) color = Color::SrgbToLinear(color); *(ptr + x + y * texture->Width) = Color32(color); @@ -623,7 +624,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix dst.Items.Resize(arraySize, false); auto formatSize = PixelFormatExtensions::SizeInBytes(textureData->Format); auto components = PixelFormatExtensions::ComputeComponentsCount(textureData->Format); - auto sampler = TextureTool::GetSampler(textureData->Format); + auto sampler = PixelFormatSampler::Get(textureData->Format); if (!sampler) { LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast(textureData->Format)); @@ -747,7 +748,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix #endif { int32 bytesPerPixel = PixelFormatExtensions::SizeInBytes(dstFormat); - auto dstSampler = TextureTool::GetSampler(dstFormat); + auto dstSampler = PixelFormatSampler::Get(dstFormat); if (!dstSampler) { LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast(dstFormat)); @@ -782,10 +783,10 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix for (int32 x = 0; x < mipWidth; x++) { // Sample source texture - Color color = TextureTool::SamplePoint(sampler, x, y, srcMip.Data.Get(), srcMip.RowPitch); + Color color = sampler->SamplePoint(srcMip.Data.Get(), x, y, srcMip.RowPitch); // Store destination texture - TextureTool::Store(dstSampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color); + sampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color); } } } @@ -802,7 +803,7 @@ bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const Te auto components = PixelFormatExtensions::ComputeComponentsCount(format); auto srcMipWidth = srcMip.RowPitch / formatSize; auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch; - auto sampler = GetSampler(format); + auto sampler = PixelFormatSampler::Get(format); // Allocate memory dstMip.RowPitch = dstMipWidth * formatSize; @@ -869,8 +870,8 @@ bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const Te for (int32 x = 0; x < dstMipWidth; x++) { const Float2 uv((float)x / dstMipWidth, (float)y / dstMipHeight); - Color color = SamplePoint(sampler, uv, srcMip.Data.Get(), srcSize, srcMip.RowPitch); - Store(sampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color); + Color color = sampler->SamplePoint(srcMip.Data.Get(), uv, srcSize, srcMip.RowPitch); + sampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color); } } return false; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index e1d0f5316..53491da11 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -18,18 +18,37 @@ #include "Engine/Content/Content.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Localization/Localization.h" #if USE_EDITOR #include "Editor/Editor.h" #endif +PACK_STRUCT(struct TextRenderVertex +{ + Float3 Position; + Color32 Color; + Float1010102 Normal; + Float1010102 Tangent; + Half2 TexCoord; + + static GPUVertexLayout* GetLayout() + { + return GPUVertexLayout::Get({ + { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float }, + { VertexElement::Types::Color, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm }, + { VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }, + { VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }, + { VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float }, + }); + } +}); + TextRender::TextRender(const SpawnParams& params) : Actor(params) , _size(32) , _ib(0, sizeof(uint16)) - , _vb0(0, sizeof(VB0ElementType), String::Empty, VB0ElementType::GetLayout()) - , _vb1(0, sizeof(VB1ElementType), String::Empty, VB1ElementType::GetLayout()) - , _vb2(0, sizeof(VB2ElementType), String::Empty, VB2ElementType::GetLayout()) + , _vb(0, sizeof(TextRenderVertex), String::Empty, TextRenderVertex::GetLayout()) { _color = Color::White; _localBox = BoundingBox(Vector3::Zero); @@ -102,9 +121,7 @@ void TextRender::UpdateLayout() { // Clear _ib.Clear(); - _vb0.Clear(); - _vb1.Clear(); - _vb2.Clear(); + _vb.Clear(); _localBox = BoundingBox(Vector3::Zero); BoundingBox::Transform(_localBox, _transform, _box); BoundingSphere::FromBox(_box, _sphere); @@ -163,9 +180,7 @@ void TextRender::UpdateLayout() // Prepare buffers capacity _ib.Data.EnsureCapacity(text.Length() * 6 * sizeof(uint16)); - _vb0.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB0ElementType)); - _vb1.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB1ElementType)); - _vb2.Data.EnsureCapacity(text.Length() * 4 * sizeof(VB2ElementType)); + _vb.Data.EnsureCapacity(text.Length() * 4 * sizeof(TextRenderVertex)); _buffersDirty = true; // Init draw chunks data @@ -268,20 +283,15 @@ void TextRender::UpdateLayout() byte sign = 0; // Write vertices - VB0ElementType vb0; - VB1ElementType vb1; - VB2ElementType vb2; + TextRenderVertex v; #define WRITE_VB(pos, uv) \ - vb0.Position = Float3(-pos, 0.0f); \ - box.Merge(vb0.Position); \ - _vb0.Write(vb0); \ - vb1.TexCoord = Half2(uv); \ - vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \ - vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \ - vb1.LightmapUVs = Half2::Zero; \ - _vb1.Write(vb1); \ - vb2.Color = color; \ - _vb2.Write(vb2) + v.Position = Float3(-pos, 0.0f); \ + box.Merge(v.Position); \ + v.TexCoord = Half2(uv); \ + v.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \ + v.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \ + v.Color = color; \ + _vb.Write(v) // WRITE_VB(charRect.GetBottomRight(), rightBottomUV); WRITE_VB(charRect.GetBottomLeft(), Float2(upperLeftUV.X, rightBottomUV.Y)); @@ -317,7 +327,7 @@ void TextRender::UpdateLayout() #if MODEL_USE_PRECISE_MESH_INTERSECTS // Setup collision proxy for detailed collision detection for triangles const int32 totalIndicesCount = _ib.Data.Count() / sizeof(uint16); - _collisionProxy.Init(_vb0.Data.Count() / sizeof(Float3), totalIndicesCount / 3, (Float3*)_vb0.Data.Get(), (uint16*)_ib.Data.Get()); + _collisionProxy.Init(_vb.Data.Count() / sizeof(TextRenderVertex), totalIndicesCount / 3, (const Float3*)_vb.Data.Get(), (const uint16*)_ib.Data.Get(), sizeof(TextRenderVertex)); #endif // Update text bounds (from build vertex positions) @@ -351,16 +361,14 @@ void TextRender::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); const DrawPass drawModes = DrawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(ShadowsMode); - if (_vb0.Data.Count() > 0 && drawModes != DrawPass::None) + if (_vb.Data.Count() > 0 && drawModes != DrawPass::None) { // Flush buffers if (_buffersDirty) { _buffersDirty = false; _ib.Flush(); - _vb0.Flush(); - _vb1.Flush(); - _vb2.Flush(); + _vb.Flush(); } // Setup draw call @@ -373,9 +381,7 @@ void TextRender::Draw(RenderContext& renderContext) drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World); drawCall.PerInstanceRandom = GetPerInstanceRandom(); drawCall.Geometry.IndexBuffer = _ib.GetBuffer(); - drawCall.Geometry.VertexBuffers[0] = _vb0.GetBuffer(); - drawCall.Geometry.VertexBuffers[1] = _vb1.GetBuffer(); - drawCall.Geometry.VertexBuffers[2] = _vb2.GetBuffer(); + drawCall.Geometry.VertexBuffers[0] = _vb.GetBuffer(); drawCall.InstanceCount = 1; // Submit draw calls diff --git a/Source/Engine/UI/TextRender.h b/Source/Engine/UI/TextRender.h index d842a9531..e7f16ca29 100644 --- a/Source/Engine/UI/TextRender.h +++ b/Source/Engine/UI/TextRender.h @@ -44,9 +44,7 @@ private: BoundingBox _localBox; GeometryDrawStateData _drawState; DynamicIndexBuffer _ib; - DynamicVertexBuffer _vb0; - DynamicVertexBuffer _vb1; - DynamicVertexBuffer _vb2; + DynamicVertexBuffer _vb; #if MODEL_USE_PRECISE_MESH_INTERSECTS CollisionProxy _collisionProxy; #endif diff --git a/Source/Engine/Utilities/MeshDataCache.cs b/Source/Engine/Utilities/MeshDataCache.cs index 9ea8e6db0..8a6ba64a2 100644 --- a/Source/Engine/Utilities/MeshDataCache.cs +++ b/Source/Engine/Utilities/MeshDataCache.cs @@ -24,11 +24,18 @@ namespace FlaxEngine.Utilities /// /// The vertex buffer. + /// [Deprecated in v1.10] /// + [Obsolete("Use new VertexAccessor.")] public Mesh.Vertex[] VertexBuffer; + + /// + /// The vertex buffer accessor (with all available vertex buffers loaded in). + /// + public MeshAccessor VertexAccessor; } - private Model _model; + private ModelBase _model; private MeshData[][] _meshDatas; private bool _inProgress; private bool _cancel; @@ -46,9 +53,9 @@ namespace FlaxEngine.Utilities /// /// Requests the mesh data. /// - /// The model to get it's data. + /// The model to get its data. /// True if has valid data to access, otherwise false if it's during downloading. - public bool RequestMeshData(Model model) + public bool RequestMeshData(ModelBase model) { if (model == null) throw new ArgumentNullException(); @@ -110,23 +117,29 @@ namespace FlaxEngine.Utilities if (_model.WaitForLoaded()) throw new Exception("WaitForLoaded failed"); - var lods = _model.LODs; - _meshDatas = new MeshData[lods.Length][]; + var lodsCount = _model.LODsCount; + _meshDatas = new MeshData[lodsCount][]; - for (int lodIndex = 0; lodIndex < lods.Length && !_cancel; lodIndex++) + Span vertexBufferTypes = stackalloc MeshBufferType[3] { MeshBufferType.Vertex0, MeshBufferType.Vertex1, MeshBufferType.Vertex2 }; + for (int lodIndex = 0; lodIndex < lodsCount && !_cancel; lodIndex++) { - var lod = lods[lodIndex]; - var meshes = lod.Meshes; + _model.GetMeshes(out var meshes, lodIndex); _meshDatas[lodIndex] = new MeshData[meshes.Length]; for (int meshIndex = 0; meshIndex < meshes.Length && !_cancel; meshIndex++) { var mesh = meshes[meshIndex]; - _meshDatas[lodIndex][meshIndex] = new MeshData + var meshData = new MeshData { IndexBuffer = mesh.DownloadIndexBuffer(), - VertexBuffer = mesh.DownloadVertexBuffer() +#pragma warning disable 0618 + VertexBuffer = mesh is Mesh m ? m.DownloadVertexBuffer() : null, +#pragma warning restore 0618 + VertexAccessor = new MeshAccessor(), }; + if (meshData.VertexAccessor.LoadMesh(mesh, false, vertexBufferTypes)) + throw new Exception("MeshAccessor.LoadMesh failed"); + _meshDatas[lodIndex][meshIndex] = meshData; } } success = true; From bc73d38d3426af25ddc31d46d2dad26c6079b7e5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 6 Jan 2025 22:47:44 +0100 Subject: [PATCH 110/215] Reseve engine materials and models --- Content/Editor/Camera/M_Camera.flax | 2 +- Content/Editor/Camera/O_Camera.flax | 4 ++-- Content/Editor/CubeTexturePreviewMaterial.flax | 2 +- Content/Editor/DebugMaterials/DDGIDebugProbes.flax | 2 +- Content/Editor/DebugMaterials/SingleColor/Decal.flax | 2 +- Content/Editor/DebugMaterials/SingleColor/Particle.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Surface.flax | 2 +- .../Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax | 2 +- Content/Editor/DebugMaterials/SingleColor/Terrain.flax | 4 ++-- Content/Editor/DefaultFontMaterial.flax | 2 +- Content/Editor/Gizmo/FoliageBrushMaterial.flax | 2 +- Content/Editor/Gizmo/Material.flax | 2 +- Content/Editor/Gizmo/MaterialWire.flax | 2 +- Content/Editor/Gizmo/RotationAxis.flax | 4 ++-- Content/Editor/Gizmo/ScaleAxis.flax | 4 ++-- Content/Editor/Gizmo/SelectionOutlineMaterial.flax | 2 +- Content/Editor/Gizmo/TranslationAxis.flax | 4 ++-- Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax | 2 +- Content/Editor/Highlight Material.flax | 2 +- Content/Editor/Icons/IconsMaterial.flax | 2 +- Content/Editor/IesProfilePreviewMaterial.flax | 2 +- Content/Editor/Particles/Particle Material Color.flax | 4 ++-- Content/Editor/Particles/Smoke Material.flax | 4 ++-- Content/Editor/Primitives/Cube.flax | 4 ++-- Content/Editor/Primitives/Cylinder.flax | 4 ++-- Content/Editor/Primitives/Plane.flax | 4 ++-- Content/Editor/Primitives/Sphere.flax | 4 ++-- Content/Editor/SpriteMaterial.flax | 2 +- Content/Editor/Terrain/Circle Brush Material.flax | 4 ++-- Content/Editor/Terrain/Highlight Terrain Material.flax | 4 ++-- Content/Editor/TexturePreviewMaterial.flax | 2 +- Content/Editor/Wires Debug Material.flax | 2 +- Content/Engine/DefaultDeformableMaterial.flax | 2 +- Content/Engine/DefaultMaterial.flax | 2 +- Content/Engine/DefaultRadialMenu.flax | 2 +- Content/Engine/DefaultTerrainMaterial.flax | 4 ++-- Content/Engine/Models/Box.flax | 4 ++-- Content/Engine/Models/Quad.flax | 4 ++-- Content/Engine/Models/SimpleBox.flax | 4 ++-- Content/Engine/Models/Sphere.flax | 4 ++-- Content/Engine/Models/SphereLowPoly.flax | 4 ++-- Content/Engine/SingleColorMaterial.flax | 2 +- Content/Engine/SkyboxMaterial.flax | 2 +- 43 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 81921e0a9..38e11bc91 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d2e306ad841a731dd9beced0fd653ff9649403e30545d5e31eed9b3f575513d +oid sha256:67045d0b06d504465d30c8415174b91137c09181185c89436085f420c29e6e59 size 28071 diff --git a/Content/Editor/Camera/O_Camera.flax b/Content/Editor/Camera/O_Camera.flax index 47d37472d..795438bfe 100644 --- a/Content/Editor/Camera/O_Camera.flax +++ b/Content/Editor/Camera/O_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f9bbd661420e4f930a995acc46c9530fcacb8836db4d0bbfa5df184e4d1a5cc -size 88495 +oid sha256:df80cdde1b74c2fc3fb5de2ab68c36118e3441d0b317aedaa54df8b4baac921c +size 88545 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index b1dd13160..ee4d8c479 100644 --- a/Content/Editor/CubeTexturePreviewMaterial.flax +++ b/Content/Editor/CubeTexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9618fe8e4e7673a8fe9c51393c00ff8028fd48c5433b2982d8368832f919dbbb +oid sha256:a5b4bfc128caf88f79e0a9b94fb3826ff19bfc6dbee5d07a8937717ed2101692 size 29786 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index a1b73ca44..6862b1bf9 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2016b2a5f524d0c5610539108b925c784c7d56eb18b050f78a0805150a90a5b2 +oid sha256:4edc791689d30a60fdbe9f28eca8c9747b0842726d8f99370a950b17e71fdd75 size 39019 diff --git a/Content/Editor/DebugMaterials/SingleColor/Decal.flax b/Content/Editor/DebugMaterials/SingleColor/Decal.flax index 04300ddb5..c99a14f6c 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Decal.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef3e97169279f1484bdb28d913587058d526172457878f86f8c54634c4c1b1cd +oid sha256:a69bfa758716071d2c6621cca6d049904a9f96d16b41ed90705d7f262be2cdab size 7489 diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax index 99eb3da18..66d5568d5 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af65802e9b6437c3b3d1f0cbf0c320fde5d9774acea14bdf374a8b0dd28d5610 -size 32168 +oid sha256:bc8ea0bf4f94b246f9b8b653ade5a12aa06fb98770f049273ade594dfcab09ec +size 32014 diff --git a/Content/Editor/DebugMaterials/SingleColor/Surface.flax b/Content/Editor/DebugMaterials/SingleColor/Surface.flax index 569144f73..3610a41e1 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Surface.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Surface.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e26ff8a13ba0613915fa52ede6620a922e97d8e1f1bed9fae23b446178801929 +oid sha256:161dbee3faaa9380ca354d2252661f6c3a13b38dee3c2fcdda828ff1b6eb4520 size 27967 diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax index 04eb52e4c..af8ef0de5 100644 --- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax +++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74078179b7b49c50718135028852415829c2202a2f45e057407f889c0e638047 +oid sha256:3adbb88f53a09d0c219936f167dd3e129391adb6abb323ad6023de2e55e2bbdc size 30152 diff --git a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax index 2625a8a6b..55b9ddc1e 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07d8d36958fc592217a7999b6864bb9885e0a27d718d36d9216c16701fa124bc -size 21314 +oid sha256:7040dea98e3adddecac69814f0f307051bb0df15cb052beeb6d7b3cff0dcb32c +size 21203 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index 3c9304b8e..2f0069068 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83a576dd776ba57c7d8ee5947438328acde6fab63442787d8411ee5377db0920 +oid sha256:9c5fc9e89e8ce4f2511d6bcaf68c7f1ee713dafaf3260222bf704be74bc3d6ad size 28146 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index bcc7272d5..6dda99261 100644 --- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax +++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59006b776f59d1445758fd5e04892583d85506bf6b7312d15613d79975886932 +oid sha256:f42eaf0a9d28be161480112d7432a68bdb443fe2b8f48e42dfed93de59dca7ff size 36179 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index 87e7c078d..75b781865 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ace1f3a9b078208da9a8742e2a7de74b10509abb3dcf12ddc70e4da918bbb756 +oid sha256:ea8bc05a1a45d8b4e937c23a42a6acfaebfd739a74d17e6d02acf4bca1a67113 size 30756 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 0f93a951d..9976f7b0d 100644 --- a/Content/Editor/Gizmo/MaterialWire.flax +++ b/Content/Editor/Gizmo/MaterialWire.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fa1233fa8f24ad42190b127cb4fc9aeb5832e188e27151f95a88614881463ee +oid sha256:6db773836280d041aa67ff94f73383a86cf1e0169cedc698c4e88a6557807553 size 29894 diff --git a/Content/Editor/Gizmo/RotationAxis.flax b/Content/Editor/Gizmo/RotationAxis.flax index a743e30a6..67f1199dd 100644 --- a/Content/Editor/Gizmo/RotationAxis.flax +++ b/Content/Editor/Gizmo/RotationAxis.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d8f319dcd39842cb991a4e8e67e4823c7159a493a1aec757232077e5d56ad9f -size 41685 +oid sha256:eb6446b5931ca57dd35ea37b62cbb8dec2cb0a9861170651c1d00d732a3419e9 +size 41735 diff --git a/Content/Editor/Gizmo/ScaleAxis.flax b/Content/Editor/Gizmo/ScaleAxis.flax index 8f774c883..e77866982 100644 --- a/Content/Editor/Gizmo/ScaleAxis.flax +++ b/Content/Editor/Gizmo/ScaleAxis.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8743967dc27d5839b88dfa1e599cbc9b290427afd6b65b2424ffa5218b330beb -size 40754 +oid sha256:5fcfc658f89f41477086540f866263a87b657df152575e618292a3b7af40a470 +size 40804 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 5cde31ea7..a8396c3f2 100644 --- a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax +++ b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c53438a299dbf8d0eced6a47477470e0263ccbe40acbaa62a5f684eea0e0f112 +oid sha256:51d9d5bd33e7e95181a1b26337d5c9702362659b0ca9544efd3d5fa101856a73 size 16166 diff --git a/Content/Editor/Gizmo/TranslationAxis.flax b/Content/Editor/Gizmo/TranslationAxis.flax index f91e2b18a..13d1bb339 100644 --- a/Content/Editor/Gizmo/TranslationAxis.flax +++ b/Content/Editor/Gizmo/TranslationAxis.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6096ef866920a861ec91f0660999a9ea07b2fbd26bcf52ba2c0112ba5e02336 -size 8915 +oid sha256:a9eb79cb2acf80e6d30ee4339e7d920518232ae5a9d5518a708b2b351804f08d +size 8965 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index d7e89b477..eab9e0968 100644 --- a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax +++ b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c8a8eda823e4cf72cc8889a4f448d1d25c0564131ade142c552e3dba0dc36a8 +oid sha256:ab487469fd2dcd9bb8a0f41f33cf43f6d3b576a9266b38490a21fde6eb491c0f size 29080 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 6a8c10a52..a8b39cdd9 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0da04f087f99fdb90a3bb4baac3400761048953d659964d108513bfa4303084a +oid sha256:d1be526615a5f57af0011ebfbcd1b87d203d91dc4bd684cea951069e276126f6 size 28549 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index d5d64147e..5193741b2 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6733e421934f43bb8149e78b3be2b28fd14920e0580ea6333aee698cd3bbc303 +oid sha256:601d16d02c7fa6d80cafef8c61cd009433f373a549a16432730ad4a45d2d940b size 28477 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index fc0d20df9..8a59e01f6 100644 --- a/Content/Editor/IesProfilePreviewMaterial.flax +++ b/Content/Editor/IesProfilePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c473ea456efdce9c3e916bd95c6c286a13646e413aa17f1569f7bd51c811358 +oid sha256:dc1e903d13905455ec7c6dfb04bd0920093e0fdd57c7243810c0c9ef7177df7d size 18205 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 45b6918dd..c6632f49e 100644 --- a/Content/Editor/Particles/Particle Material Color.flax +++ b/Content/Editor/Particles/Particle Material Color.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c76a3d52b95cd64d95f3db343b05aec2a66c59f0317fb3967c50240db8af0e22 -size 30407 +oid sha256:7c79c213d19fb1707e7ac88272d99be2ec6a038a8b35ddf0149fc0177fa4ab1b +size 30253 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index c32380913..3b15ce8e5 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94d4ec85842448736c087594f30df7a5f6fe33a16c2cc59781f36adebdbc0b9a -size 39167 +oid sha256:55da6e79fa1a42dbae7d74b724800f3f7937eb109fa92834e4df6d91776bf1cb +size 38401 diff --git a/Content/Editor/Primitives/Cube.flax b/Content/Editor/Primitives/Cube.flax index e383f23d6..6328a7bbc 100644 --- a/Content/Editor/Primitives/Cube.flax +++ b/Content/Editor/Primitives/Cube.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4384d8a57a90063d62c5526384d31f6a699b686eec5a0a3ff62fa458f5725216 -size 23487 +oid sha256:b5a18bf58e0b93c8bba9459fd77a92898f0fae373b58d3acbcb9e36f66c89cd7 +size 23537 diff --git a/Content/Editor/Primitives/Cylinder.flax b/Content/Editor/Primitives/Cylinder.flax index 037a78d41..0e44c8fc6 100644 --- a/Content/Editor/Primitives/Cylinder.flax +++ b/Content/Editor/Primitives/Cylinder.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3220e2e081ba3219cb3b3f35eb295b912df77c407cc9e385dc8fc71d9a0f1724 -size 16271 +oid sha256:e88868ab5a587b4ca1984a7db6cfec2188080cd6c080cc5a409bd25d409998c0 +size 16321 diff --git a/Content/Editor/Primitives/Plane.flax b/Content/Editor/Primitives/Plane.flax index 57fc0959a..4e368fe02 100644 --- a/Content/Editor/Primitives/Plane.flax +++ b/Content/Editor/Primitives/Plane.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a82456ad825b8bca32b4eec7e33ced41029551183ebad712462e73d006b887c -size 3321 +oid sha256:835990c21d45238821a42efb02b6b1e92a2497f72ddb1652e1b7ad4e615ea438 +size 3371 diff --git a/Content/Editor/Primitives/Sphere.flax b/Content/Editor/Primitives/Sphere.flax index 99b5cac11..3cfab377b 100644 --- a/Content/Editor/Primitives/Sphere.flax +++ b/Content/Editor/Primitives/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c240cd5211bd02fc4dc2c50e15c34644ecb80330839ac8b00ef7b77f72a88f9 -size 43127 +oid sha256:a0bc62448b0e951bad9749ef740e5d292b2ec3281534740bf9af0a66a0b3864d +size 43177 diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax index df5d7fd21..6446ea7fc 100644 --- a/Content/Editor/SpriteMaterial.flax +++ b/Content/Editor/SpriteMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e76b14ab9f0e53b0d50e24b44a7d3406f156bcdca64f154f1c1d92f8efaa6cf +oid sha256:b3b4a3e52999678654341698e9192b52545c45466727b4adaa1997f29ea088fd size 29159 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index 6ed469392..830793653 100644 --- a/Content/Editor/Terrain/Circle Brush Material.flax +++ b/Content/Editor/Terrain/Circle Brush Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3399c6639c78aed32767e27cdf3c8d5866a51bab8867f3ebceb1ffe5b886debe -size 27986 +oid sha256:64dc3a4e39d42de0e7ff71d31676a2e15a2f6d66d37013799bc7a3cda48ec175 +size 27875 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 592a0c1f5..44c0d46e6 100644 --- a/Content/Editor/Terrain/Highlight Terrain Material.flax +++ b/Content/Editor/Terrain/Highlight Terrain Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:578169c0df3168d3984677e5682f29e9694ebc284229a217ae43dfd275734e58 -size 21367 +oid sha256:a566b0589214c88d8947fc1df5f25837721836758aa773b6f308e269639c6951 +size 21256 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 5523b8376..0a9184d34 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91c76d393572fbcd535461d3815f1e0e475007792a7536f20dc5fa50ecb3c179 +oid sha256:bf0dffaeb28d26a77ead3cf5d01096b90e100c1a686dd969a636207b727d3b5c size 10570 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index aa5ddd243..52e1c1cc8 100644 --- a/Content/Editor/Wires Debug Material.flax +++ b/Content/Editor/Wires Debug Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97c617375c8ca7ef511487b3d060e1f2a628c27388e23ad8f6fa862425d2a5b9 +oid sha256:1845e47b1a2f872ed66444c83ea068044de52da6c585ead4d98ee2d3f0f8105e size 28549 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax index ba318b106..ed3edfcdc 100644 --- a/Content/Engine/DefaultDeformableMaterial.flax +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb66093810d808a26084f552b5a5da5daa0d3a57f7ea41d7e202e4b4d09129c4 +oid sha256:bda2374d296e4e80213b16f42e11a26198fd4bcd5db6cd04f3fa56d419ff3e68 size 18514 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index 6038d06be..37442ca8b 100644 --- a/Content/Engine/DefaultMaterial.flax +++ b/Content/Engine/DefaultMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3207b95298f954ea187cce08b28db57ade1863f523626c10c3fd0c60ac8af42c +oid sha256:0261d6715eaf0bab21fd6b01d927dad8eff40d767b59a6459063fcdd94d1192a size 29992 diff --git a/Content/Engine/DefaultRadialMenu.flax b/Content/Engine/DefaultRadialMenu.flax index 582ef5cfe..060d9f7b4 100644 --- a/Content/Engine/DefaultRadialMenu.flax +++ b/Content/Engine/DefaultRadialMenu.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29db27b56a03e53a04b10239d722ae69369d80d6acd7acfa090af833f7a2c584 +oid sha256:fc132311f3f1f83525252aa3a10d3f95d9070ec38db5d81b9d4d595b1b9c5757 size 20340 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index 355158581..6c94d866b 100644 --- a/Content/Engine/DefaultTerrainMaterial.flax +++ b/Content/Engine/DefaultTerrainMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cce61627c31b12d2d79abf59c485889044b4b9a803c0d2b7a988dc25906d6a35 -size 30264 +oid sha256:123d3f1bbf71929447355f550c79f3db9f1038e0c43b348927a21b2c8cb5e950 +size 23500 diff --git a/Content/Engine/Models/Box.flax b/Content/Engine/Models/Box.flax index 91fa289c2..9fea6ab44 100644 --- a/Content/Engine/Models/Box.flax +++ b/Content/Engine/Models/Box.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb14616f130595eedfb800d3805266c441ee653edba703c4a6973a6f07302a01 -size 3121 +oid sha256:be3e9498a6f55c631f20187d7084158089a265d59c2c8c81748ae41dde81fb96 +size 3171 diff --git a/Content/Engine/Models/Quad.flax b/Content/Engine/Models/Quad.flax index 3e1ac4692..957be4c60 100644 --- a/Content/Engine/Models/Quad.flax +++ b/Content/Engine/Models/Quad.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7f1082e461386e0b2d89ce3cec3ddd7b670bce2a867e9503c841681721236bf -size 965 +oid sha256:1bc157593dd5209616dd17f39ea04e4b2f609a6c6b38da648d7df0b8b15fb28f +size 1015 diff --git a/Content/Engine/Models/SimpleBox.flax b/Content/Engine/Models/SimpleBox.flax index db97d287a..a0f3953c2 100644 --- a/Content/Engine/Models/SimpleBox.flax +++ b/Content/Engine/Models/SimpleBox.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbcbcd47cefbad93a2d14504c081fb7836ff24c4f875c0984552602a55877deb -size 2155 +oid sha256:2c6269591e15337ff7b0235b3b8d8b641296a33298aa0bf77bd69b3b576af270 +size 2205 diff --git a/Content/Engine/Models/Sphere.flax b/Content/Engine/Models/Sphere.flax index 182cb2e6f..0fd819128 100644 --- a/Content/Engine/Models/Sphere.flax +++ b/Content/Engine/Models/Sphere.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6287b7eb7d31e1a8a910b9601c376a2bc870f514b6a569d84f112264fb853099 -size 111969 +oid sha256:d06d281960b16ebf13cc69e71317e03261225d2992cee45da44b848632d1585e +size 112019 diff --git a/Content/Engine/Models/SphereLowPoly.flax b/Content/Engine/Models/SphereLowPoly.flax index 57dfc621c..4b5f5a464 100644 --- a/Content/Engine/Models/SphereLowPoly.flax +++ b/Content/Engine/Models/SphereLowPoly.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:572d0b6d951c1fe3a39aa2bfc6007b951a89abb010b72d138dd9d51ec12ec617 -size 3757 +oid sha256:3da1c828b3d55c08385f6a0b9a9bba36597d8f065a0fc827915dece33be165f9 +size 3807 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index 56a190cd4..88fce758b 100644 --- a/Content/Engine/SingleColorMaterial.flax +++ b/Content/Engine/SingleColorMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31eba5266d5b1354caea2d4d29528661a5d8b50fd38a3fb70c36fb83b2033c9e +oid sha256:9aec50596d9177062d707167f8140645db7d0693da38329dda2df293cf7d044f size 28168 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 9a4d091f2..0a1a15f92 100644 --- a/Content/Engine/SkyboxMaterial.flax +++ b/Content/Engine/SkyboxMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:031971460da7490fdb2645a580003a9fbb99a403f10759285ccc36fc8b6a1c88 +oid sha256:2147855bff19d96e07f816022642cafdadf681fe786e714cbefaf4e77257ec4c size 29366 From 3b4d4d234d66a3e19978f60911e19291a4a9f62d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 6 Jan 2025 22:49:27 +0100 Subject: [PATCH 111/215] Fix LOD Preview regression due to new instancing logic --- Source/Engine/Renderer/Editor/LODPreview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/Editor/LODPreview.cpp b/Source/Engine/Renderer/Editor/LODPreview.cpp index d3a633405..34f2ef3ba 100644 --- a/Source/Engine/Renderer/Editor/LODPreview.cpp +++ b/Source/Engine/Renderer/Editor/LODPreview.cpp @@ -34,7 +34,7 @@ bool LODPreviewMaterialShader::IsReady() const bool LODPreviewMaterialShader::CanUseInstancing(InstancingHandler& handler) const { - return _material->CanUseInstancing(handler); + return false; } DrawPass LODPreviewMaterialShader::GetDrawModes() const From edaed7bda17dea0511a594729563bcb3ed02bce6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 7 Jan 2025 16:12:11 +0100 Subject: [PATCH 112/215] Add missing layout for ribbon vertex buffer --- Source/Engine/Particles/Particles.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 9305c7931..fc645bb60 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -94,6 +94,16 @@ PACK_STRUCT(struct RibbonParticleVertex { uint32 PrevParticleIndex; float Distance; // TODO: pack into half/uint16 data + + static GPUVertexLayout* GetLayout() + { + return GPUVertexLayout::Get({ + { VertexElement::Types::TexCoord0, 0, 0, 0, PixelFormat::R32_UInt }, + { VertexElement::Types::TexCoord1, 0, 0, 0, PixelFormat::R32_UInt }, + { VertexElement::Types::TexCoord2, 0, 0, 0, PixelFormat::R32_UInt }, + { VertexElement::Types::TexCoord3, 0, 0, 0, PixelFormat::R32_Float }, + }); + } }); struct EmitterCache @@ -306,7 +316,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa else buffer->GPU.RibbonIndexBufferDynamic->Clear(); if (!buffer->GPU.RibbonVertexBufferDynamic) - buffer->GPU.RibbonVertexBufferDynamic = New(0, (uint32)sizeof(RibbonParticleVertex), TEXT("RibbonVertexBufferDynamic")); + buffer->GPU.RibbonVertexBufferDynamic = New(0, (uint32)sizeof(RibbonParticleVertex), TEXT("RibbonVertexBufferDynamic"), RibbonParticleVertex::GetLayout()); else buffer->GPU.RibbonVertexBufferDynamic->Clear(); auto& indexBuffer = buffer->GPU.RibbonIndexBufferDynamic->Data; From 7f0d852f49c9b17ff738ac7d0a84061a014aa058 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 7 Jan 2025 21:55:31 +0100 Subject: [PATCH 113/215] Fix minor shader compilation warnings --- Content/Editor/MaterialTemplates/Particle.shader | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 5cf8b9263..3623e5115 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -299,18 +299,18 @@ half3x3 CalcTangentToLocal(ModelInput input) float3 normal = input.Normal.xyz * 2.0 - 1.0; float3 tangent = input.Tangent.xyz * 2.0 - 1.0; float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); + return (half3x3)float3x3(tangent, bitangent, normal); } half3x3 CalcTangentToWorld(in float4x4 world, in half3x3 tangentToLocal) { - half3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); + half3x3 localToWorld = (half3x3)RemoveScaleFromLocalToWorld((float3x3)world); return mul(tangentToLocal, localToWorld); } -float3 GetParticlePosition(uint ParticleIndex) +float3 GetParticlePosition(uint particleIndex) { - return TransformParticlePosition(GetParticleVec3(ParticleIndex, PositionOffset)); + return TransformParticlePosition(GetParticleVec3(particleIndex, PositionOffset)); } // Vertex Shader function for Sprite Rendering @@ -405,7 +405,7 @@ VertexOutput VS_Sprite(SpriteInput input, uint particleIndex : SV_InstanceID) output.InstanceParams = PerInstanceRandom; // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToLocal = float3x3(axisX, axisY, axisZ); + half3x3 tangentToLocal = half3x3(axisX, axisY, axisZ); half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); output.TBN = tangentToWorld; @@ -610,7 +610,7 @@ VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID) { output.TexCoord.x = (float)input.Order / (float)RibbonSegmentCount; } - output.TexCoord.y = (vertexIndex + 1) & 0x1; + output.TexCoord.y = (float)((vertexIndex + 1) & 0x1); output.TexCoord = output.TexCoord * RibbonUVScale + RibbonUVOffset; // Compute world space vertex position @@ -629,7 +629,7 @@ VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID) output.InstanceParams = PerInstanceRandom; // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToLocal = float3x3(tangentRight, tangentUp, cross(tangentRight, tangentUp)); + half3x3 tangentToLocal = half3x3(tangentRight, tangentUp, cross(tangentRight, tangentUp)); half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); output.TBN = tangentToWorld; From 7aa240e5eb631fad9887f5dba583d8beaec25da0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 7 Jan 2025 23:26:06 +0100 Subject: [PATCH 114/215] Add vertex shader input layout reading via shader compiler reflection to handle missing vertex elements binding when explicit layout got deprecated --- Source/Engine/Graphics/Shaders/GPUShader.cpp | 16 +- Source/Engine/Graphics/Shaders/GPUShader.h | 4 +- .../Graphics/Shaders/GPUShaderProgram.h | 3 + .../DirectX/DX11/GPUShaderDX11.cpp | 29 +- .../DirectX/DX11/GPUShaderProgramDX11.h | 3 +- .../DirectX/DX12/DescriptorHeapDX12.cpp | 2 +- .../DirectX/DX12/GPUPipelineStateDX12.cpp | 2 +- .../DirectX/DX12/GPUShaderDX12.cpp | 5 +- .../DirectX/DX12/GPUShaderProgramDX12.h | 3 +- .../Vulkan/GPUPipelineStateVulkan.cpp | 2 +- .../Vulkan/GPUShaderProgramVulkan.h | 3 +- .../GraphicsDevice/Vulkan/GPUShaderVulkan.cpp | 5 +- .../DirectX/ShaderCompilerD3D.cpp | 78 +++-- .../DirectX/ShaderCompilerD3D.h | 3 - .../DirectX/ShaderCompilerDX.cpp | 62 +++- .../DirectX/ShaderCompilerDX.h | 3 - .../ShadersCompilation/ShaderCompiler.cpp | 50 ++- .../ShadersCompilation/ShaderCompiler.h | 19 +- .../Vulkan/ShaderCompilerVulkan.cpp | 287 ++++++++++-------- .../Vulkan/ShaderCompilerVulkan.h | 3 - 20 files changed, 350 insertions(+), 232 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index 82983b1b9..8ede61fc6 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -177,22 +177,28 @@ GPUShaderProgram* GPUShader::GetShader(ShaderStage stage, const StringAnsiView& return shader; } -GPUVertexLayout* GPUShader::ReadVertexLayout(MemoryReadStream& stream) +void GPUShader::ReadVertexLayout(MemoryReadStream& stream, GPUVertexLayout*& inputLayout, GPUVertexLayout*& vertexLayout) { + inputLayout = vertexLayout = nullptr; + + // Read input layout (based on shader reflection) + GPUVertexLayout::Elements elements; + stream.Read(elements); + inputLayout = GPUVertexLayout::Get(elements); + // [Deprecated in v1.10] byte inputLayoutSize; stream.ReadByte(&inputLayoutSize); if (inputLayoutSize == 0) - return nullptr; + return; void* elementsData = stream.Move(sizeof(VertexElement) * inputLayoutSize); if (inputLayoutSize > GPU_MAX_VS_ELEMENTS) { LOG(Error, "Incorrect input layout size."); - return nullptr; + return; } - GPUVertexLayout::Elements elements; elements.Set((VertexElement*)elementsData, inputLayoutSize); - return GPUVertexLayout::Get(elements); + vertexLayout = GPUVertexLayout::Get(elements); } GPUResourceType GPUShader::GetResourceType() const diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 6879c3d5b..97bc6fabf 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -12,7 +12,7 @@ class GPUShaderProgram; /// /// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate). /// -#define GPU_SHADER_CACHE_VERSION 11 +#define GPU_SHADER_CACHE_VERSION 12 /// /// The GPU resource with shader programs that can run on the GPU and are able to perform rendering calculation using textures, vertices and other resources. @@ -135,7 +135,7 @@ public: protected: GPUShaderProgram* GetShader(ShaderStage stage, const StringAnsiView& name, int32 permutationIndex) const; virtual GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) = 0; - static GPUVertexLayout* ReadVertexLayout(MemoryReadStream& stream); + static void ReadVertexLayout(MemoryReadStream& stream, GPUVertexLayout*& inputLayout, GPUVertexLayout*& vertexLayout); public: // [GPUResource] diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h index aa8368ea0..4ae6bd63f 100644 --- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h +++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h @@ -136,6 +136,9 @@ public: // [Deprecated in v1.10] GPUVertexLayout* Layout = nullptr; + // Vertex shader inputs layout. Used to ensure that binded vertex buffers provide all required elements. + GPUVertexLayout* InputLayout = nullptr; + public: // [GPUShaderProgram] ShaderStage GetStage() const override diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index 7bb23de41..4251b3a55 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -26,7 +26,7 @@ ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* v { if (vertexLayout && vertexLayout->InputElementsCount) { - auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout); + auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout ? Layout : InputLayout); LOG_DIRECTX_RESULT(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(mergedVertexLayout->InputElements, mergedVertexLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); } _cache.Add(vertexLayout, inputLayout); @@ -42,42 +42,30 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const { case ShaderStage::Vertex: { - // Load Input Layout - GPUVertexLayout* vertexLayout = ReadVertexLayout(stream); - - // Create shader + GPUVertexLayout* inputLayout, *vertexLayout; + ReadVertexLayout(stream, inputLayout, vertexLayout); ID3D11VertexShader* buffer = nullptr; result = _device->GetDevice()->CreateVertexShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); - - // Create object - shader = New(initializer, buffer, vertexLayout, bytecode); + shader = New(initializer, buffer, inputLayout, vertexLayout, bytecode); break; } #if GPU_ALLOW_TESSELLATION_SHADERS case ShaderStage::Hull: { - // Read control points int32 controlPointsCount; stream.ReadInt32(&controlPointsCount); - - // Create shader ID3D11HullShader* buffer = nullptr; result = _device->GetDevice()->CreateHullShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); - - // Create object shader = New(initializer, buffer, controlPointsCount); break; } case ShaderStage::Domain: { - // Create shader ID3D11DomainShader* buffer = nullptr; result = _device->GetDevice()->CreateDomainShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); - - // Create object shader = New(initializer, buffer); break; } @@ -92,35 +80,26 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const #if GPU_ALLOW_GEOMETRY_SHADERS case ShaderStage::Geometry: { - // Create shader ID3D11GeometryShader* buffer = nullptr; result = _device->GetDevice()->CreateGeometryShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); - - // Create object shader = New(initializer, buffer); break; } #endif case ShaderStage::Pixel: { - // Create shader ID3D11PixelShader* buffer = nullptr; result = _device->GetDevice()->CreatePixelShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); - - // Create object shader = New(initializer, buffer); break; } case ShaderStage::Compute: { - // Create shader ID3D11ComputeShader* buffer = nullptr; result = _device->GetDevice()->CreateComputeShader(bytecode.Get(), bytecode.Length(), nullptr, &buffer); LOG_DIRECTX_RESULT_WITH_RETURN(result, nullptr); - - // Create object shader = New(initializer, buffer); break; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h index d43e60b2d..6c87c1d0a 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderProgramDX11.h @@ -58,9 +58,10 @@ private: Dictionary _cache; public: - GPUShaderProgramVSDX11(const GPUShaderProgramInitializer& initializer, ID3D11VertexShader* buffer, GPUVertexLayout* vertexLayout, Span bytecode) + GPUShaderProgramVSDX11(const GPUShaderProgramInitializer& initializer, ID3D11VertexShader* buffer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, Span bytecode) : GPUShaderProgramDX11(initializer, buffer) { + InputLayout = inputLayout; Layout = vertexLayout; Bytecode.Copy(bytecode); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp index 931def028..98e1630f9 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp @@ -124,7 +124,7 @@ void DescriptorHeapWithSlotsDX12::ReleaseSlot(uint32 index) { uint32& value = _usage[index / 32]; const uint32 mask = 1 << (index & 31); - ASSERT_LOW_LAYER((value & mask) == mask); + //ASSERT_LOW_LAYER((value & mask) == mask); value &= ~mask; } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index 7653992ce..c68fda240 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -180,7 +180,7 @@ bool GPUPipelineStateDX12::Init(const Description& desc) INIT_SHADER_STAGE(PS, GPUShaderProgramPSDX12); // Input Assembly - VertexLayout = desc.VS ? (GPUVertexLayoutDX12*)desc.VS->Layout : nullptr; + VertexLayout = desc.VS ? (GPUVertexLayoutDX12*)(desc.VS->Layout ? desc.VS->Layout : desc.VS->InputLayout) : nullptr; const D3D12_PRIMITIVE_TOPOLOGY_TYPE primTypes1[] = { D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED, diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp index 0fe07a466..a088a7840 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp @@ -19,8 +19,9 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const { case ShaderStage::Vertex: { - GPUVertexLayout* vertexLayout = ReadVertexLayout(stream); - shader = New(initializer, header, bytecode, vertexLayout); + GPUVertexLayout* inputLayout, *vertexLayout; + ReadVertexLayout(stream, inputLayout, vertexLayout); + shader = New(initializer, header, bytecode, inputLayout, vertexLayout); break; } #if GPU_ALLOW_TESSELLATION_SHADERS diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h index 01e21c4f2..48639abb4 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h @@ -46,9 +46,10 @@ public: class GPUShaderProgramVSDX12 : public GPUShaderProgramDX12 { public: - GPUShaderProgramVSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode, GPUVertexLayout* vertexLayout) + GPUShaderProgramVSDX12(const GPUShaderProgramInitializer& initializer, const DxShaderHeader* header, Span bytecode, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout) : GPUShaderProgramDX12(initializer, header, bytecode) { + InputLayout = inputLayout; Layout = vertexLayout; } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 7f3896038..32b34e4f6 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -320,7 +320,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _desc.pStages = _shaderStages; // Input Assembly - VertexShaderLayout = desc.VS ? (GPUVertexLayoutVulkan*)desc.VS->Layout : nullptr; + VertexShaderLayout = desc.VS ? (GPUVertexLayoutVulkan*)(desc.VS->Layout ? desc.VS->Layout : desc.VS->InputLayout) : nullptr; RenderToolsVulkan::ZeroStruct(_descInputAssembly, VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO);; switch (desc.PrimitiveTopology) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h index 9f6391265..79b0023f0 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h @@ -66,9 +66,10 @@ public: class GPUShaderProgramVSVulkan : public GPUShaderProgramVulkan { public: - GPUShaderProgramVSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule, GPUVertexLayout* vertexLayout) + GPUShaderProgramVSVulkan(GPUDeviceVulkan* device, const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, VkShaderModule shaderModule, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout) : GPUShaderProgramVulkan(device, initializer, descriptorInfo, shaderModule) { + InputLayout = inputLayout; Layout = vertexLayout; } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp index 9608d5207..b04f07c7d 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp @@ -138,8 +138,9 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons { case ShaderStage::Vertex: { - GPUVertexLayout* vertexLayout = ReadVertexLayout(stream); - shader = New(_device, initializer, header->DescriptorInfo, shaderModule, vertexLayout); + GPUVertexLayout* inputLayout, *vertexLayout; + ReadVertexLayout(stream, inputLayout, vertexLayout); + shader = New(_device, initializer, header->DescriptorInfo, shaderModule, inputLayout, vertexLayout); break; } #if GPU_ALLOW_TESSELLATION_SHADERS diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp index 819e9e745..131d77033 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp @@ -16,11 +16,9 @@ class IncludeD3D : public ID3DInclude { private: - ShaderCompilationContext* _context; public: - IncludeD3D(ShaderCompilationContext* context) { _context = context; @@ -84,14 +82,9 @@ namespace // Extract constant buffers usage information for (uint32 a = 0; a < desc.ConstantBuffers; a++) { - // Get CB auto cb = reflector->GetConstantBufferByIndex(a); - - // Get CB description D3D11_SHADER_BUFFER_DESC cbDesc; cb->GetDesc(&cbDesc); - - // Check buffer type if (cbDesc.Type == D3D_CT_CBUFFER) { // Find CB slot index @@ -132,22 +125,20 @@ namespace // Extract resources usage for (uint32 i = 0; i < desc.BoundResources; i++) { - // Get resource description D3D11_SHADER_INPUT_BIND_DESC resDesc; reflector->GetResourceBindingDesc(i, &resDesc); - switch (resDesc.Type) { - // Sampler + // Sampler case D3D_SIT_SAMPLER: break; - // Constant Buffer + // Constant Buffer case D3D_SIT_CBUFFER: case D3D_SIT_TBUFFER: break; - // Shader Resource + // Shader Resource case D3D_SIT_TEXTURE: case D3D_SIT_STRUCTURED: case D3D_SIT_BYTEADDRESS: @@ -155,7 +146,7 @@ namespace bindings.UsedSRsMask |= 1 << (resDesc.BindPoint + shift); break; - // Unordered Access + // Unordered Access case D3D_SIT_UAV_RWTYPED: case D3D_SIT_UAV_RWSTRUCTURED: case D3D_SIT_UAV_RWBYTEADDRESS: @@ -212,7 +203,6 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation else { profileName += "_4_0"; - if (type == ShaderStage::Domain || type == ShaderStage::Hull) { _context->OnError("Tessellation is not supported on DirectX 10"); @@ -221,6 +211,7 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation } // Compile all shader function permutations + AdditionalDataVS additionalDataVS; for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++) { _macros.Clear(); @@ -253,8 +244,6 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation 0, &shader, &errors); - - // Check compilation result if (FAILED(result)) { const auto msg = static_cast(errors->GetBufferPointer()); @@ -279,24 +268,68 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation reflector->GetDesc(&desc); // Process shader reflection data + void* additionalData = nullptr; + if (type == ShaderStage::Vertex) + { + additionalData = &additionalDataVS; + additionalDataVS.Inputs.Clear(); + for (UINT inputIdx = 0; inputIdx < desc.InputParameters; inputIdx++) + { + D3D11_SIGNATURE_PARAMETER_DESC inputDesc; + reflector->GetInputParameterDesc(inputIdx, &inputDesc); + if (inputDesc.ReadWriteMask == 0 || inputDesc.SystemValueType != D3D10_NAME_UNDEFINED) + continue; + auto format = PixelFormat::Unknown; + switch (inputDesc.ComponentType) + { + case D3D_REGISTER_COMPONENT_UINT32: + if (inputDesc.Mask >= 0b1111) + format = PixelFormat::R32G32B32A32_UInt; + else if (inputDesc.Mask >= 0b111) + format = PixelFormat::R32G32B32_UInt; + else if (inputDesc.Mask >= 0b11) + format = PixelFormat::R32G32_UInt; + else + format = PixelFormat::R32_UInt; + break; + case D3D_REGISTER_COMPONENT_SINT32: + if (inputDesc.Mask >= 0b1111) + format = PixelFormat::R32G32B32A32_SInt; + else if (inputDesc.Mask >= 0b111) + format = PixelFormat::R32G32B32_SInt; + else if (inputDesc.Mask >= 0b11) + format = PixelFormat::R32G32_SInt; + else + format = PixelFormat::R32_SInt; + break; + case D3D_REGISTER_COMPONENT_FLOAT32: + if (inputDesc.Mask >= 0b1111) + format = PixelFormat::R32G32B32A32_Float; + else if (inputDesc.Mask >= 0b111) + format = PixelFormat::R32G32B32_Float; + else if (inputDesc.Mask >= 0b11) + format = PixelFormat::R32G32_Float; + else + format = PixelFormat::R32_Float; + break; + } + additionalDataVS.Inputs.Add({ ParseVertexElementType(inputDesc.SemanticName, inputDesc.SemanticIndex), 0, 0, 0, format }); + } + } ShaderBindings bindings = { desc.InstructionCount, 0, 0, 0 }; if (ProcessShader(_context, _constantBuffers, reflector.Get(), desc, bindings)) return true; #ifdef GPU_USE_SHADERS_DEBUG_LAYER - // Generate debug information if (ProcessDebugInfo(_context, meta, permutationIndex, shaderBuffer, shaderBufferSize)) return true; - #endif // Strip shader bytecode for an optimization ComPtr shaderStripped; if (!options->GenerateDebugData) { - //auto prevShaderBufferSize = shaderBufferSize; - // Strip shader bytes result = D3DStripShader( shaderBuffer, @@ -312,15 +345,12 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation // Set new buffer shaderBuffer = shaderStripped->GetBufferPointer(); shaderBufferSize = static_cast(shaderStripped->GetBufferSize()); - - //auto strippedBytes = prevShaderBufferSize - shaderBufferSize; - //auto strippedBytesPercentage = Math::FloorToInt(strippedBytes * 100.0f / prevShaderBufferSize); } if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, shaderBuffer, shaderBufferSize)) return true; - if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros)) + if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros, additionalData)) return true; } diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.h b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.h index b78d04a37..b9d8952c5 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.h +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.h @@ -12,12 +12,10 @@ class ShaderCompilerD3D : public ShaderCompiler { private: - Array _funcNameDefineBuffer; uint32 _flags; public: - /// /// Initializes a new instance of the class. /// @@ -25,7 +23,6 @@ public: ShaderCompilerD3D(ShaderProfile profile); protected: - // [ShaderCompiler] bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override; bool OnCompileBegin() override; diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index 9619d0548..c2431c485 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -4,7 +4,6 @@ #include "ShaderCompilerDX.h" #include "Engine/Core/Log.h" -#include "Engine/Threading/Threading.h" #include "Engine/Engine/Globals.h" #include "Engine/Graphics/Config.h" #include "Engine/GraphicsDevice/DirectX/DX12/Types.h" @@ -22,12 +21,10 @@ class IncludeDX : public IDxcIncludeHandler { private: - ShaderCompilationContext* _context; IDxcLibrary* _library; public: - IncludeDX(ShaderCompilationContext* context, IDxcLibrary* library) { _context = context; @@ -185,6 +182,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD } // Compile all shader function permutations + AdditionalDataVS additionalDataVS; for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++) { _macros.Clear(); @@ -320,6 +318,54 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD shaderReflection->GetDesc(&desc); // Process shader reflection data + void* additionalData = nullptr; + if (type == ShaderStage::Vertex) + { + additionalData = &additionalDataVS; + additionalDataVS.Inputs.Clear(); + for (UINT inputIdx = 0; inputIdx < desc.InputParameters; inputIdx++) + { + D3D12_SIGNATURE_PARAMETER_DESC inputDesc; + shaderReflection->GetInputParameterDesc(inputIdx, &inputDesc); + if (inputDesc.ReadWriteMask == 0 || inputDesc.SystemValueType != D3D10_NAME_UNDEFINED) + continue; + auto format = PixelFormat::Unknown; + switch (inputDesc.ComponentType) + { + case D3D_REGISTER_COMPONENT_UINT32: + if (inputDesc.Mask >= 0b1111) + format = PixelFormat::R32G32B32A32_UInt; + else if (inputDesc.Mask >= 0b111) + format = PixelFormat::R32G32B32_UInt; + else if (inputDesc.Mask >= 0b11) + format = PixelFormat::R32G32_UInt; + else + format = PixelFormat::R32_UInt; + break; + case D3D_REGISTER_COMPONENT_SINT32: + if (inputDesc.Mask >= 0b1111) + format = PixelFormat::R32G32B32A32_SInt; + else if (inputDesc.Mask >= 0b111) + format = PixelFormat::R32G32B32_SInt; + else if (inputDesc.Mask >= 0b11) + format = PixelFormat::R32G32_SInt; + else + format = PixelFormat::R32_SInt; + break; + case D3D_REGISTER_COMPONENT_FLOAT32: + if (inputDesc.Mask >= 0b1111) + format = PixelFormat::R32G32B32A32_Float; + else if (inputDesc.Mask >= 0b111) + format = PixelFormat::R32G32B32_Float; + else if (inputDesc.Mask >= 0b11) + format = PixelFormat::R32G32_Float; + else + format = PixelFormat::R32_Float; + break; + } + additionalDataVS.Inputs.Add({ ParseVertexElementType(inputDesc.SemanticName, inputDesc.SemanticIndex), 0, 0, 0, format }); + } + } DxShaderHeader header; Platform::MemoryClear(&header, sizeof(header)); ShaderBindings bindings = { desc.InstructionCount, 0, 0, 0 }; @@ -370,16 +416,16 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD shaderReflection->GetResourceBindingDesc(i, &resDesc); switch (resDesc.Type) { - // Sampler + // Sampler case D3D_SIT_SAMPLER: break; - // Constant Buffer + // Constant Buffer case D3D_SIT_CBUFFER: case D3D_SIT_TBUFFER: break; - // Shader Resource + // Shader Resource case D3D_SIT_TEXTURE: for (UINT shift = 0; shift < resDesc.BindCount; shift++) { @@ -396,7 +442,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD } break; - // Unordered Access + // Unordered Access case D3D_SIT_UAV_RWTYPED: case D3D_SIT_UAV_RWSTRUCTURED: case D3D_SIT_UAV_RWBYTEADDRESS: @@ -415,7 +461,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), shaderBuffer->GetBufferPointer(), (int32)shaderBuffer->GetBufferSize())) return true; - if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros)) + if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros, additionalData)) return true; } diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h index 935f3ca87..30076db83 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h @@ -12,14 +12,12 @@ class ShaderCompilerDX : public ShaderCompiler { private: - Array _funcNameDefineBuffer; void* _compiler; void* _library; void* _containerReflection; public: - /// /// Initializes a new instance of the class. /// @@ -32,7 +30,6 @@ public: ~ShaderCompilerDX(); protected: - // [ShaderCompiler] bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override; bool OnCompileBegin() override; diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 21b619b86..8e3ca2abf 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -271,7 +271,6 @@ bool ShaderCompiler::WriteShaderFunctionBegin(ShaderCompilationContext* context, bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* header, int32 headerSize, const void* cache, int32 cacheSize) { auto output = context->Output; - output->Write((uint32)(cacheSize + headerSize)); output->WriteBytes(header, headerSize); output->WriteBytes(cache, cacheSize); @@ -293,18 +292,22 @@ bool ShaderCompiler::WriteShaderFunctionEnd(ShaderCompilationContext* context, S return false; } -bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros) +bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros, void* additionalData) { - // [Deprecated in v1.10] auto output = context->Output; + + // Write vertex shader inputs (based on compiled shader reflection) to bind any missing vertex buffer streaming at runtime (during drawing - see usage of GPUVertexLayout::Merge) + if (auto* additionalDataVS = (AdditionalDataVS*)additionalData) + output->Write(additionalDataVS->Inputs); + else + output->WriteInt32(0); + + // [Deprecated in v1.10] auto& metaVS = *(VertexShaderMeta*)&meta; auto& layout = metaVS.InputLayout; #if FLAXENGINE_VERSION_MAJOR > 2 || (FLAXENGINE_VERSION_MAJOR == 2 && FLAXENGINE_VERSION_MINOR >= 1) if (layout.HasItems()) LOG(Warning, "Vertex Shader '{}' (asset '{}') uses explicit vertex layout via 'META_VS_IN_ELEMENT' macros which has been deprecated. Remove this code and migrate to GPUVertexLayout with VertexElement array in code (assigned to vertex buffer).", String(meta.Name), context->Options->TargetName); -#elif FLAXENGINE_VERSION_MAJOR == 1 && FLAXENGINE_VERSION_MINOR >= 11 - if (layout.HasItems()) - LOG(Warning, "Vertex Shader '{}' (asset '{}') uses explicit vertex layout via 'META_VS_IN_ELEMENT' macros which has been deprecated. Remove this code and migrate to GPUVertexLayout with VertexElement array in code (assigned to vertex buffer).", String(meta.Name), context->Options->TargetName); #endif // Get visible entries (based on `visible` flag switch) @@ -443,7 +446,7 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader return false; } -bool ShaderCompiler::WriteCustomDataHS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros) +bool ShaderCompiler::WriteCustomDataHS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros, void* additionalData) { auto output = context->Output; auto& metaHS = *(HullShaderMeta*)&meta; @@ -457,7 +460,7 @@ bool ShaderCompiler::WriteCustomDataHS(ShaderCompilationContext* context, Shader void ShaderCompiler::GetDefineForFunction(ShaderFunctionMeta& meta, Array& macros) { auto& functionName = meta.Name; - const int32 functionNameLength = static_cast(functionName.Length()); + const int32 functionNameLength = functionName.Length(); _funcNameDefineBuffer.Clear(); _funcNameDefineBuffer.EnsureCapacity(functionNameLength + 2); _funcNameDefineBuffer.Add('_'); @@ -466,4 +469,35 @@ void ShaderCompiler::GetDefineForFunction(ShaderFunctionMeta& meta, Array /// Base class for the objects that can compile shaders source code. @@ -14,7 +15,6 @@ class ShaderCompiler { public: - struct ShaderResourceBuffer { byte Slot; @@ -23,11 +23,9 @@ public: }; private: - Array _funcNameDefineBuffer; protected: - ShaderProfile _profile; ShaderCompilationContext* _context = nullptr; Array _globalMacros; @@ -35,7 +33,6 @@ protected: Array _constantBuffers; public: - /// /// Initializes a new instance of the class. /// @@ -51,7 +48,6 @@ public: virtual ~ShaderCompiler() = default; public: - /// /// Gets shader profile supported by this compiler. /// @@ -85,8 +81,13 @@ public: static void DisposeIncludedFilesCache(); protected: + // Input elements read from reflection after shader compilation. Rough approx or attributes without exact format nor bind slot (only semantics and value dimensions match). + struct AdditionalDataVS + { + GPUVertexLayout::Elements Inputs; + }; - typedef bool (*WritePermutationData)(ShaderCompilationContext*, ShaderFunctionMeta&, int32, const Array&); + typedef bool (*WritePermutationData)(ShaderCompilationContext*, ShaderFunctionMeta&, int32, const Array&, void*); virtual bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) = 0; @@ -99,9 +100,11 @@ protected: 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* cache, int32 cacheSize); static bool WriteShaderFunctionEnd(ShaderCompilationContext* context, ShaderFunctionMeta& meta); - static bool WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros); - static bool WriteCustomDataHS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros); + static bool WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros, void* additionalData); + static bool WriteCustomDataHS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros, void* additionalData); void GetDefineForFunction(ShaderFunctionMeta& meta, Array& macros); + + static VertexElement::Types ParseVertexElementType(StringAnsiView semantic, uint32 index = 0); }; #endif diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp index 5f509ef1e..c3fa1483d 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp @@ -234,14 +234,14 @@ SpirvShaderResourceType GetTextureType(const glslang::TSampler& sampler) } } -PixelFormat GetResourceFormat(const glslang::TSampler& sampler) +PixelFormat GetResourceFormat(glslang::TBasicType basicType, uint32 vectorSize) { - switch (sampler.type) + switch (basicType) { case glslang::EbtVoid: return PixelFormat::Unknown; case glslang::EbtFloat: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R32_Float; @@ -254,7 +254,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtFloat16: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R16_Float; @@ -265,7 +265,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtUint: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R32_UInt; @@ -278,7 +278,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtInt: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R32_SInt; @@ -291,7 +291,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtUint8: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R8_UInt; @@ -302,7 +302,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtInt8: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R8_SInt; @@ -313,7 +313,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtUint16: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R16_UInt; @@ -324,7 +324,7 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) } break; case glslang::EbtInt16: - switch (sampler.vectorSize) + switch (vectorSize) { case 1: return PixelFormat::R16_SInt; @@ -340,6 +340,16 @@ PixelFormat GetResourceFormat(const glslang::TSampler& sampler) return PixelFormat::Unknown; } +PixelFormat GetResourceFormat(const glslang::TSampler& sampler) +{ + return GetResourceFormat(sampler.type, sampler.vectorSize); +} + +PixelFormat GetResourceFormat(const glslang::TType& type) +{ + return GetResourceFormat(type.getBasicType(), type.getVectorSize()); +} + bool IsUavType(const glslang::TType& type) { if (type.getQualifier().isReadOnly()) @@ -611,6 +621,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat EShMessages messages = (EShMessages)(EShMsgReadHlsl | EShMsgSpvRules | EShMsgVulkanRules); // Compile all shader function permutations + AdditionalDataVS additionalDataVS; for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++) { #if PRINT_DESCRIPTORS @@ -721,157 +732,167 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat } // Process shader reflection data + void* additionalData = nullptr; SpirvShaderHeader header; Platform::MemoryClear(&header, sizeof(header)); ShaderBindings bindings = { 0, 0, 0, 0 }; + if (type == ShaderStage::Vertex) { - // Extract constant buffers usage information - for (int blockIndex = 0; blockIndex < program.getNumLiveUniformBlocks(); blockIndex++) + additionalData = &additionalDataVS; + additionalDataVS.Inputs.Clear(); + for (int inputIndex = 0; inputIndex < program.getNumPipeInputs(); inputIndex++) { - auto size = program.getUniformBlockSize(blockIndex); - auto uniform = program.getUniformBlockTType(blockIndex); - auto& qualifier = uniform->getQualifier(); - auto binding = (int32)qualifier.layoutBinding; - - if (!qualifier.hasBinding()) - { - // Each uniform must have a valid binding - //LOG(Warning, "Found a uniform block \'{0}\' without a binding qualifier. Each uniform block must have an explicitly defined binding number.", String(uniform->getTypeName().c_str())); + const glslang::TObjectReflection& input = program.getPipeInput(inputIndex); + if (!input.getType() || input.getType()->containsBuiltIn()) continue; - } + additionalDataVS.Inputs.Add({ ParseVertexElementType(input.getType()->getQualifier().semanticName), 0, 0, 0, GetResourceFormat(*input.getType()) }); + } + } + for (int blockIndex = 0; blockIndex < program.getNumLiveUniformBlocks(); blockIndex++) + { + auto size = program.getUniformBlockSize(blockIndex); + auto uniform = program.getUniformBlockTType(blockIndex); + auto& qualifier = uniform->getQualifier(); + auto binding = (int32)qualifier.layoutBinding; - // Shared storage buffer - if (qualifier.storage == glslang::EvqBuffer) + if (!qualifier.hasBinding()) + { + // Each uniform must have a valid binding + //LOG(Warning, "Found a uniform block \'{0}\' without a binding qualifier. Each uniform block must have an explicitly defined binding number.", String(uniform->getTypeName().c_str())); + continue; + } + + // Shared storage buffer + if (qualifier.storage == glslang::EvqBuffer) + { + // RWBuffer + } + else + { + // Uniform buffer + bool found = false; + for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++) { - // RWBuffer + auto& descriptor = descriptorsCollector.Descriptors[i]; + if (descriptor.BindingType == SpirvShaderResourceBindingType::CB && descriptor.Binding == binding) + { + found = true; + descriptor.Size = size; + break; + } } - else + if (!found) { - // Uniform buffer - bool found = false; - for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++) - { - auto& descriptor = descriptorsCollector.Descriptors[i]; - if (descriptor.BindingType == SpirvShaderResourceBindingType::CB && descriptor.Binding == binding) - { - found = true; - descriptor.Size = size; - break; - } - } - if (!found) - { - LOG(Warning, "Failed to find descriptor for the uniform block \'{0}\' of size {1} (bytes), binding: {2}.", String(uniform->getTypeName().c_str()), size, binding); - } + LOG(Warning, "Failed to find descriptor for the uniform block \'{0}\' of size {1} (bytes), binding: {2}.", String(uniform->getTypeName().c_str()), size, binding); } } + } #if PRINT_UNIFORMS - // Debug printing all uniforms - for (int32 index = 0; index < program.getNumLiveUniformVariables(); index++) - { - auto uniform = program.getUniformTType(index); - auto qualifier = uniform->getQualifier(); - if (!uniform->isArray()) - LOG(Warning, "Shader {0}:{1} - uniform: {2} {3} at binding {4}", - _context->TargetNameAnsi, - String(meta.Name), - uniform->getCompleteString().c_str(), - program.getUniformName(index), - qualifier.layoutBinding - ); - } + // Debug printing all uniforms + for (int32 index = 0; index < program.getNumLiveUniformVariables(); index++) + { + auto uniform = program.getUniformTType(index); + auto qualifier = uniform->getQualifier(); + if (!uniform->isArray()) + LOG(Warning, "Shader {0}:{1} - uniform: {2} {3} at binding {4}", + _context->TargetNameAnsi, + String(meta.Name), + uniform->getCompleteString().c_str(), + program.getUniformName(index), + qualifier.layoutBinding + ); + } #endif - // Process all descriptors - header.DescriptorInfo.ImageInfosCount = descriptorsCollector.Images; - header.DescriptorInfo.BufferInfosCount = descriptorsCollector.Buffers; - header.DescriptorInfo.TexelBufferViewsCount = descriptorsCollector.TexelBuffers; - for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++) + // Process all descriptors + header.DescriptorInfo.ImageInfosCount = descriptorsCollector.Images; + header.DescriptorInfo.BufferInfosCount = descriptorsCollector.Buffers; + header.DescriptorInfo.TexelBufferViewsCount = descriptorsCollector.TexelBuffers; + for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++) + { + auto& descriptor = descriptorsCollector.Descriptors[i]; + + // Skip cases (eg. AppendStructuredBuffer counter buffer) + if (descriptor.Slot == MAX_uint16) + continue; + + auto& d = header.DescriptorInfo.DescriptorTypes[header.DescriptorInfo.DescriptorTypesCount++]; + d.Binding = descriptor.Binding; + d.Set = stageSet; + d.Slot = descriptor.Slot; + d.BindingType = descriptor.BindingType; + d.DescriptorType = descriptor.DescriptorType; + d.ResourceType = descriptor.ResourceType; + d.ResourceFormat = descriptor.ResourceFormat; + d.Count = descriptor.Count; + + switch (descriptor.BindingType) { - auto& descriptor = descriptorsCollector.Descriptors[i]; + case SpirvShaderResourceBindingType::CB: + ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_CB_BINDED); + bindings.UsedCBsMask |= 1 << descriptor.Slot; + break; + case SpirvShaderResourceBindingType::SRV: + ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_SR_BINDED); + bindings.UsedSRsMask |= 1 << descriptor.Slot; + break; + case SpirvShaderResourceBindingType::UAV: + ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_UA_BINDED); + bindings.UsedUAsMask |= 1 << descriptor.Slot; + break; + } - // Skip cases (eg. AppendStructuredBuffer counter buffer) - if (descriptor.Slot == MAX_uint16) + if (descriptor.BindingType == SpirvShaderResourceBindingType::CB) + { + if (descriptor.Size == -1) + { + // Skip unused constant buffers continue; - - auto& d = header.DescriptorInfo.DescriptorTypes[header.DescriptorInfo.DescriptorTypesCount++]; - d.Binding = descriptor.Binding; - d.Set = stageSet; - d.Slot = descriptor.Slot; - d.BindingType = descriptor.BindingType; - d.DescriptorType = descriptor.DescriptorType; - d.ResourceType = descriptor.ResourceType; - d.ResourceFormat = descriptor.ResourceFormat; - d.Count = descriptor.Count; - - switch (descriptor.BindingType) + } + if (descriptor.Size == 0) { - case SpirvShaderResourceBindingType::CB: - ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_CB_BINDED); - bindings.UsedCBsMask |= 1 << descriptor.Slot; - break; - case SpirvShaderResourceBindingType::SRV: - ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_SR_BINDED); - bindings.UsedSRsMask |= 1 << descriptor.Slot; - break; - case SpirvShaderResourceBindingType::UAV: - ASSERT_LOW_LAYER(descriptor.Slot >= 0 && descriptor.Slot < GPU_MAX_UA_BINDED); - bindings.UsedUAsMask |= 1 << descriptor.Slot; - break; + LOG(Warning, "Found constant buffer \'{1}\' at slot {0} but it's not used or has no valid size.", descriptor.Slot, String(descriptor.Name.c_str())); + continue; } - if (descriptor.BindingType == SpirvShaderResourceBindingType::CB) + for (int32 b = 0; b < _constantBuffers.Count(); b++) { - if (descriptor.Size == -1) + auto& cc = _constantBuffers[b]; + if (cc.Slot == descriptor.Slot) { - // Skip unused constant buffers - continue; - } - if (descriptor.Size == 0) - { - LOG(Warning, "Found constant buffer \'{1}\' at slot {0} but it's not used or has no valid size.", descriptor.Slot, String(descriptor.Name.c_str())); - continue; - } - - for (int32 b = 0; b < _constantBuffers.Count(); b++) - { - auto& cc = _constantBuffers[b]; - if (cc.Slot == descriptor.Slot) - { - // Mark as used and cache some data - cc.IsUsed = true; - cc.Size = descriptor.Size; - break; - } + // Mark as used and cache some data + cc.IsUsed = true; + cc.Size = descriptor.Size; + break; } } + } #if PRINT_DESCRIPTORS - String type; - switch (descriptor.BindingType) - { - case SpirvShaderResourceBindingType::INVALID: - type = TEXT("INVALID"); - break; - case SpirvShaderResourceBindingType::CB: - type = TEXT("CB"); - break; - case SpirvShaderResourceBindingType::SAMPLER: - type = TEXT("SAMPLER"); - break; - case SpirvShaderResourceBindingType::SRV: - type = TEXT("SRV"); - break; - case SpirvShaderResourceBindingType::UAV: - type = TEXT("UAV"); - break; - default: - type = TEXT("?"); - } - LOG(Warning, "VULKAN SHADER RESOURCE: slot: {1}, binding: {2}, name: {0}, type: {3}", String(descriptor.Name.c_str()), descriptor.Slot, descriptor.Binding, type); -#endif + String type; + switch (descriptor.BindingType) + { + case SpirvShaderResourceBindingType::INVALID: + type = TEXT("INVALID"); + break; + case SpirvShaderResourceBindingType::CB: + type = TEXT("CB"); + break; + case SpirvShaderResourceBindingType::SAMPLER: + type = TEXT("SAMPLER"); + break; + case SpirvShaderResourceBindingType::SRV: + type = TEXT("SRV"); + break; + case SpirvShaderResourceBindingType::UAV: + type = TEXT("UAV"); + break; + default: + type = TEXT("?"); } + LOG(Warning, "VULKAN SHADER RESOURCE: slot: {1}, binding: {2}, name: {0}, type: {3}", String(descriptor.Name.c_str()), descriptor.Slot, descriptor.Binding, type); +#endif } // Generate SPIR-V (optimize it at the same time) @@ -916,7 +937,7 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), &spirv[0], spirvBytesCount)) return true; - if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros)) + if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros, additionalData)) return true; } diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h index efac16a53..d82a5b8f1 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.h @@ -12,11 +12,9 @@ class ShaderCompilerVulkan : public ShaderCompiler { private: - Array _funcNameDefineBuffer; public: - /// /// Initializes a new instance of the class. /// @@ -29,7 +27,6 @@ public: ~ShaderCompilerVulkan(); protected: - // [ShaderCompiler] bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override; bool OnCompileBegin() override; From 7e165d61277914e46590732b2e3c054905800eb9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 8 Jan 2025 09:39:19 +0100 Subject: [PATCH 115/215] Allow vertex colors on skinned meshes #3044 --- Source/Engine/Tools/ModelTool/ModelTool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index db7131df1..515016ddf 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -180,7 +180,7 @@ public: API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Geometry\", \"Import LODs\"), VisibleIf(nameof(ShowGeometry))") bool ImportLODs = true; // Enable/disable importing vertex colors (channel 0 only). - API_FIELD(Attributes="EditorOrder(80), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowModel))") + API_FIELD(Attributes="EditorOrder(80), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") bool ImportVertexColors = true; // Enable/disable importing blend shapes (morph targets). API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))") From 2b2ace0d004fe5ee1f81f22d4e0724c249556d22 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 8 Jan 2025 18:09:45 +0100 Subject: [PATCH 116/215] Fixes for Vulkan backend after recent changes --- .../Graphics/Shaders/GPUVertexLayout.cpp | 82 ++++++++++++------- .../Engine/Graphics/Shaders/GPUVertexLayout.h | 4 +- .../Vulkan/GPUContextVulkan.cpp | 14 +++- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 34 +------- .../Vulkan/GPUPipelineStateVulkan.cpp | 57 ++++++++++++- .../Vulkan/GPUPipelineStateVulkan.h | 3 +- .../Vulkan/GPUVertexLayoutVulkan.h | 4 +- 7 files changed, 127 insertions(+), 71 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 59e40483b..e3afae47e 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -206,47 +206,71 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span& layouts) return result; } -GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference) +GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing) { GPUVertexLayout* result = base ? base : reference; if (base && reference && base != reference) { - bool anyMissing = false; - const Elements& baseElements = base->GetElements(); - Elements newElements = baseElements; - for (const VertexElement& e : reference->GetElements()) + bool elementsModified = false; + Elements newElements = base->GetElements(); + if (removeUnused) { - bool missing = true; - for (const VertexElement& ee : baseElements) + for (int32 i = newElements.Count() - 1; i >= 0; i--) { - if (ee.Type == e.Type) + bool missing = true; + const VertexElement& e = newElements.Get()[i]; + for (const VertexElement& ee : reference->GetElements()) { - missing = false; - break; - } - } - if (missing) - { - // Insert any missing elements - VertexElement ne = { e.Type, e.Slot, 0, e.PerInstance, e.Format }; - if (e.Type == VertexElement::Types::TexCoord1 || e.Type == VertexElement::Types::TexCoord2 || e.Type == VertexElement::Types::TexCoord3) - { - // Alias missing texcoords with existing texcoords - for (const VertexElement& ee : newElements) + if (ee.Type == e.Type) { - if (ee.Type == VertexElement::Types::TexCoord0) - { - ne = ee; - ne.Type = e.Type; - break; - } + missing = false; + break; } } - newElements.Add(ne); - anyMissing = true; + if (missing) + { + // Remove unused element + newElements.RemoveAtKeepOrder(i); + elementsModified = true; + } } } - if (anyMissing) + if (addMissing) + { + for (const VertexElement& e : reference->GetElements()) + { + bool missing = true; + for (const VertexElement& ee : base->GetElements()) + { + if (ee.Type == e.Type) + { + missing = false; + break; + } + } + if (missing) + { + // Insert any missing elements + VertexElement ne = { e.Type, e.Slot, 0, e.PerInstance, e.Format }; + if (e.Type == VertexElement::Types::TexCoord1 || e.Type == VertexElement::Types::TexCoord2 || e.Type == VertexElement::Types::TexCoord3) + { + // Alias missing texcoords with existing texcoords + for (const VertexElement& ee : newElements) + { + if (ee.Type == VertexElement::Types::TexCoord0) + { + ne = ee; + ne.Type = e.Type; + break; + } + } + } + newElements.Add(ne); + elementsModified = true; + } + } + } + if (elementsModified) result = Get(newElements, true); } return result; diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index 9212626a3..e804b087e 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -74,8 +74,10 @@ public: /// /// The list of vertex buffers for the layout. /// The list of reference inputs. + /// 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. /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. - static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference); + static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused = false, bool addMissing = true); public: // [GPUResource] diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index ebf95f7cc..02dc86770 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -651,14 +651,14 @@ void GPUContextVulkan::OnDrawCall() } // Bind any missing vertex buffers to null if required by the current state - GPUVertexLayoutVulkan* vertexLayout = _vertexLayout ? _vertexLayout : pipelineState->VertexShaderLayout; + GPUVertexLayoutVulkan* vertexLayout = _vertexLayout ? _vertexLayout : pipelineState->VertexBufferLayout; #if GPU_ENABLE_ASSERTION_LOW_LAYERS - if (!vertexLayout && pipelineState && !pipelineState->VertexShaderLayout && (pipelineState->UsedStagesMask & (1 << (int32)DescriptorSet::Vertex)) != 0 && !_vertexLayout && _vbCount) + if (!vertexLayout && pipelineState && !pipelineState->VertexBufferLayout && (pipelineState->UsedStagesMask & (1 << (int32)DescriptorSet::Vertex)) != 0 && !_vertexLayout && _vbCount) { LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); } #endif - const int32 missingVBs = vertexLayout ? (int32)vertexLayout->CreateInfo.vertexBindingDescriptionCount - _vbCount : 0; + const int32 missingVBs = vertexLayout ? vertexLayout->MaxSlot + 1 - _vbCount : 0; if (missingVBs > 0) { VkBuffer buffers[GPU_MAX_VB_BINDED]; @@ -1034,7 +1034,13 @@ void GPUContextVulkan::BindUA(int32 slot, GPUResourceView* view) void GPUContextVulkan::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets, GPUVertexLayout* vertexLayout) { _vbCount = vertexBuffers.Length(); - _vertexLayout = (GPUVertexLayoutVulkan*)(vertexLayout ? vertexLayout : GPUVertexLayout::Get(vertexBuffers)); + if (!vertexLayout) + vertexLayout = GPUVertexLayout::Get(vertexBuffers); + if (_vertexLayout != vertexLayout) + { + _vertexLayout = (GPUVertexLayoutVulkan*)vertexLayout; + _psDirtyFlag = true; + } if (vertexBuffers.Length() == 0) return; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 21e653a36..efe6a3334 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -453,39 +453,12 @@ GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elem : GPUResourceVulkan(device, StringView::Empty) { SetElements(elements, explicitOffsets); - for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) - { - VkVertexInputBindingDescription& binding = Bindings[i]; - binding.binding = i; - binding.stride = 0; - binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - } - uint32 bindingsCount = 0; + MaxSlot = 0; for (int32 i = 0; i < elements.Count(); i++) { const VertexElement& src = GetElements().Get()[i]; - const int32 size = PixelFormatExtensions::SizeInBytes(src.Format); - - ASSERT_LOW_LAYER(src.Slot < GPU_MAX_VB_BINDED); - VkVertexInputBindingDescription& binding = Bindings[src.Slot]; - binding.binding = src.Slot; - binding.stride = Math::Max(binding.stride, (uint32_t)(src.Offset + size)); - binding.inputRate = src.PerInstance ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription& attribute = Attributes[i]; - attribute.location = i; - attribute.binding = src.Slot; - attribute.format = RenderToolsVulkan::ToVulkanFormat(src.Format); - attribute.offset = src.Offset; - - bindingsCount = Math::Max(bindingsCount, (uint32)src.Slot + 1); + MaxSlot = Math::Max(MaxSlot, (int32)src.Slot); } - - RenderToolsVulkan::ZeroStruct(CreateInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); - CreateInfo.vertexBindingDescriptionCount = bindingsCount; - CreateInfo.pVertexBindingDescriptions = Bindings; - CreateInfo.vertexAttributeDescriptionCount = elements.Count(); - CreateInfo.pVertexAttributeDescriptions = Attributes; } FramebufferVulkan::FramebufferVulkan(GPUDeviceVulkan* device, const Key& key, const VkExtent2D& extent, uint32 layers) @@ -936,7 +909,8 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyVertexBuffer() if (!_dummyVB) { _dummyVB = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyVertexBuffer")); - _dummyVB->Init(GPUBufferDescription::Vertex(nullptr, sizeof(Color32), 1, &Color32::Transparent)); + auto* layout = GPUVertexLayout::Get({{ VertexElement::Types::Attribute3, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm }}); + _dummyVB->Init(GPUBufferDescription::Vertex(layout, sizeof(Color32), 1, &Color32::Transparent)); } return _dummyVB; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 32b34e4f6..fe96d8f10 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -10,6 +10,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Graphics/PixelFormatExtensions.h" static VkStencilOp ToVulkanStencilOp(const StencilOperation value) { @@ -222,8 +223,54 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer PROFILE_CPU_NAMED("Create Pipeline"); // Bind vertex input - vertexLayout = (GPUVertexLayoutVulkan*)GPUVertexLayout::Merge(vertexLayout, VertexShaderLayout); - _desc.pVertexInputState = vertexLayout ? &vertexLayout->CreateInfo : nullptr; + VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo; + VkVertexInputBindingDescription vertexInputBindings[GPU_MAX_VB_BINDED]; + VkVertexInputAttributeDescription vertexInputAttributes[GPU_MAX_VS_ELEMENTS]; + _desc.pVertexInputState = nullptr; + if (!vertexLayout) + vertexLayout = VertexBufferLayout; // Fallback to shader-specified layout (if using old APIs) + if (vertexLayout) + { + // Vertex bindings based on vertex buffers assigned + for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++) + { + VkVertexInputBindingDescription& binding = vertexInputBindings[i]; + binding.binding = i; + binding.stride = 0; + binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + } + uint32 bindingsCount = 0; + for (int32 i = 0; i < vertexLayout->GetElements().Count(); i++) + { + const VertexElement& src = vertexLayout->GetElements().Get()[i]; + const int32 size = PixelFormatExtensions::SizeInBytes(src.Format); + ASSERT_LOW_LAYER(src.Slot < GPU_MAX_VB_BINDED); + VkVertexInputBindingDescription& binding = vertexInputBindings[src.Slot]; + binding.binding = src.Slot; + binding.stride = Math::Max(binding.stride, (uint32_t)(src.Offset + size)); + binding.inputRate = src.PerInstance ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; + bindingsCount = Math::Max(bindingsCount, (uint32)src.Slot + 1); + } + + // Vertex elements (including any merged elements from reference layout from shader reflection) + vertexLayout = (GPUVertexLayoutVulkan*)GPUVertexLayout::Merge(vertexLayout, VertexInputLayout, true, true); + for (int32 i = 0; i < vertexLayout->GetElements().Count(); i++) + { + const VertexElement& src = vertexLayout->GetElements().Get()[i]; + VkVertexInputAttributeDescription& attribute = vertexInputAttributes[i]; + attribute.location = i; + attribute.binding = src.Slot; + attribute.format = RenderToolsVulkan::ToVulkanFormat(src.Format); + attribute.offset = src.Offset; + } + + RenderToolsVulkan::ZeroStruct(vertexInputCreateInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); + vertexInputCreateInfo.vertexBindingDescriptionCount = bindingsCount; + vertexInputCreateInfo.pVertexBindingDescriptions = vertexInputBindings; + vertexInputCreateInfo.vertexAttributeDescriptionCount = vertexLayout->GetElements().Count(); + vertexInputCreateInfo.pVertexAttributeDescriptions = vertexInputAttributes; + _desc.pVertexInputState = &vertexInputCreateInfo; + } // Update description to match the pipeline _descColorBlend.attachmentCount = renderPass->Layout.RTsCount; @@ -320,7 +367,11 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _desc.pStages = _shaderStages; // Input Assembly - VertexShaderLayout = desc.VS ? (GPUVertexLayoutVulkan*)(desc.VS->Layout ? desc.VS->Layout : desc.VS->InputLayout) : nullptr; + if (desc.VS) + { + VertexInputLayout = (GPUVertexLayoutVulkan*)desc.VS->InputLayout; + VertexBufferLayout = (GPUVertexLayoutVulkan*)desc.VS->Layout; + } RenderToolsVulkan::ZeroStruct(_descInputAssembly, VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO);; switch (desc.PrimitiveTopology) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h index 600cb1841..ada069bce 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h @@ -146,7 +146,8 @@ public: const DescriptorSetLayoutVulkan* DescriptorSetsLayout = nullptr; TypedDescriptorPoolSetVulkan* CurrentTypedDescriptorPoolSet = nullptr; - GPUVertexLayoutVulkan* VertexShaderLayout = nullptr; + GPUVertexLayoutVulkan* VertexInputLayout = nullptr; + GPUVertexLayoutVulkan* VertexBufferLayout = nullptr; Array DescriptorSetHandles; Array DynamicOffsets; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h index 67a05824f..32ab50804 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUVertexLayoutVulkan.h @@ -15,9 +15,7 @@ class GPUVertexLayoutVulkan : public GPUResourceVulkan public: GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements, bool explicitOffsets); - VkPipelineVertexInputStateCreateInfo CreateInfo; - VkVertexInputBindingDescription Bindings[GPU_MAX_VB_BINDED]; - VkVertexInputAttributeDescription Attributes[GPU_MAX_VS_ELEMENTS]; + int32 MaxSlot; }; #endif From 9264db831732f1328c609a2cb5a57a174762a5ef Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 8 Jan 2025 18:10:48 +0100 Subject: [PATCH 117/215] Simplify vertex colors debug draw mode --- Source/Engine/Renderer/Renderer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index c1a87385f..f27baf813 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -422,6 +422,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont case ViewMode::GlobalSurfaceAtlas: case ViewMode::GlobalSDF: case ViewMode::MaterialComplexity: + case ViewMode::VertexColors: drawShadows = false; break; } @@ -555,6 +556,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont else if (renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas) GlobalSurfaceAtlasPass::Instance()->RenderDebug(renderContext, context, lightBuffer); if (renderContext.View.Mode == ViewMode::Emissive || + renderContext.View.Mode == ViewMode::VertexColors || renderContext.View.Mode == ViewMode::LightmapUVsDensity || renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas || renderContext.View.Mode == ViewMode::GlobalSDF) From 1f294605a300386f338444b763b6e4652c9b765d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 8 Jan 2025 18:11:47 +0100 Subject: [PATCH 118/215] Add more Vulkan memory layout safety stages --- Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index 6bcd5fe7f..b09f2a72f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -94,7 +94,7 @@ public: switch (layout) { case VK_IMAGE_LAYOUT_UNDEFINED: - accessFlags = 0; + accessFlags = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; stageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; break; case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: @@ -102,7 +102,7 @@ public: stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; break; case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: - accessFlags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + accessFlags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; break; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: @@ -116,12 +116,12 @@ public: stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; break; case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: - accessFlags = 0; + accessFlags = VK_ACCESS_NONE; stageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; break; case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: accessFlags = VK_ACCESS_SHADER_READ_BIT; - stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; break; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL: From 3505b8971b751f66794bde6edf69fd313ce6bf27 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 8 Jan 2025 18:48:50 +0100 Subject: [PATCH 119/215] Add support for up to `65536` skeleton bones in skinned meshes https://forum.flaxengine.com/t/import-fbx-with-bones-more-than-256/1196 --- Source/Engine/Content/Assets/ModelBase.cpp | 25 +++++++- Source/Engine/Content/Assets/SkinnedModel.cpp | 27 +++++++-- Source/Engine/Graphics/Models/Config.h | 2 +- .../Engine/Graphics/PixelFormatExtensions.cpp | 60 +++++++++++++++++++ .../Graphics/Shaders/GPUVertexLayout.cpp | 10 ++++ .../Engine/Graphics/Shaders/GPUVertexLayout.h | 7 +++ 6 files changed, 123 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index bee811a72..747e7d2f9 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -613,6 +613,15 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l Array> vbElements; const bool useSeparatePositions = !isSkinned; const bool useSeparateColors = !isSkinned; + PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt; + for (const Int4& indices : mesh.BlendIndices) + { + if (indices.MaxValue() > MAX_uint8) + { + blendIndicesFormat = PixelFormat::R16G16B16A16_UInt; + break; + } + } { byte vbIndex = 0; // TODO: add option to quantize vertex positions (eg. 16-bit) @@ -640,7 +649,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm }); if (isSkinned) { - vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UInt }); + vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, blendIndicesFormat }); vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float }); } if (!useSeparateColors && hasColors) @@ -714,8 +723,18 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l case VertexElement::Types::BlendIndices: { const Int4 blendIndices = mesh.BlendIndices.Get()[vertex]; - const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W); - stream.Write(blendIndicesEnc); + if (blendIndicesFormat == PixelFormat::R8G8B8A8_UInt) + { + // 8-bit indices + const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W); + stream.Write(blendIndicesEnc); + } + else + { + // 16-bit indices + const uint16 blendIndicesEnc[4] = { (uint16)blendIndices.X, (uint16)blendIndices.Y, (uint16)blendIndices.Z, (uint16)blendIndices.W }; + stream.Write(blendIndicesEnc); + } break; } case VertexElement::Types::BlendWeights: diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index ff3a5036a..acfa53a09 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -396,8 +396,27 @@ bool SkinnedModel::SetupSkeleton(const Array& nodes, const Array MAX_uint16) return true; - if (bones.Count() <= 0 || bones.Count() > MODEL_MAX_BONES_PER_MODEL) - return true; + if (bones.Count() <= 0) + { + if (bones.Count() > 255) + { + for (auto& lod : LODs) + { + for (auto& mesh : lod.Meshes) + { + auto* vertexLayout = mesh.GetVertexLayout(); + VertexElement element = vertexLayout ? vertexLayout->FindElement(VertexElement::Types::BlendIndices) : VertexElement(); + if (element.Format == PixelFormat::R8G8B8A8_UInt) + { + LOG(Warning, "Cannot use more than 255 bones if skinned model uses 8-bit storage for blend indices in vertices."); + return true; + } + } + } + } + if (bones.Count() > MODEL_MAX_BONES_PER_MODEL) + return true; + } auto model = this; if (!model->IsVirtual()) return true; @@ -557,7 +576,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) lod._model = this; lod._lodIndex = lodIndex; stream.Read(lod.ScreenSize); - + // Meshes uint16 meshesCount; stream.Read(meshesCount); @@ -579,7 +598,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) return true; } mesh.SetMaterialSlotIndex(materialSlotIndex); - + // Bounds BoundingBox box; stream.Read(box); diff --git a/Source/Engine/Graphics/Models/Config.h b/Source/Engine/Graphics/Models/Config.h index 3a543ec52..eaf7a978b 100644 --- a/Source/Engine/Graphics/Models/Config.h +++ b/Source/Engine/Graphics/Models/Config.h @@ -32,6 +32,6 @@ #define MAX_BONES_PER_VERTEX MODEL_MAX_BONES_PER_VERTEX // Defines the maximum allowed amount of skeleton bones to be used with skinned model -#define MODEL_MAX_BONES_PER_MODEL 256 +#define MODEL_MAX_BONES_PER_MODEL 0xffff // [Deprecated in v1.10] Use MODEL_MAX_BONES_PER_MODEL #define MAX_BONES_PER_MODEL MODEL_MAX_BONES_PER_MODEL diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 8b9a50fe5..289e1b4ee 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1436,6 +1436,66 @@ static PixelFormatSampler PixelFormatSamplers[] = Platform::MemoryCopy(ptr, data, sizeof(data)); }, }, + { + PixelFormat::R16G16B16A16_UInt, + sizeof(uint16) * 4, + [](const void* ptr) + { + uint16 data[4]; + Platform::MemoryCopy(data, ptr, sizeof(data)); + return Float4(data[0], data[1], data[2], data[3]); + }, + [](void* ptr, const Float4& value) + { + uint16 data[4] = { (uint16)value.X, (uint16)value.Y, (uint16)value.Z, (uint16)value.W}; + Platform::MemoryCopy(ptr, data, sizeof(data)); + }, + }, + { + PixelFormat::R16G16B16A16_SInt, + sizeof(int16) * 4, + [](const void* ptr) + { + int16 data[4]; + Platform::MemoryCopy(data, ptr, sizeof(data)); + return Float4(data[0], data[1], data[2], data[3]); + }, + [](void* ptr, const Float4& value) + { + int16 data[4] = { (int16)value.X, (int16)value.Y, (int16)value.Z, (int16)value.W}; + Platform::MemoryCopy(ptr, data, sizeof(data)); + }, + }, + { + PixelFormat::R32G32B32A32_UInt, + sizeof(uint32) * 4, + [](const void* ptr) + { + uint32 data[4]; + Platform::MemoryCopy(data, ptr, sizeof(data)); + return Float4(data[0], data[1], data[2], data[3]); + }, + [](void* ptr, const Float4& value) + { + uint32 data[4] = { (uint32)value.X, (uint32)value.Y, (uint32)value.Z, (uint32)value.W}; + Platform::MemoryCopy(ptr, data, sizeof(data)); + }, + }, + { + PixelFormat::R32G32B32A32_SInt, + sizeof(int32) * 4, + [](const void* ptr) + { + int32 data[4]; + Platform::MemoryCopy(data, ptr, sizeof(data)); + return Float4(data[0], data[1], data[2], data[3]); + }, + [](void* ptr, const Float4& value) + { + int32 data[4] = { (int32)value.X, (int32)value.Y, (int32)value.Z, (int32)value.W}; + Platform::MemoryCopy(ptr, data, sizeof(data)); + }, + }, }; void PixelFormatSampler::Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index e3afae47e..2791acc48 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -126,6 +126,16 @@ String GPUVertexLayout::GetElementsString() const return result; } +VertexElement GPUVertexLayout::FindElement(VertexElement::Types type) const +{ + for (const VertexElement& e : _elements) + { + if (e.Type == type) + return e; + } + return VertexElement(); +} + GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets) { // Hash input layout diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index e804b087e..9e2c3454f 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -47,6 +47,13 @@ public: return _stride; } + /// + /// Searches for a given element type in a layout. + /// + /// The type of element to find. + /// Found element with properties or empty if missing. + API_FUNCTION() VertexElement FindElement(VertexElement::Types type) const; + /// /// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list. /// From 99788e474353e51b604cb955fd8a8b8166826a22 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 9 Jan 2025 21:46:22 +0100 Subject: [PATCH 120/215] More fixes for Vulkan rendering to be on pair with DirectX when it comes to accessing missing vertex buffer components --- Source/Engine/Graphics/GPUBuffer.cpp | 13 ++++++-- .../Graphics/Shaders/GPUVertexLayout.cpp | 8 ++--- .../Engine/Graphics/Shaders/GPUVertexLayout.h | 3 +- .../Vulkan/GPUContextVulkan.cpp | 18 ++++------- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 3 ++ .../GraphicsDevice/Vulkan/GPUDeviceVulkan.h | 1 + .../Vulkan/GPUPipelineStateVulkan.cpp | 32 ++++++++++++++++--- 7 files changed, 54 insertions(+), 24 deletions(-) diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index eca708af9..722518233 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -169,15 +169,22 @@ GPUBuffer::GPUBuffer() bool GPUBuffer::Init(const GPUBufferDescription& desc) { - ASSERT(Math::IsInRange(desc.Size, 1, MAX_int32) - && Math::IsInRange(desc.Stride, 0, 1024)); - // Validate description #if !BUILD_RELEASE #define GET_NAME() GetName() #else #define GET_NAME() TEXT("") #endif + if (Math::IsNotInRange(desc.Size, 1, MAX_int32)) + { + LOG(Warning, "Cannot create buffer '{}'. Incorrect size {}.", GET_NAME(), desc.Size); + return true; + } + if (Math::IsNotInRange(desc.Stride, 0, 1024)) + { + LOG(Warning, "Cannot create buffer '{}'. Incorrect stride {}.", GET_NAME(), desc.Stride); + return true; + } if (EnumHasAnyFlags(desc.Flags, GPUBufferFlags::Structured)) { if (desc.Stride <= 0) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 2791acc48..822766e67 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -96,12 +96,12 @@ GPUVertexLayout::GPUVertexLayout() void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets) { - uint32 offsets[GPU_MAX_VB_BINDED] = {}; + uint32 offsets[GPU_MAX_VB_BINDED + 1] = {}; _elements = elements; for (int32 i = 0; i < _elements.Count(); i++) { VertexElement& e = _elements[i]; - ASSERT(e.Slot < GPU_MAX_VB_BINDED); + ASSERT(e.Slot <= GPU_MAX_VB_BINDED); // One special slot after all VBs for any missing vertex elements binding (on Vulkan) uint32& offset = offsets[e.Slot]; if (e.Offset != 0 || explicitOffsets) offset = e.Offset; @@ -216,7 +216,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span& layouts) return result; } -GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing) +GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride) { GPUVertexLayout* result = base ? base : reference; if (base && reference && base != reference) @@ -261,7 +261,7 @@ GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* if (missing) { // Insert any missing elements - VertexElement ne = { e.Type, e.Slot, 0, e.PerInstance, e.Format }; + VertexElement ne = { e.Type, missingSlotOverride != -1 ? (byte)missingSlotOverride : e.Slot, 0, e.PerInstance, e.Format }; if (e.Type == VertexElement::Types::TexCoord1 || e.Type == VertexElement::Types::TexCoord2 || e.Type == VertexElement::Types::TexCoord3) { // Alias missing texcoords with existing texcoords diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index 9e2c3454f..9de8e417b 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -83,8 +83,9 @@ public: /// The list of reference inputs. /// 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. /// 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); + static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused = false, bool addMissing = true, int32 missingSlotOverride = -1); public: // [GPUResource] diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 02dc86770..89122a262 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -529,6 +529,8 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des auto handle = handles[slot]; if (!handle) { + if (descriptor.ResourceFormat == PixelFormat::Unknown) + break; const auto dummy = _device->HelperResources.GetDummyBuffer(descriptor.ResourceFormat); handle = (DescriptorOwnerResourceVulkan*)dummy->View()->GetNativePtr(); } @@ -650,23 +652,14 @@ void GPUContextVulkan::OnDrawCall() } } - // Bind any missing vertex buffers to null if required by the current state - GPUVertexLayoutVulkan* vertexLayout = _vertexLayout ? _vertexLayout : pipelineState->VertexBufferLayout; #if GPU_ENABLE_ASSERTION_LOW_LAYERS + // Check for missing vertex buffers layout + GPUVertexLayoutVulkan* vertexLayout = _vertexLayout ? _vertexLayout : pipelineState->VertexBufferLayout; if (!vertexLayout && pipelineState && !pipelineState->VertexBufferLayout && (pipelineState->UsedStagesMask & (1 << (int32)DescriptorSet::Vertex)) != 0 && !_vertexLayout && _vbCount) { LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); } #endif - const int32 missingVBs = vertexLayout ? vertexLayout->MaxSlot + 1 - _vbCount : 0; - if (missingVBs > 0) - { - VkBuffer buffers[GPU_MAX_VB_BINDED]; - VkDeviceSize offsets[GPU_MAX_VB_BINDED] = {}; - for (int32 i = 0; i < missingVBs; i++) - buffers[i] = _device->HelperResources.GetDummyVertexBuffer()->GetHandle(); - vkCmdBindVertexBuffers(cmdBuffer->GetHandle(), _vbCount, missingVBs, buffers, offsets); - } // Start render pass if not during one if (cmdBuffer->IsOutsideRenderPass()) @@ -734,6 +727,9 @@ void GPUContextVulkan::FrameBegin() // Init command buffer const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); vkCmdSetStencilReference(cmdBuffer->GetHandle(), VK_STENCIL_FRONT_AND_BACK, _stencilRef); + VkBuffer buffers[1] = { _device->HelperResources.GetDummyVertexBuffer()->GetHandle() }; + VkDeviceSize offsets[1] = {}; + vkCmdBindVertexBuffers(cmdBuffer->GetHandle(), GPU_MAX_VB_BINDED, 1, buffers, offsets); #if VULKAN_RESET_QUERY_POOLS // Reset pending queries diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index efe6a3334..e662f7505 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -499,6 +499,7 @@ RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLa : Device(device) , Handle(VK_NULL_HANDLE) , Layout(layout) + , CanDepthWrite(true) { const int32 colorAttachmentsCount = layout.RTsCount; const bool hasDepthStencilAttachment = layout.DepthFormat != PixelFormat::Unknown; @@ -585,6 +586,8 @@ RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLa depthStencilReference.attachment = colorAttachmentsCount; depthStencilReference.layout = depthStencilLayout; subpassDesc.pDepthStencilAttachment = &depthStencilReference; + if (!layout.WriteDepth && !layout.WriteStencil) + CanDepthWrite = false; } VkRenderPassCreateInfo createInfo; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 10a6c1b77..b2fa9a859 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -239,6 +239,7 @@ public: GPUDeviceVulkan* Device; VkRenderPass Handle; RenderTargetLayoutVulkan Layout; + bool CanDepthWrite; #if VULKAN_USE_DEBUG_DATA VkRenderPassCreateInfo DebugCreateInfo; #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index fe96d8f10..573eee70c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -224,7 +224,7 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer // Bind vertex input VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo; - VkVertexInputBindingDescription vertexInputBindings[GPU_MAX_VB_BINDED]; + VkVertexInputBindingDescription vertexInputBindings[GPU_MAX_VB_BINDED + 1]; VkVertexInputAttributeDescription vertexInputAttributes[GPU_MAX_VS_ELEMENTS]; _desc.pVertexInputState = nullptr; if (!vertexLayout) @@ -253,12 +253,33 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer } // Vertex elements (including any merged elements from reference layout from shader reflection) - vertexLayout = (GPUVertexLayoutVulkan*)GPUVertexLayout::Merge(vertexLayout, VertexInputLayout, true, true); + uint32 missingSlotBinding = bindingsCount; + int32 missingSlotOverride = GPU_MAX_VB_BINDED; // Use additional slot with empty VB + vertexLayout = (GPUVertexLayoutVulkan*)GPUVertexLayout::Merge(vertexLayout, VertexInputLayout, true, true, missingSlotOverride); for (int32 i = 0; i < vertexLayout->GetElements().Count(); i++) { const VertexElement& src = vertexLayout->GetElements().Get()[i]; VkVertexInputAttributeDescription& attribute = vertexInputAttributes[i]; attribute.location = i; + if (VertexInputLayout) + { + // Sync locations with vertex shader inputs to ensure that shader will load correct attributes + const auto& vertexInputLayoutElements = VertexInputLayout->GetElements(); + for (int32 j = 0; j < vertexInputLayoutElements.Count(); j++) + { + if (vertexInputLayoutElements.Get()[j].Type == src.Type) + { + attribute.location = j; + break; + } + } + } + if (src.Slot == missingSlotOverride) + { + // Element is missing and uses special empty VB + vertexInputBindings[missingSlotBinding] = { GPU_MAX_VB_BINDED, sizeof(byte[4]), VK_VERTEX_INPUT_RATE_INSTANCE }; + bindingsCount = missingSlotBinding + 1; + } attribute.binding = src.Slot; attribute.format = RenderToolsVulkan::ToVulkanFormat(src.Format); attribute.offset = src.Offset; @@ -277,14 +298,15 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer _descMultisample.rasterizationSamples = (VkSampleCountFlagBits)renderPass->Layout.MSAA; _desc.renderPass = renderPass->Handle; - // Check if has missing layout + // Ensure to have valid layout set if (_desc.layout == VK_NULL_HANDLE) - { _desc.layout = GetLayout()->Handle; - } // Create object + auto depthWrite = _descDepthStencil.depthWriteEnable; + _descDepthStencil.depthWriteEnable &= renderPass->CanDepthWrite; const VkResult result = vkCreateGraphicsPipelines(_device->Device, _device->PipelineCache, 1, &_desc, nullptr, &pipeline); + _descDepthStencil.depthWriteEnable = depthWrite; LOG_VULKAN_RESULT(result); if (result != VK_SUCCESS) { From 756ba0a533d93a6759759e19a9287f9808f2b17d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 9 Jan 2025 21:46:41 +0100 Subject: [PATCH 121/215] Fix crash on shadows when using D3D10 --- Source/Engine/Renderer/ShadowsPass.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 0487d7ce2..1b7131d9b 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -348,7 +348,7 @@ public: void InitStaticAtlas() { - const int32 atlasResolution = Resolution * 2; + const int32 atlasResolution = Math::Min(Resolution * 2, GPUDevice::Instance->Limits.MaximumTexture2DSize); if (StaticAtlas.Width == atlasResolution) return; StaticAtlas.Init(atlasResolution, atlasResolution); @@ -1120,6 +1120,7 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch& default: return; } + atlasResolution = Math::Min(atlasResolution, GPUDevice::Instance->Limits.MaximumTexture2DSize); if (shadows.Resolution != atlasResolution) { shadows.Reset(); From a1c46d2e6e599ba769eca7b1171bbbac59759e5e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 11 Jan 2025 22:40:20 +0100 Subject: [PATCH 122/215] Add support for up to 4 texture channels when importing meshes #2667 --- .../Editor/MaterialTemplates/Particle.shader | 2 +- .../Editor/MaterialTemplates/Surface.shader | 35 +++-- Source/Editor/Surface/Archetypes/Textures.cs | 8 +- .../Editor/Windows/Assets/ModelBaseWindow.cs | 134 ++++++++++++++++-- Source/Editor/Windows/Assets/ModelWindow.cs | 87 +----------- .../Windows/Assets/SkinnedModelWindow.cs | 81 +---------- Source/Engine/CSG/CSGData.cpp | 11 +- Source/Engine/CSG/Types.h | 2 + Source/Engine/Content/Assets/ModelBase.cpp | 43 +++--- Source/Engine/Content/Assets/SkinnedModel.cpp | 6 +- .../Engine/ContentImporters/ImportModel.cpp | 5 +- .../Graphics/Materials/MaterialShader.h | 2 +- Source/Engine/Graphics/Models/Mesh.cpp | 27 +++- Source/Engine/Graphics/Models/Mesh.h | 2 +- Source/Engine/Graphics/Models/MeshBase.cpp | 2 +- Source/Engine/Graphics/Models/MeshBase.h | 2 +- .../Engine/Graphics/Models/ModelData.Tool.cpp | 101 +++++++++---- Source/Engine/Graphics/Models/ModelData.cpp | 61 ++++---- Source/Engine/Graphics/Models/ModelData.h | 12 +- .../Engine/Graphics/Shaders/VertexElement.h | 2 + .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 2 + .../ShadersCompilation/ShaderCompiler.cpp | 2 + .../MaterialGenerator.Textures.cpp | 14 +- .../Tools/MaterialGenerator/MaterialLayer.cpp | 1 - .../Tools/ModelTool/ModelTool.Assimp.cpp | 64 ++------- .../ModelTool/ModelTool.AutodeskFbxSdk.cpp | 65 ++------- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 68 ++------- Source/Engine/Tools/ModelTool/ModelTool.cpp | 7 +- Source/Shaders/BakeLightmap.shader | 3 +- .../Shaders/Editor/LightmapUVsDensity.shader | 20 ++- Source/Shaders/MaterialCommon.hlsl | 31 +++- 31 files changed, 427 insertions(+), 475 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 3623e5115..6f0be21e0 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -514,7 +514,7 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID) output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes - output.TexCoord = input.TexCoord; + output.TexCoord = input.TexCoord0; output.ParticleIndex = particleIndex; #if USE_VERTEX_COLOR output.VertexColor = input.Color; diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index 1c142f463..b0465d2ff 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -2,6 +2,7 @@ // Version: @0 #define MATERIAL 1 +#define MATERIAL_TEXCOORDS 4 #define USE_PER_VIEW_CONSTANTS 1 #define USE_PER_DRAW_CONSTANTS 1 @3 @@ -24,17 +25,19 @@ Buffer BoneMatrices : register(t1); Buffer PrevBoneMatrices : register(t2); #endif #endif + // Geometry data passed though the graphics rendering stages up to the pixel shader struct GeometryData { float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; + float4 TexCoords01 : TEXCOORD1; + float4 TexCoords23 : TEXCOORD2; + float2 LightmapUV : TEXCOORD3; #if USE_VERTEX_COLOR half4 VertexColor : COLOR; #endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; + float3 WorldNormal : TEXCOORD4; + float4 WorldTangent : TEXCOORD5; float3 PrevWorldPosition : TEXCOORD7; nointerpolation uint ObjectIndex : TEXCOORD8; }; @@ -68,7 +71,7 @@ struct MaterialInput { float3 WorldPosition; float TwoSidedSign; - float2 TexCoord; + float2 TexCoords[MATERIAL_TEXCOORDS]; #if USE_LIGHTMAP float2 LightmapUV; #endif @@ -86,12 +89,18 @@ struct MaterialInput #endif }; +// Map access to the main texure coordinate channel as UV0 +#define TexCoord TexCoords[0] + // Extracts geometry data to the material input MaterialInput GetGeometryMaterialInput(GeometryData geometry) { MaterialInput output = (MaterialInput)0; output.WorldPosition = geometry.WorldPosition; - output.TexCoord = geometry.TexCoord; + output.TexCoords[0] = geometry.TexCoords01.xy; + output.TexCoords[1] = geometry.TexCoords01.zw; + output.TexCoords[2] = geometry.TexCoords23.xy; + output.TexCoords[3] = geometry.TexCoords23.zw; #if USE_LIGHTMAP output.LightmapUV = geometry.LightmapUV; #endif @@ -126,8 +135,8 @@ MaterialInput GetGeometryMaterialInput(GeometryData geometry) GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) { GeometryData output = (GeometryData)0; - output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; - output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2; + output.TexCoords01 = p0.TexCoords01 * w0 + p1.TexCoords01 * w1 + p2.TexCoords01 * w2; + output.TexCoords23 = p0.TexCoords23 * w0 + p1.TexCoords23 * w1 + p2.TexCoords23 * w2; #if USE_VERTEX_COLOR output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; #endif @@ -312,14 +321,15 @@ VertexOutput VS(ModelInput input) output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes - output.Geometry.TexCoord = input.TexCoord; + output.Geometry.TexCoords01 = float4(input.TexCoord0, input.TexCoord1); + output.Geometry.TexCoords23 = float4(input.TexCoord2, input.TexCoord3); #if USE_VERTEX_COLOR output.Geometry.VertexColor = input.Color; #endif #if CAN_USE_LIGHTMAP output.Geometry.LightmapUV = input.LightmapUV * object.LightmapArea.zw + object.LightmapArea.xy; #else - output.Geometry.LightmapUV = input.LightmapUV; + output.Geometry.LightmapUV = float2(0, 0); #endif // Calculate tanget space to world space transformation matrix for unit vectors @@ -486,9 +496,10 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes - output.Geometry.TexCoord = input.TexCoord; + output.Geometry.TexCoords01 = float4(input.TexCoord0, input.TexCoord1); + output.Geometry.TexCoords23 = float4(input.TexCoord2, input.TexCoord3); #if USE_VERTEX_COLOR - output.Geometry.VertexColor = float4(0, 0, 0, 1); + output.Geometry.VertexColor = input.Color; #endif output.Geometry.LightmapUV = float2(0, 0); diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index be310c9cd..d941da939 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -123,9 +123,15 @@ namespace FlaxEditor.Surface.Archetypes AlternativeTitles = new string[] { "UV", "UVs" }, Description = "Texture coordinates", Flags = NodeFlags.MaterialGraph, - Size = new Float2(110, 30), + Size = new Float2(150, 30), + DefaultValues = new object[] + { + 0u + }, Elements = new[] { + NodeElementArchetype.Factory.Text(0, 1, "Channel:"), + NodeElementArchetype.Factory.UnsignedInteger(50, 0, 0, -1, 0, 3), NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0) } }, diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index ead4b7bd9..b1b531e5c 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -93,6 +93,118 @@ namespace FlaxEditor.Windows.Assets } } + protected class UVsPropertiesProxyBase : PropertiesProxyBase + { + public enum UVChannel + { + None, + TexCoord0, + TexCoord1, + TexCoord2, + TexCoord3, + LightmapUVs, + }; + + private UVChannel _uvChannel = UVChannel.None; + + [EditorOrder(0), EditorDisplay(null, "Preview UV Channel"), EnumDisplay(EnumDisplayAttribute.FormatMode.None)] + [Tooltip("Set UV channel to preview.")] + public UVChannel Channel + { + get => _uvChannel; + set + { + if (_uvChannel == value) + return; + _uvChannel = value; + Window._meshData?.RequestMeshData(Window._asset); + } + } + + [EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs), VisibleIf("ShowUVs")] + [Tooltip("Level Of Detail index to preview UVs layout.")] + public int LOD = 0; + + [EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000), VisibleIf("ShowUVs")] + [Tooltip("Mesh index to preview UVs layout. Use -1 for all meshes")] + public int Mesh = -1; + + private bool ShowUVs => _uvChannel != UVChannel.None; + + /// + public override void OnClean() + { + Channel = UVChannel.None; + + base.OnClean(); + } + + protected class ProxyEditor : ProxyEditorBase + { + private UVsLayoutPreviewControl _uvsPreview; + + public override void Initialize(LayoutElementsContainer layout) + { + var proxy = (UVsPropertiesProxyBase)Values[0]; + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) + return; + + base.Initialize(layout); + + _uvsPreview = layout.Custom().CustomControl; + _uvsPreview.Window = proxy.Window; + } + + /// + public override void Refresh() + { + base.Refresh(); + + if (_uvsPreview != null) + { + var proxy = (UVsPropertiesProxyBase)Values[0]; + switch (proxy._uvChannel) + { + case UVChannel.TexCoord0: _uvsPreview.Channel = 0; break; + case UVChannel.TexCoord1: _uvsPreview.Channel = 1; break; + case UVChannel.TexCoord2: _uvsPreview.Channel = 2; break; + case UVChannel.TexCoord3: _uvsPreview.Channel = 3; break; + case UVChannel.LightmapUVs: + { + _uvsPreview.Channel = -1; + if (proxy.Window.Asset && proxy.Window.Asset.IsLoaded) + { + // Pick UVs channel index from the first mesh + proxy.Window.Asset.GetMeshes(out var meshes); + foreach (var mesh in meshes) + { + if (mesh is Mesh m && m.HasLightmapUVs) + { + _uvsPreview.Channel = m.LightmapUVsIndex; + break; + } + } + } + break; + } + default: _uvsPreview.Channel = -1; break; + } + _uvsPreview.LOD = proxy.LOD; + _uvsPreview.Mesh = proxy.Mesh; + _uvsPreview.HighlightIndex = proxy.Window._highlightIndex; + _uvsPreview.IsolateIndex = proxy.Window._isolateIndex; + } + } + + protected override void Deinitialize() + { + _uvsPreview = null; + + base.Deinitialize(); + } + } + } + protected sealed class UVsLayoutPreviewControl : RenderToTextureControl { private int _channel = -1; @@ -168,15 +280,21 @@ namespace FlaxEditor.Windows.Assets } } - private void DrawMeshUVs(int meshIndex, ref MeshDataCache.MeshData meshData) + private void DrawMeshUVs(int meshIndex, ref MeshDataCache.MeshData meshData, ref Rectangle bounds) { - var uvScale = Size; if (meshData.IndexBuffer == null || meshData.VertexAccessor == null) + { + Render2D.DrawText(Style.Current.FontMedium, "Missing mesh data", bounds, Color.Red, TextAlignment.Center, TextAlignment.Center); return; + } var linesColor = _highlightIndex != -1 && _highlightIndex == meshIndex ? Style.Current.BackgroundSelected : Color.White; var texCoordStream = meshData.VertexAccessor.TexCoord(_channel); if (!texCoordStream.IsValid) + { + Render2D.DrawText(Style.Current.FontMedium, "Missing texcoords channel", bounds, Color.Yellow, TextAlignment.Center, TextAlignment.Center); return; + } + var uvScale = bounds.Size; for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) { // Cache triangle indices @@ -206,19 +324,19 @@ namespace FlaxEditor.Windows.Assets { base.DrawSelf(); - var size = Size; - if (_channel < 0 || size.MaxValue < 5.0f) + var bounds = new Rectangle(Float2.Zero, Size); + if (_channel < 0 || bounds.Size.MaxValue < 5.0f) return; if (Window._meshData == null) Window._meshData = new MeshDataCache(); if (!Window._meshData.RequestMeshData(Window._asset)) { Invalidate(); - Render2D.DrawText(Style.Current.FontMedium, "Loading...", new Rectangle(Float2.Zero, size), Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(Style.Current.FontMedium, "Loading...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center); return; } - Render2D.PushClip(new Rectangle(Float2.Zero, size)); + Render2D.PushClip(bounds); var meshDatas = Window._meshData.MeshDatas; var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1); @@ -230,12 +348,12 @@ namespace FlaxEditor.Windows.Assets { if (_isolateIndex != -1 && _isolateIndex != meshIndex) continue; - DrawMeshUVs(meshIndex, ref lod[meshIndex]); + DrawMeshUVs(meshIndex, ref lod[meshIndex], ref bounds); } } else { - DrawMeshUVs(mesh, ref lod[mesh]); + DrawMeshUVs(mesh, ref lod[mesh], ref bounds); } Render2D.PopClip(); diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 16826b508..dd4a3ec48 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -438,93 +438,8 @@ namespace FlaxEditor.Windows.Assets } [CustomEditor(typeof(ProxyEditor))] - private sealed class UVsPropertiesProxy : PropertiesProxyBase + private sealed class UVsPropertiesProxy : UVsPropertiesProxyBase { - public enum UVChannel - { - None, - TexCoord, - LightmapUVs, - }; - - private UVChannel _uvChannel = UVChannel.None; - - [EditorOrder(0), EditorDisplay(null, "Preview UV Channel"), EnumDisplay(EnumDisplayAttribute.FormatMode.None)] - [Tooltip("Set UV channel to preview.")] - public UVChannel Channel - { - get => _uvChannel; - set - { - if (_uvChannel == value) - return; - _uvChannel = value; - Window._meshData?.RequestMeshData(Window._asset); - } - } - - [EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs), VisibleIf("ShowUVs")] - [Tooltip("Level Of Detail index to preview UVs layout.")] - public int LOD = 0; - - [EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000), VisibleIf("ShowUVs")] - [Tooltip("Mesh index to preview UVs layout. Use -1 for all meshes")] - public int Mesh = -1; - - private bool ShowUVs => _uvChannel != UVChannel.None; - - /// - public override void OnClean() - { - Channel = UVChannel.None; - - base.OnClean(); - } - - private class ProxyEditor : ProxyEditorBase - { - private UVsLayoutPreviewControl _uvsPreview; - - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (UVsPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - - base.Initialize(layout); - - _uvsPreview = layout.Custom().CustomControl; - _uvsPreview.Window = proxy.Window; - } - - /// - public override void Refresh() - { - base.Refresh(); - - if (_uvsPreview != null) - { - var proxy = (UVsPropertiesProxy)Values[0]; - switch (proxy._uvChannel) - { - case UVChannel.TexCoord: _uvsPreview.Channel = 0; break; - case UVChannel.LightmapUVs: _uvsPreview.Channel = 1; break; - default: _uvsPreview.Channel = -1; break; - } - _uvsPreview.LOD = proxy.LOD; - _uvsPreview.Mesh = proxy.Mesh; - _uvsPreview.HighlightIndex = proxy.Window._highlightIndex; - _uvsPreview.IsolateIndex = proxy.Window._isolateIndex; - } - } - - protected override void Deinitialize() - { - _uvsPreview = null; - - base.Deinitialize(); - } - } } [CustomEditor(typeof(ProxyEditor))] diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index d26a2153f..21f5ee209 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -497,87 +497,8 @@ namespace FlaxEditor.Windows.Assets } [CustomEditor(typeof(ProxyEditor))] - private sealed class UVsPropertiesProxy : PropertiesProxyBase + private sealed class UVsPropertiesProxy : UVsPropertiesProxyBase { - public enum UVChannel - { - None, - TexCoord, - }; - - private UVChannel _uvChannel = UVChannel.None; - - [EditorOrder(0), EditorDisplay(null, "Preview UV Channel"), EnumDisplay(EnumDisplayAttribute.FormatMode.None)] - [Tooltip("Set UV channel to preview.")] - public UVChannel Channel - { - get => _uvChannel; - set - { - if (_uvChannel == value) - return; - _uvChannel = value; - Window._meshData?.RequestMeshData(Window._asset); - } - } - - [EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs), VisibleIf("ShowUVs")] - [Tooltip("Level Of Detail index to preview UVs layout.")] - public int LOD = 0; - - [EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000), VisibleIf("ShowUVs")] - [Tooltip("Mesh index to preview UVs layout. Use -1 for all meshes")] - public int Mesh = -1; - - private bool ShowUVs => _uvChannel != UVChannel.None; - - /// - public override void OnClean() - { - Channel = UVChannel.None; - - base.OnClean(); - } - - private class ProxyEditor : ProxyEditorBase - { - private UVsLayoutPreviewControl _uvsPreview; - - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (UVsPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - - base.Initialize(layout); - - _uvsPreview = layout.Custom().CustomControl; - _uvsPreview.Window = proxy.Window; - } - - /// - public override void Refresh() - { - base.Refresh(); - - if (_uvsPreview != null) - { - var proxy = (UVsPropertiesProxy)Values[0]; - _uvsPreview.Channel = proxy._uvChannel == UVChannel.TexCoord ? 0 : -1; - _uvsPreview.LOD = proxy.LOD; - _uvsPreview.Mesh = proxy.Mesh; - _uvsPreview.HighlightIndex = proxy.Window._highlightIndex; - _uvsPreview.IsolateIndex = proxy.Window._isolateIndex; - } - } - - protected override void Deinitialize() - { - _uvsPreview = null; - - base.Deinitialize(); - } - } } [CustomEditor(typeof(ProxyEditor))] diff --git a/Source/Engine/CSG/CSGData.cpp b/Source/Engine/CSG/CSGData.cpp index 96c768d0c..db59466f9 100644 --- a/Source/Engine/CSG/CSGData.cpp +++ b/Source/Engine/CSG/CSGData.cpp @@ -167,23 +167,22 @@ void RawData::ToModelData(ModelData& modelData) const auto& surface = slot->Surfaces[i]; vertexCount += surface.Vertices.Count(); } - mesh->EnsureCapacity(vertexCount, vertexCount, false, false); + mesh->EnsureCapacity(vertexCount, vertexCount, false, false, false, 2); // Write surfaces into vertex and index buffers int32 index = 0; for (int32 i = 0; i < slot->Surfaces.Count(); i++) { - auto& surface = slot->Surfaces[i]; - + auto& surface = slot->Surfaces.Get()[i]; for (int32 vIndex = 0; vIndex < surface.Vertices.Count(); vIndex++) { - auto& v = surface.Vertices[vIndex]; + auto& v = surface.Vertices.Get()[vIndex]; mesh->Positions.Add(v.Position); - mesh->UVs.Add(v.TexCoord); + mesh->UVs.Get()[0].Add(v.TexCoord); + mesh->UVs.Get()[1].Add(v.LightmapUVs * surface.UVsArea.Size + surface.UVsArea.Location); mesh->Normals.Add(v.Normal); mesh->Tangents.Add(v.Tangent); - mesh->LightmapUVs.Add(v.LightmapUVs * surface.UVsArea.Size + surface.UVsArea.Location); mesh->Indices.Add(index++); } diff --git a/Source/Engine/CSG/Types.h b/Source/Engine/CSG/Types.h index 7100ef694..16e399822 100644 --- a/Source/Engine/CSG/Types.h +++ b/Source/Engine/CSG/Types.h @@ -3,6 +3,8 @@ #pragma once #include "Engine/Core/Config.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Math/Vector3.h" #include "Engine/Level/Actors/BrushMode.h" namespace CSG diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 747e7d2f9..cf7969e5d 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -562,11 +562,13 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l LOG(Warning, "Cannot save model with empty meshes."); return true; } - bool hasUVs = mesh.UVs.HasItems(); - if (hasUVs && (uint32)mesh.UVs.Count() != vertices) + for (auto& channel : mesh.UVs) { - LOG(Error, "Invalid size of {0} stream.", TEXT("UVs")); - return true; + if ((uint32)channel.Count() != vertices) + { + LOG(Error, "Invalid size of {0} stream.", TEXT("UVs")); + return true; + } } bool hasNormals = mesh.Normals.HasItems(); if (hasNormals && (uint32)mesh.Normals.Count() != vertices) @@ -586,12 +588,6 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns")); return true; } - bool hasLightmapUVs = mesh.LightmapUVs.HasItems(); - if (hasLightmapUVs && (uint32)mesh.LightmapUVs.Count() != vertices) - { - LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs")); - return true; - } bool hasColors = mesh.Colors.HasItems(); if (hasColors && (uint32)mesh.Colors.Count() != vertices) { @@ -626,7 +622,6 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l byte vbIndex = 0; // TODO: add option to quantize vertex positions (eg. 16-bit) // TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords) - // TODO: add support for 16-bit blend indices (up to 65535 bones) // Position if (useSeparatePositions) @@ -641,10 +636,14 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l auto& vb = vbElements.AddOne(); if (!useSeparatePositions) vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float }); - if (hasUVs) - vb.Add({ VertexElement::Types::TexCoord, vbIndex, 0, 0, PixelFormat::R16G16_Float }); - if (hasLightmapUVs) - vb.Add({ VertexElement::Types::TexCoord1, vbIndex, 0, 0, PixelFormat::R16G16_Float }); + for (int32 channelIdx = 0; channelIdx < mesh.UVs.Count(); channelIdx++) + { + auto& channel = mesh.UVs.Get()[channelIdx]; + if (channel.HasItems()) + { + vb.Add({ (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + channelIdx), vbIndex, 0, 0, PixelFormat::R16G16_Float }); + } + } vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm }); vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm }); if (isSkinned) @@ -745,20 +744,16 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l break; } case VertexElement::Types::TexCoord0: + case VertexElement::Types::TexCoord1: + case VertexElement::Types::TexCoord2: + case VertexElement::Types::TexCoord3: { - const Float2 uv = hasUVs ? mesh.UVs.Get()[vertex] : Float2::Zero; + const int32 channelIdx = (int32)element.Type - (int32)VertexElement::Types::TexCoord0; + const Float2 uv = mesh.UVs.Get()[channelIdx].Get()[vertex]; const Half2 uvEnc(uv); stream.Write(uvEnc); break; } - case VertexElement::Types::TexCoord1: - { - // TODO: refactor LightmapUVs into a generic TexCoord channel and support up to 4 UVs - const Float2 lightmapUV = hasLightmapUVs ? mesh.LightmapUVs.Get()[vertex] : Float2::Zero; - const Half2 lightmapUVEnc(lightmapUV); - stream.Write(lightmapUVEnc); - break; - } default: LOG(Error, "Unsupported vertex element: {}", element.ToString()); return true; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index acfa53a09..0c866de37 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -6,17 +6,17 @@ #include "Engine/Engine/Engine.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Streaming/StreamingGroup.h" -#include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/Threading.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTask.h" -#include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Graphics/Models/Config.h" +#include "Engine/Graphics/Models/MeshDeformation.h" +#include "Engine/Graphics/Models/ModelInstanceEntry.h" +#include "Engine/Graphics/Shaders/GPUVertexLayout.h" #include "Engine/Content/Content.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" -#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #if USE_EDITOR diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index c33ab3853..3d93842c7 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -132,7 +132,10 @@ void RepackMeshLightmapUVs(ModelData& data) { Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv); Float2 uvScale(entry.Slot->Width * atlasSizeInv, entry.Slot->Height * atlasSizeInv); - for (auto& uv : entry.Mesh->LightmapUVs) + if (entry.Mesh->LightmapUVsIndex == -1) + continue; + auto& lightmapUVs = entry.Mesh->UVs[entry.Mesh->LightmapUVsIndex]; + for (auto& uv : lightmapUVs) { uv = uv * uvScale + uvOffset; } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 8170c8e5a..0ca7a0c4f 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 170 +#define MATERIAL_GRAPH_VERSION 171 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 370d56931..f2736e18a 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -385,8 +385,33 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder); } -bool Mesh::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) +bool Mesh::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, Array> vbLayout) { + // Inject lightmap uv coordinate index into the vertex layout of one of the buffers + if (LightmapUVsIndex != -1) + { + const auto vertexElementType = (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + LightmapUVsIndex); + for (int32 vbIndex = 0; vbIndex < vbLayout.Count(); vbIndex++) + { + // Check if layout contains lightmap uvs texcoords channel + GPUVertexLayout* layout = vbLayout[vbIndex]; + VertexElement element = layout->FindElement(vertexElementType); + if (element.Type == vertexElementType) + { + // Ensure element doesn't exist in this layout + if (layout->FindElement(VertexElement::Types::Lightmap).Format == PixelFormat::Unknown) + { + GPUVertexLayout::Elements elements = layout->GetElements(); + element.Type = VertexElement::Types::Lightmap; + elements.Add(element); + layout = GPUVertexLayout::Get(elements, true); + vbLayout[vbIndex] = layout; + } + break; + } + } + } + if (MeshBase::Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) return true; diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 9d8f532c1..7ca50a9c2 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -172,7 +172,7 @@ public: public: // [MeshBase] - bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) override; + bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, Array> vbLayout) override; void Release() override; private: diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index a845bdf4b..7df3670cc 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -330,7 +330,7 @@ GPUVertexLayout* MeshBase::GetVertexLayout() const return GPUVertexLayout::Get(Span(_vertexBuffers, MODEL_MAX_VB)); } -bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) +bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, Array> vbLayout) { CHECK_RETURN(vbData.HasItems() && vertices, true); CHECK_RETURN(ibData, true); diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index e256b4826..1b80edb1b 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -221,7 +221,7 @@ public: /// True to use 16-bit indices for the index buffer (true: uint16, false: uint32). /// Layout descriptors for the vertex buffers attributes (one for each vertex buffer). /// True if failed, otherwise false. - API_FUNCTION(Sealed) virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout); + API_FUNCTION(Sealed) virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, Array> vbLayout); /// /// Releases the mesh data (GPU buffers and local cache). diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp index a62fa6bae..cce73061d 100644 --- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp +++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp @@ -6,7 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Utilities.h" #include "Engine/Core/Types/DateTime.h" -#include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Core/Collections/BitArray.h" #include "Engine/Tools/ModelTool/ModelTool.h" #include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h" @@ -77,8 +77,39 @@ void RemapArrayHelper(Array& target, const std::vector& remap) } } +void MeshData::SetLightmapUVsSource(ModelLightmapUVsSource source) +{ + if (source == ModelLightmapUVsSource::Disable) + { + // No lightmap UVs + } + else if (source == ModelLightmapUVsSource::Generate) + { + // Generate lightmap UVs + if (GenerateLightmapUVs()) + { + LOG(Error, "Failed to generate lightmap uvs"); + } + } + else + { + // Select input channel index + const int32 inputChannelIndex = (int32)source - (int32)ModelLightmapUVsSource::Channel0; + if (inputChannelIndex >= 0 && inputChannelIndex < UVs.Count() && UVs[inputChannelIndex].HasItems()) + { + LightmapUVsIndex = inputChannelIndex; + } + else + { + LOG(Warning, "Cannot import result lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex); + } + } +} + bool MeshData::GenerateLightmapUVs() { + if (Positions.IsEmpty() || Indices.IsEmpty()) + return true; PROFILE_CPU(); #if PLATFORM_WINDOWS // Prepare @@ -87,8 +118,7 @@ bool MeshData::GenerateLightmapUVs() int32 facesCount = Indices.Count() / 3; DirectX::XMFLOAT3* positions = (DirectX::XMFLOAT3*)Positions.Get(); LOG(Info, "Generating lightmaps UVs ({0} vertices, {1} triangles)...", verticesCount, facesCount); - - DateTime startTime = DateTime::Now(); + Stopwatch stopwatch; // Generate adjacency data const float adjacencyEpsilon = 0.001f; @@ -126,27 +156,30 @@ bool MeshData::GenerateLightmapUVs() return true; } - const DateTime endTime = DateTime::Now(); - // Log info - const int32 nTotalVerts = (int32)vb.size(); - const int32 msTime = Math::CeilToInt((float)(endTime - startTime).GetTotalMilliseconds()); - LOG(Info, "Lightmap UVs generated! Charts: {0}, stretching: {1}, {2} vertices. Time: {3}ms", outCharts, outStretch, nTotalVerts, msTime); + stopwatch.Stop(); + LOG(Info, "Lightmap UVs generated! Charts: {0}, stretching: {1}, {2} vertices. Time: {3}ms", outCharts, outStretch, (int32)vb.size(), stopwatch.GetMilliseconds()); // Update mesh data (remap vertices due to vertex buffer and index buffer change) RemapArrayHelper(Positions, vertexRemapArray); - RemapArrayHelper(UVs, vertexRemapArray); + LightmapUVsIndex = Math::Min(UVs.Count(), MODEL_MAX_UV - 1); + for (int32 channel = 0; channel < LightmapUVsIndex; channel++) + RemapArrayHelper(UVs[channel], vertexRemapArray); RemapArrayHelper(Normals, vertexRemapArray); RemapArrayHelper(Tangents, vertexRemapArray); RemapArrayHelper(Colors, vertexRemapArray); RemapArrayHelper(BlendIndices, vertexRemapArray); RemapArrayHelper(BlendWeights, vertexRemapArray); - LightmapUVs.Resize(nTotalVerts, false); - for (int32 i = 0; i < nTotalVerts; i++) - LightmapUVs[i] = *(Float2*)&vb[i].uv; uint32* ibP = (uint32*)ib.data(); for (int32 i = 0; i < Indices.Count(); i++) Indices[i] = *ibP++; + + // Add generated data + UVs.Resize(LightmapUVsIndex + 1); + auto& lightmapChannel = UVs[LightmapUVsIndex]; + lightmapChannel.Resize((int32)vb.size(), false); + for (int32 i = 0; i < (int32)vb.size(); i++) + lightmapChannel.Get()[i] = *(Float2*)&vb[i].uv; #else LOG(Error, "Model lightmap UVs generation is not supported on this platform."); #endif @@ -162,6 +195,10 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3 ) { const float uvEpsSqr = (1.0f / 250.0f) * (1.0f / 250.0f); + const Float2* uv0 = mesh.UVs.Count() > 0 && mesh.UVs[0].HasItems() ? mesh.UVs[0].Get() : nullptr; + const Float2* uv1 = mesh.UVs.Count() > 1 && mesh.UVs[1].HasItems() ? mesh.UVs[1].Get() : nullptr; + const Float2* uv2 = mesh.UVs.Count() > 2 && mesh.UVs[2].HasItems() ? mesh.UVs[2].Get() : nullptr; + const Float2* uv3 = mesh.UVs.Count() > 3 && mesh.UVs[3].HasItems() ? mesh.UVs[3].Get() : nullptr; #if USE_SPATIAL_SORT const Float3 vPosition = mesh.Positions[vertexIndex]; @@ -169,10 +206,12 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3 if (spatialSortCache.empty()) return INVALID_INDEX; - const Float2 vUV = mesh.UVs.HasItems() ? mesh.UVs[vertexIndex] : Float2::Zero; + const Float2 vUV0 = uv0 ? uv0[vertexIndex] : Float2::Zero; + const Float2 vUV1 = uv1 ? uv1[vertexIndex] : Float2::Zero; + const Float2 vUV2 = uv2 ? uv2[vertexIndex] : Float2::Zero; + const Float2 vUV3 = uv3 ? uv3[vertexIndex] : Float2::Zero; const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero; const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero; - const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero; const Color vColor = mesh.Colors.HasItems() ? mesh.Colors[vertexIndex] : Color::Black; // Assuming Color::Black as a default color const int32 end = startIndex + searchRange; @@ -184,10 +223,12 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3 continue; #else const Float3 vPosition = mesh.Positions[vertexIndex]; - const Float2 vUV = mesh.UVs.HasItems() ? mesh.UVs[vertexIndex] : Float2::Zero; + const Float2 vUV0 = uv0 ? uv0[vertexIndex] : Float2::Zero; + const Float2 vUV1 = uv1 ? uv1[vertexIndex] : Float2::Zero; + const Float2 vUV2 = uv2 ? uv2[vertexIndex] : Float2::Zero; + const Float2 vUV3 = uv3 ? uv3[vertexIndex] : Float2::Zero; const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero; const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero; - const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero; const Color vColor = mesh.Colors.HasItems() ? mesh.Colors[vertexIndex] : Color::Black; // Assuming Color::Black as a default color const int32 end = startIndex + searchRange; @@ -199,17 +240,20 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3 #endif if (mapping[v] == INVALID_INDEX) continue; - if (mesh.UVs.HasItems() && (vUV - mesh.UVs[v]).LengthSquared() > uvEpsSqr) + if (uv0 && (vUV0 - uv0[v]).LengthSquared() > uvEpsSqr) + continue; + if (uv1 && (vUV1 - uv1[v]).LengthSquared() > uvEpsSqr) + continue; + if (uv2 && (vUV2 - uv2[v]).LengthSquared() > uvEpsSqr) + continue; + if (uv3 && (vUV3 - uv3[v]).LengthSquared() > uvEpsSqr) continue; if (mesh.Normals.HasItems() && Float3::Dot(vNormal, mesh.Normals[v]) < 0.98f) continue; if (mesh.Tangents.HasItems() && Float3::Dot(vTangent, mesh.Tangents[v]) < 0.98f) continue; - if (mesh.LightmapUVs.HasItems() && (vLightmapUV - mesh.LightmapUVs[v]).LengthSquared() > uvEpsSqr) - continue; if (mesh.Colors.HasItems() && vColor != mesh.Colors[v]) continue; - // TODO: check more components? return v; } @@ -238,7 +282,7 @@ void RemapBuffer(Array& src, Array& dst, const Array& mapping, int3 void MeshData::BuildIndexBuffer() { PROFILE_CPU(); - const auto startTime = Platform::GetTimeSeconds(); + Stopwatch stopwatch; const int32 vertexCount = Positions.Count(); MeshData newMesh; @@ -287,14 +331,15 @@ void MeshData::BuildIndexBuffer() newMesh.SwapBuffers(*this); #define REMAP_BUFFER(name) RemapBuffer(newMesh.name, name, mapping, newVertexCounter) REMAP_BUFFER(Positions); - REMAP_BUFFER(UVs); REMAP_BUFFER(Normals); REMAP_BUFFER(Tangents); REMAP_BUFFER(BitangentSigns); - REMAP_BUFFER(LightmapUVs); REMAP_BUFFER(Colors); REMAP_BUFFER(BlendIndices); REMAP_BUFFER(BlendWeights); + UVs.Resize(newMesh.UVs.Count()); + for (int32 channelIdx = 0; channelIdx < UVs.Count(); channelIdx++) + REMAP_BUFFER(UVs[channelIdx]); #undef REMAP_BUFFER BlendShapes.Resize(newMesh.BlendShapes.Count()); for (int32 blendShapeIndex = 0; blendShapeIndex < newMesh.BlendShapes.Count(); blendShapeIndex++) @@ -317,8 +362,8 @@ void MeshData::BuildIndexBuffer() } } - const auto endTime = Platform::GetTimeSeconds(); - const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime); + stopwatch.Stop(); + const double time = Utilities::RoundTo2DecimalPlaces(stopwatch.GetTotalSeconds()); if (time > 0.5f) // Don't log if generation was fast enough LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, Indices.Count(), TEXT("indices")); } @@ -494,7 +539,7 @@ namespace void GetTexCoord(const SMikkTSpaceContext* pContext, float fvTexcOut[], const int iFace, const int iVert) { const auto meshData = (MeshData*)pContext->m_pUserData; - const auto e = meshData->UVs[meshData->Indices[iFace * 3 + iVert]]; + const auto e = meshData->UVs[0][meshData->Indices[iFace * 3 + iVert]]; fvTexcOut[0] = e.X; fvTexcOut[1] = e.Y; } @@ -518,7 +563,7 @@ bool MeshData::GenerateTangents(float smoothingAngle) } if (Normals.IsEmpty() || UVs.IsEmpty()) { - LOG(Warning, "Missing normals or texcoors data to generate tangents."); + LOG(Warning, "Missing normals or texcoords data to generate tangents."); return true; } PROFILE_CPU(); @@ -549,7 +594,7 @@ bool MeshData::GenerateTangents(float smoothingAngle) vertexDone.SetAll(false); const Float3* meshNorm = Normals.Get(); - const Float2* meshTex = UVs.Get(); + const Float2* meshTex = UVs[0].Get(); Float3* meshTang = Tangents.Get(); // Calculate the tangent per-triangle diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index fddde25ce..be9e33b38 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -17,21 +17,21 @@ void MeshData::Clear() Normals.Clear(); Tangents.Clear(); BitangentSigns.Clear(); - LightmapUVs.Clear(); Colors.Clear(); BlendIndices.Clear(); BlendWeights.Clear(); BlendShapes.Clear(); } -void MeshData::EnsureCapacity(int32 vertices, int32 indices, bool preserveContents, bool withColors, bool withSkin) +void MeshData::EnsureCapacity(int32 vertices, int32 indices, bool preserveContents, bool withColors, bool withSkin, int32 texcoords) { Positions.EnsureCapacity(vertices, preserveContents); Indices.EnsureCapacity(indices, preserveContents); - UVs.EnsureCapacity(vertices, preserveContents); + UVs.Resize(texcoords); + for (auto& channel : UVs) + channel.EnsureCapacity(vertices, preserveContents); Normals.EnsureCapacity(vertices, preserveContents); Tangents.EnsureCapacity(vertices, preserveContents); - LightmapUVs.EnsureCapacity(vertices, preserveContents); Colors.EnsureCapacity(withColors ? vertices : 0, preserveContents); BlendIndices.EnsureCapacity(withSkin ? vertices : 0, preserveContents); BlendWeights.EnsureCapacity(withSkin ? vertices : 0, preserveContents); @@ -45,7 +45,6 @@ void MeshData::SwapBuffers(MeshData& other) Normals.Swap(other.Normals); Tangents.Swap(other.Tangents); BitangentSigns.Swap(other.BitangentSigns); - LightmapUVs.Swap(other.LightmapUVs); Colors.Swap(other.Colors); BlendIndices.Swap(other.BlendIndices); BlendWeights.Swap(other.BlendWeights); @@ -61,7 +60,6 @@ void MeshData::Release() Normals.Resize(0); Tangents.Resize(0); BitangentSigns.Resize(0); - LightmapUVs.Resize(0); Colors.Resize(0); BlendIndices.Resize(0); BlendWeights.Resize(0); @@ -72,11 +70,12 @@ PRAGMA_DISABLE_DEPRECATION_WARNINGS void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount) { Positions.Resize(verticesCount, false); - UVs.Resize(verticesCount, false); + UVs.Resize(2); + UVs[0].Resize(verticesCount, false); + UVs[1].Resize(verticesCount, false); Normals.Resize(verticesCount, false); Tangents.Resize(verticesCount, false); BitangentSigns.Resize(0); - LightmapUVs.Resize(verticesCount, false); Colors.Resize(0); BlendIndices.Resize(0); BlendWeights.Resize(0); @@ -85,10 +84,10 @@ void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCou for (uint32 i = 0; i < verticesCount; i++) { Positions[i] = vertices->Position; - UVs[i] = vertices->TexCoord.ToFloat2(); + UVs[0][i] = vertices->TexCoord.ToFloat2(); + UVs[1][i] = vertices->LightmapUVs.ToFloat2(); Normals[i] = vertices->Normal.ToFloat3() * 2.0f - 1.0f; Tangents[i] = vertices->Tangent.ToFloat3() * 2.0f - 1.0f; - LightmapUVs[i] = vertices->LightmapUVs.ToFloat2(); Colors[i] = Color(vertices->Color); vertices++; @@ -98,11 +97,12 @@ void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCou void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, uint32 verticesCount) { Positions.Resize(verticesCount, false); - UVs.Resize(verticesCount, false); + UVs.Resize(2); + UVs[0].Resize(verticesCount, false); + UVs[1].Resize(verticesCount, false); Normals.Resize(verticesCount, false); Tangents.Resize(verticesCount, false); BitangentSigns.Resize(0); - LightmapUVs.Resize(verticesCount, false); Colors.Resize(0); BlendIndices.Resize(0); BlendWeights.Resize(0); @@ -111,10 +111,10 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb for (uint32 i = 0; i < verticesCount; i++) { Positions[i] = vb0->Position; - UVs[i] = vb1->TexCoord.ToFloat2(); + UVs[0][i] = vb1->TexCoord.ToFloat2(); + UVs[1][i] = vb1->LightmapUVs.ToFloat2(); Normals[i] = vb1->Normal.ToFloat3() * 2.0f - 1.0f; Tangents[i] = vb1->Tangent.ToFloat3() * 2.0f - 1.0f; - LightmapUVs[i] = vb1->LightmapUVs.ToFloat2(); vb0++; vb1++; @@ -124,19 +124,16 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount) { Positions.Resize(verticesCount, false); - UVs.Resize(verticesCount, false); + UVs.Resize(2); + UVs[0].Resize(verticesCount, false); + UVs[1].Resize(verticesCount, false); Normals.Resize(verticesCount, false); Tangents.Resize(verticesCount, false); BitangentSigns.Resize(0); - LightmapUVs.Resize(verticesCount, false); if (vb2) - { Colors.Resize(verticesCount, false); - } else - { Colors.Resize(0); - } BlendIndices.Resize(0); BlendWeights.Resize(0); BlendShapes.Resize(0); @@ -144,10 +141,10 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb for (uint32 i = 0; i < verticesCount; i++) { Positions[i] = vb0->Position; - UVs[i] = vb1->TexCoord.ToFloat2(); + UVs[0][i] = vb1->TexCoord.ToFloat2(); + UVs[1][i] = vb1->LightmapUVs.ToFloat2(); Normals[i] = vb1->Normal.ToFloat3() * 2.0f - 1.0f; Tangents[i] = vb1->Tangent.ToFloat3() * 2.0f - 1.0f; - LightmapUVs[i] = vb1->LightmapUVs.ToFloat2(); if (vb2) { Colors[i] = Color(vb2->Color); @@ -310,22 +307,32 @@ void MeshData::Merge(MeshData& other) } // Merge vertex buffer -#define MERGE(item, defautValue) \ +#define MERGE(item, defaultValue) \ if (item.HasItems() && other.item.HasItems()) \ item.Add(other.item); \ else if (item.HasItems() && !other.item.HasItems()) \ - for (int32 i = 0; i < other.Positions.Count(); i++) item.Add(defautValue); \ + for (int32 i = 0; i < other.Positions.Count(); i++) item.Add(defaultValue); \ else if (!item.HasItems() && other.item.HasItems()) \ - for (int32 i = 0; i < Positions.Count(); i++) item.Add(defautValue) + for (int32 i = 0; i < Positions.Count(); i++) item.Add(defaultValue) MERGE(Positions, Float3::Zero); - MERGE(UVs, Float2::Zero); MERGE(Normals, Float3::Forward); MERGE(Tangents, Float3::Right); MERGE(BitangentSigns, 1.0f); - MERGE(LightmapUVs, Float2::Zero); MERGE(Colors, Color::Black); MERGE(BlendIndices, Int4::Zero); MERGE(BlendWeights, Float4::Zero); + if (other.UVs.Count() > UVs.Count()) + UVs.Resize(other.UVs.Count()); + for (int32 channelIdx = 0; channelIdx < UVs.Count(); channelIdx++) + { + if (other.UVs.Count() <= channelIdx) + { + for (int32 i = 0; i < other.Positions.Count(); i++) + UVs[channelIdx].Add(Float2::Zero); + continue; + } + MERGE(UVs[channelIdx], Float2::Zero); + } #undef MERGE // Merge blend shapes diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 615a3d31a..fba857ff3 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -41,9 +41,7 @@ public: /// /// Texture coordinates (list of channels) /// - // TODO: multiple UVs - Array UVs; - Array LightmapUVs; // TODO: remove this and move to UVs + Array, FixedAllocation> UVs; /// /// Normals vector @@ -121,7 +119,8 @@ public: /// Failed if clear data otherwise will try to preserve the buffers contents. /// True if use vertex colors buffer. /// True if use vertex blend indices and weights buffer. - void EnsureCapacity(int32 vertices, int32 indices, bool preserveContents = false, bool withColors = true, bool withSkin = true); + /// Amount of texture coordinate channels to use. + void EnsureCapacity(int32 vertices, int32 indices, bool preserveContents = false, bool withColors = true, bool withSkin = true, int32 texcoords = 1); /// /// Swaps the vertex and index buffers contents (without a data copy) with the other mesh. @@ -189,6 +188,11 @@ public: void CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const; #if COMPILE_WITH_MODEL_TOOL + /// + /// Setups Lightmap UVs based on the option. + /// + void SetLightmapUVsSource(ModelLightmapUVsSource source); + /// /// Generate lightmap uvs for the mesh entry /// diff --git a/Source/Engine/Graphics/Shaders/VertexElement.h b/Source/Engine/Graphics/Shaders/VertexElement.h index 3744c2f6a..8fbe75473 100644 --- a/Source/Engine/Graphics/Shaders/VertexElement.h +++ b/Source/Engine/Graphics/Shaders/VertexElement.h @@ -55,6 +55,8 @@ PACK_BEGIN() struct FLAXENGINE_API VertexElement Attribute2 = 17, // General purpose attribute (at index 3). Maps to 'ATTRIBUTE3' semantic in the shader. Attribute3 = 18, + // Lightmap UVs that usually map one of the texture coordinate channels. Maps to 'LIGHTMAP' semantic in the shader. + Lightmap = 30, // Texture coordinate. Maps to 'TEXCOORD' semantic in the shader. TexCoord = TexCoord0, // General purpose attribute. Maps to 'ATTRIBUTE0' semantic in the shader. diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index 94e2513d5..d64df3857 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -325,6 +325,8 @@ LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& se case VertexElement::Types::Attribute3: semanticIndex = 3; return "ATTRIBUTE"; + case VertexElement::Types::Lightmap: + return "LIGHTMAP"; default: LOG(Fatal, "Invalid vertex shader element semantic type"); return ""; diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 8e3ca2abf..d931ee5b0 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -488,6 +488,8 @@ VertexElement::Types ShaderCompiler::ParseVertexElementType(StringAnsiView seman return VertexElement::Types::Tangent; if (semantic == "BLENDINDICES") return VertexElement::Types::BlendIndices; + if (semantic == "LIGHTMAP") + return VertexElement::Types::Lightmap; if (semantic == "BLENDWEIGHTS" || semantic == "BLENDWEIGHT") // [Deprecated in v1.10] return VertexElement::Types::BlendWeights; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 58448f5ea..691924507 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -189,8 +189,20 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } // TexCoord case 2: - value = getUVs; + { + const auto layer = GetRootLayer(); + if (layer && layer->Domain == MaterialDomain::Surface) + { + const uint32 channel = node->Values.HasItems() ? Math::Min((uint32)node->Values[0], 3u) : 0u; + value = Value(VariantType::Float2, String::Format(TEXT("input.TexCoords[{}]"), channel)); + } + else + { + // TODO: migrate all material domain templates to TexCoords array (of size MATERIAL_TEXCOORDS=1) + value = getUVs; + } break; + } // Cube Texture case 3: { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp index 3e54f14be..f6024ce8e 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialLayer.cpp @@ -15,7 +15,6 @@ MaterialLayer::MaterialLayer(const Guid& id) , ShadingModel(MaterialShadingModel::Lit) , MaskThreshold(0.3f) , OpacityThreshold(0.12f) - , ParamIdsMappings(8) { ASSERT(ID.IsValid()); } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index ff8dc31fe..df6a5e90f 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -246,13 +246,15 @@ bool ProcessMesh(ModelData& result, AssimpImporterData& data, const aiMesh* aMes mesh.Positions.Set((const Float3*)aMesh->mVertices, aMesh->mNumVertices); // Texture coordinates - if (aMesh->mTextureCoords[0]) + for (int32 channelIndex = 0; channelIndex < MODEL_MAX_UV && aMesh->mTextureCoords[channelIndex]; channelIndex++) { - mesh.UVs.Resize(aMesh->mNumVertices, false); - aiVector3D* a = aMesh->mTextureCoords[0]; + mesh.UVs.Resize(channelIndex + 1); + auto& channel = mesh.UVs[channelIndex]; + channel.Resize(aMesh->mNumVertices, false); + aiVector3D* a = aMesh->mTextureCoords[channelIndex]; for (uint32 v = 0; v < aMesh->mNumVertices; v++) { - mesh.UVs[v] = *(Float2*)a; + channel.Get()[v] = *(Float2*)a; a++; } } @@ -265,7 +267,7 @@ bool ProcessMesh(ModelData& result, AssimpImporterData& data, const aiMesh* aMes const auto face = &aMesh->mFaces[faceIndex]; if (face->mNumIndices != 3) { - errorMsg = TEXT("All faces in a mesh must be trangles!"); + errorMsg = TEXT("All faces in a mesh must be triangles!"); return true; } @@ -296,57 +298,7 @@ bool ProcessMesh(ModelData& result, AssimpImporterData& data, const aiMesh* aMes } // Lightmap UVs - if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Disable) - { - // No lightmap UVs - } - else if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Generate) - { - // Generate lightmap UVs - if (mesh.GenerateLightmapUVs()) - { - LOG(Error, "Failed to generate lightmap uvs"); - } - } - else - { - // Select input channel index - int32 inputChannelIndex; - switch (data.Options.LightmapUVsSource) - { - case ModelLightmapUVsSource::Channel0: - inputChannelIndex = 0; - break; - case ModelLightmapUVsSource::Channel1: - inputChannelIndex = 1; - break; - case ModelLightmapUVsSource::Channel2: - inputChannelIndex = 2; - break; - case ModelLightmapUVsSource::Channel3: - inputChannelIndex = 3; - break; - default: - inputChannelIndex = INVALID_INDEX; - break; - } - - // Check if has that channel texcoords - if (inputChannelIndex >= 0 && inputChannelIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS && aMesh->mTextureCoords[inputChannelIndex]) - { - mesh.LightmapUVs.Resize(aMesh->mNumVertices, false); - aiVector3D* a = aMesh->mTextureCoords[inputChannelIndex]; - for (uint32 v = 0; v < aMesh->mNumVertices; v++) - { - mesh.LightmapUVs[v] = *(Float2*)a; - a++; - } - } - else - { - LOG(Warning, "Cannot import result lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex); - } - } + mesh.SetLightmapUVsSource(data.Options.LightmapUVsSource); // Vertex Colors if (data.Options.ImportVertexColors && aMesh->mColors[0]) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp b/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp index 1ea6a22ad..03eda839c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp @@ -386,10 +386,12 @@ bool ProcessMesh(ImporterData& data, FbxMesh* fbxMesh, MeshData& mesh, String& e } // Texture coordinates - FbxGeometryElementUV* texcoords = fbxMesh->GetElementUV(0); - if (texcoords) + for (int32 channelIndex = 0; channelIndex < MODEL_MAX_UV && fbxMesh->GetElementUV(channelIndex); channelIndex++) { - ReadLayerData(fbxMesh, *texcoords, mesh.UVs); + FbxGeometryElementUV* texcoords = fbxMesh->GetElementUV(0); + mesh.UVs.Resize(channelIndex + 1); + auto& channel = mesh.UVs[channelIndex]; + ReadLayerData(fbxMesh, *texcoords, channel); } // Normals @@ -405,53 +407,7 @@ bool ProcessMesh(ImporterData& data, FbxMesh* fbxMesh, MeshData& mesh, String& e } // Lightmap UVs - if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Disable) - { - // No lightmap UVs - } - else if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Generate) - { - // Generate lightmap UVs - if (mesh.GenerateLightmapUVs()) - { - // TODO: we could propagate this message to Debug Console in editor? or create interface to gather some msgs from importing service - LOG(Warning, "Failed to generate lightmap uvs"); - } - } - else - { - // Select input channel index - int32 inputChannelIndex; - switch (data.Options.LightmapUVsSource) - { - case ModelLightmapUVsSource::Channel0: - inputChannelIndex = 0; - break; - case ModelLightmapUVsSource::Channel1: - inputChannelIndex = 1; - break; - case ModelLightmapUVsSource::Channel2: - inputChannelIndex = 2; - break; - case ModelLightmapUVsSource::Channel3: - inputChannelIndex = 3; - break; - default: - inputChannelIndex = INVALID_INDEX; - break; - } - - // Check if has that channel texcoords - if (inputChannelIndex >= 0 && inputChannelIndex < fbxMesh->GetElementUVCount() && fbxMesh->GetElementUV(inputChannelIndex)) - { - ReadLayerData(fbxMesh, *fbxMesh->GetElementUV(inputChannelIndex), mesh.LightmapUVs); - } - else - { - // TODO: we could propagate this message to Debug Console in editor? or create interface to gather some msgs from importing service - LOG(Warning, "Cannot import model lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex); - } - } + mesh.SetLightmapUVsSource(data.Options.LightmapUVsSource); // Vertex Colors if (data.Options.ImportVertexColors && fbxMesh->GetElementVertexColorCount() > 0) @@ -609,10 +565,11 @@ bool ProcessMesh(ImporterData& data, FbxMesh* fbxMesh, MeshData& mesh, String& e } // Flip the Y in texcoords - for (int32 i = 0; i < mesh.UVs.Count(); i++) - mesh.UVs[i].Y = 1.0f - mesh.UVs[i].Y; - for (int32 i = 0; i < mesh.LightmapUVs.Count(); i++) - mesh.LightmapUVs[i].Y = 1.0f - mesh.LightmapUVs[i].Y; + for (auto& channel : mesh.UVs) + { + for (int32 i = 0; i < channel.Count(); i++) + channel[i].Y = 1.0f - channel[i].Y; + } // Handle missing material case (could never happen but it's better to be sure it will work) if (mesh.MaterialSlotIndex == -1) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index dcb0b6c5f..750b0e059 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -690,7 +690,6 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* const ofbx::GeometryPartition& partition = geometryData.getPartition(partitionIndex); const int vertexCount = partition.triangles_count * 3; const ofbx::Vec3Attributes& positions = geometryData.getPositions(); - const ofbx::Vec2Attributes& uvs = geometryData.getUVs(); const ofbx::Vec3Attributes& normals = geometryData.getNormals(); const ofbx::Vec3Attributes& tangents = geometryData.getTangents(); const ofbx::Vec4Attributes& colors = geometryData.getColors(); @@ -723,15 +722,18 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* mesh.Indices.Get()[i] = i; // Texture coordinates - if (uvs.values) + for (int32 channelIndex = 0; channelIndex < MODEL_MAX_UV && geometryData.getUVs(channelIndex).values; channelIndex++) { - mesh.UVs.Resize(vertexCount, false); + const ofbx::Vec2Attributes& uvs = geometryData.getUVs(channelIndex); + mesh.UVs.Resize(channelIndex + 1); + auto& channel = mesh.UVs[channelIndex]; + channel.Resize(vertexCount, false); for (int i = 0; i < vertexCount; i++) - mesh.UVs.Get()[i] = ToFloat2(uvs.get(triangulatedIndices[i])); + channel.Get()[i] = ToFloat2(uvs.get(triangulatedIndices[i])); if (data.ConvertRH) { - for (int32 v = 0; v < vertexCount; v++) - mesh.UVs[v].Y = 1.0f - mesh.UVs[v].Y; + for (int v = 0; v < vertexCount; v++) + channel.Get()[v].Y = 1.0f - channel.Get()[v].Y; } } @@ -776,59 +778,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* } // Lightmap UVs - if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Disable) - { - // No lightmap UVs - } - else if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Generate) - { - // Generate lightmap UVs - if (mesh.GenerateLightmapUVs()) - { - LOG(Error, "Failed to generate lightmap uvs"); - } - } - else - { - // Select input channel index - int32 inputChannelIndex; - switch (data.Options.LightmapUVsSource) - { - case ModelLightmapUVsSource::Channel0: - inputChannelIndex = 0; - break; - case ModelLightmapUVsSource::Channel1: - inputChannelIndex = 1; - break; - case ModelLightmapUVsSource::Channel2: - inputChannelIndex = 2; - break; - case ModelLightmapUVsSource::Channel3: - inputChannelIndex = 3; - break; - default: - inputChannelIndex = INVALID_INDEX; - break; - } - - // Check if has that channel texcoords - const auto lightmapUVs = geometryData.getUVs(inputChannelIndex); - if (lightmapUVs.values) - { - mesh.LightmapUVs.Resize(vertexCount, false); - for (int i = 0; i < vertexCount; i++) - mesh.LightmapUVs.Get()[i] = ToFloat2(lightmapUVs.get(triangulatedIndices[i])); - if (data.ConvertRH) - { - for (int32 v = 0; v < vertexCount; v++) - mesh.LightmapUVs[v].Y = 1.0f - mesh.LightmapUVs[v].Y; - } - } - else - { - LOG(Warning, "Cannot import model lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex); - } - } + mesh.SetLightmapUVsSource(data.Options.LightmapUVsSource); // Vertex Colors if (data.Options.ImportVertexColors && colors.values) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 9a1721569..874017833 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -2027,11 +2027,14 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option meshopt_remapVertexBuffer(dstMesh->name.Get(), srcMesh->name.Get(), srcMeshVertexCount, sizeof(type), remap.Get()); \ } REMAP_VERTEX_BUFFER(Positions, Float3); - REMAP_VERTEX_BUFFER(UVs, Float2); + dstMesh->UVs.Resize(srcMesh->UVs.Count()); + for (int32 channelIdx = 0; channelIdx < srcMesh->UVs.Count(); channelIdx++) + { + REMAP_VERTEX_BUFFER(UVs[channelIdx], Float2); + } REMAP_VERTEX_BUFFER(Normals, Float3); REMAP_VERTEX_BUFFER(Tangents, Float3); REMAP_VERTEX_BUFFER(Tangents, Float3); - REMAP_VERTEX_BUFFER(LightmapUVs, Float2); REMAP_VERTEX_BUFFER(Colors, Color); REMAP_VERTEX_BUFFER(BlendIndices, Int4); REMAP_VERTEX_BUFFER(BlendWeights, Float4); diff --git a/Source/Shaders/BakeLightmap.shader b/Source/Shaders/BakeLightmap.shader index b70ced7af..292b8db44 100644 --- a/Source/Shaders/BakeLightmap.shader +++ b/Source/Shaders/BakeLightmap.shader @@ -126,8 +126,7 @@ RenderCacheVSOutput VS_RenderCacheTerrain(TerrainVertexInput input) output.WorldNormal = tangentToWorld[2]; // Transform lightmap UV to clip-space - float2 texCoord = input.TexCoord; - float2 lightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; + float2 lightmapUV = input.TexCoord * LightmapArea.zw + LightmapArea.xy; lightmapUV.y = 1.0 - lightmapUV.y; lightmapUV.xy = lightmapUV.xy * 2.0 - 1.0; output.Position = float4(lightmapUV, 0, 1); diff --git a/Source/Shaders/Editor/LightmapUVsDensity.shader b/Source/Shaders/Editor/LightmapUVsDensity.shader index 588332a00..b9763846e 100644 --- a/Source/Shaders/Editor/LightmapUVsDensity.shader +++ b/Source/Shaders/Editor/LightmapUVsDensity.shader @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #define MATERIAL 1 +#define MATERIAL_TEXCOORDS 4 #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" @@ -19,20 +20,18 @@ Texture2D GridTexture : register(t0); struct VertexOutput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; + float4 Position : SV_Position; + float3 WorldPosition : TEXCOORD0; + float2 LightmapUV : TEXCOORD1; + float3 WorldNormal : TEXCOORD2; }; struct PixelInput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; + float4 Position : SV_Position; + float3 WorldPosition : TEXCOORD0; + float2 LightmapUV : TEXCOORD1; + float3 WorldNormal : TEXCOORD2; }; float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) @@ -67,7 +66,6 @@ VertexOutput VS(ModelInput input) VertexOutput output; output.WorldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz; output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - output.TexCoord = input.TexCoord; output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; output.WorldNormal = tangentToWorld[2]; return output; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 6b20e2d71..c7a46c171 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -31,6 +31,9 @@ #ifndef MATERIAL_SHADING_MODEL #define MATERIAL_SHADING_MODEL SHADING_MODEL_LIT #endif +#ifndef MATERIAL_TEXCOORDS +#define MATERIAL_TEXCOORDS 1 +#endif #ifndef USE_INSTANCING #define USE_INSTANCING 0 #endif @@ -186,10 +189,21 @@ cbuffer DrawData : register(b2) struct ModelInput { float3 Position : POSITION; - float2 TexCoord : TEXCOORD0; +#if MATERIAL_TEXCOORDS > 0 + float2 TexCoord0 : TEXCOORD0; +#endif +#if MATERIAL_TEXCOORDS > 1 + float2 TexCoord1 : TEXCOORD1; +#endif +#if MATERIAL_TEXCOORDS > 2 + float2 TexCoord2 : TEXCOORD2; +#endif +#if MATERIAL_TEXCOORDS > 3 + float2 TexCoord3 : TEXCOORD3; +#endif + float2 LightmapUV : LIGHTMAP; float4 Normal : NORMAL; float4 Tangent : TANGENT; - float2 LightmapUV : TEXCOORD1; #if USE_VERTEX_COLOR half4 Color : COLOR; #endif @@ -209,7 +223,18 @@ struct ModelInput_PosOnly struct ModelInput_Skinned { float3 Position : POSITION; - float2 TexCoord : TEXCOORD0; +#if MATERIAL_TEXCOORDS > 0 + float2 TexCoord0 : TEXCOORD0; +#endif +#if MATERIAL_TEXCOORDS > 1 + float2 TexCoord1 : TEXCOORD1; +#endif +#if MATERIAL_TEXCOORDS > 2 + float2 TexCoord2 : TEXCOORD2; +#endif +#if MATERIAL_TEXCOORDS > 3 + float2 TexCoord3 : TEXCOORD3; +#endif float4 Normal : NORMAL; float4 Tangent : TANGENT; uint4 BlendIndices : BLENDINDICES; From 237ea121be0c0040f2471ad97965fa9a4adfc30d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 11 Jan 2025 22:43:15 +0100 Subject: [PATCH 123/215] Add vertex colors to skinned meshes --- Source/Shaders/MaterialCommon.hlsl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index c7a46c171..cb0398138 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -237,6 +237,9 @@ struct ModelInput_Skinned #endif float4 Normal : NORMAL; float4 Tangent : TANGENT; +#if USE_VERTEX_COLOR + half4 Color : COLOR; +#endif uint4 BlendIndices : BLENDINDICES; float4 BlendWeights : BLENDWEIGHTS; }; From 647d74af0d8a534ddf41352172b8e9c2854e5533 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 11 Jan 2025 22:44:38 +0100 Subject: [PATCH 124/215] Fixes for vertex layouts binding into graphics backend --- .../Graphics/Shaders/GPUVertexLayout.cpp | 4 +++- .../DirectX/DX11/GPUShaderDX11.cpp | 6 ++++- .../DirectX/DX12/GPUContextDX12.cpp | 11 ++++++--- .../DirectX/DX12/GPUPipelineStateDX12.cpp | 23 +++++++++++++++---- .../DirectX/DX12/GPUPipelineStateDX12.h | 3 ++- .../DirectX/ShaderCompilerD3D.cpp | 2 +- .../DirectX/ShaderCompilerDX.cpp | 2 +- 7 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 822766e67..354a6dfa6 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -262,7 +262,9 @@ GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* { // Insert any missing elements VertexElement ne = { e.Type, missingSlotOverride != -1 ? (byte)missingSlotOverride : e.Slot, 0, e.PerInstance, e.Format }; - if (e.Type == VertexElement::Types::TexCoord1 || e.Type == VertexElement::Types::TexCoord2 || e.Type == VertexElement::Types::TexCoord3) + if (e.Type == VertexElement::Types::TexCoord1 || + e.Type == VertexElement::Types::TexCoord2 || + e.Type == VertexElement::Types::TexCoord3) { // Alias missing texcoords with existing texcoords for (const VertexElement& ee : newElements) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp index 4251b3a55..3b78882ae 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp @@ -26,7 +26,11 @@ ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* v { if (vertexLayout && vertexLayout->InputElementsCount) { - auto mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout ? Layout : InputLayout); + GPUVertexLayoutDX11* mergedVertexLayout = vertexLayout; + if (!mergedVertexLayout) + mergedVertexLayout = (GPUVertexLayoutDX11*)Layout; // Fallback to shader-specified layout (if using old APIs) + if (InputLayout) + mergedVertexLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(mergedVertexLayout, InputLayout); LOG_DIRECTX_RESULT(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(mergedVertexLayout->InputElements, mergedVertexLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout)); } _cache.Add(vertexLayout, inputLayout); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 6f9be059f..6a1f4ec01 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -563,7 +563,7 @@ void GPUContextDX12::flushPS() // Change state ASSERT(_currentState->IsValid()); #if GPU_ENABLE_ASSERTION_LOW_LAYERS - if (!_vertexLayout && _vbHandles[0] && !_currentState->VertexLayout) + if (!_vertexLayout && _vbHandles[0] && !_currentState->VertexBufferLayout) { LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); } @@ -957,7 +957,6 @@ void GPUContextDX12::BindUA(int32 slot, GPUResourceView* view) void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets, GPUVertexLayout* vertexLayout) { ASSERT(vertexBuffers.Length() >= 0 && vertexBuffers.Length() <= GPU_MAX_VB_BINDED); - bool vbEdited = _vbCount != vertexBuffers.Length(); D3D12_VERTEX_BUFFER_VIEW views[GPU_MAX_VB_BINDED]; for (int32 i = 0; i < vertexBuffers.Length(); i++) @@ -990,7 +989,13 @@ void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* #endif _commandList->IASetVertexBuffers(0, vertexBuffers.Length(), views); } - _vertexLayout = (GPUVertexLayoutDX12*)(vertexLayout ? vertexLayout : GPUVertexLayout::Get(vertexBuffers)); + if (!vertexLayout) + vertexLayout = GPUVertexLayout::Get(vertexBuffers); + if (_vertexLayout != vertexLayout) + { + _vertexLayout = (GPUVertexLayoutDX12*)vertexLayout; + _psDirtyFlag = true; + } } void GPUContextDX12::BindIB(GPUBuffer* indexBuffer) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index c68fda240..ae8e29918 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -83,9 +83,20 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i _desc.SampleDesc.Quality = key.MSAA == MSAALevel::None ? 0 : GPUDeviceDX12::GetMaxMSAAQuality((int32)key.MSAA); _desc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK; _desc.DSVFormat = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindDepthStencilFormat(key.DepthFormat)); - vertexLayout = (GPUVertexLayoutDX12*)GPUVertexLayout::Merge(vertexLayout, VertexLayout); - _desc.InputLayout.pInputElementDescs = vertexLayout ? vertexLayout->InputElements : nullptr; - _desc.InputLayout.NumElements = vertexLayout ? vertexLayout->InputElementsCount : 0; + if (!vertexLayout) + vertexLayout = VertexBufferLayout; // Fallback to shader-specified layout (if using old APIs) + if (vertexLayout) + { + if (VertexInputLayout) + vertexLayout = (GPUVertexLayoutDX12*)GPUVertexLayout::Merge(vertexLayout, VertexInputLayout); + _desc.InputLayout.pInputElementDescs = vertexLayout->InputElements; + _desc.InputLayout.NumElements = vertexLayout->InputElementsCount; + } + else + { + _desc.InputLayout.pInputElementDescs = nullptr; + _desc.InputLayout.NumElements = 0; + } // Create object const HRESULT result = _device->GetDevice()->CreateGraphicsPipelineState(&_desc, IID_PPV_ARGS(&state)); @@ -180,7 +191,11 @@ bool GPUPipelineStateDX12::Init(const Description& desc) INIT_SHADER_STAGE(PS, GPUShaderProgramPSDX12); // Input Assembly - VertexLayout = desc.VS ? (GPUVertexLayoutDX12*)(desc.VS->Layout ? desc.VS->Layout : desc.VS->InputLayout) : nullptr; + if (desc.VS) + { + VertexBufferLayout = (GPUVertexLayoutDX12*)desc.VS->Layout; + VertexInputLayout = (GPUVertexLayoutDX12*)desc.VS->InputLayout; + } const D3D12_PRIMITIVE_TOPOLOGY_TYPE primTypes1[] = { D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED, diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h index aaaae48d2..7645e9914 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h @@ -58,7 +58,8 @@ public: public: D3D_PRIMITIVE_TOPOLOGY PrimitiveTopology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; DxShaderHeader Header; - GPUVertexLayoutDX12* VertexLayout; + GPUVertexLayoutDX12* VertexBufferLayout = nullptr; + GPUVertexLayoutDX12* VertexInputLayout = nullptr; /// /// Gets DirectX 12 graphics pipeline state object for the given rendering state. Uses depth buffer and render targets formats and multi-sample levels to setup a proper PSO. Uses caching. diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp index 131d77033..bb2c408cc 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp @@ -277,7 +277,7 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation { D3D11_SIGNATURE_PARAMETER_DESC inputDesc; reflector->GetInputParameterDesc(inputIdx, &inputDesc); - if (inputDesc.ReadWriteMask == 0 || inputDesc.SystemValueType != D3D10_NAME_UNDEFINED) + if (inputDesc.SystemValueType != D3D10_NAME_UNDEFINED) continue; auto format = PixelFormat::Unknown; switch (inputDesc.ComponentType) diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index c2431c485..d224503e2 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -327,7 +327,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD { D3D12_SIGNATURE_PARAMETER_DESC inputDesc; shaderReflection->GetInputParameterDesc(inputIdx, &inputDesc); - if (inputDesc.ReadWriteMask == 0 || inputDesc.SystemValueType != D3D10_NAME_UNDEFINED) + if (inputDesc.SystemValueType != D3D10_NAME_UNDEFINED) continue; auto format = PixelFormat::Unknown; switch (inputDesc.ComponentType) From b71f8035547b83103308ed7dc35afdd8587551bb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 11 Jan 2025 22:45:04 +0100 Subject: [PATCH 125/215] Fix regression in particles --- Source/Engine/Particles/ParticleSystem.cpp | 13 +++++-------- Source/Engine/Particles/Particles.cpp | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 6f001bf44..e70e5769d 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -424,15 +424,12 @@ Asset::LoadResult ParticleSystem::load() Emitters[i]->WaitForLoaded(); } - if (version <= 3) - { - // [Deprecated on 03.09.2021 expires on 03.09.2023] - } - else - { - // Load parameters overrides - int32 overridesCount = 0; + // Load parameters overrides + int32 overridesCount = 0; + if (stream.CanRead()) stream.ReadInt32(&overridesCount); + if (overridesCount != 0) + { EmitterParameterOverrideKey key; Variant value; for (int32 i = 0; i < overridesCount; i++) diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index fc645bb60..21001d91f 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -620,7 +620,7 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa auto emitter = buffer->Emitter; // Check if need to perform any particles sorting - if (emitter->Graph.SortModules.HasItems() && renderContext.View.Pass != DrawPass::Depth) + if (emitter->Graph.SortModules.HasItems() && renderContext.View.Pass != DrawPass::Depth && buffer->GPU.ParticlesCountMax != 0) { PROFILE_GPU_CPU_NAMED("Sort Particles"); From 1b97e49ed9e7f3a48baaa2055420ec91e7720331 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 11 Jan 2025 22:45:18 +0100 Subject: [PATCH 126/215] Fix shadow maps rendering regression bug --- Source/Engine/Renderer/RenderList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 1d9f4301f..b803f86af 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -1027,7 +1027,7 @@ bool SurfaceDrawCallHandler::CanBatch(const DrawCall& a, const DrawCall& b, Draw const MaterialInfo& bInfo = b.Material->GetInfo(); constexpr MaterialUsageFlags complexUsageFlags = MaterialUsageFlags::UseMask | MaterialUsageFlags::UsePositionOffset | MaterialUsageFlags::UseDisplacement; const bool aIsSimple = EnumHasNoneFlags(aInfo.UsageFlags, complexUsageFlags) && aInfo.BlendMode == MaterialBlendMode::Opaque; - const bool bIsSimple = EnumHasNoneFlags(bInfo.UsageFlags, complexUsageFlags) && aInfo.BlendMode == MaterialBlendMode::Opaque; + const bool bIsSimple = EnumHasNoneFlags(bInfo.UsageFlags, complexUsageFlags) && bInfo.BlendMode == MaterialBlendMode::Opaque; return aIsSimple && bIsSimple; } return false; From 506efb75385e038bde1d02c8b7603da20aaffb29 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 01:04:56 +0100 Subject: [PATCH 127/215] Merge model and skinned model windows code into shared base class --- .../CustomEditors/CustomEditorsUtil.cpp | 17 +- .../Editor/Windows/Assets/ModelBaseWindow.cs | 360 ++++++++++++- Source/Editor/Windows/Assets/ModelWindow.cs | 491 +++--------------- .../Windows/Assets/SkinnedModelWindow.cs | 362 +------------ Source/Engine/Content/Assets/Model.cpp | 95 ++-- Source/Engine/Content/Assets/Model.h | 56 +- Source/Engine/Content/Assets/ModelBase.cpp | 64 +++ Source/Engine/Content/Assets/ModelBase.h | 92 ++++ Source/Engine/Content/Assets/SkinnedModel.cpp | 113 ++-- Source/Engine/Content/Assets/SkinnedModel.h | 67 +-- 10 files changed, 701 insertions(+), 1016 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp index 692428a03..75f23c088 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp @@ -39,14 +39,9 @@ CustomEditorsUtilService CustomEditorsUtilServiceInstance; struct Entry { - MClass* DefaultEditor; - MClass* CustomEditor; - - Entry() - { - DefaultEditor = nullptr; - CustomEditor = nullptr; - } + MClass* DefaultEditor = nullptr; + MClass* CustomEditor = nullptr; + MType* CustomEditorType = nullptr; }; Dictionary Cache(512); @@ -63,11 +58,11 @@ MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType) Entry result; if (Cache.TryGet(type, result)) { + if (result.CustomEditorType) + return INTERNAL_TYPE_GET_OBJECT(result.CustomEditorType); MClass* editor = result.CustomEditor ? result.CustomEditor : result.DefaultEditor; if (editor) - { return MUtils::GetType(editor); - } } return nullptr; } @@ -157,7 +152,7 @@ void OnAssemblyLoaded(MAssembly* assembly) else if (typeClass) { auto& entry = Cache[mclass->GetType()]; - entry.CustomEditor = typeClass; + entry.CustomEditorType = type; //LOG(Info, "Custom Editor {0} for type {1}", String(typeClass->GetFullName()), String(mclass->GetFullName())); } diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index b1b531e5c..91a3d2057 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -1,11 +1,15 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using System.Collections.Generic; +using System.Reflection; using System.Xml; using FlaxEditor.Content; +using FlaxEditor.Content.Import; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -93,6 +97,306 @@ namespace FlaxEditor.Windows.Assets } } + protected class MeshesPropertiesProxyBase : PropertiesProxyBase + { + private readonly List _materialSlotComboBoxes = new List(); + private readonly List _isolateCheckBoxes = new List(); + private readonly List _highlightCheckBoxes = new List(); + + public override void OnLoad(TWindow window) + { + base.OnLoad(window); + + Window._isolateIndex = -1; + Window._highlightIndex = -1; + } + + public override void OnClean() + { + Window._isolateIndex = -1; + Window._highlightIndex = -1; + + base.OnClean(); + } + + /// + /// Updates the highlight/isolate effects on UI. + /// + public void UpdateEffectsOnUI() + { + Window._skipEffectsGuiEvents = true; + + for (int i = 0; i < _isolateCheckBoxes.Count; i++) + { + var checkBox = _isolateCheckBoxes[i]; + checkBox.Checked = Window._isolateIndex == ((MeshBase)checkBox.Tag).MaterialSlotIndex; + } + + for (int i = 0; i < _highlightCheckBoxes.Count; i++) + { + var checkBox = _highlightCheckBoxes[i]; + checkBox.Checked = Window._highlightIndex == ((MeshBase)checkBox.Tag).MaterialSlotIndex; + } + + Window._skipEffectsGuiEvents = false; + } + + /// + /// Updates the material slots UI parts. Should be called after material slot rename. + /// + public void UpdateMaterialSlotsUI() + { + Window._skipEffectsGuiEvents = true; + + // Generate material slots labels (with index prefix) + var slots = Asset.MaterialSlots; + var slotsLabels = new string[slots.Length]; + for (int i = 0; i < slots.Length; i++) + { + slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name); + } + + // Update comboboxes + for (int i = 0; i < _materialSlotComboBoxes.Count; i++) + { + var comboBox = _materialSlotComboBoxes[i]; + comboBox.SetItems(slotsLabels); + comboBox.SelectedIndex = ((MeshBase)comboBox.Tag).MaterialSlotIndex; + } + + Window._skipEffectsGuiEvents = false; + } + + /// + /// Sets the material slot index to the mesh. + /// + /// The mesh. + /// New index of the material slot to use. + public void SetMaterialSlot(MeshBase mesh, int newSlotIndex) + { + if (Window._skipEffectsGuiEvents) + return; + + mesh.MaterialSlotIndex = newSlotIndex == -1 ? 0 : newSlotIndex; + Window.UpdateEffectsOnAsset(); + UpdateEffectsOnUI(); + Window.MarkAsEdited(); + } + + /// + /// Sets the material slot to isolate. + /// + /// The mesh. + public void SetIsolate(MeshBase mesh) + { + if (Window._skipEffectsGuiEvents) + return; + + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; + Window.UpdateEffectsOnAsset(); + UpdateEffectsOnUI(); + } + + /// + /// Sets the material slot index to highlight. + /// + /// The mesh. + public void SetHighlight(MeshBase mesh) + { + if (Window._skipEffectsGuiEvents) + return; + + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; + Window.UpdateEffectsOnAsset(); + UpdateEffectsOnUI(); + } + + protected virtual void OnGeneral(LayoutElementsContainer layout) + { + } + + protected class ProxyEditor : ProxyEditorBase + { + public override void Initialize(LayoutElementsContainer layout) + { + var proxy = (MeshesPropertiesProxyBase)Values[0]; + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) + return; + proxy._materialSlotComboBoxes.Clear(); + proxy._isolateCheckBoxes.Clear(); + proxy._highlightCheckBoxes.Clear(); + var countLODs = proxy.Asset.LODsCount; + var loadedLODs = proxy.Asset.LoadedLODs; + + // General properties + { + var group = layout.Group("General"); + + var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature."); + minScreenSize.ValueBox.MinValue = 0.0f; + minScreenSize.ValueBox.MaxValue = 1.0f; + minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize; + minScreenSize.ValueBox.ValueChanged += () => + { + proxy.Asset.MinScreenSize = minScreenSize.ValueBox.Value; + proxy.Window.MarkAsEdited(); + }; + } + proxy.OnGeneral(layout); + + // Group per LOD + for (int lodIndex = 0; lodIndex < countLODs; lodIndex++) + { + var group = layout.Group("LOD " + lodIndex); + if (lodIndex < countLODs - loadedLODs) + { + group.Label("Loading LOD..."); + continue; + } + var lod = proxy.Asset.GetLOD(lodIndex); + proxy.Asset.GetMeshes(out var meshes, lodIndex); + + int triangleCount = 0, vertexCount = 0; + for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) + { + var mesh = meshes[meshIndex]; + triangleCount += mesh.TriangleCount; + vertexCount += mesh.VertexCount; + } + + group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); + group.Label("Size: " + lod.Box.Size).AddCopyContextMenu(); + var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); + screenSize.ValueBox.MinValue = 0.0f; + screenSize.ValueBox.MaxValue = 10.0f; + screenSize.ValueBox.Value = lod.ScreenSize; + screenSize.ValueBox.ValueChanged += () => + { + lod.ScreenSize = screenSize.ValueBox.Value; + proxy.Window.MarkAsEdited(); + }; + + // Every mesh properties + for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) + { + var mesh = meshes[meshIndex]; + group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, vert: {mesh.VertexCount:N0})").AddCopyContextMenu(); + + // Material Slot + var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); + materialSlot.ComboBox.Tag = mesh; + materialSlot.ComboBox.SelectedIndexChanged += comboBox => proxy.SetMaterialSlot((MeshBase)comboBox.Tag, comboBox.SelectedIndex); + proxy._materialSlotComboBoxes.Add(materialSlot.ComboBox); + + // Isolate + var isolate = group.Checkbox("Isolate", "Shows only this mesh (and meshes using the same material slot)"); + isolate.CheckBox.Tag = mesh; + isolate.CheckBox.StateChanged += (box) => proxy.SetIsolate(box.Checked ? (MeshBase)box.Tag : null); + proxy._isolateCheckBoxes.Add(isolate.CheckBox); + + // Highlight + var highlight = group.Checkbox("Highlight", "Highlights this mesh with a tint color (and meshes using the same material slot)"); + highlight.CheckBox.Tag = mesh; + highlight.CheckBox.StateChanged += (box) => proxy.SetHighlight(box.Checked ? (MeshBase)box.Tag : null); + proxy._highlightCheckBoxes.Add(highlight.CheckBox); + } + } + + // Refresh UI + proxy.UpdateMaterialSlotsUI(); + } + } + } + + protected class MaterialsPropertiesProxyBase : PropertiesProxyBase + { + [Collection(CanReorderItems = true, NotNullItems = true, OverrideEditorTypeName = "FlaxEditor.CustomEditors.Editors.GenericEditor", Spacing = 10)] + [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] + public MaterialSlot[] MaterialSlots + { + get => Asset != null ? Asset.MaterialSlots : null; + set + { + if (Asset != null) + { + if (Asset.MaterialSlots.Length != value.Length) + { + MaterialBase[] materials = new MaterialBase[value.Length]; + string[] names = new string[value.Length]; + ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length]; + for (int i = 0; i < value.Length; i++) + { + if (value[i] != null) + { + materials[i] = value[i].Material; + names[i] = value[i].Name; + shadowsModes[i] = value[i].ShadowsMode; + } + else + { + materials[i] = null; + names[i] = "Material " + i; + shadowsModes[i] = ShadowsCastingMode.All; + } + } + + Asset.SetupMaterialSlots(value.Length); + + var slots = Asset.MaterialSlots; + for (int i = 0; i < slots.Length; i++) + { + slots[i].Material = materials[i]; + slots[i].Name = names[i]; + slots[i].ShadowsMode = shadowsModes[i]; + } + + UpdateMaterialSlotsUI(); + } + } + } + } + + private readonly List _materialSlotComboBoxes = new List(); + + /// + /// Updates the material slots UI parts. Should be called after material slot rename. + /// + public void UpdateMaterialSlotsUI() + { + Window._skipEffectsGuiEvents = true; + + // Generate material slots labels (with index prefix) + var slots = Asset.MaterialSlots; + var slotsLabels = new string[slots.Length]; + for (int i = 0; i < slots.Length; i++) + { + slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name); + } + + // Update comboboxes + for (int i = 0; i < _materialSlotComboBoxes.Count; i++) + { + var comboBox = _materialSlotComboBoxes[i]; + comboBox.SetItems(slotsLabels); + comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex; + } + + Window._skipEffectsGuiEvents = false; + } + + protected class ProxyEditor : ProxyEditorBase + { + public override void Initialize(LayoutElementsContainer layout) + { + var proxy = (MaterialsPropertiesProxyBase)Values[0]; + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) + return; + + base.Initialize(layout); + } + } + } + protected class UVsPropertiesProxyBase : PropertiesProxyBase { public enum UVChannel @@ -375,10 +679,52 @@ namespace FlaxEditor.Windows.Assets } } + protected class ImportPropertiesProxyBase : PropertiesProxyBase + { + private ModelImportSettings ImportSettings; + + /// + public override void OnLoad(TWindow window) + { + base.OnLoad(window); + + ImportSettings = window._importSettings; + } + + public void Reimport() + { + Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)Window.Item, ImportSettings, true); + } + + protected class ProxyEditor : ProxyEditorBase + { + public override void Initialize(LayoutElementsContainer layout) + { + var proxy = (ImportPropertiesProxyBase)Values[0]; + if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) + return; + + // Import Settings + { + var group = layout.Group("Import Settings"); + + var importSettingsField = typeof(ImportPropertiesProxyBase).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance); + var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; + group.Object(importSettingsValues); + + layout.Space(5); + var reimportButton = group.Button("Reimport"); + reimportButton.Button.Clicked += () => ((ImportPropertiesProxyBase)Values[0]).Reimport(); + } + } + } + } + protected readonly SplitPanel _split; protected readonly Tabs _tabs; protected readonly ToolStripButton _saveButton; + protected ModelImportSettings _importSettings = new ModelImportSettings(); protected bool _refreshOnLODsLoaded; protected bool _skipEffectsGuiEvents; protected int _isolateIndex = -1; @@ -416,6 +762,11 @@ namespace FlaxEditor.Windows.Assets }; } + /// + /// Updates the highlight/isolate effects on a model asset. + /// + protected abstract void UpdateEffectsOnAsset(); + /// protected override void UpdateToolstrip() { @@ -430,8 +781,8 @@ namespace FlaxEditor.Windows.Assets _meshData?.WaitForMeshDataRequestEnd(); foreach (var child in _tabs.Children) { - if (child is Tab tab && tab.Proxy.Window != null) - tab.Proxy.OnClean(); + if (child is Tab tab && tab.Proxy?.Window != null) + tab.Proxy?.OnClean(); } base.UnlinkItem(); @@ -440,11 +791,14 @@ namespace FlaxEditor.Windows.Assets /// protected override void OnAssetLoaded() { + _refreshOnLODsLoaded = true; + Editor.TryRestoreImportOptions(ref _importSettings.Settings, Item.Path); + UpdateEffectsOnAsset(); foreach (var child in _tabs.Children) { if (child is Tab tab) { - tab.Proxy.OnLoad((TWindow)this); + tab.Proxy?.OnLoad((TWindow)this); tab.Presenter.BuildLayout(); } } diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index dd4a3ec48..6fb24f069 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -1,14 +1,11 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. -using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using FlaxEditor.Content; using FlaxEditor.Content.Import; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; -using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; using FlaxEngine; @@ -55,386 +52,113 @@ namespace FlaxEditor.Windows.Assets } } - [CustomEditor(typeof(ProxyEditor))] - private sealed class MeshesPropertiesProxy : PropertiesProxyBase + [CustomEditor(typeof(MeshesPropertiesProxy.ProxyEditor))] + private sealed class MeshesPropertiesProxy : MeshesPropertiesProxyBase { - private readonly List _materialSlotComboBoxes = new List(); - private readonly List _isolateCheckBoxes = new List(); - private readonly List _highlightCheckBoxes = new List(); + private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex; + private CustomEditorPresenter _presenter; - public override void OnLoad(ModelWindow window) + protected override void OnGeneral(LayoutElementsContainer layout) { - base.OnLoad(window); + base.OnGeneral(layout); - Window._isolateIndex = -1; - Window._highlightIndex = -1; - } + _presenter = layout.Presenter; - public override void OnClean() - { - Window._isolateIndex = -1; - Window._highlightIndex = -1; - - base.OnClean(); - } - - private void UpdateEffectsOnUI() - { - Window._skipEffectsGuiEvents = true; - - for (int i = 0; i < _isolateCheckBoxes.Count; i++) + // SDF { - var checkBox = _isolateCheckBoxes[i]; - checkBox.Checked = Window._isolateIndex == ((Mesh)checkBox.Tag).MaterialSlotIndex; - } + var group = layout.Group("SDF"); + var sdfOptions = Window._sdfOptions; - for (int i = 0; i < _highlightCheckBoxes.Count; i++) - { - var checkBox = _highlightCheckBoxes[i]; - checkBox.Checked = Window._highlightIndex == ((Mesh)checkBox.Tag).MaterialSlotIndex; - } - - Window._skipEffectsGuiEvents = false; - } - - private void UpdateMaterialSlotsUI() - { - Window._skipEffectsGuiEvents = true; - - // Generate material slots labels (with index prefix) - var slots = Asset.MaterialSlots; - var slotsLabels = new string[slots.Length]; - for (int i = 0; i < slots.Length; i++) - { - slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name); - } - - // Update comboboxes - for (int i = 0; i < _materialSlotComboBoxes.Count; i++) - { - var comboBox = _materialSlotComboBoxes[i]; - comboBox.SetItems(slotsLabels); - comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex; - } - - Window._skipEffectsGuiEvents = false; - } - - private void SetMaterialSlot(Mesh mesh, int newSlotIndex) - { - if (Window._skipEffectsGuiEvents) - return; - - mesh.MaterialSlotIndex = newSlotIndex == -1 ? 0 : newSlotIndex; - Window.UpdateEffectsOnAsset(); - UpdateEffectsOnUI(); - Window.MarkAsEdited(); - } - - private void SetIsolate(Mesh mesh) - { - if (Window._skipEffectsGuiEvents) - return; - - Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; - Window.UpdateEffectsOnAsset(); - UpdateEffectsOnUI(); - } - - private void SetHighlight(Mesh mesh) - { - if (Window._skipEffectsGuiEvents) - return; - - Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; - Window.UpdateEffectsOnAsset(); - UpdateEffectsOnUI(); - } - - private class ProxyEditor : ProxyEditorBase - { - private CustomEditors.Elements.IntegerValueElement _sdfModelLodIndex; - - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (MeshesPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - proxy._materialSlotComboBoxes.Clear(); - proxy._isolateCheckBoxes.Clear(); - proxy._highlightCheckBoxes.Clear(); - var lods = proxy.Asset.LODs; - var loadedLODs = proxy.Asset.LoadedLODs; - - // General properties + var sdf = Asset.SDF; + if (sdf.Texture != null) { - var group = layout.Group("General"); - - var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature."); - minScreenSize.ValueBox.MinValue = 0.0f; - minScreenSize.ValueBox.MaxValue = 1.0f; - minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize; - minScreenSize.ValueBox.BoxValueChanged += b => - { - proxy.Asset.MinScreenSize = b.Value; - proxy.Window.MarkAsEdited(); - }; + var size = sdf.Texture.Size3; + group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu(); + } + else + { + group.Label("No SDF"); } - // SDF + var resolution = group.FloatValue("Resolution Scale", Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution))); + resolution.ValueBox.MinValue = 0.0001f; + resolution.ValueBox.MaxValue = 100.0f; + resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f; + resolution.ValueBox.BoxValueChanged += b => { Window._importSettings.Settings.SDFResolution = b.Value; }; + Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale; + + var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage)."); + gpu.CheckBox.Checked = sdfOptions.GPU; + + var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh."); + var backfacesThreshold = backfacesThresholdProp.FloatValue(); + var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last(); + backfacesThreshold.ValueBox.MinValue = 0.001f; + backfacesThreshold.ValueBox.MaxValue = 1.0f; + backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold; + backfacesThreshold.ValueBox.BoxValueChanged += b => { Window._sdfOptions.BackfacesThreshold = b.Value; }; + + // Toggle Backfaces Threshold visibility (CPU-only option) + gpu.CheckBox.StateChanged += c => { - var group = layout.Group("SDF"); - var sdfOptions = proxy.Window._sdfOptions; + Window._sdfOptions.GPU = c.Checked; + backfacesThresholdLabel.Visible = !c.Checked; + backfacesThreshold.ValueBox.Visible = !c.Checked; + }; + backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked; + backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked; - var sdf = proxy.Asset.SDF; - if (sdf.Texture != null) - { - var size = sdf.Texture.Size3; - group.Label($"SDF Texture {size.X}x{size.Y}x{size.Z} ({Utilities.Utils.FormatBytesCount(sdf.Texture.MemoryUsage)})").AddCopyContextMenu(); - } - else - { - group.Label("No SDF"); - } + var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building."); + lodIndex.IntValue.MinValue = 0; + lodIndex.IntValue.MaxValue = Asset.LODsCount - 1; + lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6; + _sdfModelLodIndex = lodIndex; - var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution))); - resolution.ValueBox.MinValue = 0.0001f; - resolution.ValueBox.MaxValue = 100.0f; - resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f; - resolution.ValueBox.BoxValueChanged += b => { proxy.Window._importSettings.Settings.SDFResolution = b.Value; }; - proxy.Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale; - - var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage)."); - gpu.CheckBox.Checked = sdfOptions.GPU; - - var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh."); - var backfacesThreshold = backfacesThresholdProp.FloatValue(); - var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last(); - backfacesThreshold.ValueBox.MinValue = 0.001f; - backfacesThreshold.ValueBox.MaxValue = 1.0f; - backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold; - backfacesThreshold.ValueBox.BoxValueChanged += b => { proxy.Window._sdfOptions.BackfacesThreshold = b.Value; }; - - // Toggle Backfaces Threshold visibility (CPU-only option) - gpu.CheckBox.StateChanged += c => - { - proxy.Window._sdfOptions.GPU = c.Checked; - backfacesThresholdLabel.Visible = !c.Checked; - backfacesThreshold.ValueBox.Visible = !c.Checked; - }; - backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked; - backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked; - - var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building."); - lodIndex.IntValue.MinValue = 0; - lodIndex.IntValue.MaxValue = lods.Length - 1; - lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6; - _sdfModelLodIndex = lodIndex; - - var buttons = group.CustomContainer(); - var gridControl = buttons.CustomControl; - gridControl.ClipChildren = false; - gridControl.Height = Button.DefaultHeight; - gridControl.SlotsHorizontally = 2; - gridControl.SlotsVertically = 1; - var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button; - rebuildButton.Clicked += OnRebuildSDF; - var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button; - removeButton.Enabled = sdf.Texture != null; - removeButton.Clicked += OnRemoveSDF; - } - - // Group per LOD - for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++) - { - var group = layout.Group("LOD " + lodIndex); - if (lodIndex < lods.Length - loadedLODs) - { - group.Label("Loading LOD..."); - continue; - } - var lod = lods[lodIndex]; - var meshes = lod.Meshes; - - int triangleCount = 0, vertexCount = 0; - for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) - { - var mesh = meshes[meshIndex]; - triangleCount += mesh.TriangleCount; - vertexCount += mesh.VertexCount; - } - - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); - group.Label("Size: " + lod.Box.Size).AddCopyContextMenu(); - var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); - screenSize.ValueBox.MinValue = 0.0f; - screenSize.ValueBox.MaxValue = 10.0f; - screenSize.ValueBox.Value = lod.ScreenSize; - screenSize.ValueBox.BoxValueChanged += b => - { - lod.ScreenSize = b.Value; - proxy.Window.MarkAsEdited(); - }; - - // Every mesh properties - for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) - { - var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu(); - - // Material Slot - var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); - materialSlot.ComboBox.Tag = mesh; - materialSlot.ComboBox.SelectedIndexChanged += comboBox => proxy.SetMaterialSlot((Mesh)comboBox.Tag, comboBox.SelectedIndex); - proxy._materialSlotComboBoxes.Add(materialSlot.ComboBox); - - // Isolate - var isolate = group.Checkbox("Isolate", "Shows only this mesh (and meshes using the same material slot)"); - isolate.CheckBox.Tag = mesh; - isolate.CheckBox.StateChanged += (box) => proxy.SetIsolate(box.Checked ? (Mesh)box.Tag : null); - proxy._isolateCheckBoxes.Add(isolate.CheckBox); - - // Highlight - var highlight = group.Checkbox("Highlight", "Highlights this mesh with a tint color (and meshes using the same material slot)"); - highlight.CheckBox.Tag = mesh; - highlight.CheckBox.StateChanged += (box) => proxy.SetHighlight(box.Checked ? (Mesh)box.Tag : null); - proxy._highlightCheckBoxes.Add(highlight.CheckBox); - } - } - - // Refresh UI - proxy.UpdateMaterialSlotsUI(); + var buttons = group.CustomContainer(); + var gridControl = buttons.CustomControl; + gridControl.ClipChildren = false; + gridControl.Height = Button.DefaultHeight; + gridControl.SlotsHorizontally = 2; + gridControl.SlotsVertically = 1; + var rebuildButton = buttons.Button("Rebuild", "Rebuilds the model SDF.").Button; + rebuildButton.Clicked += OnRebuildSDF; + var removeButton = buttons.Button("Remove", "Removes the model SDF data from the asset.").Button; + removeButton.Enabled = sdf.Texture != null; + removeButton.Clicked += OnRemoveSDF; } + } - private void OnRebuildSDF() + private void OnRebuildSDF() + { + Window.Enabled = false; + Task.Run(() => { - var proxy = (MeshesPropertiesProxy)Values[0]; - proxy.Window.Enabled = false; - Task.Run(() => + var sdfOptions = Window._sdfOptions; + bool failed = Asset.GenerateSDF(Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, sdfOptions.BackfacesThreshold, sdfOptions.GPU); + FlaxEngine.Scripting.InvokeOnUpdate(() => { - var sdfOptions = proxy.Window._sdfOptions; - bool failed = proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, sdfOptions.BackfacesThreshold, sdfOptions.GPU); - FlaxEngine.Scripting.InvokeOnUpdate(() => - { - proxy.Window.Enabled = true; - if (!failed) - proxy.Window.MarkAsEdited(); - Presenter.BuildLayoutOnUpdate(); + Window.Enabled = true; + if (!failed) + Window.MarkAsEdited(); + _presenter.BuildLayoutOnUpdate(); - // Save some SDF options locally in the project cache - proxy.Window.Editor.ProjectCache.SetCustomData(JsonSerializer.GetStringID(proxy.Window.Item.ID) + ".SDF", JsonSerializer.Serialize(sdfOptions)); - }); + // Save some SDF options locally in the project cache + Window.Editor.ProjectCache.SetCustomData(JsonSerializer.GetStringID(Window.Item.ID) + ".SDF", JsonSerializer.Serialize(sdfOptions)); }); - } + }); + } - private void OnRemoveSDF() - { - var proxy = (MeshesPropertiesProxy)Values[0]; - proxy.Asset.SetSDF(new ModelBase.SDFData()); - proxy.Window.MarkAsEdited(); - Presenter.BuildLayoutOnUpdate(); - } - - internal override void RefreshInternal() - { - // Skip updates when model is not loaded - var proxy = (MeshesPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - return; - - base.RefreshInternal(); - } + private void OnRemoveSDF() + { + Asset.SetSDF(new ModelBase.SDFData()); + Window.MarkAsEdited(); + _presenter.BuildLayoutOnUpdate(); } } [CustomEditor(typeof(ProxyEditor))] - private sealed class MaterialsPropertiesProxy : PropertiesProxyBase + private sealed class MaterialsPropertiesProxy : MaterialsPropertiesProxyBase { - [Collection(CanReorderItems = true, NotNullItems = true, OverrideEditorTypeName = "FlaxEditor.CustomEditors.Editors.GenericEditor", Spacing = 10)] - [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] - public MaterialSlot[] MaterialSlots - { - get => Asset != null ? Asset.MaterialSlots : null; - set - { - if (Asset != null) - { - if (Asset.MaterialSlots.Length != value.Length) - { - MaterialBase[] materials = new MaterialBase[value.Length]; - string[] names = new string[value.Length]; - ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length]; - for (int i = 0; i < value.Length; i++) - { - if (value[i] != null) - { - materials[i] = value[i].Material; - names[i] = value[i].Name; - shadowsModes[i] = value[i].ShadowsMode; - } - else - { - materials[i] = null; - names[i] = "Material " + i; - shadowsModes[i] = ShadowsCastingMode.All; - } - } - - Asset.SetupMaterialSlots(value.Length); - - var slots = Asset.MaterialSlots; - for (int i = 0; i < slots.Length; i++) - { - slots[i].Material = materials[i]; - slots[i].Name = names[i]; - slots[i].ShadowsMode = shadowsModes[i]; - } - - UpdateMaterialSlotsUI(); - } - } - } - } - - private readonly List _materialSlotComboBoxes = new List(); - - /// - /// Updates the material slots UI parts. Should be called after material slot rename. - /// - public void UpdateMaterialSlotsUI() - { - Window._skipEffectsGuiEvents = true; - - // Generate material slots labels (with index prefix) - var slots = Asset.MaterialSlots; - var slotsLabels = new string[slots.Length]; - for (int i = 0; i < slots.Length; i++) - { - slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name); - } - - // Update comboboxes - for (int i = 0; i < _materialSlotComboBoxes.Count; i++) - { - var comboBox = _materialSlotComboBoxes[i]; - comboBox.SetItems(slotsLabels); - comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex; - } - - Window._skipEffectsGuiEvents = false; - } - - private class ProxyEditor : ProxyEditorBase - { - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (MaterialsPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - - base.Initialize(layout); - } - } } [CustomEditor(typeof(ProxyEditor))] @@ -443,45 +167,8 @@ namespace FlaxEditor.Windows.Assets } [CustomEditor(typeof(ProxyEditor))] - private sealed class ImportPropertiesProxy : PropertiesProxyBase + private sealed class ImportPropertiesProxy : ImportPropertiesProxyBase { - private ModelImportSettings ImportSettings; - - /// - public override void OnLoad(ModelWindow window) - { - base.OnLoad(window); - - ImportSettings = window._importSettings; - } - - public void Reimport() - { - Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)Window.Item, ImportSettings, true); - } - - private class ProxyEditor : ProxyEditorBase - { - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (ImportPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - - // Import Settings - { - var group = layout.Group("Import Settings"); - - var importSettingsField = typeof(ImportPropertiesProxy).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance); - var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; - group.Object(importSettingsValues); - - layout.Space(5); - var reimportButton = group.Button("Reimport"); - reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport(); - } - } - } } private class MeshesTab : Tab @@ -532,7 +219,6 @@ namespace FlaxEditor.Windows.Assets private readonly ModelPreview _preview; private StaticModel _highlightActor; - private ModelImportSettings _importSettings = new ModelImportSettings(); private ModelSdfOptions _sdfOptions; private ToolStripButton _showCurrentLODButton; @@ -579,10 +265,8 @@ namespace FlaxEditor.Windows.Assets _preview.Task.AddCustomActor(_highlightActor); } - /// - /// Updates the highlight/isolate effects on a model asset. - /// - private void UpdateEffectsOnAsset() + /// + protected override void UpdateEffectsOnAsset() { var entries = _preview.PreviewActor.Entries; if (entries != null) @@ -688,12 +372,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void OnAssetLoaded() { - _refreshOnLODsLoaded = true; _preview.ViewportCamera.SetArcBallView(Asset.GetBox()); - Editor.TryRestoreImportOptions(ref _importSettings.Settings, Item.Path); - UpdateEffectsOnAsset(); - - // TODO: disable streaming for this model base.OnAssetLoaded(); } diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 21f5ee209..f54fe9d73 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using FlaxEditor.Content; -using FlaxEditor.Content.Import; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; @@ -54,220 +52,8 @@ namespace FlaxEditor.Windows.Assets } [CustomEditor(typeof(ProxyEditor))] - private sealed class MeshesPropertiesProxy : PropertiesProxyBase + private sealed class MeshesPropertiesProxy : MeshesPropertiesProxyBase { - private readonly List _materialSlotComboBoxes = new List(); - private readonly List _isolateCheckBoxes = new List(); - private readonly List _highlightCheckBoxes = new List(); - - public override void OnLoad(SkinnedModelWindow window) - { - base.OnLoad(window); - - Window._isolateIndex = -1; - Window._highlightIndex = -1; - } - - public override void OnClean() - { - Window._isolateIndex = -1; - Window._highlightIndex = -1; - - base.OnClean(); - } - - /// - /// Updates the highlight/isolate effects on UI. - /// - public void UpdateEffectsOnUI() - { - Window._skipEffectsGuiEvents = true; - - for (int i = 0; i < _isolateCheckBoxes.Count; i++) - { - var checkBox = _isolateCheckBoxes[i]; - checkBox.Checked = Window._isolateIndex == ((SkinnedMesh)checkBox.Tag).MaterialSlotIndex; - } - - for (int i = 0; i < _highlightCheckBoxes.Count; i++) - { - var checkBox = _highlightCheckBoxes[i]; - checkBox.Checked = Window._highlightIndex == ((SkinnedMesh)checkBox.Tag).MaterialSlotIndex; - } - - Window._skipEffectsGuiEvents = false; - } - - /// - /// Updates the material slots UI parts. Should be called after material slot rename. - /// - public void UpdateMaterialSlotsUI() - { - Window._skipEffectsGuiEvents = true; - - // Generate material slots labels (with index prefix) - var slots = Asset.MaterialSlots; - var slotsLabels = new string[slots.Length]; - for (int i = 0; i < slots.Length; i++) - { - slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name); - } - - // Update comboboxes - for (int i = 0; i < _materialSlotComboBoxes.Count; i++) - { - var comboBox = _materialSlotComboBoxes[i]; - comboBox.SetItems(slotsLabels); - comboBox.SelectedIndex = ((SkinnedMesh)comboBox.Tag).MaterialSlotIndex; - } - - Window._skipEffectsGuiEvents = false; - } - - /// - /// Sets the material slot index to the mesh. - /// - /// The mesh. - /// New index of the material slot to use. - public void SetMaterialSlot(SkinnedMesh mesh, int newSlotIndex) - { - if (Window._skipEffectsGuiEvents) - return; - - mesh.MaterialSlotIndex = newSlotIndex == -1 ? 0 : newSlotIndex; - Window.UpdateEffectsOnAsset(); - UpdateEffectsOnUI(); - Window.MarkAsEdited(); - } - - /// - /// Sets the material slot to isolate. - /// - /// The mesh. - public void SetIsolate(SkinnedMesh mesh) - { - if (Window._skipEffectsGuiEvents) - return; - - Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; - Window.UpdateEffectsOnAsset(); - UpdateEffectsOnUI(); - } - - /// - /// Sets the material slot index to highlight. - /// - /// The mesh. - public void SetHighlight(SkinnedMesh mesh) - { - if (Window._skipEffectsGuiEvents) - return; - - Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; - Window.UpdateEffectsOnAsset(); - UpdateEffectsOnUI(); - } - - private class ProxyEditor : ProxyEditorBase - { - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (MeshesPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - proxy._materialSlotComboBoxes.Clear(); - proxy._isolateCheckBoxes.Clear(); - proxy._highlightCheckBoxes.Clear(); - var lods = proxy.Asset.LODs; - var loadedLODs = proxy.Asset.LoadedLODs; - - // General properties - { - var group = layout.Group("General"); - - var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature."); - minScreenSize.ValueBox.MinValue = 0.0f; - minScreenSize.ValueBox.MaxValue = 1.0f; - minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize; - minScreenSize.ValueBox.ValueChanged += () => - { - proxy.Asset.MinScreenSize = minScreenSize.ValueBox.Value; - proxy.Window.MarkAsEdited(); - }; - } - - // Group per LOD - for (int lodIndex = 0; lodIndex < lods.Length; lodIndex++) - { - var group = layout.Group("LOD " + lodIndex); - if (lodIndex < lods.Length - loadedLODs) - { - group.Label("Loading LOD..."); - continue; - } - var lod = lods[lodIndex]; - var meshes = lod.Meshes; - - int triangleCount = 0, vertexCount = 0; - for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) - { - var mesh = meshes[meshIndex]; - triangleCount += mesh.TriangleCount; - vertexCount += mesh.VertexCount; - } - - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); - group.Label("Size: " + lod.Box.Size); - var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); - screenSize.ValueBox.MinValue = 0.0f; - screenSize.ValueBox.MaxValue = 10.0f; - screenSize.ValueBox.Value = lod.ScreenSize; - screenSize.ValueBox.ValueChanged += () => - { - lod.ScreenSize = screenSize.ValueBox.Value; - proxy.Window.MarkAsEdited(); - }; - - // Every mesh properties - for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) - { - var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, verts: {mesh.VertexCount:N0})").AddCopyContextMenu(); - - // Material Slot - var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); - materialSlot.ComboBox.Tag = mesh; - materialSlot.ComboBox.SelectedIndexChanged += comboBox => proxy.SetMaterialSlot((SkinnedMesh)comboBox.Tag, comboBox.SelectedIndex); - proxy._materialSlotComboBoxes.Add(materialSlot.ComboBox); - - // Isolate - var isolate = group.Checkbox("Isolate", "Shows only this mesh (and meshes using the same material slot)"); - isolate.CheckBox.Tag = mesh; - isolate.CheckBox.StateChanged += (box) => proxy.SetIsolate(box.Checked ? (SkinnedMesh)box.Tag : null); - proxy._isolateCheckBoxes.Add(isolate.CheckBox); - - // Highlight - var highlight = group.Checkbox("Highlight", "Highlights this mesh with a tint color (and meshes using the same material slot)"); - highlight.CheckBox.Tag = mesh; - highlight.CheckBox.StateChanged += (box) => proxy.SetHighlight(box.Checked ? (SkinnedMesh)box.Tag : null); - proxy._highlightCheckBoxes.Add(highlight.CheckBox); - } - } - - // Refresh UI - proxy.UpdateMaterialSlotsUI(); - } - - internal override void RefreshInternal() - { - // Skip updates when model is not loaded - var proxy = (MeshesPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - return; - - base.RefreshInternal(); - } - } } [CustomEditor(typeof(ProxyEditor))] @@ -393,107 +179,12 @@ namespace FlaxEditor.Windows.Assets } } } - - internal override void RefreshInternal() - { - // Skip updates when model is not loaded - var proxy = (MeshesPropertiesProxy)Values[0]; - if (proxy.Asset == null || !proxy.Asset.IsLoaded) - return; - - base.RefreshInternal(); - } } } [CustomEditor(typeof(ProxyEditor))] - private sealed class MaterialsPropertiesProxy : PropertiesProxyBase + private sealed class MaterialsPropertiesProxy : MaterialsPropertiesProxyBase { - [Collection(CanReorderItems = true, NotNullItems = true, OverrideEditorTypeName = "FlaxEditor.CustomEditors.Editors.GenericEditor", Spacing = 10)] - [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] - public MaterialSlot[] MaterialSlots - { - get => Asset != null ? Asset.MaterialSlots : null; - set - { - if (Asset != null) - { - if (Asset.MaterialSlots.Length != value.Length) - { - MaterialBase[] materials = new MaterialBase[value.Length]; - string[] names = new string[value.Length]; - ShadowsCastingMode[] shadowsModes = new ShadowsCastingMode[value.Length]; - for (int i = 0; i < value.Length; i++) - { - if (value[i] != null) - { - materials[i] = value[i].Material; - names[i] = value[i].Name; - shadowsModes[i] = value[i].ShadowsMode; - } - else - { - materials[i] = null; - names[i] = "Material " + i; - shadowsModes[i] = ShadowsCastingMode.All; - } - } - - Asset.SetupMaterialSlots(value.Length); - - var slots = Asset.MaterialSlots; - for (int i = 0; i < slots.Length; i++) - { - slots[i].Material = materials[i]; - slots[i].Name = names[i]; - slots[i].ShadowsMode = shadowsModes[i]; - } - - UpdateMaterialSlotsUI(); - } - } - } - } - - private readonly List _materialSlotComboBoxes = new List(); - - /// - /// Updates the material slots UI parts. Should be called after material slot rename. - /// - public void UpdateMaterialSlotsUI() - { - Window._skipEffectsGuiEvents = true; - - // Generate material slots labels (with index prefix) - var slots = Asset.MaterialSlots; - var slotsLabels = new string[slots.Length]; - for (int i = 0; i < slots.Length; i++) - { - slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name); - } - - // Update comboboxes - for (int i = 0; i < _materialSlotComboBoxes.Count; i++) - { - var comboBox = _materialSlotComboBoxes[i]; - comboBox.SetItems(slotsLabels); - comboBox.SelectedIndex = ((SkinnedMesh)comboBox.Tag).MaterialSlotIndex; - } - - Window._skipEffectsGuiEvents = false; - } - - private class ProxyEditor : ProxyEditorBase - { - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (MaterialsPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - - base.Initialize(layout); - } - } } [CustomEditor(typeof(ProxyEditor))] @@ -788,45 +479,8 @@ namespace FlaxEditor.Windows.Assets } [CustomEditor(typeof(ProxyEditor))] - private sealed class ImportPropertiesProxy : PropertiesProxyBase + private sealed class ImportPropertiesProxy : ImportPropertiesProxyBase { - private ModelImportSettings ImportSettings = new ModelImportSettings(); - - /// - public override void OnLoad(SkinnedModelWindow window) - { - base.OnLoad(window); - - Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path); - } - - public void Reimport() - { - Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)Window.Item, ImportSettings, true); - } - - private class ProxyEditor : ProxyEditorBase - { - public override void Initialize(LayoutElementsContainer layout) - { - var proxy = (ImportPropertiesProxy)Values[0]; - if (Utilities.Utils.OnAssetProperties(layout, proxy.Asset)) - return; - - // Import Settings - { - var group = layout.Group("Import Settings"); - - var importSettingsField = typeof(ImportPropertiesProxy).GetField("ImportSettings", BindingFlags.NonPublic | BindingFlags.Instance); - var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; - group.Object(importSettingsValues); - - layout.Space(5); - var reimportButton = group.Button("Reimport"); - reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport(); - } - } - } } private class MeshesTab : Tab @@ -960,10 +614,8 @@ namespace FlaxEditor.Windows.Assets _preview.Task.AddCustomActor(_highlightActor); } - /// - /// Updates the highlight/isolate effects on a model asset. - /// - private void UpdateEffectsOnAsset() + /// + protected override void UpdateEffectsOnAsset() { var entries = _preview.PreviewActor.Entries; if (entries != null) @@ -1071,11 +723,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void OnAssetLoaded() { - _refreshOnLODsLoaded = true; _preview.ViewportCamera.SetArcBallView(_preview.GetBounds()); - UpdateEffectsOnAsset(); - - // TODO: disable streaming for this model // Reset any root motion _preview.PreviewActor.ResetLocalTransform(); diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index a97c82a33..e7e1330b7 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -7,7 +7,6 @@ #include "Engine/Content/WeakAssetReference.h" #include "Engine/Content/Upgraders/ModelAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" -#include "Engine/Core/Math/Transform.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" @@ -529,6 +528,18 @@ int32 Model::GetLODsCount() const return LODs.Count(); } +const ModelLODBase* Model::GetLOD(int32 lodIndex) const +{ + CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr); + return &LODs.Get()[lodIndex]; +} + +ModelLODBase* Model::GetLOD(int32 lodIndex) +{ + CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr); + return &LODs.Get()[lodIndex]; +} + const MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) const { auto& lod = LODs[lodIndex]; @@ -543,18 +554,12 @@ MeshBase* Model::GetMesh(int32 meshIndex, int32 lodIndex) void Model::GetMeshes(Array& meshes, int32 lodIndex) const { - auto& lod = LODs[lodIndex]; - meshes.Resize(lod.Meshes.Count()); - for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) - meshes[meshIndex] = &lod.Meshes[meshIndex]; + LODs[lodIndex].GetMeshes(meshes); } void Model::GetMeshes(Array& meshes, int32 lodIndex) { - auto& lod = LODs[lodIndex]; - meshes.Resize(lod.Meshes.Count()); - for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) - meshes[meshIndex] = &lod.Meshes[meshIndex]; + LODs[lodIndex].GetMeshes(meshes); } void Model::InitAsVirtual() @@ -672,12 +677,6 @@ AssetChunksFlag Model::getChunksToPreload() const return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15); } -bool ModelLOD::HasAnyMeshInitialized() const -{ - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); -} - bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh) { bool result = false; @@ -722,57 +721,31 @@ bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& dist return result; } -BoundingBox ModelLOD::GetBox(const Matrix& world) const +int32 ModelLOD::GetMeshesCount() const { - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - const auto& mesh = Meshes[meshIndex]; - mesh.GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - return BoundingBox(min, max); + return Meshes.Count(); } -BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const +const MeshBase* ModelLOD::GetMesh(int32 index) const { - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - const auto& mesh = Meshes[meshIndex]; - BoundingBox box = mesh.GetBox(); - if (deformation) - deformation->GetBounds(_lodIndex, meshIndex, box); - box.GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - transform.LocalToWorld(corners[i], tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - return BoundingBox(min, max); + return Meshes.Get() + index; } -BoundingBox ModelLOD::GetBox() const +MeshBase* ModelLOD::GetMesh(int32 index) { - Vector3 min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - Meshes[meshIndex].GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - min = Vector3::Min(min, corners[i]); - max = Vector3::Max(max, corners[i]); - } - } - return BoundingBox(min, max); + return Meshes.Get() + index; +} + +void ModelLOD::GetMeshes(Array& meshes) +{ + meshes.Resize(Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + meshes[meshIndex] = &Meshes.Get()[meshIndex]; +} + +void ModelLOD::GetMeshes(Array& meshes) const +{ + meshes.Resize(Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + meshes[meshIndex] = &Meshes.Get()[meshIndex]; } diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index 3907e2703..aedc23dbf 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -8,16 +8,15 @@ /// /// Represents single Level Of Detail for the model. Contains a collection of the meshes. /// -API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ModelLODBase { - DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ScriptingObject); + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ModelLODBase); friend Model; friend Mesh; private: - Model* _model = nullptr; - int32 _lodIndex = 0; uint32 _verticesCount = 0; + Model* _model = nullptr; void Link(Model* model, int32 lodIndex) { @@ -27,30 +26,11 @@ private: } public: - /// - /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. - /// - API_FIELD() float ScreenSize = 1.0f; - /// /// The meshes array. /// API_FIELD(ReadOnly) Array Meshes; - /// - /// Determines whether any mesh has been initialized. - /// - /// True if any mesh has been initialized, otherwise false. - bool HasAnyMeshInitialized() const; - - /// - /// Gets the model LOD index. - /// - API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const - { - return _lodIndex; - } - /// /// Gets the vertex count for this model LOD level. /// @@ -82,26 +62,6 @@ public: /// True whether the two objects intersected bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh); - /// - /// Get model bounding box in transformed world matrix. - /// - /// World matrix - /// Bounding box - BoundingBox GetBox(const Matrix& world) const; - - /// - /// Get model bounding box in transformed world. - /// - /// The instance transformation. - /// The meshes deformation container (optional). - /// Bounding box - BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; - - /// - /// Gets the bounding box combined for all meshes in this model LOD. - /// - API_PROPERTY() BoundingBox GetBox() const; - /// /// Draws the meshes. Binds vertex and index buffers and invokes the draw calls. /// @@ -152,6 +112,14 @@ public: for (int32 i = 0; i < Meshes.Count(); i++) Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor); } + +public: + // [ModelLODBase] + int32 GetMeshesCount() const override; + const MeshBase* GetMesh(int32 index) const override; + MeshBase* GetMesh(int32 index) override; + void GetMeshes(Array& meshes) override; + void GetMeshes(Array& meshes) const override; }; /// @@ -312,6 +280,8 @@ public: // [ModelBase] void SetupMaterialSlots(int32 slotsCount) override; int32 GetLODsCount() const override; + const ModelLODBase* GetLOD(int32 lodIndex) const override; + ModelLODBase* GetLOD(int32 lodIndex) override; const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override; MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override; void GetMeshes(Array& meshes, int32 lodIndex = 0) const override; diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index cf7969e5d..31e52c1c7 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -7,6 +7,7 @@ #include "Engine/Graphics/Config.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Models/MeshBase.h" +#include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" #if GPU_ENABLE_ASYNC_RESOURCES_CREATION #include "Engine/Threading/ThreadPoolTask.h" @@ -102,6 +103,69 @@ public: } }; +bool ModelLODBase::HasAnyMeshInitialized() const +{ + // Note: we initialize all meshes at once so the last one can be used to check it. + const int32 meshCount = GetMeshesCount(); + return meshCount != 0 && GetMesh(meshCount - 1)->IsInitialized(); +} + +BoundingBox ModelLODBase::GetBox() const +{ + Vector3 min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + const int32 meshCount = GetMeshesCount(); + for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++) + { + GetMesh(meshIndex)->GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + min = Vector3::Min(min, corners[i]); + max = Vector3::Max(max, corners[i]); + } + } + return BoundingBox(min, max); +} + +BoundingBox ModelLODBase::GetBox(const Matrix& world) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + const int32 meshCount = GetMeshesCount(); + for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++) + { + GetMesh(meshIndex)->GetBox().GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + Vector3::Transform(corners[i], world, tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + +BoundingBox ModelLODBase::GetBox(const Transform& transform, const MeshDeformation* deformation) const +{ + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + const int32 meshCount = GetMeshesCount(); + for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++) + { + BoundingBox box = GetMesh(meshIndex)->GetBox(); + if (deformation) + deformation->GetBounds(_lodIndex, meshIndex, box); + box.GetCorners(corners); + for (int32 i = 0; i < 8; i++) + { + transform.LocalToWorld(corners[i], tmp); + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + return BoundingBox(min, max); +} + ModelBase::~ModelBase() { ASSERT(_streamingTask == nullptr); diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 22d502f25..73d70537c 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -22,6 +22,88 @@ class MeshBase; class StreamModelLODTask; struct RenderContextBatch; +/// +/// Base class for mesh LOD objects. Contains a collection of the meshes. +/// +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelLODBase : public ScriptingObject +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(ModelLODBase); + +protected: + int32 _lodIndex = 0; + + explicit ModelLODBase(const SpawnParams& params) + : ScriptingObject(params) + { + } + +public: + /// + /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. + /// + API_FIELD() float ScreenSize = 1.0f; + + /// + /// Gets the model LOD index. + /// + API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const + { + return _lodIndex; + } + + /// + /// Determines whether any mesh has been initialized. + /// + bool HasAnyMeshInitialized() const; + +public: + /// + /// Gets the bounding box combined for all meshes in this model LOD. + /// + API_PROPERTY() BoundingBox GetBox() const; + + /// + /// Get model bounding box in transformed world matrix. + /// + /// World matrix + /// Bounding box + BoundingBox GetBox(const Matrix& world) const; + + /// + /// Get model bounding box in transformed world. + /// + /// The instance transformation. + /// The meshes deformation container (optional). + /// Bounding box + BoundingBox GetBox(const Transform& transform, const class MeshDeformation* deformation = nullptr) const; + +public: + /// + /// Gets the amount of meshes in this LOD. + /// + virtual int32 GetMeshesCount() const = 0; + + /// + /// Gets the specific mesh in this LOD. + /// + virtual const MeshBase* GetMesh(int32 index) const = 0; + + /// + /// Gets the specific mesh in this LOD. + /// + API_FUNCTION(Sealed) virtual MeshBase* GetMesh(int32 index) = 0; + + /// + /// Gets the meshes in this LOD. + /// + virtual void GetMeshes(Array& meshes) const = 0; + + /// + /// Gets the meshes in this LOD. + /// + API_FUNCTION(Sealed) virtual void GetMeshes(Array& meshes) = 0; +}; + /// /// Base class for asset types that can contain a model resource. /// @@ -180,6 +262,16 @@ public: /// API_PROPERTY(Sealed) virtual int32 GetLODsCount() const = 0; + /// + /// Gets the mesh for a particular LOD index. + /// + virtual const ModelLODBase* GetLOD(int32 lodIndex) const = 0; + + /// + /// Gets the mesh for a particular LOD index. + /// + API_FUNCTION(Sealed) virtual ModelLODBase* GetLOD(int32 lodIndex) = 0; + /// /// Gets the mesh for a particular LOD index. /// diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 0c866de37..59cb699e0 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -880,6 +880,18 @@ int32 SkinnedModel::GetLODsCount() const return LODs.Count(); } +const ModelLODBase* SkinnedModel::GetLOD(int32 lodIndex) const +{ + CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr); + return &LODs.Get()[lodIndex]; +} + +ModelLODBase* SkinnedModel::GetLOD(int32 lodIndex) +{ + CHECK_RETURN(LODs.IsValidIndex(lodIndex), nullptr); + return &LODs.Get()[lodIndex]; +} + const MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) const { auto& lod = LODs[lodIndex]; @@ -894,18 +906,12 @@ MeshBase* SkinnedModel::GetMesh(int32 meshIndex, int32 lodIndex) void SkinnedModel::GetMeshes(Array& meshes, int32 lodIndex) const { - auto& lod = LODs[lodIndex]; - meshes.Resize(lod.Meshes.Count()); - for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) - meshes[meshIndex] = &lod.Meshes[meshIndex]; + LODs[lodIndex].GetMeshes(meshes); } void SkinnedModel::GetMeshes(Array& meshes, int32 lodIndex) { - auto& lod = LODs[lodIndex]; - meshes.Resize(lod.Meshes.Count()); - for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) - meshes[meshIndex] = &lod.Meshes[meshIndex]; + LODs[lodIndex].GetMeshes(meshes); } void SkinnedModel::InitAsVirtual() @@ -977,12 +983,6 @@ AssetChunksFlag SkinnedModel::getChunksToPreload() const return GET_CHUNK_FLAG(0); } -bool SkinnedModelLOD::HasAnyMeshInitialized() const -{ - // Note: we initialize all meshes at once so the last one can be used to check it. - return Meshes.HasItems() && Meshes.Last().IsInitialized(); -} - bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh) { // Check all meshes @@ -1033,78 +1033,31 @@ bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Rea return result; } -BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const +int32 SkinnedModelLOD::GetMeshesCount() const { - // Find minimum and maximum points of all the meshes - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) - { - const auto& mesh = Meshes[j]; - mesh.GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - - return BoundingBox(min, max); + return Meshes.Count(); } -BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const +const MeshBase* SkinnedModelLOD::GetMesh(int32 index) const { - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; + return Meshes.Get() + index; +} + +MeshBase* SkinnedModelLOD::GetMesh(int32 index) +{ + return Meshes.Get() + index; +} + +void SkinnedModelLOD::GetMeshes(Array& meshes) +{ + meshes.Resize(Meshes.Count()); for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) - { - const auto& mesh = Meshes[meshIndex]; - BoundingBox box = mesh.GetBox(); - if (deformation) - deformation->GetBounds(_lodIndex, meshIndex, box); - box.GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - transform.LocalToWorld(corners[i], tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - } - return BoundingBox(min, max); + meshes[meshIndex] = &Meshes.Get()[meshIndex]; } -BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const +void SkinnedModelLOD::GetMeshes(Array& meshes) const { - // Find minimum and maximum points of the mesh - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - const auto& mesh = Meshes[meshIndex]; - mesh.GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - Vector3::Transform(corners[i], world, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); - } - - return BoundingBox(min, max); -} - -BoundingBox SkinnedModelLOD::GetBox() const -{ - // Find minimum and maximum points of the mesh in given world - Vector3 min = Vector3::Maximum, max = Vector3::Minimum; - Vector3 corners[8]; - for (int32 j = 0; j < Meshes.Count(); j++) - { - Meshes[j].GetBox().GetCorners(corners); - for (int32 i = 0; i < 8; i++) - { - min = Vector3::Min(min, corners[i]); - max = Vector3::Max(max, corners[i]); - } - } - - return BoundingBox(min, max); + meshes.Resize(Meshes.Count()); + for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++) + meshes[meshIndex] = &Meshes.Get()[meshIndex]; } diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index c996e987c..c90cb5362 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -10,38 +10,19 @@ /// /// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes. /// -API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ModelLODBase { - DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ScriptingObject); + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ModelLODBase); friend SkinnedModel; private: SkinnedModel* _model = nullptr; - int32 _lodIndex = 0; public: - /// - /// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD. - /// - API_FIELD() float ScreenSize = 1.0f; - /// /// The meshes array. /// API_FIELD(ReadOnly) Array Meshes; - /// - /// Gets the model LOD index. - /// - API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const - { - return _lodIndex; - } - - /// - /// Determines whether any mesh has been initialized. - /// - bool HasAnyMeshInitialized() const; - public: /// /// Determines if there is an intersection between the Model and a Ray in given world using given instance @@ -65,34 +46,6 @@ public: /// True whether the two objects intersected bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh); - /// - /// Get model bounding box in transformed world for given instance buffer - /// - /// World matrix - /// Bounding box - BoundingBox GetBox(const Matrix& world) const; - - /// - /// Get model bounding box in transformed world. - /// - /// The instance transformation. - /// The meshes deformation container (optional). - /// Bounding box - BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const; - - /// - /// Get model bounding box in transformed world for given instance buffer for only one mesh - /// - /// World matrix - /// esh index - /// Bounding box - BoundingBox GetBox(const Matrix& world, int32 meshIndex) const; - - /// - /// Gets the bounding box combined for all meshes in this model LOD. - /// - API_PROPERTY() BoundingBox GetBox() const; - /// /// Draws the meshes. Binds vertex and index buffers and invokes the draw calls. /// @@ -100,9 +53,7 @@ public: FORCE_INLINE void Render(GPUContext* context) { for (int32 i = 0; i < Meshes.Count(); i++) - { Meshes.Get()[i].Render(context); - } } /// @@ -114,9 +65,7 @@ public: FORCE_INLINE void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const { for (int32 i = 0; i < Meshes.Count(); i++) - { Meshes.Get()[i].Draw(renderContext, info, lodDitherFactor); - } } /// @@ -128,10 +77,16 @@ public: FORCE_INLINE void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const { for (int32 i = 0; i < Meshes.Count(); i++) - { Meshes.Get()[i].Draw(renderContextBatch, info, lodDitherFactor); - } } + +public: + // [ModelLODBase] + int32 GetMeshesCount() const override; + const MeshBase* GetMesh(int32 index) const override; + MeshBase* GetMesh(int32 index) override; + void GetMeshes(Array& meshes) override; + void GetMeshes(Array& meshes) const override; }; /// @@ -384,6 +339,8 @@ public: uint64 GetMemoryUsage() const override; void SetupMaterialSlots(int32 slotsCount) override; int32 GetLODsCount() const override; + const ModelLODBase* GetLOD(int32 lodIndex) const override; + ModelLODBase* GetLOD(int32 lodIndex) override; const MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) const override; MeshBase* GetMesh(int32 meshIndex, int32 lodIndex = 0) override; void GetMeshes(Array& meshes, int32 lodIndex = 0) const override; From d60f3bdfb14c849fc5225fb30c407c70ab6d0a1d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 01:30:58 +0100 Subject: [PATCH 128/215] Add mesh vertex layout and GPU memory size display in model window --- .../Editor/Windows/Assets/ModelBaseWindow.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 91a3d2057..229db6a07 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Xml; using FlaxEditor.Content; @@ -242,6 +243,35 @@ namespace FlaxEditor.Windows.Assets proxy.Window.MarkAsEdited(); }; } + { + var group = layout.Group("Vertex Layout"); + GPUVertexLayout vertexLayout = null; + for (int lodIndex = 0; lodIndex < countLODs; lodIndex++) + { + var lod = proxy.Asset.GetLOD(lodIndex); + if (lodIndex >= countLODs - loadedLODs) + { + var mesh = lod.GetMesh(0); + vertexLayout = mesh.VertexLayout; + if (vertexLayout != null && vertexLayout.Elements.Length != 0) + break; + vertexLayout = null; + } + } + if (vertexLayout != null) + { + var elements = vertexLayout.Elements; + for (uint slot = 0; slot < 4u; slot++) + { + var slotElements = elements.Where(x => x.Slot == slot && x.PerInstance == 0).ToArray(); + if (slotElements.Length == 0) + continue; + group.Label($" Vertex Buffer {slot}:"); + foreach (var e in slotElements) + group.Label($" {e.Type}, {e.Format} ({PixelFormatExtensions.SizeInBytes(e.Format)} bytes), offset {e.Offset}"); + } + } + } proxy.OnGeneral(layout); // Group per LOD @@ -257,14 +287,30 @@ namespace FlaxEditor.Windows.Assets proxy.Asset.GetMeshes(out var meshes, lodIndex); int triangleCount = 0, vertexCount = 0; + ulong[] meshMemory = new ulong[meshes.Length]; + ulong lodMemory = 0; for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; triangleCount += mesh.TriangleCount; vertexCount += mesh.VertexCount; + ulong memory = 0; + for (int vbIndex = 0; vbIndex < 4; vbIndex++) + { + var vb = mesh.GetVertexBuffer(vbIndex); + if (vb != null) + memory += vb.Size; + } + { + var ib = mesh.GetIndexBuffer(); + if (ib != null) + memory += ib.Size; + } + meshMemory[meshIndex] = memory; + lodMemory += memory; } - group.Label(string.Format("Triangles: {0:N0} Vertices: {1:N0}", triangleCount, vertexCount)).AddCopyContextMenu(); + group.Label(string.Format($"Triangles: {{0:N0}} Vertices: {{1:N0}} Memory: {Utilities.Utils.FormatBytesCount(lodMemory)}", triangleCount, vertexCount)).AddCopyContextMenu(); group.Label("Size: " + lod.Box.Size).AddCopyContextMenu(); var screenSize = group.FloatValue("Screen Size", "The screen size to switch LODs. Bottom limit of the model screen size to render this LOD."); screenSize.ValueBox.MinValue = 0.0f; @@ -280,7 +326,7 @@ namespace FlaxEditor.Windows.Assets for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; - group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, vert: {mesh.VertexCount:N0})").AddCopyContextMenu(); + group.Label($"Mesh {meshIndex} (tris: {mesh.TriangleCount:N0}, vert: {mesh.VertexCount:N0}, mem: {Utilities.Utils.FormatBytesCount(meshMemory[meshIndex])})").AddCopyContextMenu(); // Material Slot var materialSlot = group.ComboBox("Material Slot", "Material slot used by this mesh during rendering"); From 02df6bafac155f15ccdc30280f8f6dea5340af67 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 01:33:18 +0100 Subject: [PATCH 129/215] Update engine assets --- Content/Editor/Camera/M_Camera.flax | 4 ++-- Content/Editor/DebugMaterials/DDGIDebugProbes.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Decal.flax | 2 +- Content/Editor/DebugMaterials/SingleColor/Particle.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Surface.flax | 4 ++-- .../Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Terrain.flax | 2 +- Content/Editor/DefaultFontMaterial.flax | 4 ++-- Content/Editor/Gizmo/Material.flax | 4 ++-- Content/Editor/Gizmo/SelectionOutlineMaterial.flax | 2 +- Content/Editor/Highlight Material.flax | 4 ++-- Content/Editor/Icons/IconsMaterial.flax | 4 ++-- Content/Editor/Particles/Particle Material Color.flax | 4 ++-- Content/Editor/Particles/Smoke Material.flax | 4 ++-- Content/Engine/DefaultMaterial.flax | 4 ++-- Content/Engine/DefaultRadialMenu.flax | 2 +- Content/Engine/DefaultTerrainMaterial.flax | 2 +- Content/Engine/SingleColorMaterial.flax | 4 ++-- Content/Engine/SkyboxMaterial.flax | 4 ++-- Content/Shaders/BakeLightmap.flax | 4 ++-- Content/Shaders/Editor/LightmapUVsDensity.flax | 4 ++-- 21 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 38e11bc91..f2fa86005 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67045d0b06d504465d30c8415174b91137c09181185c89436085f420c29e6e59 -size 28071 +oid sha256:abee67ec702a0c51b20383420a78b7b37c7c09a828bb3a54edb260a9ca9fd2b3 +size 28611 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index 6862b1bf9..b0a5ad387 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4edc791689d30a60fdbe9f28eca8c9747b0842726d8f99370a950b17e71fdd75 -size 39019 +oid sha256:ce33227101e39743018e58f6b81a33b59eec684cd440b89fba2ba2be1dc6aad7 +size 39559 diff --git a/Content/Editor/DebugMaterials/SingleColor/Decal.flax b/Content/Editor/DebugMaterials/SingleColor/Decal.flax index c99a14f6c..d8ed52acf 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Decal.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a69bfa758716071d2c6621cca6d049904a9f96d16b41ed90705d7f262be2cdab +oid sha256:d398931a452215ce613378897906681eccb6def7e50f753f31de920b1f1f1b1a size 7489 diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax index 66d5568d5..c9403453c 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc8ea0bf4f94b246f9b8b653ade5a12aa06fb98770f049273ade594dfcab09ec -size 32014 +oid sha256:893a53353feb0c2bf63cf33b6c061ed76771b9e959a904a01286c433a88251d8 +size 32040 diff --git a/Content/Editor/DebugMaterials/SingleColor/Surface.flax b/Content/Editor/DebugMaterials/SingleColor/Surface.flax index 3610a41e1..e2c54a6b9 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Surface.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Surface.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:161dbee3faaa9380ca354d2252661f6c3a13b38dee3c2fcdda828ff1b6eb4520 -size 27967 +oid sha256:0d901e8d5b1237dee6746306da3d8cdb0c33686630b2b4686dbaf8f818bc9901 +size 28507 diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax index af8ef0de5..b82bca2cd 100644 --- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax +++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3adbb88f53a09d0c219936f167dd3e129391adb6abb323ad6023de2e55e2bbdc -size 30152 +oid sha256:2cb52bea70277ccdc6a89cd1aba14e50d09f0842a6dc5b35baece3bf8d733492 +size 30692 diff --git a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax index 55b9ddc1e..1f954f5f8 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7040dea98e3adddecac69814f0f307051bb0df15cb052beeb6d7b3cff0dcb32c +oid sha256:73d390d83aa605c26d2879edc5faff503c1e29f2056b501a9753843eb7e29980 size 21203 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index 2f0069068..1b7142da5 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c5fc9e89e8ce4f2511d6bcaf68c7f1ee713dafaf3260222bf704be74bc3d6ad -size 28146 +oid sha256:b68e47fa6052bfc2813ee919c239228e4871e18e0c8216ba04301283b8c3477b +size 28686 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index 75b781865..29fb07078 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea8bc05a1a45d8b4e937c23a42a6acfaebfd739a74d17e6d02acf4bca1a67113 -size 30756 +oid sha256:c676cb17f94be1f86c9a60d45da718a7c00649955fb00fa80b8917d4e0b93c3c +size 31296 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index a8396c3f2..e63875b54 100644 --- a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax +++ b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51d9d5bd33e7e95181a1b26337d5c9702362659b0ca9544efd3d5fa101856a73 +oid sha256:09c7e36108cd272387d8fd7deb02488b633d01cc913e8e6422a52613593ea350 size 16166 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index a8b39cdd9..e8bae9708 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1be526615a5f57af0011ebfbcd1b87d203d91dc4bd684cea951069e276126f6 -size 28549 +oid sha256:89d4b1de21cb5aafbf8de151372339d77c2225d0402cc8d965a9c9826baa5afa +size 29089 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 5193741b2..a7da1de3f 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:601d16d02c7fa6d80cafef8c61cd009433f373a549a16432730ad4a45d2d940b -size 28477 +oid sha256:a3d78e259c9692d9d358c9ca3baa7d948a1e76647674b25e496d75a7f1355e29 +size 29017 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index c6632f49e..5af02cba8 100644 --- a/Content/Editor/Particles/Particle Material Color.flax +++ b/Content/Editor/Particles/Particle Material Color.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c79c213d19fb1707e7ac88272d99be2ec6a038a8b35ddf0149fc0177fa4ab1b -size 30253 +oid sha256:5788e73182bd74f8ae86dc6d1a3e3ce8770d770336a60d378b0adfb324db0275 +size 30279 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index 3b15ce8e5..aaebd3630 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55da6e79fa1a42dbae7d74b724800f3f7937eb109fa92834e4df6d91776bf1cb -size 38401 +oid sha256:3c0f585b0808e5cfc7472ba2eda26ed71ce53bd584a52cc773038869f3673ef7 +size 38427 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index 37442ca8b..48aa481a7 100644 --- a/Content/Engine/DefaultMaterial.flax +++ b/Content/Engine/DefaultMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0261d6715eaf0bab21fd6b01d927dad8eff40d767b59a6459063fcdd94d1192a -size 29992 +oid sha256:e6f40053f6ff8060180cfb592abe35f973343611e4e123d3c834572862f9d5e0 +size 30532 diff --git a/Content/Engine/DefaultRadialMenu.flax b/Content/Engine/DefaultRadialMenu.flax index 060d9f7b4..3ebe4072a 100644 --- a/Content/Engine/DefaultRadialMenu.flax +++ b/Content/Engine/DefaultRadialMenu.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc132311f3f1f83525252aa3a10d3f95d9070ec38db5d81b9d4d595b1b9c5757 +oid sha256:48836a2f237180c2f90cb51f54242790bddd558693d4782fe19ae3ed840623bf size 20340 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index 6c94d866b..d18df0ee2 100644 --- a/Content/Engine/DefaultTerrainMaterial.flax +++ b/Content/Engine/DefaultTerrainMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:123d3f1bbf71929447355f550c79f3db9f1038e0c43b348927a21b2c8cb5e950 +oid sha256:f5dd522c412f91d76d979cf2886211824eafc526f48590203ec2b5c20f88394f size 23500 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index 88fce758b..3aa389842 100644 --- a/Content/Engine/SingleColorMaterial.flax +++ b/Content/Engine/SingleColorMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9aec50596d9177062d707167f8140645db7d0693da38329dda2df293cf7d044f -size 28168 +oid sha256:214b54846d487f6e2264ddccbf02eb9e06522add8ff0742f617a748704757b54 +size 28708 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 0a1a15f92..1381ede4a 100644 --- a/Content/Engine/SkyboxMaterial.flax +++ b/Content/Engine/SkyboxMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2147855bff19d96e07f816022642cafdadf681fe786e714cbefaf4e77257ec4c -size 29366 +oid sha256:b578128ade99102a95a983b1b54a8de504e56601bdcfca93306597dae881a38c +size 29906 diff --git a/Content/Shaders/BakeLightmap.flax b/Content/Shaders/BakeLightmap.flax index a8a8b9812..e392ae0a8 100644 --- a/Content/Shaders/BakeLightmap.flax +++ b/Content/Shaders/BakeLightmap.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:666d921fcffb82ac5a51615b2a5361752edde1692a4114f8bb63b054b24fa9bd -size 15794 +oid sha256:00c8187f0cac8b20c54a18817c60c7034415b6ae511b8de62add20be08e03ebc +size 15765 diff --git a/Content/Shaders/Editor/LightmapUVsDensity.flax b/Content/Shaders/Editor/LightmapUVsDensity.flax index d7f265055..384740211 100644 --- a/Content/Shaders/Editor/LightmapUVsDensity.flax +++ b/Content/Shaders/Editor/LightmapUVsDensity.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0ed25e40158253b2fdd565e7ad0e745308eadc5091a89502cbc1fa22288039f -size 4515 +oid sha256:4d8c7df4bc9a57b58fc4524da6cbec75ae9ee14c86b46175519af5b3e90b4b09 +size 4375 From 54869f1da060347216d150869f143f4987605c5a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 17:59:08 +0100 Subject: [PATCH 130/215] Adjust label height --- Source/Editor/Windows/Assets/ModelBaseWindow.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 229db6a07..7acdc10cb 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -266,9 +266,10 @@ namespace FlaxEditor.Windows.Assets var slotElements = elements.Where(x => x.Slot == slot && x.PerInstance == 0).ToArray(); if (slotElements.Length == 0) continue; - group.Label($" Vertex Buffer {slot}:"); + var height = 14; + group.Label($" Vertex Buffer {slot}:").Label.Height = height; foreach (var e in slotElements) - group.Label($" {e.Type}, {e.Format} ({PixelFormatExtensions.SizeInBytes(e.Format)} bytes), offset {e.Offset}"); + group.Label($" {e.Type}, {e.Format} ({PixelFormatExtensions.SizeInBytes(e.Format)} bytes), offset {e.Offset}").Label.Height = height; } } } From e75d7ad25741cf5c4c0173def02dac4fbe5987d7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 20:30:43 +0100 Subject: [PATCH 131/215] Fix compilation warnings --- Source/Engine/Graphics/PixelFormatExtensions.cpp | 4 ++-- .../Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 289e1b4ee..8cc5d6753 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1473,7 +1473,7 @@ static PixelFormatSampler PixelFormatSamplers[] = { uint32 data[4]; Platform::MemoryCopy(data, ptr, sizeof(data)); - return Float4(data[0], data[1], data[2], data[3]); + return Float4((float)data[0], (float)data[1], (float)data[2], (float)data[3]); }, [](void* ptr, const Float4& value) { @@ -1488,7 +1488,7 @@ static PixelFormatSampler PixelFormatSamplers[] = { int32 data[4]; Platform::MemoryCopy(data, ptr, sizeof(data)); - return Float4(data[0], data[1], data[2], data[3]); + return Float4((float)data[0], (float)data[1], (float)data[2], (float)data[3]); }, [](void* ptr, const Float4& value) { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index 573eee70c..7e937c4f8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -304,7 +304,7 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer // Create object auto depthWrite = _descDepthStencil.depthWriteEnable; - _descDepthStencil.depthWriteEnable &= renderPass->CanDepthWrite; + _descDepthStencil.depthWriteEnable &= renderPass->CanDepthWrite ? 1 : 0; const VkResult result = vkCreateGraphicsPipelines(_device->Device, _device->PipelineCache, 1, &_desc, nullptr, &pipeline); _descDepthStencil.depthWriteEnable = depthWrite; LOG_VULKAN_RESULT(result); From 344d342610559204aeeaf2fc90f652304e7ac4b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 20:31:00 +0100 Subject: [PATCH 132/215] Update assets --- Content/Editor/Gizmo/FoliageBrushMaterial.flax | 4 ++-- Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax | 4 ++-- Content/Editor/Wires Debug Material.flax | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index 6dda99261..eb56b11e5 100644 --- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax +++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f42eaf0a9d28be161480112d7432a68bdb443fe2b8f48e42dfed93de59dca7ff -size 36179 +oid sha256:f8b27ed5124e16059411584d56db41add0ca95523c3ac29daa588c0be3c351dc +size 36719 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index eab9e0968..7c8cb4abf 100644 --- a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax +++ b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab487469fd2dcd9bb8a0f41f33cf43f6d3b576a9266b38490a21fde6eb491c0f -size 29080 +oid sha256:586b9ef40a3e65c0636126451b76ce171941437551417ac3e0520e16f82f114d +size 29620 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 52e1c1cc8..77ca3d389 100644 --- a/Content/Editor/Wires Debug Material.flax +++ b/Content/Editor/Wires Debug Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1845e47b1a2f872ed66444c83ea068044de52da6c585ead4d98ee2d3f0f8105e -size 28549 +oid sha256:b05087861471a54921da90e977ecb760ea4191d085270825efc4f20dbb7887ad +size 29089 From d751c6a6c6069d4607d7fcde50c9aeaae0c122b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 21:22:35 +0100 Subject: [PATCH 133/215] Add `PackNormal`/`UnpackNormal` and `GetInt`/`SetInt` to `MeshAccessor` --- .../Editor/Viewport/Previews/ModelPreview.cs | 13 +++-- .../Engine/ContentExporters/ExportModel.cpp | 3 +- Source/Engine/Graphics/Models/MeshAccessor.cs | 48 ++++++++++++++++++- Source/Engine/Graphics/Models/MeshAccessor.h | 25 +++++++++- Source/Engine/Level/Actors/AnimatedModel.cpp | 16 ++++--- 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index b8687d848..9695aeed1 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -304,7 +304,8 @@ namespace FlaxEditor.Viewport.Previews for (int i = 0; i < count; i++) { var position = positionStream.GetFloat3(i); - var normal = normalStream.GetFloat3(i) * 2.0f - 1.0f; + var normal = normalStream.GetFloat3(i); + MeshAccessor.UnpackNormal(ref normal); DebugDraw.DrawLine(position, position + normal * 4.0f, Color.Blue); } } @@ -328,7 +329,8 @@ namespace FlaxEditor.Viewport.Previews for (int i = 0; i < count; i++) { var position = positionStream.GetFloat3(i); - var tangent = tangentStream.GetFloat3(i) * 2.0f - 1.0f; + var tangent = tangentStream.GetFloat3(i); + MeshAccessor.UnpackNormal(ref tangent); DebugDraw.DrawLine(position, position + tangent * 4.0f, Color.Red); } } @@ -353,10 +355,11 @@ namespace FlaxEditor.Viewport.Previews for (int i = 0; i < count; i++) { var position = positionStream.GetFloat3(i); - var normal = normalStream.GetFloat3(i) * 2.0f - 1.0f; + var normal = normalStream.GetFloat3(i); var tangent = tangentStream.GetFloat4(i); - var bitangentSign = tangent.W > Mathf.Epsilon ? -1.0f : +1.0f; - var bitangent = Float3.Cross(normal, new Float3(tangent) * 2.0f - 1.0f) * bitangentSign; + MeshAccessor.UnpackNormal(ref normal); + MeshAccessor.UnpackNormal(ref tangent, out var bitangentSign); + var bitangent = Float3.Cross(normal, new Float3(tangent)) * bitangentSign; DebugDraw.DrawLine(position, position + bitangent * 4.0f, Color.Green); } } diff --git a/Source/Engine/ContentExporters/ExportModel.cpp b/Source/Engine/ContentExporters/ExportModel.cpp index 74e5a140a..caa3e9649 100644 --- a/Source/Engine/ContentExporters/ExportModel.cpp +++ b/Source/Engine/ContentExporters/ExportModel.cpp @@ -81,7 +81,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) { for (uint32 i = 0; i < meshData.Vertices; i++) { - auto v = normalStream.GetFloat3(i) * 2.0f - 1.0f; + auto v = normalStream.GetFloat3(i); + MeshAccessor::UnpackNormal(v); output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z)); } output->WriteChar('\n'); diff --git a/Source/Engine/Graphics/Models/MeshAccessor.cs b/Source/Engine/Graphics/Models/MeshAccessor.cs index 2eef70ff0..aef9caab5 100644 --- a/Source/Engine/Graphics/Models/MeshAccessor.cs +++ b/Source/Engine/Graphics/Models/MeshAccessor.cs @@ -69,6 +69,17 @@ namespace FlaxEngine return _format == expectedFormat && _stride == PixelFormatExtensions.SizeInBytes(_format); } + /// + /// Reads an integer value from a given item. + /// + /// Zero-based index of the item. + /// Loaded value. + public int GetInt(int index) + { + fixed (byte* data = _data) + return (int)_sampler.Read(data + index * _stride).X; + } + /// /// Reads a float value from a given item. /// @@ -113,6 +124,18 @@ namespace FlaxEngine return _sampler.Read(data + index * _stride); } + /// + /// Writes an integer value to a given item. + /// + /// Zero-based index of the item. + /// Value to assign. + public void SetInt(int index, int value) + { + var v = new Float4(value); + fixed (byte* data = _data) + _sampler.Write(data + index * _stride, ref v); + } + /// /// Writes a float value to a given item. /// @@ -713,13 +736,34 @@ namespace FlaxEngine } } - private static void UnpackNormal(ref Float3 value) + /// + /// Unpacks normal/tangent vector from normalized range to full range. + /// + /// In and out value. + public static void UnpackNormal(ref Float3 value) { // [0; 1] -> [-1; 1] value = value * 2.0f - 1.0f; } - private static void PackNormal(ref Float3 value) + /// + /// Unpacks normal/tangent vector from normalized range to full range. + /// + /// In and out value. + /// Encoded sign in the alpha channel. + public static void UnpackNormal(ref Float4 value, out float sign) + { + sign = value.W > Mathf.Epsilon ? -1.0f : +1.0f; + + // [0; 1] -> [-1; 1] + value = value * 2.0f - 1.0f; + } + + /// + /// Packs normal/tangent vector to normalized range from full range. + /// + /// In and out value. + public static void PackNormal(ref Float3 value) { // [-1; 1] -> [0; 1] value = value * 0.5f + 0.5f; diff --git a/Source/Engine/Graphics/Models/MeshAccessor.h b/Source/Engine/Graphics/Models/MeshAccessor.h index cd17ccd15..3fc7f689f 100644 --- a/Source/Engine/Graphics/Models/MeshAccessor.h +++ b/Source/Engine/Graphics/Models/MeshAccessor.h @@ -37,6 +37,11 @@ public: bool IsValid() const; bool IsLinear(PixelFormat expectedFormat) const; + FORCE_INLINE int32 GetInt(int32 index) const + { + return (int32)_sampler.Read(_data.Get() + index * _stride).X; + } + FORCE_INLINE float GetFloat(int32 index) const { return _sampler.Read(_data.Get() + index * _stride).X; @@ -57,7 +62,12 @@ public: return _sampler.Read(_data.Get() + index * _stride); } - FORCE_INLINE void SetFloat(int32 index, const float& value) + FORCE_INLINE void SetInt(int32 index, const int32 value) + { + _sampler.Write(_data.Get() + index * _stride, Float4((float)value)); + } + + FORCE_INLINE void SetFloat(int32 index, const float value) { _sampler.Write(_data.Get() + index * _stride, Float4(value)); } @@ -182,4 +192,17 @@ public: { return Attribute((VertexElement::Types)((byte)VertexElement::Types::TexCoord0 + channel)); } + +public: + // Unpacks normal/tangent vector from normalized range to full range. + FORCE_INLINE static void UnpackNormal(Float3& normal) + { + normal = normal * 2.0f - 1.0f; + } + + // Packs normal/tangent vector to normalized range from full range. + FORCE_INLINE static void PackNormal(Float3& normal) + { + normal = normal * 0.5f + 0.5f; + } }; diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 818c35689..73fd44d27 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -653,9 +653,10 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD { const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i]; - Float3 normal = normalStream.GetFloat3(blendShapeVertex.VertexIndex) * 2.0f - 1.0f; + Float3 normal = normalStream.GetFloat3(blendShapeVertex.VertexIndex); + MeshAccessor::UnpackNormal(normal); normal = normal + blendShapeVertex.NormalDelta * q.Second; - normal = normal * 0.5f + 0.5f; // TODO: optimize unpacking and packing to just apply it to the normal delta + MeshAccessor::PackNormal(normal); // TODO: optimize unpacking and packing to just apply it to the normal delta normalStream.SetFloat3(blendShapeVertex.VertexIndex, normal); } } @@ -667,18 +668,21 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD auto tangentStream = accessor.Tangent(); for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++) { - Float3 normal = normalStream.GetFloat3(vertexIndex) * 2.0f - 1.0f; + Float3 normal = normalStream.GetFloat3(vertexIndex); + MeshAccessor::UnpackNormal(normal); normal.Normalize(); - normal = normal * 0.5f + 0.5f; + MeshAccessor::PackNormal(normal); normalStream.SetFloat3(vertexIndex, normal); if (tangentStream.IsValid()) { Float4 tangentRaw = normalStream.GetFloat4(vertexIndex); - Float3 tangent = Float3(tangentRaw) * 2.0f - 1.0f; + Float3 tangent = Float3(tangentRaw); + MeshAccessor::UnpackNormal(tangent); tangent = tangent - ((tangent | normal) * normal); tangent.Normalize(); - tangentRaw = Float4(tangent * 0.5f + 0.5f, tangentRaw.W); + MeshAccessor::PackNormal(tangent); + tangentRaw = Float4(tangent, tangentRaw.W); tangentStream.SetFloat4(vertexIndex, tangentRaw); } } From ea96418764c6b06192a5744bcb0b996783531eff Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 12 Jan 2025 21:22:53 +0100 Subject: [PATCH 134/215] Update old `Float1010102` to `FloatR10G10B10A2` --- Source/Engine/Content/Assets/ModelBase.cpp | 2 +- Source/Engine/Graphics/Models/Mesh.cpp | 1 - Source/Engine/Graphics/Models/Types.h | 20 +++++++++---------- .../Engine/Graphics/PixelFormatExtensions.cpp | 6 +++--- Source/Engine/Graphics/RenderTools.cpp | 8 ++++---- Source/Engine/UI/TextRender.cpp | 8 ++++---- 6 files changed, 22 insertions(+), 23 deletions(-) diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 31e52c1c7..839908cf6 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -779,7 +779,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ; const Float3 tangent = hasTangents ? mesh.Tangents.Get()[vertex] : Float3::UnitX; const float bitangentSign = hasBitangentSigns ? mesh.BitangentSigns.Get()[vertex] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent); - const Float1010102 tangentEnc(tangent * 0.5f + 0.5f, (byte)(bitangentSign < 0 ? 1 : 0)); + const FloatR10G10B10A2 tangentEnc(tangent * 0.5f + 0.5f, (byte)(bitangentSign < 0 ? 1 : 0)); stream.Write(tangentEnc.Value); break; } diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index f2736e18a..810cb23fc 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -6,7 +6,6 @@ #include "ModelInstanceEntry.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Assets/Model.h" -#include "Engine/Core/Log.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" diff --git a/Source/Engine/Graphics/Models/Types.h b/Source/Engine/Graphics/Models/Types.h index 3acde27d9..20946b116 100644 --- a/Source/Engine/Graphics/Models/Types.h +++ b/Source/Engine/Graphics/Models/Types.h @@ -77,8 +77,8 @@ PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayou { Float3 Position; Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; + FloatR10G10B10A2 Normal; + FloatR10G10B10A2 Tangent; Half2 LightmapUVs; Color32 Color; }); @@ -113,8 +113,8 @@ PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayou PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB1ElementType18 { Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; + FloatR10G10B10A2 Normal; + FloatR10G10B10A2 Tangent; Half2 LightmapUVs; static GPUVertexLayout* GetLayout(); @@ -143,8 +143,8 @@ PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayou { Float3 Position; Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; + FloatR10G10B10A2 Normal; + FloatR10G10B10A2 Tangent; Color32 BlendIndices; Color32 BlendWeights; }); @@ -171,8 +171,8 @@ PACK_STRUCT(struct DEPRECATED("Use newer format.") VB0SkinnedElementType1 { Float3 Position; Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; + FloatR10G10B10A2 Normal; + FloatR10G10B10A2 Tangent; Color32 BlendIndices; Color32 BlendWeights; }); @@ -182,8 +182,8 @@ PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayou { Float3 Position; Half2 TexCoord; - Float1010102 Normal; - Float1010102 Tangent; + FloatR10G10B10A2 Normal; + FloatR10G10B10A2 Tangent; Color32 BlendIndices; Half4 BlendWeights; diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 8cc5d6753..39b5dcd04 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1396,14 +1396,14 @@ static PixelFormatSampler PixelFormatSamplers[] = }, { PixelFormat::R10G10B10A2_UNorm, - sizeof(Float1010102), + sizeof(FloatR10G10B10A2), [](const void* ptr) { - return ((Float1010102*)ptr)->ToFloat4(); + return ((FloatR10G10B10A2*)ptr)->ToFloat4(); }, [](void* ptr, const Float4& value) { - *(Float1010102*)ptr = Float1010102(value.X, value.Y, value.Z, value.W); + *(FloatR10G10B10A2*)ptr = FloatR10G10B10A2(value.X, value.Y, value.Z, value.W); }, }, { diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index 27e23a2b6..14fcd4270 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -562,8 +562,8 @@ void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10 Float3 n; Float4 t; CalculateTangentFrame(n, t, normal); - resultNormal = Float1010102(n, 0); - resultTangent = Float1010102(t); + resultNormal = FloatR10G10B10A2(n, 0); + resultTangent = FloatR10G10B10A2(t); } void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent) @@ -572,8 +572,8 @@ void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10 Float3 n; Float4 t; CalculateTangentFrame(n, t, normal, tangent); - resultNormal = Float1010102(n, 0); - resultTangent = Float1010102(t); + resultNormal = FloatR10G10B10A2(n, 0); + resultTangent = FloatR10G10B10A2(t); } void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal) diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 53491da11..8ff691204 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -28,8 +28,8 @@ PACK_STRUCT(struct TextRenderVertex { Float3 Position; Color32 Color; - Float1010102 Normal; - Float1010102 Tangent; + FloatR10G10B10A2 Normal; + FloatR10G10B10A2 Tangent; Half2 TexCoord; static GPUVertexLayout* GetLayout() @@ -288,8 +288,8 @@ void TextRender::UpdateLayout() v.Position = Float3(-pos, 0.0f); \ box.Merge(v.Position); \ v.TexCoord = Half2(uv); \ - v.Normal = Float1010102(normal * 0.5f + 0.5f, 0); \ - v.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign); \ + v.Normal = FloatR10G10B10A2(normal * 0.5f + 0.5f, 0); \ + v.Tangent = FloatR10G10B10A2(tangent * 0.5f + 0.5f, sign); \ v.Color = color; \ _vb.Write(v) // From 754e0c4e29bfa62cf2192ae62365268896695ccd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Jan 2025 18:09:19 +0100 Subject: [PATCH 135/215] Update build number --- Flax.flaxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 629bf4087..70cfd649d 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 10, "Revision": 0, - "Build": 6701 + "Build": 6702 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", From 90ba7f4a92ce437a61efdd1c171bfdf5e2fe4f97 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 13 Jan 2025 18:25:26 +0100 Subject: [PATCH 136/215] Mark `CommonValue` as deprecated to generate warnings in old code --- Source/Engine/Core/Types/CommonValue.cpp | 4 ++++ Source/Engine/Core/Types/CommonValue.h | 2 +- Source/Engine/Core/Types/Variant.cpp | 4 ++-- Source/Engine/Particles/ParticleEffect.cpp | 2 ++ Source/Engine/Serialization/JsonTools.cpp | 2 ++ Source/Engine/Serialization/JsonWriter.cpp | 2 ++ Source/Engine/Serialization/Stream.cpp | 8 ++++++++ 7 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Types/CommonValue.cpp b/Source/Engine/Core/Types/CommonValue.cpp index fd69ded3c..2a9d2cd18 100644 --- a/Source/Engine/Core/Types/CommonValue.cpp +++ b/Source/Engine/Core/Types/CommonValue.cpp @@ -3,6 +3,8 @@ #include "CommonValue.h" #include "Engine/Scripting/ScriptingObject.h" +PRAGMA_DISABLE_DEPRECATION_WARNINGS + const CommonValue CommonValue::Zero(0.0f); const CommonValue CommonValue::One(1.0f); const CommonValue CommonValue::Null(static_cast(nullptr)); @@ -176,3 +178,5 @@ void CommonValue::UnlinkObject() { AsObject->Deleted.Unbind(this); } + +PRAGMA_ENABLE_DEPRECATION_WARNINGS diff --git a/Source/Engine/Core/Types/CommonValue.h b/Source/Engine/Core/Types/CommonValue.h index f021b1070..0ff3c6097 100644 --- a/Source/Engine/Core/Types/CommonValue.h +++ b/Source/Engine/Core/Types/CommonValue.h @@ -49,7 +49,7 @@ class ScriptingObject; /// Container for value that can have different types /// [Deprecated on 31.07.2020, expires on 31.07.2022] /// -struct FLAXENGINE_API CommonValue +struct DEPRECATED("Use Variant.") FLAXENGINE_API CommonValue { public: /// diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 38a494650..a9d1fb5a4 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -5,7 +5,6 @@ #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Asset.h" -#include "Engine/Content/AssetReference.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Mathd.h" #include "Engine/Core/Math/BoundingBox.h" @@ -23,7 +22,6 @@ #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MCore.h" -#include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Utilities/Crc.h" #include "Engine/Utilities/StringConverter.h" @@ -889,6 +887,7 @@ Variant::Variant(const Span& v) } } +PRAGMA_DISABLE_DEPRECATION_WARNINGS Variant::Variant(const CommonValue& value) : Variant() { @@ -956,6 +955,7 @@ Variant::Variant(const CommonValue& value) CRASH; } } +PRAGMA_ENABLE_DEPRECATION_WARNINGS Variant::~Variant() { diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index f06a9b945..9a89a0154 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -663,6 +663,7 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* // [Deprecated on 25.11.2018, expires on 25.11.2022] if (modifier->EngineBuild < 6197) { + PRAGMA_DISABLE_DEPRECATION_WARNINGS const auto& overrides = overridesMember->value; ASSERT(overrides.IsArray()); _parametersOverrides.EnsureCapacity(_parametersOverrides.Count() + overrides.Size()); @@ -700,6 +701,7 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* p.Value = Variant(JsonTools::GetCommonValue(mValue->value)); } } + PRAGMA_ENABLE_DEPRECATION_WARNINGS } else { diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index 689ce58c2..7d6faf4ff 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -284,6 +284,7 @@ DateTime JsonTools::GetDateTime(const Value& value) return DateTime(value.GetInt64()); } +PRAGMA_DISABLE_DEPRECATION_WARNINGS CommonValue JsonTools::GetCommonValue(const Value& value) { // [Deprecated on 31.07.2020, expires on 31.07.2022] @@ -363,3 +364,4 @@ CommonValue JsonTools::GetCommonValue(const Value& value) } return result; } +PRAGMA_ENABLE_DEPRECATION_WARNINGS diff --git a/Source/Engine/Serialization/JsonWriter.cpp b/Source/Engine/Serialization/JsonWriter.cpp index 58ee2c126..673262a7f 100644 --- a/Source/Engine/Serialization/JsonWriter.cpp +++ b/Source/Engine/Serialization/JsonWriter.cpp @@ -244,6 +244,7 @@ void JsonWriter::Matrix(const ::Matrix& value) EndObject(); } +PRAGMA_DISABLE_DEPRECATION_WARNINGS void JsonWriter::CommonValue(const ::CommonValue& value) { // [Deprecated on 31.07.2020, expires on 31.07.2022] @@ -319,6 +320,7 @@ void JsonWriter::CommonValue(const ::CommonValue& value) EndObject(); } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void JsonWriter::Transform(const ::Transform& value) { diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index a225ca8f5..192769732 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -105,6 +105,7 @@ void ReadStream::Read(String& data, int16 lock) } } +PRAGMA_DISABLE_DEPRECATION_WARNINGS void ReadStream::Read(CommonValue& data) { // [Deprecated on 31.07.2020, expires on 31.07.2022] @@ -233,6 +234,7 @@ void ReadStream::Read(CommonValue& data) default: CRASH; } } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void ReadStream::Read(VariantType& data) { @@ -547,10 +549,12 @@ void ReadStream::ReadString(String* data, int16 lock) Read(*data, lock); } +PRAGMA_DISABLE_DEPRECATION_WARNINGS void ReadStream::ReadCommonValue(CommonValue* data) { Read(*data); } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void ReadStream::ReadVariantType(VariantType* data) { @@ -735,6 +739,7 @@ void WriteStream::Write(const StringAnsiView& data, int8 lock) WriteUint8((uint8)((uint8)data[i] ^ lock)); } +PRAGMA_DISABLE_DEPRECATION_WARNINGS void WriteStream::Write(const CommonValue& data) { // [Deprecated on 31.07.2020, expires on 31.07.2022] @@ -797,6 +802,7 @@ void WriteStream::Write(const CommonValue& data) default: CRASH; } } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void WriteStream::Write(const VariantType& data) { @@ -997,10 +1003,12 @@ void WriteStream::WriteStringAnsi(const StringAnsiView& data, int8 lock) Write(data, lock); } +PRAGMA_DISABLE_DEPRECATION_WARNINGS void WriteStream::WriteCommonValue(const CommonValue& data) { Write(data); } +PRAGMA_ENABLE_DEPRECATION_WARNINGS void WriteStream::WriteVariantType(const VariantType& data) { From 59061c01d306fd26f53dbd222b10ba87737df9e0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Jan 2025 22:29:06 +0100 Subject: [PATCH 137/215] Fix compilation error --- Source/Engine/Content/Assets/ModelBase.cpp | 3 ++- Source/Engine/Content/Assets/ModelBase.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 839908cf6..97da2e444 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -2,10 +2,10 @@ #include "ModelBase.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Transform.h" #include "Engine/Content/WeakAssetReference.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Graphics/Config.h" -#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Models/MeshBase.h" #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" @@ -18,6 +18,7 @@ #endif #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #endif diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 73d70537c..d8f4d7b4d 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -101,7 +101,7 @@ public: /// /// Gets the meshes in this LOD. /// - API_FUNCTION(Sealed) virtual void GetMeshes(Array& meshes) = 0; + API_FUNCTION(Sealed) virtual void GetMeshes(API_PARAM(Out) Array& meshes) = 0; }; /// From be8686bbb2740625324cd596dd37c2894055588c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Jan 2025 22:45:03 +0100 Subject: [PATCH 138/215] Fix new code on non-windows editor platforms --- Source/Engine/Tools/TextureTool/TextureTool.stb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 2caad5b59..a527badf2 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -691,7 +691,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix { for (int32 x = 0; x < 4; x++) { - Color color = TextureTool::SamplePoint(sampler, xBlock * 4 + x, yBlock * 4 + y, srcMip.Data.Get(), srcMip.RowPitch); + Color color = sampler->SamplePoint(srcMip.Data.Get(), xBlock * 4 + x, yBlock * 4 + y, srcMip.RowPitch); if (isDstSRGB) color = Color::LinearToSrgb(color); srcBlock[y * 4 + x] = Color32(color); @@ -786,7 +786,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix Color color = sampler->SamplePoint(srcMip.Data.Get(), x, y, srcMip.RowPitch); // Store destination texture - sampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color); + dstSampler->Store(dstMip.Data.Get(), x, y, dstMip.RowPitch, color); } } } From d0a6edbb2c71c69af539b90a79b30ecb84f005e7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Jan 2025 23:26:26 +0100 Subject: [PATCH 139/215] Another compilation fix --- Source/Engine/Content/Assets/Model.cpp | 4 ++-- Source/Engine/Content/Assets/ModelBase.cpp | 24 +++++++++---------- Source/Engine/Content/Assets/ModelBase.h | 6 ++--- Source/Engine/Content/Assets/SkinnedModel.cpp | 12 +++++----- Source/Engine/Platform/Linux/LinuxWindow.cpp | 12 ++++------ 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index e7e1330b7..01adbd207 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -404,7 +404,7 @@ bool Model::SaveHeader(WriteStream& stream) { if (ModelBase::SaveHeader(stream)) return true; - static_assert(HeaderVersion == 2, "Update code"); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); // LODs stream.Write((byte)LODs.Count()); @@ -431,7 +431,7 @@ bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData) { if (ModelBase::SaveHeader(stream, modelData)) return true; - static_assert(HeaderVersion == 2, "Update code"); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); // LODs stream.Write((byte)modelData.LODs.Count()); diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 97da2e444..9aa2aed6a 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -66,7 +66,7 @@ public: Array meshes; model->GetMeshes(meshes, _lodIndex); byte meshVersion = stream.ReadByte(); - if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion) + if (meshVersion < 2 || meshVersion > MODEL_MESH_VERSION) { LOG(Warning, "Unsupported mesh version {}", meshVersion); return true; @@ -315,12 +315,12 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion) { // Basic info stream.Read(headerVersion); - if (headerVersion < 2 || headerVersion > HeaderVersion) + if (headerVersion < 2 || headerVersion > MODEL_HEADER_VERSION) { LOG(Warning, "Unsupported model asset header version {}", headerVersion); return true; } - static_assert(HeaderVersion == 2, "Update code"); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); stream.Read(MinScreenSize); // Materials @@ -344,7 +344,7 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion) bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly) { // Load descriptor - static_assert(MeshVersion == 2, "Update code"); + static_assert(MODEL_MESH_VERSION == 2, "Update code"); uint32 vertices, triangles; stream.Read(vertices); stream.Read(triangles); @@ -402,8 +402,8 @@ bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* m bool ModelBase::SaveHeader(WriteStream& stream) { // Basic info - static_assert(HeaderVersion == 2, "Update code"); - stream.Write(HeaderVersion); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); + stream.Write(MODEL_HEADER_VERSION); stream.Write(MinScreenSize); // Materials @@ -446,8 +446,8 @@ bool ModelBase::SaveHeader(WriteStream& stream, const ModelData& modelData) } // Basic info - static_assert(HeaderVersion == 2, "Update code"); - stream.Write(HeaderVersion); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); + stream.Write(MODEL_HEADER_VERSION); stream.Write(modelData.MinScreenSize); // Materials @@ -518,8 +518,8 @@ bool ModelBase::SaveLOD(WriteStream& stream, int32 lodIndex) const return true; // Create meshes data - static_assert(MeshVersion == 2, "Update code"); - stream.Write(MeshVersion); + static_assert(MODEL_MESH_VERSION == 2, "Update code"); + stream.Write(MODEL_MESH_VERSION); for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++) { const auto& mesh = *meshes[meshIndex]; @@ -609,8 +609,8 @@ bool ModelBase::SaveLOD(WriteStream& stream, int32 lodIndex) const bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool saveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex)) { // Create meshes data - static_assert(MeshVersion == 2, "Update code"); - stream.Write(MeshVersion); + static_assert(MODEL_MESH_VERSION == 2, "Update code"); + stream.Write(MODEL_MESH_VERSION); const auto& lod = modelData.LODs[lodIndex]; for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++) { diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index d8f4d7b4d..56e07911d 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -18,6 +18,9 @@ // Chunk 15: SDF #define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) +#define MODEL_HEADER_VERSION (byte)2 +#define MODEL_MESH_VERSION (byte)2 + class MeshBase; class StreamModelLODTask; struct RenderContextBatch; @@ -114,9 +117,6 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset friend StreamModelLODTask; protected: - static constexpr byte HeaderVersion = 2; - static constexpr byte MeshVersion = 2; - bool _initialized = false; int32 _loadedLODs = 0; StreamModelLODTask* _streamingTask = nullptr; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 59cb699e0..c614dc2c0 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -533,7 +533,7 @@ bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase { if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly)) return true; - static_assert(MeshVersion == 2, "Update code"); + static_assert(MODEL_MESH_VERSION == 2, "Update code"); auto skinnedMesh = (SkinnedMesh*)mesh; // Blend Shapes @@ -562,7 +562,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) { if (ModelBase::LoadHeader(stream, headerVersion)) return true; - static_assert(HeaderVersion == 2, "Update code"); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); // LODs byte lods = stream.ReadByte(); @@ -665,7 +665,7 @@ bool SkinnedModel::SaveHeader(WriteStream& stream) { if (ModelBase::SaveHeader(stream)) return true; - static_assert(HeaderVersion == 2, "Update code"); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); // LODs stream.Write((byte)LODs.Count()); @@ -727,7 +727,7 @@ bool SkinnedModel::SaveHeader(WriteStream& stream, const ModelData& modelData) { if (ModelBase::SaveHeader(stream, modelData)) return true; - static_assert(HeaderVersion == 2, "Update code"); + static_assert(MODEL_HEADER_VERSION == 2, "Update code"); // LODs stream.Write((byte)modelData.LODs.Count()); @@ -788,7 +788,7 @@ bool SkinnedModel::SaveMesh(WriteStream& stream, const MeshBase* mesh) const { if (ModelBase::SaveMesh(stream, mesh)) return true; - static_assert(MeshVersion == 2, "Update code"); + static_assert(MODEL_MESH_VERSION == 2, "Update code"); auto skinnedMesh = (const SkinnedMesh*)mesh; // Blend Shapes @@ -802,7 +802,7 @@ bool SkinnedModel::SaveMesh(WriteStream& stream, const MeshBase* mesh) const bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex) { - static_assert(MeshVersion == 2, "Update code"); + static_assert(MODEL_MESH_VERSION == 2, "Update code"); auto& mesh = modelData.LODs[lodIndex].Meshes[meshIndex]; // Blend Shapes diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 0272bc8c2..89a7adeee 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -14,10 +14,8 @@ #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/Textures/TextureData.h" -// hack using TextureTool in Platform module -> TODO: move texture data sampling to texture data itself -#define COMPILE_WITH_TEXTURE_TOOL 1 -#include "Engine/Tools/TextureTool/TextureTool.h" #include "IncludeX11.h" // ICCCM @@ -888,13 +886,13 @@ void LinuxWindow::SetIcon(TextureData& icon) const auto mipData = mip.Data.Get(); const auto iconData = icon.GetData(0, 0); const Int2 iconSize(icon.Width, icon.Height); - const auto sampler = TextureTool::GetSampler(icon.Format); + const auto sampler = PixelFormatSampler::GetSampler(icon.Format); for (int32 y = 0; y < icon.Height; y++) { for (int32 x = 0; x < icon.Width; x++) { const Float2 uv((float)x / icon.Width, (float)y / icon.Height); - Color color = TextureTool::SampleLinear(sampler, uv, iconData->Data.Get(), iconSize, iconData->RowPitch); + Color color = sampler->SampleLinear(iconData->Data.Get(), uv, iconSize, iconData->RowPitch); *(mipData + y * icon.Width + x) = Color32(color); } } @@ -932,13 +930,13 @@ void LinuxWindow::SetIcon(TextureData& icon) const auto mipData = mip.Data.Get(); const auto iconData = icon.GetData(0, 0); const Int2 iconSize(icon.Width, icon.Height); - const auto sampler = TextureTool::GetSampler(img.Format); + const auto sampler = PixelFormatSampler::GetSampler(img.Format); for (int32 y = 0; y < newHeight; y++) { for (int32 x = 0; x < newWidth; x++) { const Float2 uv((float)x / newWidth, (float)y / newHeight); - Color color = TextureTool::SampleLinear(sampler, uv, iconData->Data.Get(), iconSize, iconData->RowPitch); + Color color = sampler->SampleLinear(iconData->Data.Get(), uv, iconSize, iconData->RowPitch); *(mipData + y * newWidth + x) = Color32(color); } } From e02f2990d996494d06f386b16b09369f0c64001b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Jan 2025 23:43:31 +0100 Subject: [PATCH 140/215] Another compilation fix --- Source/Engine/Platform/Linux/LinuxWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 89a7adeee..bc94815c8 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -886,7 +886,7 @@ void LinuxWindow::SetIcon(TextureData& icon) const auto mipData = mip.Data.Get(); const auto iconData = icon.GetData(0, 0); const Int2 iconSize(icon.Width, icon.Height); - const auto sampler = PixelFormatSampler::GetSampler(icon.Format); + const auto sampler = PixelFormatSampler::Get(icon.Format); for (int32 y = 0; y < icon.Height; y++) { for (int32 x = 0; x < icon.Width; x++) @@ -930,7 +930,7 @@ void LinuxWindow::SetIcon(TextureData& icon) const auto mipData = mip.Data.Get(); const auto iconData = icon.GetData(0, 0); const Int2 iconSize(icon.Width, icon.Height); - const auto sampler = PixelFormatSampler::GetSampler(img.Format); + const auto sampler = PixelFormatSampler::Get(img.Format); for (int32 y = 0; y < newHeight; y++) { for (int32 x = 0; x < newWidth; x++) From 39419787fa5a023b381748ef2bd4d1ce3cea1f3f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 15 Jan 2025 08:48:03 +0100 Subject: [PATCH 141/215] Another compilation fix --- Content/Editor/MaterialTemplates/Deformable.shader | 2 +- Source/Engine/Platform/Linux/LinuxWindow.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader index 5be86e33b..9ff0161eb 100644 --- a/Content/Editor/MaterialTemplates/Deformable.shader +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -297,7 +297,7 @@ VertexOutput VS_SplineModel(ModelInput input) output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes - output.Geometry.TexCoord = input.TexCoord; + output.Geometry.TexCoord = input.TexCoord0; #if USE_VERTEX_COLOR output.Geometry.VertexColor = input.Color; #endif diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index bc94815c8..0ac8b6ae9 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -8,6 +8,7 @@ #include "Engine/Input/Keyboard.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Collections/Array.h" From 6111f67e3394e940987a85da1766da7dfa6bbe2d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 16 Jan 2025 17:35:28 +0100 Subject: [PATCH 142/215] Add `ToSpan` for `MemoryWriteStream` and simplify code with it --- Source/Engine/CSG/CSGBuilder.cpp | 2 +- Source/Engine/Content/Assets/Animation.cpp | 4 ++-- Source/Engine/Content/Assets/AnimationGraph.cpp | 4 ++-- Source/Engine/Content/Assets/Material.cpp | 4 ++-- Source/Engine/Content/Assets/MaterialInstance.cpp | 2 +- Source/Engine/Content/Assets/Model.cpp | 4 ++-- Source/Engine/Content/Assets/ModelBase.cpp | 4 ++-- Source/Engine/Content/Assets/SkeletonMask.cpp | 4 ++-- Source/Engine/Content/Assets/VisualScript.cpp | 4 ++-- Source/Engine/ContentImporters/CreateAnimation.h | 2 +- .../Engine/ContentImporters/CreateAnimationGraph.cpp | 2 +- .../ContentImporters/CreateAnimationGraphFunction.h | 2 +- Source/Engine/ContentImporters/CreateBehaviorTree.h | 2 +- Source/Engine/ContentImporters/CreateMaterial.cpp | 2 +- .../Engine/ContentImporters/CreateMaterialFunction.h | 2 +- .../Engine/ContentImporters/CreateMaterialInstance.h | 2 +- .../Engine/ContentImporters/CreateParticleEmitter.h | 2 +- .../ContentImporters/CreateParticleEmitterFunction.h | 2 +- .../Engine/ContentImporters/CreateParticleSystem.h | 2 +- .../Engine/ContentImporters/CreateSceneAnimation.h | 2 +- Source/Engine/ContentImporters/CreateVisualScript.h | 4 ++-- Source/Engine/ContentImporters/ImportModel.cpp | 12 ++++++------ Source/Engine/ContentImporters/ImportTexture.cpp | 4 ++-- Source/Engine/Engine/GameplayGlobals.cpp | 2 +- .../Graphics/Shaders/Cache/ShaderAssetBase.cpp | 4 ++-- Source/Engine/Navigation/NavMesh.cpp | 2 +- Source/Engine/Particles/ParticleEmitter.cpp | 2 +- Source/Engine/Serialization/MemoryReadStream.cpp | 9 +++++++++ Source/Engine/Serialization/MemoryReadStream.h | 11 ++--------- Source/Engine/Serialization/MemoryWriteStream.h | 6 ++++++ 30 files changed, 59 insertions(+), 51 deletions(-) diff --git a/Source/Engine/CSG/CSGBuilder.cpp b/Source/Engine/CSG/CSGBuilder.cpp index fa8ce6291..323c93480 100644 --- a/Source/Engine/CSG/CSGBuilder.cpp +++ b/Source/Engine/CSG/CSGBuilder.cpp @@ -433,7 +433,7 @@ bool CSGBuilderImpl::generateRawDataAsset(Scene* scene, RawData& meshData, Guid& // Serialize BytesContainer bytesContainer; - bytesContainer.Link(stream.GetHandle(), stream.GetPosition()); + bytesContainer.Link(ToSpan(stream)); return AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer); } diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 8d4a03abb..af534d22a 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -221,7 +221,7 @@ void Animation::LoadTimeline(BytesContainer& result) const } } - result.Copy(stream.GetHandle(), stream.GetPosition()); + result.Copy(ToSpan(stream)); } bool Animation::SaveTimeline(BytesContainer& data) @@ -476,7 +476,7 @@ bool Animation::Save(const StringView& path) // Set data to the chunk asset auto chunk0 = GetOrCreateChunk(0); ASSERT(chunk0 != nullptr); - chunk0->Data.Copy(stream.GetHandle(), stream.GetPosition()); + chunk0->Data.Copy(ToSpan(stream)); } // Save diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index f5d3a8073..8a8570435 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -123,7 +123,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b // Load Graph data (with initialization) ScopeLock lock(Locker); - MemoryReadStream readStream(writeStream.GetHandle(), writeStream.GetPosition()); + MemoryReadStream readStream(ToSpan(writeStream)); return Graph.Load(&readStream, USE_EDITOR); } @@ -140,7 +140,7 @@ BytesContainer AnimationGraph::LoadSurface() if (!Graph.Save(&stream, USE_EDITOR)) { BytesContainer result; - result.Copy(stream.GetHandle(), stream.GetPosition()); + result.Copy(ToSpan(stream)); return result; } } diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 3c2330c93..9b0f537af 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -229,7 +229,7 @@ Asset::LoadResult Material::load() // Save layer to the chunk data MemoryWriteStream stream(512); layer->Graph.Save(&stream, false); - surfaceChunk->Data.Copy(stream.GetHandle(), stream.GetPosition()); + surfaceChunk->Data.Copy(ToSpan(stream)); } generator.AddLayer(layer); @@ -542,7 +542,7 @@ BytesContainer Material::LoadSurface(bool createDefaultIfMissing) layer->Graph.Save(&stream, false); // Set output data - result.Copy(stream.GetHandle(), stream.GetPosition()); + result.Copy(ToSpan(stream)); return result; } diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 13d46db09..28bbf8a8c 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -334,7 +334,7 @@ bool MaterialInstance::Save(const StringView& path) // Save parameters Params.Save(&stream); } - SetChunk(0, ToSpan(stream.GetHandle(), stream.GetPosition())); + SetChunk(0, ToSpan(stream)); // Setup asset data AssetInitData data; diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 01adbd207..915066fc2 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -279,7 +279,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, f if (cacheData) { auto chunk = GetOrCreateChunk(15); - chunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); + chunk->Data.Copy(ToSpan(sdfStream)); chunk->Flags |= FlaxChunkFlags::KeepInMemory; // Prevent GC-ing chunk data so it will be properly saved } #endif @@ -484,7 +484,7 @@ bool Model::Save(bool withMeshDataFromGpu, Function& getChunk sdfStream.WriteBytes(&mipData, sizeof(mipData)); sdfStream.WriteBytes(mip.Data.Get(), mip.Data.Length()); } - sdfChunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition()); + sdfChunk->Data.Copy(ToSpan(sdfStream)); } } else diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 9aa2aed6a..b194d9861 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -265,7 +265,7 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path) auto lodChunk = getChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); if (lodChunk == nullptr) return true; - lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); + lodChunk->Data.Copy(ToSpan(meshesStream)); } } else if (!IsVirtual()) @@ -289,7 +289,7 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path) return true; auto headerChunk = getChunk(0); ASSERT(headerChunk != nullptr); - headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition()); + headerChunk->Data.Copy(ToSpan(headerStream)); } // Save file diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 260e8ddf0..48fd71678 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -98,7 +98,7 @@ bool SkeletonMask::Save(const StringView& path) Platform::MemoryClear(tmpChunks, sizeof(tmpChunks)); FlaxChunk chunk; tmpChunks[0] = &chunk; - tmpChunks[0]->Data.Link(stream.GetHandle(), stream.GetPosition()); + tmpChunks[0]->Data.Link(ToSpan(stream)); AssetInitData initData; initData.SerializedVersion = SerializedVersion; @@ -109,7 +109,7 @@ bool SkeletonMask::Save(const StringView& path) else { auto chunk0 = GetChunk(0); - chunk0->Data.Copy(stream.GetHandle(), stream.GetPosition()); + chunk0->Data.Copy(ToSpan(stream)); AssetInitData initData; initData.SerializedVersion = SerializedVersion; diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 1cba4a709..c8b0fbf7e 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1700,7 +1700,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri // Special case for C# object property in Visual Script so duplicate the object instead of cloning the reference to it MemoryWriteStream writeStream; writeStream.Write(param); - MemoryReadStream readStream(writeStream.GetHandle(), writeStream.GetPosition()); + MemoryReadStream readStream(ToSpan(writeStream)); readStream.Read(param); } } @@ -2231,7 +2231,7 @@ bool VisualScript::SaveSurface(const BytesContainer& data, const Metadata& meta) metaStream.Write(meta.BaseTypename, 31); metaStream.Write((int32)meta.Flags); } - GetOrCreateChunk(1)->Data.Copy(metaStream.GetHandle(), metaStream.GetPosition()); + GetOrCreateChunk(1)->Data.Copy(ToSpan(metaStream)); // Save AssetInitData assetData; diff --git a/Source/Engine/ContentImporters/CreateAnimation.h b/Source/Engine/ContentImporters/CreateAnimation.h index dd6db2caf..8749fe98d 100644 --- a/Source/Engine/ContentImporters/CreateAnimation.h +++ b/Source/Engine/ContentImporters/CreateAnimation.h @@ -43,7 +43,7 @@ public: // Copy to asset chunk if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp index 77cb22560..da4995eca 100644 --- a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp +++ b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp @@ -40,7 +40,7 @@ CreateAssetResult CreateAnimationGraph::Create(CreateAssetContext& context) // Copy to asset chunk if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateAnimationGraphFunction.h b/Source/Engine/ContentImporters/CreateAnimationGraphFunction.h index 1d212f0f4..f4e6b0871 100644 --- a/Source/Engine/ContentImporters/CreateAnimationGraphFunction.h +++ b/Source/Engine/ContentImporters/CreateAnimationGraphFunction.h @@ -44,7 +44,7 @@ public: return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateBehaviorTree.h b/Source/Engine/ContentImporters/CreateBehaviorTree.h index e1c77228c..1665b6726 100644 --- a/Source/Engine/ContentImporters/CreateBehaviorTree.h +++ b/Source/Engine/ContentImporters/CreateBehaviorTree.h @@ -26,7 +26,7 @@ public: const BehaviorTreeGraph graph; MemoryWriteStream stream(64); graph.Save(&stream, true); - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); } return CreateAssetResult::Ok; diff --git a/Source/Engine/ContentImporters/CreateMaterial.cpp b/Source/Engine/ContentImporters/CreateMaterial.cpp index 96cfe6ce2..c0f89a5c8 100644 --- a/Source/Engine/ContentImporters/CreateMaterial.cpp +++ b/Source/Engine/ContentImporters/CreateMaterial.cpp @@ -191,7 +191,7 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context) MemoryWriteStream stream(512); layer->Graph.Save(&stream, true); - context.Data.Header.Chunks[SHADER_FILE_CHUNK_VISJECT_SURFACE]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[SHADER_FILE_CHUNK_VISJECT_SURFACE]->Data.Copy(ToSpan(stream)); Delete(layer); } else diff --git a/Source/Engine/ContentImporters/CreateMaterialFunction.h b/Source/Engine/ContentImporters/CreateMaterialFunction.h index 2bd34aeda..b9f531b54 100644 --- a/Source/Engine/ContentImporters/CreateMaterialFunction.h +++ b/Source/Engine/ContentImporters/CreateMaterialFunction.h @@ -45,7 +45,7 @@ public: return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateMaterialInstance.h b/Source/Engine/ContentImporters/CreateMaterialInstance.h index f0ef2f197..8e612e8d2 100644 --- a/Source/Engine/ContentImporters/CreateMaterialInstance.h +++ b/Source/Engine/ContentImporters/CreateMaterialInstance.h @@ -32,7 +32,7 @@ public: MemoryWriteStream stream(256); stream.Write(Guid::Empty); MaterialParams::Save(&stream, nullptr); - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateParticleEmitter.h b/Source/Engine/ContentImporters/CreateParticleEmitter.h index e935c8b30..1bc219cb4 100644 --- a/Source/Engine/ContentImporters/CreateParticleEmitter.h +++ b/Source/Engine/ContentImporters/CreateParticleEmitter.h @@ -37,7 +37,7 @@ public: graph.CreateDefault(); MemoryWriteStream stream(512); graph.Save(&stream, false); - context.Data.Header.Chunks[SHADER_FILE_CHUNK_VISJECT_SURFACE]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[SHADER_FILE_CHUNK_VISJECT_SURFACE]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateParticleEmitterFunction.h b/Source/Engine/ContentImporters/CreateParticleEmitterFunction.h index 4b5de35dc..9d3bc6a14 100644 --- a/Source/Engine/ContentImporters/CreateParticleEmitterFunction.h +++ b/Source/Engine/ContentImporters/CreateParticleEmitterFunction.h @@ -44,7 +44,7 @@ public: return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateParticleSystem.h b/Source/Engine/ContentImporters/CreateParticleSystem.h index da55d42ed..ed3a9b9c9 100644 --- a/Source/Engine/ContentImporters/CreateParticleSystem.h +++ b/Source/Engine/ContentImporters/CreateParticleSystem.h @@ -35,7 +35,7 @@ public: stream.WriteInt32(0); // Emitters Count stream.WriteInt32(0); // Tracks Count } - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateSceneAnimation.h b/Source/Engine/ContentImporters/CreateSceneAnimation.h index 222924cad..fa6ae03cd 100644 --- a/Source/Engine/ContentImporters/CreateSceneAnimation.h +++ b/Source/Engine/ContentImporters/CreateSceneAnimation.h @@ -34,7 +34,7 @@ public: stream.WriteInt32(5 * 60); // DurationFrames stream.WriteInt32(0); // Tracks Count } - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/CreateVisualScript.h b/Source/Engine/ContentImporters/CreateVisualScript.h index 5d5378f3d..729fb06d2 100644 --- a/Source/Engine/ContentImporters/CreateVisualScript.h +++ b/Source/Engine/ContentImporters/CreateVisualScript.h @@ -33,7 +33,7 @@ public: const VisualScriptGraph graph; MemoryWriteStream stream(64); graph.Save(&stream, true); - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); } // Chunk 1 - Visual Script Metadata @@ -44,7 +44,7 @@ public: stream.Write(1); stream.Write(*baseTypename, 31); stream.Write((int32)VisualScript::Flags::None); - context.Data.Header.Chunks[1]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[1]->Data.Copy(ToSpan(stream)); } return CreateAssetResult::Ok; diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index 3d93842c7..888cc303e 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -554,7 +554,7 @@ CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const Mo return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); // Pack model LODs data const auto lodCount = modelData.LODs.Count(); @@ -566,7 +566,7 @@ CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const Mo const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[chunkIndex]->Data.Copy(ToSpan(stream)); } // Generate SDF @@ -577,7 +577,7 @@ CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const Mo { if (context.AllocateChunk(15)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[15]->Data.Copy(ToSpan(stream)); } } @@ -596,7 +596,7 @@ CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, c return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); // Pack model LODs data const auto lodCount = modelData.LODs.Count(); @@ -608,7 +608,7 @@ CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, c const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[chunkIndex]->Data.Copy(ToSpan(stream)); } return CreateAssetResult::Ok; @@ -637,7 +637,7 @@ CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, cons return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[0]->Data.Copy(ToSpan(stream)); return CreateAssetResult::Ok; } diff --git a/Source/Engine/ContentImporters/ImportTexture.cpp b/Source/Engine/ContentImporters/ImportTexture.cpp index 03a221b76..eca5e546f 100644 --- a/Source/Engine/ContentImporters/ImportTexture.cpp +++ b/Source/Engine/ContentImporters/ImportTexture.cpp @@ -178,7 +178,7 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu } if (context.AllocateChunk(15)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[15]->Data.Copy(ToSpan(stream)); } // Save mip maps @@ -317,7 +317,7 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu } if (context.AllocateChunk(15)) return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition()); + context.Data.Header.Chunks[15]->Data.Copy(ToSpan(stream)); } // Save mip maps diff --git a/Source/Engine/Engine/GameplayGlobals.cpp b/Source/Engine/Engine/GameplayGlobals.cpp index d5f6fae65..d6b9548d7 100644 --- a/Source/Engine/Engine/GameplayGlobals.cpp +++ b/Source/Engine/Engine/GameplayGlobals.cpp @@ -187,7 +187,7 @@ bool GameplayGlobals::Save(const StringView& path) { chunk = GetOrCreateChunk(0); } - chunk->Data.Copy(stream.GetHandle(), stream.GetPosition()); + chunk->Data.Copy(ToSpan(stream)); // Save AssetInitData data; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 2869eacd9..8fd1131d1 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -292,7 +292,7 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) auto cacheChunk = parent->GetOrCreateChunk(cacheChunkIndex); if (cacheChunk == nullptr) return true; - cacheChunk->Data.Copy(cacheStream.GetHandle(), cacheStream.GetPosition()); + cacheChunk->Data.Copy(ToSpan(cacheStream)); #if USE_EDITOR // Save chunks to the asset file @@ -317,7 +317,7 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) else { // Use temporary generated data without caching (but read the includes from cache) - result.Data.Copy(cacheStream.GetHandle(), cacheStream.GetLength()); + result.Data.Copy(ToSpan(cacheStream)); IsValidShaderCache(result.Data, result.Includes); return false; } diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index 5b5220898..7eb2b468a 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -56,7 +56,7 @@ void NavMesh::SaveNavMesh() MemoryWriteStream stream(streamInitialCapacity); Data.Save(stream); BytesContainer bytesContainer; - bytesContainer.Link(stream.GetHandle(), stream.GetPosition()); + bytesContainer.Link(ToSpan(stream)); // Save asset to file if (AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer)) diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 570b026c6..8e9533474 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -369,7 +369,7 @@ BytesContainer ParticleEmitter::LoadSurface(bool createDefaultIfMissing) graph.Save(&stream, false); // Set output data - result.Copy(stream.GetHandle(), stream.GetPosition()); + result.Copy(ToSpan(stream)); } return result; diff --git a/Source/Engine/Serialization/MemoryReadStream.cpp b/Source/Engine/Serialization/MemoryReadStream.cpp index 17458a52c..167808a69 100644 --- a/Source/Engine/Serialization/MemoryReadStream.cpp +++ b/Source/Engine/Serialization/MemoryReadStream.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "MemoryReadStream.h" +#include "Engine/Platform/Platform.h" MemoryReadStream::MemoryReadStream() : _buffer(nullptr) @@ -22,6 +23,14 @@ void MemoryReadStream::Init(const byte* bytes, uint32 length) _length = length; } +void* MemoryReadStream::Move(uint32 bytes) +{ + ASSERT(GetLength() - GetPosition() >= bytes); + const auto result = (void*)_position; + _position += bytes; + return result; +} + void MemoryReadStream::Flush() { } diff --git a/Source/Engine/Serialization/MemoryReadStream.h b/Source/Engine/Serialization/MemoryReadStream.h index 3db90f1a6..8e7f1315c 100644 --- a/Source/Engine/Serialization/MemoryReadStream.h +++ b/Source/Engine/Serialization/MemoryReadStream.h @@ -3,7 +3,6 @@ #pragma once #include "ReadStream.h" -#include "Engine/Platform/Platform.h" /// /// Direct memory reading stream that uses a single allocation buffer. @@ -47,7 +46,7 @@ public: /// Span with data to read from. template MemoryReadStream(const Span& data) - : MemoryReadStream(data.Get(), data.Count() * sizeof(T)) + : MemoryReadStream(data.Get(), data.Length() * sizeof(T)) { } @@ -83,13 +82,7 @@ public: /// /// The amount of bytes to read. /// The pointer to the data in memory. - void* Move(uint32 bytes) - { - ASSERT(GetLength() - GetPosition() >= bytes); - const auto result = (void*)_position; - _position += bytes; - return result; - } + void* Move(uint32 bytes); /// /// Skips the data from the target buffer without reading from it. Moves the read pointer in the buffer forward. diff --git a/Source/Engine/Serialization/MemoryWriteStream.h b/Source/Engine/Serialization/MemoryWriteStream.h index 6b181d3b0..4144783ad 100644 --- a/Source/Engine/Serialization/MemoryWriteStream.h +++ b/Source/Engine/Serialization/MemoryWriteStream.h @@ -3,6 +3,7 @@ #pragma once #include "WriteStream.h" +#include "Engine/Core/Types/Span.h" /// /// Direct memory writing stream that uses a single allocation buffer. @@ -115,3 +116,8 @@ public: void SetPosition(uint32 seek) override; void WriteBytes(const void* data, uint32 bytes) override; }; + +inline Span ToSpan(MemoryWriteStream& stream) +{ + return Span(stream.GetHandle(), (int32)stream.GetPosition()); +} From 1497acef5833eada10f578e1fcd50f1da2502802 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 16 Jan 2025 17:42:01 +0100 Subject: [PATCH 143/215] SImplify capacity presetting for Dictionary to handle memory slack scale internally as suers care about items count only --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 2 +- Source/Engine/Content/Assets/Animation.cpp | 2 +- Source/Engine/Core/Collections/Dictionary.h | 9 +++++---- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 20 +++++++++---------- Source/Engine/Level/Prefabs/Prefab.cpp | 6 +++--- Source/Engine/Level/Prefabs/PrefabManager.cpp | 4 ++-- Source/Engine/Level/Scene/SceneCSGData.cpp | 2 +- Source/Engine/Scripting/BinaryModule.cpp | 2 +- Source/Engine/Serialization/Serialization.h | 4 ++-- 9 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index f74a21103..e04f31af6 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -158,7 +158,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) LOG(Info, "Loading incremental build cooking cache (entries count: {0})", entriesCount); file->ReadBytes(&Settings, sizeof(Settings)); - Entries.EnsureCapacity(Math::RoundUpToPowerOf2(static_cast(entriesCount * 3.0f))); + Entries.EnsureCapacity(entriesCount); Array> fileDependencies; for (int32 i = 0; i < entriesCount; i++) diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index af534d22a..88c13277a 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -262,7 +262,7 @@ bool Animation::SaveTimeline(BytesContainer& data) Events.Clear(); NestedAnims.Clear(); Dictionary animationChannelTrackIndexToChannelIndex; - animationChannelTrackIndexToChannelIndex.EnsureCapacity(tracksCount * 3); + animationChannelTrackIndexToChannelIndex.EnsureCapacity(tracksCount); for (int32 trackIndex = 0; trackIndex < tracksCount; trackIndex++) { const byte trackType = stream.ReadByte(); diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index efb7bafcf..90c87d8e8 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -407,7 +407,7 @@ public: Compact(); // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount); + EnsureCapacity(_elementsCount + 1 + _deletedCount); // Find location of the item or place to insert it FindPositionResult pos; @@ -592,10 +592,11 @@ public: /// /// Ensures that collection has given capacity. /// - /// The minimum required capacity. + /// The minimum required items capacity. /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. void EnsureCapacity(int32 minCapacity, const bool preserveContents = true) { + minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE; if (_size >= minCapacity) return; int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); @@ -806,7 +807,7 @@ public: { // TODO: if both key and value are POD types then use raw memory copy for buckets Clear(); - EnsureCapacity(other.Capacity(), false); + SetCapacity(other.Capacity(), false); for (Iterator i = other.Begin(); i != other.End(); ++i) Add(i); } @@ -939,7 +940,7 @@ private: Compact(); // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount); + EnsureCapacity(_elementsCount + 1 + _deletedCount); // Find location of the item or place to insert it FindPositionResult pos; diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index e5114547e..4d100773c 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -256,7 +256,7 @@ void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabIns } // Build acceleration table - instance.PrefabInstanceIdToDataIndex.EnsureCapacity(sceneObjects->Count() * 4); + instance.PrefabInstanceIdToDataIndex.EnsureCapacity(sceneObjects->Count()); for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects.Value->At(i); @@ -296,7 +296,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI SceneQuery::GetAllSerializableSceneObjects(instance.TargetActor, *sceneObjects.Value); int32 existingObjectsCount = sceneObjects->Count(); - modifier->IdsMapping.EnsureCapacity((existingObjectsCount + newPrefabObjectIds.Count()) * 4); + modifier->IdsMapping.EnsureCapacity((existingObjectsCount + newPrefabObjectIds.Count())); // Map prefab objects to the prefab instance objects for (int32 i = 0; i < existingObjectsCount; i++) @@ -561,7 +561,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI // Build cache data IdToDataLookupType prefabObjectIdToDiffData; - prefabObjectIdToDiffData.EnsureCapacity(defaultInstanceData.Size() * 3); + prefabObjectIdToDiffData.EnsureCapacity(defaultInstanceData.Size()); for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects.Value->At(i); @@ -760,8 +760,8 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr rapidjson_flax::Document diffDataDocument; Dictionary diffPrefabObjectIdToDataIndex; // Maps Prefab Object Id -> Actor Data index in diffDataDocument json array (for actors/scripts to modify prefab) Dictionary newPrefabInstanceIdToDataIndex; // Maps Prefab Instance Id -> Actor Data index in diffDataDocument json array (for new actors/scripts to add to prefab), maps to -1 for scripts - diffPrefabObjectIdToDataIndex.EnsureCapacity(ObjectsCount * 4); - newPrefabInstanceIdToDataIndex.EnsureCapacity(ObjectsCount * 4); + diffPrefabObjectIdToDataIndex.EnsureCapacity(ObjectsCount); + newPrefabInstanceIdToDataIndex.EnsureCapacity(ObjectsCount); { // Parse json to DOM document { @@ -813,7 +813,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr // Change object ids to match the prefab objects ids (helps with linking references in scripts) Dictionary objectInstanceIdToPrefabObjectId; - objectInstanceIdToPrefabObjectId.EnsureCapacity(ObjectsCount * 3); + objectInstanceIdToPrefabObjectId.EnsureCapacity(ObjectsCount); i = 0; for (auto it = array.Begin(); it != array.End(); ++it, i++) { @@ -843,7 +843,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); // Generate new IDs for the added objects (objects in prefab has to have a unique Ids, other than the targetActor instance objects to prevent Id collisions) - newPrefabInstanceIdToPrefabObjectId.EnsureCapacity(newPrefabInstanceIdToDataIndex.Count() * 4); + newPrefabInstanceIdToPrefabObjectId.EnsureCapacity(newPrefabInstanceIdToDataIndex.Count()); for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i) { const auto prefabObjectId = Guid::New(); @@ -1175,9 +1175,9 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r const int32 objectsCount = Data->GetArray().Size(); if (objectsCount <= 0) return true; - ObjectsIds.EnsureCapacity(objectsCount * 2); + ObjectsIds.EnsureCapacity(objectsCount); NestedPrefabs.EnsureCapacity(objectsCount); - ObjectsDataCache.EnsureCapacity(objectsCount * 3); + ObjectsDataCache.EnsureCapacity(objectsCount); const auto& data = *Data; for (int32 objectIndex = 0; objectIndex < objectsCount; objectIndex++) { @@ -1197,7 +1197,7 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r { if (prefabId == _id) { - LOG(Error, "Circural reference in prefab."); + LOG(Error, "Circular reference in prefab."); continue; } diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index b85be7287..9e03a01b8 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -128,8 +128,8 @@ Asset::LoadResult Prefab::loadAsset() } // Allocate memory for objects - ObjectsIds.EnsureCapacity(objectsCount * 2); - ObjectsDataCache.EnsureCapacity(objectsCount * 3); + ObjectsIds.EnsureCapacity(objectsCount); + ObjectsDataCache.EnsureCapacity(objectsCount); // Find serialized object ids (actors and scripts), they are used later for IDs mapping on prefab spawning via PrefabManager const auto& data = *Data; @@ -157,7 +157,7 @@ Asset::LoadResult Prefab::loadAsset() { if (prefabId == _id) { - LOG(Error, "Circural reference in prefab."); + LOG(Error, "Circular reference in prefab."); return LoadResult::InvalidData; } diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 96040e8b6..251fabe09 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -111,8 +111,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac sceneObjects->Resize(dataCount); CollectionPoolCache::ScopeCache modifier = Cache::ISerializeModifier.Get(); modifier->EngineBuild = prefab->DataEngineBuild; - modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count() * 4); for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++) + modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count()); { modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New()); } @@ -352,7 +352,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat // Randomize the objects ids (prevent overlapping of the prefab instance objects ids and the prefab objects ids) Dictionary objectInstanceIdToPrefabObjectId; - objectInstanceIdToPrefabObjectId.EnsureCapacity(sceneObjects->Count() * 3); + objectInstanceIdToPrefabObjectId.EnsureCapacity(sceneObjects->Count()); if (targetActor->HasParent()) { // Unlink from parent actor diff --git a/Source/Engine/Level/Scene/SceneCSGData.cpp b/Source/Engine/Level/Scene/SceneCSGData.cpp index 8dbbe526d..08177a066 100644 --- a/Source/Engine/Level/Scene/SceneCSGData.cpp +++ b/Source/Engine/Level/Scene/SceneCSGData.cpp @@ -88,7 +88,7 @@ bool SceneCSGData::TryGetSurfaceData(const Guid& brushId, int32 brushSurfaceInde // Invalid data return false; } - DataBrushLocations.EnsureCapacity((int32)(brushesCount * 4.0f)); + DataBrushLocations.EnsureCapacity(brushesCount); for (int32 i = 0; i < brushesCount; i++) { Guid id; diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index a44bfc4da..3e7509f1e 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -931,7 +931,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) const auto& classes = assembly->GetClasses(); // Cache managed types information - ClassToTypeIndex.EnsureCapacity(Types.Count() * 4); + ClassToTypeIndex.EnsureCapacity(Types.Count()); for (int32 typeIndex = 0; typeIndex < Types.Count(); typeIndex++) { ScriptingType& type = Types[typeIndex]; diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 9c3e4f697..d45a8ee88 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -649,7 +649,7 @@ namespace Serialization { const auto& streamArray = stream.GetArray(); const int32 size = streamArray.Size(); - v.EnsureCapacity(size * 3); + v.EnsureCapacity(size); for (int32 i = 0; i < size; i++) { auto& streamItem = streamArray[i]; @@ -666,7 +666,7 @@ namespace Serialization else if (stream.IsObject()) { const int32 size = stream.MemberCount(); - v.EnsureCapacity(size * 3); + v.EnsureCapacity(size); for (auto i = stream.MemberBegin(); i != stream.MemberEnd(); ++i) { KeyType key; From 8a7ceef2881a99e28f220317587ac2bf3c262199 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:46:49 +0100 Subject: [PATCH 144/215] Add content deprecation system that auto-saves assets in Editor that use old data format --- Flax.sln.DotSettings | 1 + Source/Engine/AI/BehaviorTree.cpp | 29 +-- Source/Engine/AI/BehaviorTree.h | 5 +- .../SceneAnimations/SceneAnimation.cpp | 187 ++++++++++++++++-- .../SceneAnimations/SceneAnimation.h | 7 +- Source/Engine/Content/Asset.cpp | 73 +++++++ Source/Engine/Content/Asset.h | 10 +- Source/Engine/Content/Assets/Animation.cpp | 21 +- Source/Engine/Content/Assets/Animation.h | 10 +- .../Engine/Content/Assets/AnimationGraph.cpp | 28 +-- Source/Engine/Content/Assets/AnimationGraph.h | 5 +- .../Content/Assets/AnimationGraphFunction.cpp | 37 ++-- .../Content/Assets/AnimationGraphFunction.h | 8 +- Source/Engine/Content/Assets/Material.cpp | 59 ++++-- Source/Engine/Content/Assets/Material.h | 4 +- .../Content/Assets/MaterialFunction.cpp | 33 ++-- .../Engine/Content/Assets/MaterialFunction.h | 13 +- .../Content/Assets/MaterialInstance.cpp | 12 +- .../Engine/Content/Assets/MaterialInstance.h | 13 +- Source/Engine/Content/Assets/Model.cpp | 4 +- Source/Engine/Content/Assets/Model.h | 4 +- Source/Engine/Content/Assets/ModelBase.cpp | 20 +- Source/Engine/Content/Assets/ModelBase.h | 5 +- Source/Engine/Content/Assets/RawDataAsset.cpp | 12 +- Source/Engine/Content/Assets/RawDataAsset.h | 15 +- Source/Engine/Content/Assets/SkeletonMask.cpp | 12 +- Source/Engine/Content/Assets/SkeletonMask.h | 12 +- Source/Engine/Content/Assets/SkinnedModel.cpp | 2 +- Source/Engine/Content/Assets/SkinnedModel.h | 2 +- Source/Engine/Content/Assets/Texture.cpp | 19 +- Source/Engine/Content/Assets/Texture.h | 18 +- Source/Engine/Content/Assets/VisualScript.cpp | 33 ++-- Source/Engine/Content/Assets/VisualScript.h | 7 +- Source/Engine/Content/BinaryAsset.cpp | 17 +- Source/Engine/Content/BinaryAsset.h | 6 +- Source/Engine/Content/Content.cpp | 5 - Source/Engine/Content/Deprecated.h | 26 +++ Source/Engine/Content/JsonAsset.cpp | 78 ++++++-- Source/Engine/Content/JsonAsset.h | 10 +- Source/Engine/Core/Types/Variant.cpp | 2 + Source/Engine/Engine/GameplayGlobals.cpp | 12 +- Source/Engine/Engine/GameplayGlobals.h | 14 +- Source/Engine/Foliage/Foliage.cpp | 2 + .../Graphics/Materials/MaterialParams.cpp | 5 + .../Shaders/Cache/ShaderAssetBase.cpp | 8 +- .../Graphics/Shaders/Cache/ShaderAssetBase.h | 14 +- .../Graphics/Shaders/Cache/ShaderStorage.h | 2 +- Source/Engine/Level/Actor.cpp | 7 + Source/Engine/Level/Actors/AnimatedModel.cpp | 7 + .../Engine/Level/Actors/EnvironmentProbe.cpp | 2 + Source/Engine/Level/Actors/SplineModel.cpp | 7 + Source/Engine/Level/Actors/StaticModel.cpp | 8 + Source/Engine/Navigation/Navigation.cpp | 5 +- Source/Engine/Particles/ParticleEffect.cpp | 2 + Source/Engine/Particles/ParticleEmitter.cpp | 50 +++-- Source/Engine/Particles/ParticleEmitter.h | 26 +-- .../Particles/ParticleEmitterFunction.cpp | 35 ++-- .../Particles/ParticleEmitterFunction.h | 17 +- Source/Engine/Particles/ParticleSystem.cpp | 30 +-- Source/Engine/Particles/ParticleSystem.h | 7 +- .../Engine/Physics/Actors/WheeledVehicle.cpp | 9 +- Source/Engine/Render2D/FontAsset.cpp | 12 +- Source/Engine/Render2D/FontAsset.h | 12 +- Source/Engine/Serialization/JsonTools.cpp | 2 + Source/Engine/Terrain/Terrain.cpp | 10 +- Source/Engine/UI/TextRender.cpp | 7 + Source/Engine/Visject/Graph.h | 2 + 67 files changed, 751 insertions(+), 427 deletions(-) create mode 100644 Source/Engine/Content/Deprecated.h diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 8ff4f1621..86647d380 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -237,6 +237,7 @@ True True True + True DisabledByUser True Blue diff --git a/Source/Engine/AI/BehaviorTree.cpp b/Source/Engine/AI/BehaviorTree.cpp index 8cbb9f608..69fd0f854 100644 --- a/Source/Engine/AI/BehaviorTree.cpp +++ b/Source/Engine/AI/BehaviorTree.cpp @@ -13,6 +13,7 @@ #include "FlaxEngine.Gen.h" #if USE_EDITOR #include "Engine/Level/Level.h" +#include "Engine/Serialization/MemoryWriteStream.h" #endif REGISTER_BINARY_ASSET(BehaviorTree, "FlaxEngine.BehaviorTree", false); @@ -164,7 +165,7 @@ BehaviorTreeNode* BehaviorTree::GetNodeInstance(uint32 id) return nullptr; } -BytesContainer BehaviorTree::LoadSurface() +BytesContainer BehaviorTree::LoadSurface() const { if (WaitForLoaded()) return BytesContainer(); @@ -182,19 +183,10 @@ BytesContainer BehaviorTree::LoadSurface() #if USE_EDITOR -bool BehaviorTree::SaveSurface(const BytesContainer& data) +bool BehaviorTree::SaveSurface(const BytesContainer& data) const { - // Wait for asset to be loaded or don't if last load failed - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Set Visject Surface data @@ -250,6 +242,19 @@ void BehaviorTree::GetReferences(Array& assets, Array& files) cons } } +bool BehaviorTree::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream stream; + if (Graph.Save(&stream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(stream)); + return SaveSurface(data); +} + #endif void BehaviorTree::OnScriptingDispose() diff --git a/Source/Engine/AI/BehaviorTree.h b/Source/Engine/AI/BehaviorTree.h index 1463cd9a5..3b3265232 100644 --- a/Source/Engine/AI/BehaviorTree.h +++ b/Source/Engine/AI/BehaviorTree.h @@ -77,7 +77,7 @@ public: /// Tries to load surface graph from the asset. /// /// The surface data or empty if failed to load it. - API_FUNCTION() BytesContainer LoadSurface(); + API_FUNCTION() BytesContainer LoadSurface() const; #if USE_EDITOR /// @@ -85,7 +85,7 @@ public: /// /// Stream with graph data. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(const BytesContainer& data); + API_FUNCTION() bool SaveSurface(const BytesContainer& data) const; #endif private: @@ -99,6 +99,7 @@ public: void OnScriptingDispose() override; #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index afe4446bc..6896f242a 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -5,6 +5,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Audio/AudioClip.h" #include "Engine/Graphics/PostProcessSettings.h" @@ -35,19 +36,166 @@ const BytesContainer& SceneAnimation::LoadTimeline() #if USE_EDITOR -bool SceneAnimation::SaveTimeline(const BytesContainer& data) +void SceneAnimation::SaveData(MemoryWriteStream& stream) const { - // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); - return true; - } + // Save properties + stream.Write(4); + stream.Write(FramesPerSecond); + stream.Write(DurationFrames); + // Save tracks + stream.Write(Tracks.Count()); + for (const auto& track : Tracks) + { + stream.Write((byte)track.Type); + stream.Write((byte)track.Flag); + stream.Write((int32)track.ParentIndex); + stream.Write((int32)track.ChildrenCount); + stream.Write(track.Name, -13); + stream.Write(track.Color); + switch (track.Type) + { + case Track::Types::Folder: + break; + case Track::Types::PostProcessMaterial: + { + auto trackData = stream.Move(); + trackData->AssetID = track.Asset.GetID(); + const auto trackRuntime = track.GetRuntimeData(); + stream.Write((int32)trackRuntime->Count); + stream.WriteBytes(trackRuntime->Media, sizeof(Media) * trackRuntime->Count); + break; + } + case Track::Types::NestedSceneAnimation: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + trackData->AssetID = track.Asset.GetID(); + break; + } + case Track::Types::ScreenFade: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.WriteBytes(trackRuntime->GradientStops, sizeof(ScreenFadeTrack::GradientStop) * trackData->GradientStopsCount); + break; + } + case Track::Types::Audio: + { + auto trackData = stream.Move(); + trackData->AssetID = track.Asset.GetID(); + const auto trackRuntime = track.GetRuntimeData(); + stream.Write((int32)trackRuntime->Count); + stream.WriteBytes(trackRuntime->Media, sizeof(AudioTrack::Media) * trackRuntime->Count); + break; + } + case Track::Types::AudioVolume: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.WriteBytes(trackRuntime->Keyframes, sizeof(BezierCurveKeyframe) * trackRuntime->KeyframesCount); + break; + } + case Track::Types::Actor: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + break; + } + case Track::Types::Script: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + break; + } + case Track::Types::KeyframesProperty: + case Track::Types::ObjectReferenceProperty: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.WriteBytes(trackRuntime->PropertyName, trackData->PropertyNameLength + 1); + stream.WriteBytes(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength + 1); + stream.WriteBytes(trackRuntime->Keyframes, trackRuntime->KeyframesSize); + break; + } + case Track::Types::CurveProperty: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.WriteBytes(trackRuntime->PropertyName, trackData->PropertyNameLength + 1); + stream.WriteBytes(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength + 1); + const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3); + stream.WriteBytes(trackRuntime->Keyframes, keyframesDataSize); + break; + } + case Track::Types::StringProperty: + { + const auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.WriteBytes(trackRuntime->PropertyName, trackData->PropertyNameLength + 1); + stream.WriteBytes(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength + 1); + const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime)); + const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount); + const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount); + for (int32 j = 0; j < trackRuntime->KeyframesCount; j++) + { + stream.Write((float)keyframesTimes[j]); + stream.Write((int32)keyframesLengths[j]); + stream.WriteBytes(keyframesValues[j], keyframesLengths[j] * sizeof(Char)); + } + break; + } + case Track::Types::StructProperty: + case Track::Types::ObjectProperty: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.WriteBytes(trackRuntime->PropertyName, trackData->PropertyNameLength + 1); + stream.WriteBytes(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength + 1); + break; + } + case Track::Types::Event: + { + const auto trackRuntime = track.GetRuntimeData(); + int32 tmp = StringUtils::Length(trackRuntime->EventName); + stream.Write((int32)trackRuntime->EventParamsCount); + stream.Write((int32)trackRuntime->EventsCount); + stream.Write((int32)tmp); + stream.WriteBytes(trackRuntime->EventName, tmp + 1); + for (int j = 0; j < trackRuntime->EventParamsCount; j++) + { + stream.Write((int32)trackRuntime->EventParamSizes[j]); + stream.Write((int32)tmp); + stream.WriteBytes(trackRuntime->EventParamTypes[j], tmp + 1); + } + stream.WriteBytes(trackRuntime->DataBegin, trackRuntime->EventsCount * (sizeof(float) + trackRuntime->EventParamsSize)); + break; + } + case Track::Types::CameraCut: + { + auto trackData = stream.Move(); + *trackData = *track.GetData(); + const auto trackRuntime = track.GetRuntimeData(); + stream.Write((int32)trackRuntime->Count); + stream.WriteBytes(trackRuntime->Media, sizeof(Media) * trackRuntime->Count); + break; + } + default: + break; + } + } +} + +bool SceneAnimation::SaveTimeline(const BytesContainer& data) const +{ + if (OnCheckSave()) + return true; ScopeLock lock(Locker); // Release all chunks @@ -71,10 +219,6 @@ bool SceneAnimation::SaveTimeline(const BytesContainer& data) return false; } -#endif - -#if USE_EDITOR - void SceneAnimation::GetReferences(Array& assets, Array& files) const { // Base @@ -88,6 +232,18 @@ void SceneAnimation::GetReferences(Array& assets, Array& files) co } } +bool SceneAnimation::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream stream; + SaveData(stream); + BytesContainer data; + data.Link(ToSpan(stream)); + return SaveTimeline(data); +} + #endif Asset::LoadResult SceneAnimation::load() @@ -117,6 +273,7 @@ Asset::LoadResult SceneAnimation::load() { case 2: // [Deprecated in 2020 expires on 03.09.2023] case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023] + MARK_CONTENT_DEPRECATED(); case 4: { stream.Read(FramesPerSecond); diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h index 9232589b5..af2dec366 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h @@ -415,6 +415,10 @@ private: BytesContainer _data; MemoryWriteStream _runtimeData; +#if USE_EDITOR + void SaveData(MemoryWriteStream& stream) const; +#endif + public: /// /// The frames amount per second of the timeline animation. @@ -457,7 +461,7 @@ public: /// It cannot be used by virtual assets. /// The timeline data container. /// true failed to save data; otherwise, false. - API_FUNCTION() bool SaveTimeline(const BytesContainer& data); + API_FUNCTION() bool SaveTimeline(const BytesContainer& data) const; #endif @@ -465,6 +469,7 @@ public: // [BinaryAsset] #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 961ea7ed2..0a8672e69 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -2,6 +2,7 @@ #include "Asset.h" #include "Content.h" +#include "Deprecated.h" #include "SoftAssetReference.h" #include "Cache/AssetsCache.h" #include "Loading/Tasks/LoadAssetTask.h" @@ -10,6 +11,26 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Threading/MainThreadTask.h" +#include "Engine/Threading/ThreadLocal.h" + +#if USE_EDITOR + +ThreadLocal ContentDeprecatedFlags; + +void ContentDeprecated::Mark() +{ + ContentDeprecatedFlags.Set(true); +} + +bool ContentDeprecated::Clear(bool newValue) +{ + auto& flag = ContentDeprecatedFlags.Get(); + bool result = flag; + flag = newValue; + return result; +} + +#endif AssetReferenceBase::~AssetReferenceBase() { @@ -340,6 +361,7 @@ void Asset::Reload() // Virtual assets are memory-only so reloading them makes no sense if (IsVirtual()) return; + PROFILE_CPU_NAMED("Asset.Reload"); // It's better to call it from the main thread if (IsInMainThread()) @@ -476,6 +498,12 @@ Array Asset::GetReferences() const return result; } +bool Asset::Save(const StringView& path) +{ + LOG(Warning, "Asset type '{}' does not support saving.", GetTypeName()); + return true; +} + #endif void Asset::DeleteManaged() @@ -524,12 +552,21 @@ bool Asset::onLoad(LoadAssetTask* task) // Load asset LoadResult result; +#if USE_EDITOR + auto& deprecatedFlag = ContentDeprecatedFlags.Get(); + bool prevDeprecated = deprecatedFlag; + deprecatedFlag = false; +#endif { PROFILE_CPU_ASSET(this); result = loadAsset(); } const bool isLoaded = result == LoadResult::Ok; const bool failed = !isLoaded; +#if USE_EDITOR + const bool isDeprecated = deprecatedFlag; + deprecatedFlag = prevDeprecated; +#endif Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed)); if (failed) { @@ -550,6 +587,19 @@ bool Asset::onLoad(LoadAssetTask* task) // This allows to reduce mutexes and locks (max one frame delay isn't hurting but provides more safety) Content::onAssetLoaded(this); } + +#if USE_EDITOR + // Auto-save deprecated assets to get rid of data in an old format + if (isDeprecated && isLoaded) + { + PROFILE_CPU_NAMED("Asset.Save"); + LOG(Info, "Resaving asset '{}' that uses deprecated data format", ToString()); + if (Save()) + { + LOG(Error, "Failed to resave asset '{}'", ToString()); + } + } +#endif return failed; } @@ -595,3 +645,26 @@ void Asset::onUnload_MainThread() loadingTask->Cancel(); } } + +#if USE_EDITOR + +bool Asset::OnCheckSave(const StringView& path) const +{ + if (LastLoadFailed()) + { + LOG(Warning, "Saving asset that failed to load."); + } + if (WaitForLoaded()) + { + LOG(Error, "Asset loading failed. Cannot save it."); + return true; + } + if (IsVirtual() && path.IsEmpty()) + { + LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); + return true; + } + return false; +} + +#endif diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index de79a640b..a82c1ce23 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -75,7 +75,7 @@ public: EventType OnUnloaded; /// - /// General purpose mutex for an asset object. Should guard most of asset functionalities to be secure. + /// General purpose mutex for an asset object. Should guard most of the asset functionalities to be secure. /// CriticalSection Locker; @@ -209,6 +209,13 @@ public: /// /// The collection of the asset ids referenced by this asset. API_FUNCTION() Array GetReferences() const; + + /// + /// Saves this asset to the file. Supported only in Editor. + /// + /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. + /// True when cannot save data, otherwise false. + API_FUNCTION(Sealed) virtual bool Save(const StringView& path = StringView::Empty); #endif /// @@ -253,6 +260,7 @@ protected: virtual void onLoaded_MainThread(); virtual void onUnload_MainThread(); #if USE_EDITOR + bool OnCheckSave(const StringView& path = StringView::Empty) const; virtual void onRename(const StringView& newPath) = 0; #endif diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 88c13277a..473aef700 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -226,16 +226,8 @@ void Animation::LoadTimeline(BytesContainer& result) const bool Animation::SaveTimeline(BytesContainer& data) { - // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } ScopeLock lock(Locker); MemoryReadStream stream(data.Get(), data.Length()); @@ -401,17 +393,8 @@ bool Animation::SaveTimeline(BytesContainer& data) bool Animation::Save(const StringView& path) { - // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - ScopeLock lock(Locker); // Serialize animation data to the stream diff --git a/Source/Engine/Content/Assets/Animation.h b/Source/Engine/Content/Assets/Animation.h index 68e1f9c5e..02888420d 100644 --- a/Source/Engine/Content/Assets/Animation.h +++ b/Source/Engine/Content/Assets/Animation.h @@ -138,17 +138,10 @@ public: /// /// Saves the serialized timeline data to the asset as animation. /// - /// The cannot be used by virtual assets. + /// This cannot be used by virtual assets. /// The timeline data container. /// true failed to save data; otherwise, false. API_FUNCTION() bool SaveTimeline(BytesContainer& data); - - /// - /// Saves the animation data to the asset. Supported only in Editor. - /// - /// The cannot be used by virtual assets. - /// true failed to save data; otherwise, false. - bool Save(const StringView& path = StringView::Empty); #endif private: @@ -161,6 +154,7 @@ public: // [BinaryAsset] #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif uint64 GetMemoryUsage() const override; void OnScriptingDispose() override; diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index 8a8570435..ecd1e71af 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -127,7 +127,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b return Graph.Load(&readStream, USE_EDITOR); } -BytesContainer AnimationGraph::LoadSurface() +BytesContainer AnimationGraph::LoadSurface() const { if (!IsVirtual() && WaitForLoaded()) return BytesContainer(); @@ -160,19 +160,10 @@ BytesContainer AnimationGraph::LoadSurface() #if USE_EDITOR -bool AnimationGraph::SaveSurface(BytesContainer& data) +bool AnimationGraph::SaveSurface(const BytesContainer& data) { - // Wait for asset to be loaded or don't if last load failed - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); if (IsVirtual()) @@ -228,4 +219,17 @@ void AnimationGraph::GetReferences(Array& assets, Array& files) co Graph.GetReferences(assets); } +bool AnimationGraph::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream writeStream; + if (Graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + return SaveSurface(data); +} + #endif diff --git a/Source/Engine/Content/Assets/AnimationGraph.h b/Source/Engine/Content/Assets/AnimationGraph.h index 77468e448..00379d32e 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.h +++ b/Source/Engine/Content/Assets/AnimationGraph.h @@ -45,7 +45,7 @@ public: /// Tries to load surface graph from the asset. /// /// The surface data or empty if failed to load it. - API_FUNCTION() BytesContainer LoadSurface(); + API_FUNCTION() BytesContainer LoadSurface() const; #if USE_EDITOR @@ -54,7 +54,7 @@ public: /// /// Stream with graph data. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(BytesContainer& data); + API_FUNCTION() bool SaveSurface(const BytesContainer& data); private: void FindDependencies(AnimGraphBase* graph); @@ -65,6 +65,7 @@ public: // [BinaryAsset] #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Content/Assets/AnimationGraphFunction.cpp b/Source/Engine/Content/Assets/AnimationGraphFunction.cpp index 383ecc938..ab857976e 100644 --- a/Source/Engine/Content/Assets/AnimationGraphFunction.cpp +++ b/Source/Engine/Content/Assets/AnimationGraphFunction.cpp @@ -4,6 +4,9 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Serialization/MemoryReadStream.h" +#if USE_EDITOR +#include "Engine/Serialization/MemoryWriteStream.h" +#endif #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Threading/Threading.h" @@ -84,19 +87,10 @@ void AnimationGraphFunction::GetSignature(Array> } } -bool AnimationGraphFunction::SaveSurface(const BytesContainer& data) +bool AnimationGraphFunction::SaveSurface(const BytesContainer& data) const { - // Wait for asset to be loaded or don't if last load failed - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Set Visject Surface data @@ -185,3 +179,24 @@ void AnimationGraphFunction::ProcessGraphForSignature(AnimGraphBase* graph, bool } } } + +#if USE_EDITOR + +bool AnimationGraphFunction::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + AnimGraph graph(const_cast(this), true); + MemoryReadStream readStream(GraphData.Get(), GraphData.Length()); + if (graph.Load(&readStream, true)) + return true; + MemoryWriteStream writeStream; + if (graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + return SaveSurface(data); +} + +#endif diff --git a/Source/Engine/Content/Assets/AnimationGraphFunction.h b/Source/Engine/Content/Assets/AnimationGraphFunction.h index 42956bc84..0d456bce6 100644 --- a/Source/Engine/Content/Assets/AnimationGraphFunction.h +++ b/Source/Engine/Content/Assets/AnimationGraphFunction.h @@ -53,13 +53,19 @@ public: /// /// The surface graph data. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(const BytesContainer& data); + API_FUNCTION() bool SaveSurface(const BytesContainer& data) const; #endif private: void ProcessGraphForSignature(AnimGraphBase* graph, bool canUseOutputs); +public: + // [BinaryAsset] +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; +#endif + protected: // [BinaryAsset] LoadResult load() override; diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 9b0f537af..f7d800034 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -191,12 +191,13 @@ Asset::LoadResult Material::load() auto lock = Storage->Lock(); // Prepare + const String name = ToString(); MaterialGenerator generator; generator.Error.Bind(&OnGeneratorError); if (_shaderHeader.Material.GraphVersion != MATERIAL_GRAPH_VERSION) - LOG(Info, "Converting material \'{0}\', from version {1} to {2}...", ToString(), _shaderHeader.Material.GraphVersion, MATERIAL_GRAPH_VERSION); + LOG(Info, "Converting material \'{0}\', from version {1} to {2}...", name, _shaderHeader.Material.GraphVersion, MATERIAL_GRAPH_VERSION); else - LOG(Info, "Updating material \'{0}\'...", ToString()); + LOG(Info, "Updating material \'{0}\'...", name); // Load or create material surface MaterialLayer* layer; @@ -205,7 +206,7 @@ Asset::LoadResult Material::load() // Load graph if (LoadChunks(GET_CHUNK_FLAG(SHADER_FILE_CHUNK_VISJECT_SURFACE))) { - LOG(Warning, "Cannot load \'{0}\' data from chunk {1}.", ToString(), SHADER_FILE_CHUNK_VISJECT_SURFACE); + LOG(Warning, "Cannot load \'{0}\' data from chunk {1}.", name, SHADER_FILE_CHUNK_VISJECT_SURFACE); return LoadResult::Failed; } @@ -214,7 +215,19 @@ Asset::LoadResult Material::load() MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size()); // Load layer - layer = MaterialLayer::Load(GetID(), &stream, _shaderHeader.Material.Info, ToString()); + layer = MaterialLayer::Load(GetID(), &stream, _shaderHeader.Material.Info, name); + if (ContentDeprecated::Clear()) + { + // If encountered any deprecated data when loading graph then serialize it + MaterialGraph graph; + MemoryWriteStream writeStream(1024); + stream.SetPosition(0); + if (!graph.Load(&stream, true) && !graph.Save(&writeStream, true)) + { + surfaceChunk->Data.Copy(ToSpan(writeStream)); + ContentDeprecated::Clear(); + } + } } else { @@ -245,7 +258,7 @@ Asset::LoadResult Material::load() MaterialInfo info = _shaderHeader.Material.Info; if (generator.Generate(source, info, materialParamsChunk->Data)) { - LOG(Error, "Cannot generate material source code for \'{0}\'. Please see log for more info.", ToString()); + LOG(Error, "Cannot generate material source code for \'{0}\'. Please see log for more info.", name); return LoadResult::Failed; } @@ -282,9 +295,9 @@ Asset::LoadResult Material::load() // Save to file #if USE_EDITOR - if (Save()) + if (SaveShaderAsset()) { - LOG(Error, "Cannot save \'{0}\'", ToString()); + LOG(Error, "Cannot save \'{0}\'", name); return LoadResult::Failed; } #endif @@ -505,6 +518,25 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) #endif } +bool Material::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + BytesContainer existingData = LoadSurface(true); + if (existingData.IsInvalid()) + return true; + MaterialGraph graph; + MemoryWriteStream writeStream(existingData.Length()); + MemoryReadStream readStream(existingData); + if (graph.Load(&readStream, true) || graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + auto materialInfo = _shaderHeader.Material.Info; + return SaveSurface(data, materialInfo); +} + #endif BytesContainer Material::LoadSurface(bool createDefaultIfMissing) @@ -555,17 +587,8 @@ BytesContainer Material::LoadSurface(bool createDefaultIfMissing) bool Material::SaveSurface(const BytesContainer& data, const MaterialInfo& info) { - // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Release all chunks @@ -582,7 +605,7 @@ bool Material::SaveSurface(const BytesContainer& data, const MaterialInfo& info) ASSERT(visjectSurfaceChunk != nullptr); visjectSurfaceChunk->Data.Copy(data); - if (Save()) + if (SaveShaderAsset()) { LOG(Error, "Cannot save \'{0}\'", ToString()); return true; diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index e8f398e3d..8c8871115 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -13,6 +13,7 @@ class MaterialShader; API_CLASS(NoSpawn) class FLAXENGINE_API Material : public ShaderAssetTypeBase { DECLARE_BINARY_ASSET_HEADER(Material, ShadersSerializedVersion); + private: MaterialShader* _materialShader = nullptr; @@ -25,7 +26,6 @@ public: API_FUNCTION() BytesContainer LoadSurface(bool createDefaultIfMissing); #if USE_EDITOR - /// /// Updates the material surface (save new one, discard cached data, reload asset). /// @@ -33,7 +33,6 @@ public: /// The material info structure. /// True if cannot save it, otherwise false. API_FUNCTION() bool SaveSurface(const BytesContainer& data, const MaterialInfo& info); - #endif public: @@ -52,6 +51,7 @@ public: // [ShaderAssetBase] #if USE_EDITOR void InitCompilationOptions(ShaderCompilationOptions& options) override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Content/Assets/MaterialFunction.cpp b/Source/Engine/Content/Assets/MaterialFunction.cpp index af73e2070..5b06180ea 100644 --- a/Source/Engine/Content/Assets/MaterialFunction.cpp +++ b/Source/Engine/Content/Assets/MaterialFunction.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Serialization/MemoryReadStream.h" +#include "Engine/Serialization/MemoryWriteStream.h" #endif #include "Engine/Content/Factories/BinaryAssetFactory.h" @@ -24,7 +25,7 @@ Asset::LoadResult MaterialFunction::load() if (!surfaceChunk || !surfaceChunk->IsLoaded()) return LoadResult::MissingDataChunk; MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size()); - if (Graph.Load(&stream, false)) + if (Graph.Load(&stream, USE_EDITOR)) return LoadResult::Failed; // Cache input and output nodes @@ -89,7 +90,7 @@ BytesContainer MaterialFunction::LoadSurface() return result; } -bool MaterialFunction::LoadSurface(MaterialGraph& graph) +bool MaterialFunction::LoadSurface(MaterialGraph& graph, bool loadMeta) { if (WaitForLoaded()) return true; @@ -100,7 +101,7 @@ bool MaterialFunction::LoadSurface(MaterialGraph& graph) { const auto surfaceChunk = GetChunk(0); MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size()); - return graph.Load(&stream, false); + return graph.Load(&stream, loadMeta); } } return true; @@ -128,19 +129,10 @@ void MaterialFunction::GetSignature(Array>& type #if USE_EDITOR -bool MaterialFunction::SaveSurface(BytesContainer& data) +bool MaterialFunction::SaveSurface(const BytesContainer& data) const { - // Wait for asset to be loaded or don't if last load failed - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Set Visject Surface data @@ -160,4 +152,17 @@ bool MaterialFunction::SaveSurface(BytesContainer& data) return false; } +bool MaterialFunction::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream writeStream; + if (Graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + return SaveSurface(data); +} + #endif diff --git a/Source/Engine/Content/Assets/MaterialFunction.h b/Source/Engine/Content/Assets/MaterialFunction.h index 824d956fd..bb175bc63 100644 --- a/Source/Engine/Content/Assets/MaterialFunction.h +++ b/Source/Engine/Content/Assets/MaterialFunction.h @@ -11,9 +11,9 @@ API_CLASS(NoSpawn) class FLAXENGINE_API MaterialFunction : public BinaryAsset { DECLARE_BINARY_ASSET_HEADER(MaterialFunction, 1); + public: #if COMPILE_WITH_MATERIAL_GRAPH - /// /// The loaded material function graph. /// @@ -39,8 +39,9 @@ public: /// Tries to load surface graph from the asset. /// /// The graph to load. + /// True if load metadata. /// True if failed, otherwise false. - bool LoadSurface(MaterialGraph& graph); + bool LoadSurface(MaterialGraph& graph, bool loadMeta = false); // Gets the function signature for Visject Surface editor. API_FUNCTION() void GetSignature(API_PARAM(Out) Array>& types, API_PARAM(Out) Array>& names); @@ -48,14 +49,18 @@ public: #endif #if USE_EDITOR - /// /// Updates the material graph surface (save new one, discards cached data, reloads asset). /// /// The surface graph data. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(BytesContainer& data); + API_FUNCTION() bool SaveSurface(const BytesContainer& data) const; +#endif +public: + // [BinaryAsset] +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 28bbf8a8c..8e8e58d3a 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -310,18 +310,8 @@ void MaterialInstance::ResetParameters() bool MaterialInstance::Save(const StringView& path) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - ScopeLock lock(Locker); // Save instance data diff --git a/Source/Engine/Content/Assets/MaterialInstance.h b/Source/Engine/Content/Assets/MaterialInstance.h index 9d87cdf5b..89cb534da 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.h +++ b/Source/Engine/Content/Assets/MaterialInstance.h @@ -33,18 +33,6 @@ public: /// API_FUNCTION() void ResetParameters(); -#if USE_EDITOR - - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// If you use saving with the GPU mesh data then the call has to be provided from the thread other than the main game thread. - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty); - -#endif - private: void OnBaseSet(); void OnBaseUnset(); @@ -56,6 +44,7 @@ public: bool IsMaterialInstance() const override; #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif // [IMaterial] diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 915066fc2..c622dac06 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -400,7 +400,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion) #if USE_EDITOR -bool Model::SaveHeader(WriteStream& stream) +bool Model::SaveHeader(WriteStream& stream) const { if (ModelBase::SaveHeader(stream)) return true; @@ -457,7 +457,7 @@ bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData) return false; } -bool Model::Save(bool withMeshDataFromGpu, Function& getChunk) +bool Model::Save(bool withMeshDataFromGpu, Function& getChunk) const { if (ModelBase::Save(withMeshDataFromGpu, getChunk)) return true; diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index aedc23dbf..e7c585af2 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -271,9 +271,9 @@ private: bool LoadHeader(ReadStream& stream, byte& headerVersion); #if USE_EDITOR friend class ImportModel; - bool SaveHeader(WriteStream& stream) override; + bool SaveHeader(WriteStream& stream) const override; static bool SaveHeader(WriteStream& stream, const ModelData& modelData); - bool Save(bool withMeshDataFromGpu, Function& getChunk) override; + bool Save(bool withMeshDataFromGpu, Function& getChunk) const override; #endif public: diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index b194d9861..40971bae0 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -218,16 +218,8 @@ void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path) { // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } if (withMeshDataFromGpu && IsInMainThread()) { LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread)."); @@ -238,7 +230,6 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path) LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data)."); return true; } - ScopeLock lock(Locker); // Use a temporary chunks for data storage for virtual assets @@ -399,7 +390,7 @@ bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* m #if USE_EDITOR -bool ModelBase::SaveHeader(WriteStream& stream) +bool ModelBase::SaveHeader(WriteStream& stream) const { // Basic info static_assert(MODEL_HEADER_VERSION == 2, "Update code"); @@ -852,7 +843,7 @@ bool ModelBase::SaveMesh(WriteStream& stream, const MeshBase* mesh) const return false; } -bool ModelBase::Save(bool withMeshDataFromGpu, Function& getChunk) +bool ModelBase::Save(bool withMeshDataFromGpu, Function& getChunk) const { return false; } @@ -876,6 +867,11 @@ void ModelBase::GetReferences(Array& assets, Array& files) const assets.Add(slot.Material.GetID()); } +bool ModelBase::Save(const StringView& path) +{ + return Save(false, path); +} + #endif int32 ModelBase::GetCurrentResidency() const diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 56e07911d..4044f0a2f 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -331,12 +331,12 @@ protected: virtual bool LoadMesh(class MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly = nullptr); bool LoadHeader(ReadStream& stream, byte& headerVersion); #if USE_EDITOR - virtual bool SaveHeader(WriteStream& stream); + virtual bool SaveHeader(WriteStream& stream) const; static bool SaveHeader(WriteStream& stream, const class ModelData& modelData); bool SaveLOD(WriteStream& stream, int32 lodIndex) const; static bool SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool(saveMesh)(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex) = nullptr); virtual bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const; - virtual bool Save(bool withMeshDataFromGpu, Function& getChunk); + virtual bool Save(bool withMeshDataFromGpu, Function& getChunk) const; #endif public: @@ -344,6 +344,7 @@ public: void CancelStreaming() override; #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif // [StreamableResource] diff --git a/Source/Engine/Content/Assets/RawDataAsset.cpp b/Source/Engine/Content/Assets/RawDataAsset.cpp index f567abb1f..5c39683da 100644 --- a/Source/Engine/Content/Assets/RawDataAsset.cpp +++ b/Source/Engine/Content/Assets/RawDataAsset.cpp @@ -18,18 +18,8 @@ RawDataAsset::RawDataAsset(const SpawnParams& params, const AssetInfo* info) bool RawDataAsset::Save(const StringView& path) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - ScopeLock lock(Locker); bool result; diff --git a/Source/Engine/Content/Assets/RawDataAsset.h b/Source/Engine/Content/Assets/RawDataAsset.h index 9da6e258c..813e0ab9e 100644 --- a/Source/Engine/Content/Assets/RawDataAsset.h +++ b/Source/Engine/Content/Assets/RawDataAsset.h @@ -16,20 +16,11 @@ public: /// API_FIELD() Array Data; -public: -#if USE_EDITOR - - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if failed, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty); - -#endif - public: // [BinaryAsset] +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; +#endif uint64 GetMemoryUsage() const override; protected: diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 48fd71678..aaeb01434 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -66,18 +66,8 @@ const BitArray<>& SkeletonMask::GetNodesMask() bool SkeletonMask::Save(const StringView& path) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - ScopeLock lock(Locker); // Write data diff --git a/Source/Engine/Content/Assets/SkeletonMask.h b/Source/Engine/Content/Assets/SkeletonMask.h index f14975b37..2a136fa03 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.h +++ b/Source/Engine/Content/Assets/SkeletonMask.h @@ -51,17 +51,6 @@ public: /// The constant reference to the skeleton nodes mask. API_PROPERTY() const BitArray<>& GetNodesMask(); -#if USE_EDITOR - - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty); - -#endif - private: void OnSkeletonUnload(); @@ -73,6 +62,7 @@ public: BinaryAsset::GetReferences(assets, files); assets.Add(Skeleton.GetID()); } + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index c614dc2c0..1713c157f 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -661,7 +661,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion) #if USE_EDITOR -bool SkinnedModel::SaveHeader(WriteStream& stream) +bool SkinnedModel::SaveHeader(WriteStream& stream) const { if (ModelBase::SaveHeader(stream)) return true; diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index c90cb5362..03917d732 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -299,7 +299,7 @@ private: bool LoadHeader(ReadStream& stream, byte& headerVersion); #if USE_EDITOR friend class ImportModel; - bool SaveHeader(WriteStream& stream) override; + bool SaveHeader(WriteStream& stream) const override; static bool SaveHeader(WriteStream& stream, const ModelData& modelData); bool SaveMesh(WriteStream& stream, const MeshBase* mesh) const override; static bool SaveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex); diff --git a/Source/Engine/Content/Assets/Texture.cpp b/Source/Engine/Content/Assets/Texture.cpp index 7a77ea130..e0c2f386a 100644 --- a/Source/Engine/Content/Assets/Texture.cpp +++ b/Source/Engine/Content/Assets/Texture.cpp @@ -29,24 +29,19 @@ bool Texture::IsNormalMap() const #if USE_EDITOR +bool Texture::Save(const StringView& path) +{ + return Save(path, nullptr); +} + bool Texture::Save(const StringView& path, const InitData* customData) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - ScopeLock lock(Locker); AssetInitData data; - const auto texture = StreamingTexture(); + const class StreamingTexture* texture = StreamingTexture(); // Use a temporary chunks for data storage for virtual assets FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS]; diff --git a/Source/Engine/Content/Assets/Texture.h b/Source/Engine/Content/Assets/Texture.h index 9e9596ce5..47f0c6528 100644 --- a/Source/Engine/Content/Assets/Texture.h +++ b/Source/Engine/Content/Assets/Texture.h @@ -23,17 +23,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Texture : public TextureBase public: #if USE_EDITOR - - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty) - { - return Save(path, nullptr); - } - /// /// Saves this asset to the file. Supported only in Editor. /// @@ -41,7 +30,6 @@ public: /// The custom texture data container. Can be used to override the data stored in the asset. Use null to ignore this argument. /// True if cannot save data, otherwise false. bool Save(const StringView& path, const InitData* customData); - #endif /// @@ -60,4 +48,10 @@ public: /// True if generate mipmaps for the imported texture. /// The loaded texture (virtual asset) or null if fails. API_FUNCTION() static Texture* FromFile(const StringView& path, bool generateMips = false); + +public: + // [TextureBase] +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; +#endif }; diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index c8b0fbf7e..f5ff9ca58 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -2,6 +2,7 @@ #include "VisualScript.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Content/Content.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" @@ -1303,6 +1304,23 @@ VisualScript::VisualScript(const SpawnParams& params, const AssetInfo* info) { } +#if USE_EDITOR + +bool VisualScript::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream writeStream; + if (Graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + return SaveSurface(data, Meta); +} + +#endif + Asset::LoadResult VisualScript::load() { // Build Visual Script typename that is based on asset id @@ -2184,7 +2202,7 @@ const VisualScript::Field* VisualScript::FindField(const StringAnsiView& name) c return nullptr; } -BytesContainer VisualScript::LoadSurface() +BytesContainer VisualScript::LoadSurface() const { if (WaitForLoaded()) return BytesContainer(); @@ -2202,19 +2220,10 @@ BytesContainer VisualScript::LoadSurface() #if USE_EDITOR -bool VisualScript::SaveSurface(const BytesContainer& data, const Metadata& meta) +bool VisualScript::SaveSurface(const BytesContainer& data, const Metadata& meta) const { - // Wait for asset to be loaded or don't if last load failed - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Release all chunks diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index a69429db0..9e9de1007 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -246,7 +246,7 @@ public: /// Tries to load surface graph from the asset. /// /// The surface data or empty if failed to load it. - API_FUNCTION() BytesContainer LoadSurface(); + API_FUNCTION() BytesContainer LoadSurface() const; #if USE_EDITOR @@ -256,7 +256,7 @@ public: /// Stream with graph data. /// Script metadata. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(const BytesContainer& data, API_PARAM(Ref) const Metadata& meta); + API_FUNCTION() bool SaveSurface(const BytesContainer& data, API_PARAM(Ref) const Metadata& meta) const; // Returns the amount of methods in the script. API_FUNCTION() int32 GetMethodsCount() const @@ -286,6 +286,7 @@ public: BinaryAsset::GetReferences(assets, files); Graph.GetReferences(assets); } + bool Save(const StringView& path = StringView::Empty) override; #endif protected: @@ -406,7 +407,6 @@ public: static Variant Invoke(VisualScript::Method* method, ScriptingObject* instance, Span parameters = Span()); #if VISUAL_SCRIPT_DEBUGGING - // Custom event that is called every time the Visual Script signal flows over the graph (including the data connections). Can be used to read nad visualize the Visual Script execution logic. static Action DebugFlow; @@ -420,6 +420,5 @@ public: /// The output value. Valid only if method returned true. /// True if could fetch the value, otherwise false. static bool Evaluate(VisualScript* script, ScriptingObject* instance, uint32 nodeId, uint32 boxId, Variant& result); - #endif }; diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 228a93f29..6af0bd22f 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -189,7 +189,7 @@ bool BinaryAsset::HasDependenciesModified() const #endif -FlaxChunk* BinaryAsset::GetOrCreateChunk(int32 index) +FlaxChunk* BinaryAsset::GetOrCreateChunk(int32 index) const { if (IsVirtual()) // Virtual assets don't own storage container return nullptr; @@ -205,7 +205,7 @@ FlaxChunk* BinaryAsset::GetOrCreateChunk(int32 index) // Allocate ASSERT(Storage); - _header.Chunks[index] = chunk = Storage->AllocateChunk(); + const_cast(this)->_header.Chunks[index] = chunk = Storage->AllocateChunk(); if (chunk) chunk->RegisterUsage(); @@ -263,10 +263,9 @@ void BinaryAsset::GetChunkData(int32 index, BytesContainer& data) const data.Link(chunk->Data); } -bool BinaryAsset::LoadChunk(int32 chunkIndex) +bool BinaryAsset::LoadChunk(int32 chunkIndex) const { ASSERT(Storage); - const auto chunk = _header.Chunks[chunkIndex]; if (chunk != nullptr && chunk->IsMissing() @@ -275,19 +274,14 @@ bool BinaryAsset::LoadChunk(int32 chunkIndex) if (Storage->LoadAssetChunk(chunk)) return true; } - return false; } -bool BinaryAsset::LoadChunks(AssetChunksFlag chunks) +bool BinaryAsset::LoadChunks(AssetChunksFlag chunks) const { - ASSERT(Storage); - - // Check if skip loading if (chunks == 0) return false; - - // Load all missing marked chunks + ASSERT(Storage); for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) { auto chunk = _header.Chunks[i]; @@ -300,7 +294,6 @@ bool BinaryAsset::LoadChunks(AssetChunksFlag chunks) return true; } } - return false; } diff --git a/Source/Engine/Content/BinaryAsset.h b/Source/Engine/Content/BinaryAsset.h index b3c4c8e02..6290a4800 100644 --- a/Source/Engine/Content/BinaryAsset.h +++ b/Source/Engine/Content/BinaryAsset.h @@ -166,7 +166,7 @@ public: /// /// The chunk index. /// Flax Chunk object (may be null if asset storage doesn't allow to add new chunks). - FlaxChunk* GetOrCreateChunk(int32 index); + FlaxChunk* GetOrCreateChunk(int32 index) const; /// /// Determines whether the specified chunk exists. @@ -238,14 +238,14 @@ public: /// /// The chunk index to load. /// True if failed, otherwise false. - bool LoadChunk(int32 chunkIndex); + bool LoadChunk(int32 chunkIndex) const; /// /// Loads the chunks (synchronous, blocks current thread). /// /// The chunks to load (packed in a flag). /// True if failed, otherwise false. - bool LoadChunks(AssetChunksFlag chunks); + bool LoadChunks(AssetChunksFlag chunks) const; #if USE_EDITOR /// diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 08a3a5f3f..ad1126ec6 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -1156,19 +1156,14 @@ void Content::tryCallOnLoaded(Asset* asset) void Content::onAssetLoaded(Asset* asset) { // This is called by the asset on loading end - ASSERT(asset && asset->IsLoaded()); - ScopeLock locker(LoadedAssetsToInvokeLocker); - LoadedAssetsToInvoke.Add(asset); } void Content::onAssetUnload(Asset* asset) { // This is called by the asset on unloading - ScopeLock locker(AssetsLocker); - Assets.Remove(asset->GetID()); UnloadQueue.Remove(asset); LoadedAssetsToInvoke.Remove(asset); diff --git a/Source/Engine/Content/Deprecated.h b/Source/Engine/Content/Deprecated.h new file mode 100644 index 000000000..92cd3aa44 --- /dev/null +++ b/Source/Engine/Content/Deprecated.h @@ -0,0 +1,26 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Core.h" + +#if USE_EDITOR + +// Utility for marking content as deprecated when loading it in Editor. Used to auto-upgrade (by resaving) data during development in editor or during game cooking. +class FLAXENGINE_API ContentDeprecated +{ +public: + // Marks content as deprecated (for the current thread). + static void Mark(); + // Reads and clears deprecation flag (for the current thread). + static bool Clear(bool newValue = false); +}; + +// Marks content as deprecated (for the current thread). +#define MARK_CONTENT_DEPRECATED() ContentDeprecated::Mark() + +#else + +#define MARK_CONTENT_DEPRECATED() + +#endif diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 7f3a2db32..0cf2e17ea 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -161,25 +161,19 @@ void JsonAssetBase::GetReferences(const StringAnsiView& json, Array& asset FindIds(document, assets, files); } -bool JsonAssetBase::Save(const StringView& path) const +bool JsonAssetBase::Save(const StringView& path) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } + PROFILE_CPU(); ScopeLock lock(Locker); // Serialize to json to the buffer rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writerObj(buffer); + _isResaving = true; Save(writerObj); + _isResaving = false; // Save json to file if (File::WriteAllBytes(path.HasChars() ? path : StringView(GetPath()), (byte*)buffer.GetString(), (int32)buffer.GetSize())) @@ -193,12 +187,8 @@ bool JsonAssetBase::Save(const StringView& path) const bool JsonAssetBase::Save(JsonWriter& writer) const { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } ScopeLock lock(Locker); writer.StartObject(); @@ -348,6 +338,61 @@ uint64 JsonAsset::GetMemoryUsage() const return result; } +void JsonAsset::OnGetData(rapidjson_flax::StringBuffer& buffer) const +{ + if (Instance && InstanceType && _isResaving) + { + // Serialize instance object that was loaded (from potentially deprecated data, serialize method is always up to date) + const ScriptingType& type = InstanceType.GetType(); + PrettyJsonWriter writer(buffer); + bool got = false; + switch (type.Type) + { + case ScriptingTypes::Class: + case ScriptingTypes::Structure: + { + const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); + writer.StartObject(); + ((ISerializable*)((byte*)Instance + interface->VTableOffset))->Serialize(writer, nullptr); + got = true; + break; + } + case ScriptingTypes::Script: + { + writer.StartObject(); + ToInterface((ScriptingObject*)Instance)->Serialize(writer, nullptr); + got = true; + break; + } + } + if (got) + { + writer.EndObject(); + + // Parse json document (CreateInstance uses it to spawn object) + auto* self = const_cast(this); + { + PROFILE_CPU_NAMED("Json.Parse"); + self->Document.Parse(buffer.GetString(), buffer.GetSize()); + } + if (self->Document.HasParseError()) + { + self->Data = nullptr; + Log::JsonParseException(Document.GetParseError(), Document.GetErrorOffset()); + } + else + { + self->Data = &self->Document; + self->DataEngineBuild = FLAXENGINE_VERSION_BUILD; + } + + return; + } + } + + JsonAssetBase::OnGetData(buffer); +} + Asset::LoadResult JsonAsset::loadAsset() { const auto result = JsonAssetBase::loadAsset(); @@ -387,6 +432,7 @@ void JsonAsset::onLoaded_MainThread() JsonAssetBase::onLoaded_MainThread(); // Special case for Settings assets to flush them after edited and saved in Editor + // TODO: add interface for custom JsonAsset interaction of the instance class (eg. OnJsonLoaded, or similar to C# like OnDeserialized from Newtonsoft.Json) const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length()); const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length())); if (Instance && typeHandle && typeHandle.IsSubclassOf(SettingsBase::TypeInitializer) && _isAfterReload) diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index b974f3087..607ede7bc 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -16,6 +16,7 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API JsonAssetBase : public Asset protected: String _path; bool _isVirtualDocument = false; + bool _isResaving = false; protected: /// @@ -73,13 +74,6 @@ public: /// The output list of object IDs references by the asset (appended, not cleared). API_FUNCTION() static void GetReferences(const StringAnsiView& json, API_PARAM(Out) Array& assets); - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty) const; - /// /// Saves this asset to the Json Writer buffer (both ID, Typename header and Data contents). Supported only in Editor. /// @@ -98,6 +92,7 @@ public: uint64 GetMemoryUsage() const override; #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: @@ -149,6 +144,7 @@ public: protected: // [JsonAssetBase] + void OnGetData(rapidjson_flax::StringBuffer& buffer) const override; LoadResult loadAsset() override; void unload(bool isReloading) override; void onLoaded_MainThread() override; diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index a9d1fb5a4..d5c21edec 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -888,10 +888,12 @@ Variant::Variant(const Span& v) } PRAGMA_DISABLE_DEPRECATION_WARNINGS +#include "Engine/Content/Deprecated.h" Variant::Variant(const CommonValue& value) : Variant() { // [Deprecated on 31.07.2020, expires on 31.07.2022] + MARK_CONTENT_DEPRECATED(); switch (value.Type) { case CommonType::Bool: diff --git a/Source/Engine/Engine/GameplayGlobals.cpp b/Source/Engine/Engine/GameplayGlobals.cpp index d6b9548d7..def352dd0 100644 --- a/Source/Engine/Engine/GameplayGlobals.cpp +++ b/Source/Engine/Engine/GameplayGlobals.cpp @@ -154,18 +154,8 @@ void GameplayGlobals::ResetValues() bool GameplayGlobals::Save(const StringView& path) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - ScopeLock lock(Locker); // Save to bytes diff --git a/Source/Engine/Engine/GameplayGlobals.h b/Source/Engine/Engine/GameplayGlobals.h index 4f09ba4c8..768840a55 100644 --- a/Source/Engine/Engine/GameplayGlobals.h +++ b/Source/Engine/Engine/GameplayGlobals.h @@ -80,20 +80,12 @@ public: /// API_FUNCTION() void ResetValues(); -#if USE_EDITOR - - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty); - -#endif - public: // [BinaryAsset] void InitAsVirtual() override; +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; +#endif protected: // [BinaryAsset] diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 427e2260b..d3c8af9e2 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Random.h" #include "Engine/Engine/Engine.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Content/Deprecated.h" #if !FOLIAGE_USE_SINGLE_QUAD_TREE #include "Engine/Threading/JobSystem.h" #if FOLIAGE_USE_DRAW_CALLS_BATCHING @@ -1374,6 +1375,7 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie if (modifier->EngineBuild <= 6189) { // [Deprecated on 30.11.2019, expires on 30.11.2021] + MARK_CONTENT_DEPRECATED(); InstanceEncoded1 enc; for (int32 i = 0; i < foliageInstancesCount; i++) { diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 78337dcf0..61b765056 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Math/Vector4.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Engine/GameplayGlobals.h" #include "Engine/Serialization/MemoryWriteStream.h" @@ -652,6 +653,8 @@ bool MaterialParams::Load(ReadStream* stream) { case 1: // [Deprecated on 15.11.2019, expires on 15.11.2021] { + MARK_CONTENT_DEPRECATED(); + // Size of the collection uint16 paramsCount; stream->ReadUint16(¶msCount); @@ -727,6 +730,8 @@ bool MaterialParams::Load(ReadStream* stream) break; case 2: // [Deprecated on 15.11.2019, expires on 15.11.2021] { + MARK_CONTENT_DEPRECATED(); + // Size of the collection uint16 paramsCount; stream->ReadUint16(¶msCount); diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 8fd1131d1..ecb727c11 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -8,6 +8,7 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #if COMPILE_WITH_SHADER_COMPILER #include "Engine/Engine/CommandLine.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Serialization/MemoryReadStream.h" #endif #include "Engine/ShadowsOfMordor/AtlasChartsPacker.h" @@ -98,8 +99,11 @@ bool ShaderAssetBase::initBase(AssetInitData& initData) #if USE_EDITOR -bool ShaderAssetBase::Save() +bool ShaderAssetBase::SaveShaderAsset() const { + // Asset is being saved so no longer need to resave deprecated data in it + ContentDeprecated::Clear(); + auto parent = GetShaderAsset(); AssetInitData data; data.SerializedVersion = ShaderStorage::Header::Version; @@ -296,7 +300,7 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) #if USE_EDITOR // Save chunks to the asset file - if (Save()) + if (SaveShaderAsset()) { LOG(Warning, "Cannot save '{0}'.", parent->ToString()); return true; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h index d0ebc0c8d..e2a860abe 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h @@ -30,15 +30,13 @@ public: static int32 GetCacheChunkIndex(ShaderProfile profile); #if USE_EDITOR - /// - /// Prepare shader compilation options + /// Prepare shader compilation options. /// - /// Options + /// The output options. virtual void InitCompilationOptions(struct ShaderCompilationOptions& options) { } - #endif protected: @@ -50,13 +48,11 @@ protected: virtual BinaryAsset* GetShaderAsset() const = 0; #if USE_EDITOR - /// /// Saves this shader asset to the storage container. /// /// True if failed, otherwise false. - bool Save(); - + bool SaveShaderAsset() const; #endif /// @@ -70,12 +66,10 @@ protected: DataContainer Data; #if COMPILE_WITH_SHADER_COMPILER - /// /// The list of files included by the shader source (used by the given cache on the runtime graphics platform shader profile). Paths are absolute and unique. /// Array Includes; - #endif }; @@ -87,7 +81,6 @@ protected: bool LoadShaderCache(ShaderCacheResult& result); #if COMPILE_WITH_SHADER_COMPILER - /// /// Registers shader asset for the automated reloads on source includes changes. /// @@ -100,7 +93,6 @@ protected: /// /// The asset. void UnregisterForShaderReloads(Asset* asset); - #endif }; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderStorage.h b/Source/Engine/Graphics/Shaders/Cache/ShaderStorage.h index b4660c8d1..59bcd0999 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderStorage.h +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderStorage.h @@ -75,7 +75,7 @@ public: }; /// /// File header, version 19 -/// [Deprecated on 13.07.2022, expires on 13.07.2024] + /// [Deprecated on 13.07.2022, expires on 13.07.2024] /// struct Header19 { diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 2a594bdde..9fe0f4b9d 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -12,6 +12,7 @@ #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Threading/Threading.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Debug/Exceptions/JsonParseException.h" @@ -1074,12 +1075,18 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) // StaticFlags update - added StaticFlags::Navigation // [Deprecated on 17.05.2020, expires on 17.05.2021] if (modifier->EngineBuild < 6178 && (int32)_staticFlags == (1 + 2 + 4)) + { + MARK_CONTENT_DEPRECATED(); _staticFlags |= StaticFlags::Navigation; + } // StaticFlags update - added StaticFlags::Shadow // [Deprecated on 17.05.2020, expires on 17.05.2021] if (modifier->EngineBuild < 6601 && (int32)_staticFlags == (1 + 2 + 4 + 8)) + { + MARK_CONTENT_DEPRECATED(); _staticFlags |= StaticFlags::Shadow; + } const auto tag = stream.FindMember("Tag"); if (tag != stream.MemberEnd()) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 373339e2d..07cea3d15 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -11,6 +11,7 @@ #include "Engine/Core/Math/Matrix3x3.h" #include "Editor/Editor.h" #endif +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" @@ -1153,10 +1154,16 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSDF; + } // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSurfaceAtlas; + } } const Span AnimatedModel::GetMaterialSlots() const diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 8d7155dee..305f9d8ec 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -11,6 +11,7 @@ #include "Engine/Renderer/ProbesRenderer.h" #include "Engine/Renderer/ReflectionsPass.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/ContentExporters/AssetExporters.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/Graphics/RenderTools.h" @@ -255,6 +256,7 @@ void EnvironmentProbe::Deserialize(DeserializeStream& stream, ISerializeModifier // [Deprecated on 18.07.2022, expires on 18.07.2022] if (modifier->EngineBuild <= 6332) { + MARK_CONTENT_DEPRECATED(); const auto member = stream.FindMember("AutoUpdate"); if (member != stream.MemberEnd() && member->value.IsBool() && member->value.GetBool()) UpdateMode = ProbeUpdateMode::WhenMoved; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 71b618384..a41b22c92 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Math/Matrix3x4.h" #include "Engine/Engine/Engine.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/GPUBufferDescription.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" @@ -514,10 +515,16 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSDF; + } // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSurfaceAtlas; + } } void SplineModel::OnActiveInTreeChanged() diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 054345785..06f776661 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -2,6 +2,7 @@ #include "StaticModel.h" #include "Engine/Engine/Engine.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUBufferDescription.h" #include "Engine/Graphics/GPUContext.h" @@ -534,15 +535,22 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod const auto member = stream.FindMember("HiddenShadow"); if (member != stream.MemberEnd() && member->value.IsBool() && member->value.GetBool()) { + MARK_CONTENT_DEPRECATED(); DrawModes = DrawPass::Depth; } } // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSDF; + } // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSurfaceAtlas; + } { const auto member = stream.FindMember("RenderPasses"); diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index a30367391..5d7bb7889 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -4,6 +4,7 @@ #include "NavigationSettings.h" #include "NavMeshRuntime.h" #include "NavMeshBuilder.h" +#include "NavMesh.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" @@ -14,8 +15,7 @@ #include "Engine/Level/Level.h" #include "Engine/Level/Scene/Scene.h" #endif -#include "NavMesh.h" - +#include "Engine/Content/Deprecated.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" @@ -255,6 +255,7 @@ void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifi else { // [Deprecated on 12.01.2021, expires on 12.01.2022] + MARK_CONTENT_DEPRECATED(); float WalkableRadius = 34.0f; float WalkableHeight = 144.0f; float WalkableMaxClimb = 35.0f; diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 9a89a0154..bb8769b46 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -3,6 +3,7 @@ #include "ParticleEffect.h" #include "Particles.h" #include "Engine/Core/Types/CommonValue.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/SceneRendering.h" @@ -664,6 +665,7 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* if (modifier->EngineBuild < 6197) { PRAGMA_DISABLE_DEPRECATION_WARNINGS + MARK_CONTENT_DEPRECATED(); const auto& overrides = overridesMember->value; ASSERT(overrides.IsArray()); _parametersOverrides.EnsureCapacity(_parametersOverrides.Count() + overrides.Size()); diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 8e9533474..a23a30fac 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -154,6 +154,16 @@ Asset::LoadResult ParticleEmitter::load() LOG(Warning, "Cannot load Particle Emitter GPU graph '{0}'.", GetPath()); return LoadResult::CannotLoadData; } + if (ContentDeprecated::Clear()) + { + // If encountered any deprecated data when loading graph then serialize it + MemoryWriteStream writeStream(1024); + if (!Graph.Save(&writeStream, true)) + { + surfaceChunk->Data.Copy(ToSpan(writeStream)); + ContentDeprecated::Clear(); + } + } generator.AddGraph(graph); // Get chunk with material parameters @@ -199,7 +209,7 @@ Asset::LoadResult ParticleEmitter::load() // Save to file #if USE_EDITOR - if (Save()) + if (SaveShaderAsset()) { LOG(Error, "Cannot save \'{0}\'", ToString()); return LoadResult::Failed; @@ -318,10 +328,6 @@ void ParticleEmitter::OnDependencyModified(BinaryAsset* asset) Reload(); } -#endif - -#if USE_EDITOR - void ParticleEmitter::InitCompilationOptions(ShaderCompilationOptions& options) { // Base @@ -377,19 +383,10 @@ BytesContainer ParticleEmitter::LoadSurface(bool createDefaultIfMissing) #if USE_EDITOR -bool ParticleEmitter::SaveSurface(BytesContainer& data) +bool ParticleEmitter::SaveSurface(const BytesContainer& data) { - // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Release all chunks @@ -406,7 +403,7 @@ bool ParticleEmitter::SaveSurface(BytesContainer& data) ASSERT(visjectSurfaceChunk != nullptr); visjectSurfaceChunk->Data.Copy(data); - if (Save()) + if (SaveShaderAsset()) { LOG(Error, "Cannot save \'{0}\'", ToString()); return true; @@ -420,4 +417,23 @@ bool ParticleEmitter::SaveSurface(BytesContainer& data) return false; } +void ParticleEmitter::GetReferences(Array& assets, Array& files) const +{ + BinaryAsset::GetReferences(assets, files); + Graph.GetReferences(assets); +} + +bool ParticleEmitter::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream writeStream; + if (Graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + return SaveSurface(data); +} + #endif diff --git a/Source/Engine/Particles/ParticleEmitter.h b/Source/Engine/Particles/ParticleEmitter.h index d07ffc108..321a0235b 100644 --- a/Source/Engine/Particles/ParticleEmitter.h +++ b/Source/Engine/Particles/ParticleEmitter.h @@ -20,6 +20,7 @@ class ParticleEmitterInstance; API_CLASS(NoSpawn) class FLAXENGINE_API ParticleEmitter : public ShaderAssetTypeBase { DECLARE_BINARY_ASSET_HEADER(ParticleEmitter, ShadersSerializedVersion); + public: /// /// The loaded particle graph. @@ -84,14 +85,12 @@ public: API_FUNCTION() BytesContainer LoadSurface(bool createDefaultIfMissing); #if USE_EDITOR - /// /// Updates surface (saves new one, discard cached data, reloads asset). /// /// The surface graph data. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(BytesContainer& data); - + API_FUNCTION() bool SaveSurface(const BytesContainer& data); #endif public: @@ -100,7 +99,7 @@ public: /// /// The spawn position. /// The effect playback duration (in seconds). - /// If set to true effect be be auto-destroyed after duration. + /// If set to true effect will be auto-destroyed after duration. /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(const Vector3& position, float duration = MAX_float, bool autoDestroy = false) { @@ -113,7 +112,7 @@ public: /// The spawn position. /// The spawn rotation. /// The effect playback duration (in seconds). - /// If set to true effect be be auto-destroyed after duration. + /// If set to true effect will be auto-destroyed after duration. /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(const Vector3& position, const Quaternion& rotation, float duration = MAX_float, bool autoDestroy = false) { @@ -125,7 +124,7 @@ public: /// /// The spawn transform. /// The effect playback duration (in seconds). - /// If set to true effect be be auto-destroyed after duration. + /// If set to true effect will be auto-destroyed after duration. /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(const Transform& transform, float duration = MAX_float, bool autoDestroy = false) { @@ -138,7 +137,7 @@ public: /// The parent actor (can be null to link it to the first loaded scene). /// The spawn position. /// The effect playback duration (in seconds). - /// If set to true effect be be auto-destroyed after duration. + /// If set to true effect will be auto-destroyed after duration. /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(Actor* parent, const Vector3& position, float duration = MAX_float, bool autoDestroy = false) { @@ -152,7 +151,7 @@ public: /// The spawn position. /// The spawn rotation. /// The effect playback duration (in seconds). - /// If set to true effect be be auto-destroyed after duration. + /// If set to true effect will be auto-destroyed after duration. /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(Actor* parent, const Vector3& position, const Quaternion& rotation, float duration = MAX_float, bool autoDestroy = false) { @@ -165,18 +164,15 @@ public: /// The parent actor (can be null to link it to the first loaded scene). /// The spawn transform. /// The effect playback duration (in seconds). - /// If set to true effect be be auto-destroyed after duration. + /// If set to true effect will be auto-destroyed after duration. /// The spawned effect. API_FUNCTION() ParticleEffect* Spawn(Actor* parent, const Transform& transform, float duration = MAX_float, bool autoDestroy = false); public: // [BinaryAsset] #if USE_EDITOR - void GetReferences(Array& assets, Array& files) const override - { - BinaryAsset::GetReferences(assets, files); - Graph.GetReferences(assets); - } + void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: @@ -186,8 +182,6 @@ protected: AssetChunksFlag getChunksToPreload() const override; #if USE_EDITOR void OnDependencyModified(BinaryAsset* asset) override; -#endif -#if USE_EDITOR void InitCompilationOptions(ShaderCompilationOptions& options) override; #endif }; diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp index eae85e351..50f120d83 100644 --- a/Source/Engine/Particles/ParticleEmitterFunction.cpp +++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp @@ -6,6 +6,7 @@ #include "Engine/Threading/Threading.h" #if USE_EDITOR #include "Engine/Core/Types/DataContainer.h" +#include "Engine/Serialization/MemoryWriteStream.h" #endif #include "Engine/Content/Factories/BinaryAssetFactory.h" @@ -44,7 +45,7 @@ Asset::LoadResult ParticleEmitterFunction::load() if (!surfaceChunk || !surfaceChunk->IsLoaded()) return LoadResult::MissingDataChunk; MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size()); - if (Graph.Load(&stream, false)) + if (Graph.Load(&stream, USE_EDITOR)) return LoadResult::Failed; for (int32 i = 0; i < Graph.Nodes.Count(); i++) { @@ -103,7 +104,7 @@ AssetChunksFlag ParticleEmitterFunction::getChunksToPreload() const return GET_CHUNK_FLAG(0); } -bool ParticleEmitterFunction::LoadSurface(ParticleEmitterGraphCPU& graph) +bool ParticleEmitterFunction::LoadSurface(ParticleEmitterGraphCPU& graph, bool loadMeta) { if (WaitForLoaded()) return true; @@ -114,7 +115,7 @@ bool ParticleEmitterFunction::LoadSurface(ParticleEmitterGraphCPU& graph) { const auto surfaceChunk = GetChunk(0); MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size()); - return graph.Load(&stream, false); + return graph.Load(&stream, loadMeta); } } return true; @@ -141,7 +142,7 @@ BytesContainer ParticleEmitterFunction::LoadSurface() #if COMPILE_WITH_PARTICLE_GPU_GRAPH -bool ParticleEmitterFunction::LoadSurface(ParticleEmitterGraphGPU& graph) +bool ParticleEmitterFunction::LoadSurface(ParticleEmitterGraphGPU& graph) const { if (WaitForLoaded()) return true; @@ -178,19 +179,10 @@ void ParticleEmitterFunction::GetSignature(Array } } -bool ParticleEmitterFunction::SaveSurface(BytesContainer& data) +bool ParticleEmitterFunction::SaveSurface(const BytesContainer& data) const { - // Wait for asset to be loaded or don't if last load failed - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Set Visject Surface data @@ -210,4 +202,17 @@ bool ParticleEmitterFunction::SaveSurface(BytesContainer& data) return false; } +bool ParticleEmitterFunction::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + MemoryWriteStream writeStream; + if (Graph.Save(&writeStream, true)) + return true; + BytesContainer data; + data.Link(ToSpan(writeStream)); + return SaveSurface(data); +} + #endif diff --git a/Source/Engine/Particles/ParticleEmitterFunction.h b/Source/Engine/Particles/ParticleEmitterFunction.h index 5938ff6e6..8d7499884 100644 --- a/Source/Engine/Particles/ParticleEmitterFunction.h +++ b/Source/Engine/Particles/ParticleEmitterFunction.h @@ -31,23 +31,21 @@ public: Array> Outputs; #if COMPILE_WITH_PARTICLE_GPU_GRAPH - /// /// The loaded GPU particle function graph. /// ParticleEmitterGraphGPU GraphGPU; - #endif /// /// Tries to load surface graph from the asset. /// /// The graph to load. + /// True if load metadata. /// True if failed, otherwise false. - bool LoadSurface(ParticleEmitterGraphCPU& graph); + bool LoadSurface(ParticleEmitterGraphCPU& graph, bool loadMeta = false); #if USE_EDITOR - /// /// Tries to load surface graph from the asset. /// @@ -55,14 +53,12 @@ public: API_FUNCTION() BytesContainer LoadSurface(); #if COMPILE_WITH_PARTICLE_GPU_GRAPH - /// /// Tries to load surface graph from the asset. /// /// The graph to load. /// True if failed, otherwise false. - bool LoadSurface(ParticleEmitterGraphGPU& graph); - + bool LoadSurface(ParticleEmitterGraphGPU& graph) const; #endif // Gets the function signature for Visject Surface editor. @@ -73,8 +69,13 @@ public: /// /// The surface graph data. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(BytesContainer& data); + API_FUNCTION() bool SaveSurface(const BytesContainer& data) const; +#endif +public: + // [BinaryAsset] +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index e70e5769d..fba1561b0 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -4,6 +4,7 @@ #include "ParticleEffect.h" #include "Engine/Core/Types/CommonValue.h" #include "Engine/Level/Level.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" @@ -46,7 +47,7 @@ void ParticleSystem::Init(ParticleEmitter* emitter, float duration, float fps) } } -BytesContainer ParticleSystem::LoadTimeline() +BytesContainer ParticleSystem::LoadTimeline() const { BytesContainer result; ScopeLock lock(Locker); @@ -108,25 +109,16 @@ BytesContainer ParticleSystem::LoadTimeline() } // Set output data - result.Copy(stream.GetHandle(), stream.GetPosition()); + result.Copy(ToSpan(stream)); return result; } #if USE_EDITOR -bool ParticleSystem::SaveTimeline(BytesContainer& data) +bool ParticleSystem::SaveTimeline(const BytesContainer& data) const { - // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) - if (LastLoadFailed()) - { - LOG(Warning, "Saving asset that failed to load."); - } - else if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave()) return true; - } - ScopeLock lock(Locker); // Release all chunks @@ -197,6 +189,15 @@ void ParticleSystem::GetReferences(Array& assets, Array& files) co } } +bool ParticleSystem::Save(const StringView& path) +{ + if (OnCheckSave(path)) + return true; + ScopeLock lock(Locker); + BytesContainer data = LoadTimeline(); + return SaveTimeline(data); +} + #endif Asset::LoadResult ParticleSystem::load() @@ -225,6 +226,7 @@ Asset::LoadResult ParticleSystem::load() case 1: { // [Deprecated on 23.07.2019, expires on 27.04.2021] + MARK_CONTENT_DEPRECATED(); // Load properties stream.ReadFloat(&FramesPerSecond); @@ -299,6 +301,7 @@ Asset::LoadResult ParticleSystem::load() case 2: { // [Deprecated on 31.07.2020, expires on 31.07.2022] + MARK_CONTENT_DEPRECATED(); // Load properties stream.ReadFloat(&FramesPerSecond); @@ -372,6 +375,7 @@ Asset::LoadResult ParticleSystem::load() } PRAGMA_ENABLE_DEPRECATION_WARNINGS case 3: // [Deprecated on 03.09.2021 expires on 03.09.2023] + MARK_CONTENT_DEPRECATED(); case 4: { // Load properties diff --git a/Source/Engine/Particles/ParticleSystem.h b/Source/Engine/Particles/ParticleSystem.h index cd472b49f..98b81bd47 100644 --- a/Source/Engine/Particles/ParticleSystem.h +++ b/Source/Engine/Particles/ParticleSystem.h @@ -156,17 +156,15 @@ public: /// Loads the serialized timeline data. /// /// The output surface data, or empty if failed to load. - API_FUNCTION() BytesContainer LoadTimeline(); + API_FUNCTION() BytesContainer LoadTimeline() const; #if USE_EDITOR - /// /// Saves the serialized timeline data to the asset. /// /// The timeline data container. /// true failed to save data; otherwise, false. - API_FUNCTION() bool SaveTimeline(BytesContainer& data); - + API_FUNCTION() bool SaveTimeline(const BytesContainer& data) const; #endif public: @@ -243,6 +241,7 @@ public: void InitAsVirtual() override; #if USE_EDITOR void GetReferences(Array& assets, Array& files) const override; + bool Save(const StringView& path = StringView::Empty) override; #endif protected: diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 010add151..01b088040 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -1,10 +1,11 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "WheeledVehicle.h" -#include "Engine/Physics/Physics.h" #include "Engine/Level/Scene/Scene.h" +#include "Engine/Physics/Physics.h" #include "Engine/Physics/PhysicsBackend.h" #include "Engine/Physics/PhysicsScene.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Serialization/Serialization.h" #if USE_EDITOR #include "Engine/Level/Scene/SceneRendering.h" @@ -476,7 +477,11 @@ void WheeledVehicle::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(AntiRollBars, _antiRollBars); // [Deprecated on 13.06.2023, expires on 13.06.2025] - _fixInvalidForwardDir |= modifier->EngineBuild < 6341; + if (modifier->EngineBuild < 6341) + { + MARK_CONTENT_DEPRECATED(); + _fixInvalidForwardDir = true; + } } void WheeledVehicle::OnColliderChanged(Collider* c) diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 3161c65ef..0dd3df37c 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -167,18 +167,8 @@ bool FontAsset::Init(const BytesContainer& fontFile) bool FontAsset::Save(const StringView& path) { - // Validate state - if (WaitForLoaded()) - { - LOG(Error, "Asset loading failed. Cannot save it."); + if (OnCheckSave(path)) return true; - } - if (IsVirtual() && path.IsEmpty()) - { - LOG(Error, "To save virtual asset asset you need to specify the target asset path location."); - return true; - } - ScopeLock lock(Locker); AssetInitData data; diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index 02b085db5..d5df16c4f 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -166,15 +166,6 @@ public: /// True if cannot init, otherwise false. API_FUNCTION() bool Init(const BytesContainer& fontFile); -#if USE_EDITOR - /// - /// Saves this asset to the file. Supported only in Editor. - /// - /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. - /// True if cannot save data, otherwise false. - API_FUNCTION() bool Save(const StringView& path = StringView::Empty); -#endif - /// /// Check if the font contains the glyph of a char. /// @@ -190,6 +181,9 @@ public: public: // [BinaryAsset] uint64 GetMemoryUsage() const override; +#if USE_EDITOR + bool Save(const StringView& path = StringView::Empty) override; +#endif protected: // [BinaryAsset] diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index 7d6faf4ff..76b6a5572 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -285,9 +285,11 @@ DateTime JsonTools::GetDateTime(const Value& value) } PRAGMA_DISABLE_DEPRECATION_WARNINGS +#include "Engine/Content/Deprecated.h" CommonValue JsonTools::GetCommonValue(const Value& value) { // [Deprecated on 31.07.2020, expires on 31.07.2022] + MARK_CONTENT_DEPRECATED(); CommonValue result; const auto typeMember = value.FindMember("Type"); const auto valueMember = value.FindMember("Value"); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 204a78359..6e6d43d06 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -7,13 +7,14 @@ #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Physics.h" +#include "Engine/Physics/PhysicsScene.h" #include "Engine/Physics/PhysicalMaterial.h" #include "Engine/Physics/PhysicsBackend.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Level/Scene/Scene.h" -#include "Engine/Physics/PhysicsScene.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/GlobalSignDistanceFieldPass.h" #include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h" @@ -810,16 +811,23 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSDF; + } // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSurfaceAtlas; + } // [Deprecated on 15.02.2024, expires on 15.02.2026] JsonAssetReference PhysicalMaterial; DESERIALIZE(PhysicalMaterial); if (PhysicalMaterial) { + MARK_CONTENT_DEPRECATED(); for (auto& e : _physicalMaterials) e = PhysicalMaterial; } diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 8ff691204..a1ccbda59 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -16,6 +16,7 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Content/Assets/MaterialInstance.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Shaders/GPUVertexLayout.h" @@ -482,10 +483,16 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSDF; + } // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) + { + MARK_CONTENT_DEPRECATED(); DrawModes |= DrawPass::GlobalSurfaceAtlas; + } _isDirty = true; } diff --git a/Source/Engine/Visject/Graph.h b/Source/Engine/Visject/Graph.h index 62fcfec0a..13cbbf28c 100644 --- a/Source/Engine/Visject/Graph.h +++ b/Source/Engine/Visject/Graph.h @@ -7,6 +7,7 @@ #include "VisjectMeta.h" #include "GraphNode.h" #include "GraphParameter.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Serialization/ReadStream.h" #include "Engine/Serialization/WriteStream.h" @@ -183,6 +184,7 @@ public: if (version < 7000) { // [Deprecated on 31.07.2020, expires on 31.07.2022] + MARK_CONTENT_DEPRECATED(); // Time saved int64 timeSaved; From d4a5c76c8271ad5861641b40eca991b7822b4b8e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:47:51 +0100 Subject: [PATCH 145/215] Add serialization of game settings for proper upgrade when loading deprecated data --- Source/Engine/Core/Config/GameSettings.cpp | 33 +++++++++++++++++-- Source/Engine/Core/Config/GraphicsSettings.h | 7 ++-- .../Engine/Core/Config/LayersTagsSettings.h | 3 ++ Source/Engine/Input/Input.cpp | 1 - Source/Engine/Localization/Localization.cpp | 11 +++++++ .../Localization/LocalizationSettings.h | 3 ++ Source/Engine/Navigation/Navigation.cpp | 22 +++++++++++++ Source/Engine/Navigation/NavigationSettings.h | 3 ++ Source/Engine/Physics/Physics.cpp | 32 +++++++++++++++++- Source/Engine/Physics/PhysicsSettings.h | 5 ++- Source/Engine/Streaming/Streaming.cpp | 5 --- Source/Engine/Streaming/StreamingSettings.h | 7 ++-- 12 files changed, 113 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 11617612f..9fdcec3c8 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -42,6 +42,14 @@ public: }; IMPLEMENT_ENGINE_SETTINGS_GETTER(BuildSettings, GameCooking); + +#include "Engine/Content/Deprecated.h" +void GraphicsSettings::SetUeeHDRProbes(bool value) +{ + MARK_CONTENT_DEPRECATED(); + UseHDRProbes = value; +} + IMPLEMENT_ENGINE_SETTINGS_GETTER(GraphicsSettings, Graphics); IMPLEMENT_ENGINE_SETTINGS_GETTER(NetworkSettings, Network); IMPLEMENT_ENGINE_SETTINGS_GETTER(LayersAndTagsSettings, LayersAndTags); @@ -260,6 +268,27 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(iOSPlatform); } +#if USE_EDITOR + +void LayersAndTagsSettings::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(LayersAndTagsSettings); + + stream.JKEY("Tags"); + stream.StartArray(); + for (const String& e : Tags) + stream.String(e); + stream.EndArray(); + + stream.JKEY("Layers"); + stream.StartArray(); + for (const String& e : Layers) + stream.String(e); + stream.EndArray(); +} + +#endif + void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { const auto tags = stream.FindMember("Tags"); @@ -280,7 +309,7 @@ void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeMod if (layers != stream.MemberEnd() && layers->value.IsArray()) { auto& layersArray = layers->value; - for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) + for (uint32 i = 0; i < layersArray.Size() && i < ARRAY_COUNT(Layers); i++) { auto& v = layersArray[i]; if (v.IsString()) @@ -288,7 +317,7 @@ void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeMod else Layers[i].Clear(); } - for (uint32 i = layersArray.Size(); i < 32; i++) + for (uint32 i = layersArray.Size(); i < ARRAY_COUNT(Layers); i++) { Layers[i].Clear(); } diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 9fe3db4c0..8e42748e3 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -145,15 +145,12 @@ private: /// Renamed UeeHDRProbes into UseHDRProbes /// [Deprecated on 12.10.2022, expires on 12.10.2024] /// - API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") bool GetUeeHDRProbes() const + API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") bool GetUeeHDRProbes() const { return UseHDRProbes; } - API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") void SetUeeHDRProbes(bool value) - { - UseHDRProbes = value; - } + API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") void SetUeeHDRProbes(bool value); public: /// diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index 20e3977a7..6d10d88f8 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -32,5 +32,8 @@ public: // [SettingsBase] void Apply() override; +#if USE_EDITOR + void Serialize(SerializeStream& stream, const void* otherObj) override; +#endif void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index d1ccd367e..1a505831a 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -5,7 +5,6 @@ #include "Keyboard.h" #include "Mouse.h" #include "Gamepad.h" -#include "FlaxEngine.Gen.h" #include "Engine/Platform/Window.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" diff --git a/Source/Engine/Localization/Localization.cpp b/Source/Engine/Localization/Localization.cpp index bc921c28e..325a99a36 100644 --- a/Source/Engine/Localization/Localization.cpp +++ b/Source/Engine/Localization/Localization.cpp @@ -80,6 +80,17 @@ void LocalizationSettings::Apply() Instance.OnLocalizationChanged(); } +#if USE_EDITOR + +void LocalizationSettings::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(LocalizationSettings); + SERIALIZE(LocalizedStringTables); + SERIALIZE(DefaultFallbackLanguage); +} + +#endif + void LocalizationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { DESERIALIZE(LocalizedStringTables); diff --git a/Source/Engine/Localization/LocalizationSettings.h b/Source/Engine/Localization/LocalizationSettings.h index cfd391cc3..7560aea42 100644 --- a/Source/Engine/Localization/LocalizationSettings.h +++ b/Source/Engine/Localization/LocalizationSettings.h @@ -34,5 +34,8 @@ public: // [SettingsBase] void Apply() override; +#if USE_EDITOR + void Serialize(SerializeStream& stream, const void* otherObj) override; +#endif void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 5d7bb7889..595de0fd4 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -235,6 +235,28 @@ void NavigationSettings::Apply() #endif } +#if USE_EDITOR + +void NavigationSettings::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(NavigationSettings); + SERIALIZE(AutoAddMissingNavMeshes); + SERIALIZE(AutoRemoveMissingNavMeshes); + SERIALIZE(CellHeight); + SERIALIZE(CellSize); + SERIALIZE(TileSize); + SERIALIZE(MinRegionArea); + SERIALIZE(MergeRegionArea); + SERIALIZE(MaxEdgeLen); + SERIALIZE(MaxEdgeError); + SERIALIZE(DetailSamplingDist); + SERIALIZE(MaxDetailSamplingError); + SERIALIZE(NavMeshes); + SERIALIZE(NavAreas); +} + +#endif + void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { DESERIALIZE(AutoAddMissingNavMeshes); diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index f4e6c26fa..d73f9fffb 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -104,5 +104,8 @@ public: // [SettingsBase] void Apply() override; +#if USE_EDITOR + void Serialize(SerializeStream& stream, const void* otherObj) override; +#endif void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 66d5a76bf..092837e47 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -49,6 +49,36 @@ PhysicsSettings::PhysicsSettings() LayerMasks[i] = MAX_uint32; } +#if USE_EDITOR + +void PhysicsSettings::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(PhysicsSettings); + + SERIALIZE(DefaultGravity); + SERIALIZE(TriangleMeshTriangleMinAreaThreshold); + SERIALIZE(BounceThresholdVelocity); + SERIALIZE(FrictionCombineMode); + SERIALIZE(RestitutionCombineMode); + SERIALIZE(DisableCCD); + SERIALIZE(BroadPhaseType); + SERIALIZE(SolverType); + SERIALIZE(MaxDeltaTime); + SERIALIZE(EnableSubstepping); + SERIALIZE(SubstepDeltaTime); + SERIALIZE(MaxSubsteps); + SERIALIZE(QueriesHitTriggers); + SERIALIZE(SupportCookingAtRuntime); + + stream.JKEY("LayerMasks"); + stream.StartArray(); + for (uint32 e : LayerMasks) + stream.Uint(e); + stream.EndArray(); +} + +#endif + void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { DESERIALIZE(DefaultGravity); @@ -71,7 +101,7 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* { auto& layersArray = layers->value; ASSERT(layersArray.IsArray()); - for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) + for (uint32 i = 0; i < layersArray.Size() && i < ARRAY_COUNT(LayerMasks); i++) { LayerMasks[i] = layersArray[i].GetUint(); } diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index 728a18367..4360e7dae 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -143,7 +143,7 @@ public: float TriangleMeshTriangleMinAreaThreshold = 5.0f; /// - /// If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior. + /// If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will return with a hit. Individual raycasts can override this behavior. /// API_FIELD(Attributes="EditorOrder(1200), EditorDisplay(\"Other\")") bool QueriesHitTriggers = true; @@ -166,5 +166,8 @@ public: // [SettingsBase] void Apply() override; +#if USE_EDITOR + void Serialize(SerializeStream& stream, const void* otherObj) override; +#endif void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp index fa5112d70..6d89af588 100644 --- a/Source/Engine/Streaming/Streaming.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -60,11 +60,6 @@ void StreamingSettings::Apply() TextureGroupSamplers.Resize(TextureGroups.Count(), false); } -void StreamingSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - DESERIALIZE(TextureGroups); -} - StreamableResource::StreamableResource(StreamingGroup* group) : _group(group) , _isDynamic(true) diff --git a/Source/Engine/Streaming/StreamingSettings.h b/Source/Engine/Streaming/StreamingSettings.h index 0bc0347e9..89c0c6802 100644 --- a/Source/Engine/Streaming/StreamingSettings.h +++ b/Source/Engine/Streaming/StreamingSettings.h @@ -10,9 +10,10 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API StreamingSettings : public SettingsBase { -DECLARE_SCRIPTING_TYPE_MINIMAL(StreamingSettings); -public: + DECLARE_SCRIPTING_TYPE_MINIMAL(StreamingSettings); + API_AUTO_SERIALIZATION(); +public: /// /// Textures streaming configuration (per-group). /// @@ -20,7 +21,6 @@ public: Array> TextureGroups; public: - /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// @@ -28,5 +28,4 @@ public: // [SettingsBase] void Apply() override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; From eaf48bb1769cbfbcd5727764f27707661b582acb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:48:25 +0100 Subject: [PATCH 146/215] Fix regression in dynamic mesh bounds and triangles setup after refactor --- Source/Engine/Graphics/Models/Mesh.cpp | 2 +- Source/Engine/Graphics/Models/MeshBase.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 810cb23fc..803321344 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -55,7 +55,7 @@ namespace // Index Buffer { - if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat)) + if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount * 3, indexFormat)) return true; auto indexStream = accessor.Index(); ASSERT(indexStream.IsLinear(indexFormat)); diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index 7df3670cc..857a85fff 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -311,6 +311,11 @@ void MeshBase::SetBounds(const BoundingBox& box) _box = box; BoundingSphere::FromBox(box, _sphere); _hasBounds = true; + if (_model && _model->IsLoaded()) + { + // Send event (actors using this model can update bounds, etc.) + _model->onLoaded(); + } } void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere) From f4c07388d954c06845bed4a482ab6242e91b13ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:49:00 +0100 Subject: [PATCH 147/215] Restore old upgrader for skinned models from 9f648caac8b18d30fa57f4df99950bdb0b724408 --- .../Upgraders/SkinnedModelAssetUpgrader.h | 237 +++++++++++++++++- 1 file changed, 236 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h index bf339a3ea..03a6739bd 100644 --- a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h @@ -21,12 +21,247 @@ public: { const Upgrader upgraders[] = { + { 4, 5, &Upgrade_4_To_5 }, // [Deprecated in v1.10] { 5, 30, Upgrade_5_To_30 }, // [Deprecated in v1.10] }; setup(upgraders, ARRAY_COUNT(upgraders)); } PRAGMA_DISABLE_DEPRECATION_WARNINGS + static bool Upgrade_4_To_5(AssetMigrationContext& context) + { + ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5); + + // Changes: + // - added version number to header (for easier changes in future) + // - added version number to mesh data (for easier changes in future) + // - added skeleton retarget setups to header + + // Rewrite header chunk (added header version and retarget entries) + byte lodCount; + Array meshesCounts; + { + const auto srcData = context.Input.Header.Chunks[0]; + if (srcData == nullptr || srcData->IsMissing()) + { + LOG(Warning, "Missing model header chunk"); + return true; + } + MemoryReadStream stream(srcData->Get(), srcData->Size()); + MemoryWriteStream output(srcData->Size()); + + // Header Version + output.WriteByte(1); + + // Min Screen Size + float minScreenSize; + stream.ReadFloat(&minScreenSize); + output.WriteFloat(minScreenSize); + + // Amount of material slots + int32 materialSlotsCount; + stream.ReadInt32(&materialSlotsCount); + output.WriteInt32(materialSlotsCount); + + // For each material slot + for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++) + { + // Material + Guid materialId; + stream.Read(materialId); + output.Write(materialId); + + // Shadows Mode + output.WriteByte(stream.ReadByte()); + + // Name + String name; + stream.ReadString(&name, 11); + output.WriteString(name, 11); + } + + // Amount of LODs + stream.ReadByte(&lodCount); + output.WriteByte(lodCount); + meshesCounts.Resize(lodCount); + + // For each LOD + for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) + { + // Screen Size + float screenSize; + stream.ReadFloat(&screenSize); + output.WriteFloat(screenSize); + + // Amount of meshes + uint16 meshesCount; + stream.ReadUint16(&meshesCount); + output.WriteUint16(meshesCount); + meshesCounts[lodIndex] = meshesCount; + + // For each mesh + for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++) + { + // Material Slot index + int32 materialSlotIndex; + stream.ReadInt32(&materialSlotIndex); + output.WriteInt32(materialSlotIndex); + + // Box + BoundingBox box; + stream.Read(box); + output.Write(box); + + // Sphere + BoundingSphere sphere; + stream.Read(sphere); + output.Write(sphere); + + // Blend Shapes + uint16 blendShapes; + stream.ReadUint16(&blendShapes); + output.WriteUint16(blendShapes); + for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++) + { + String blendShapeName; + stream.ReadString(&blendShapeName, 13); + output.WriteString(blendShapeName, 13); + float blendShapeWeight; + stream.ReadFloat(&blendShapeWeight); + output.WriteFloat(blendShapeWeight); + } + } + } + + // Skeleton + { + int32 nodesCount; + stream.ReadInt32(&nodesCount); + output.WriteInt32(nodesCount); + + // For each node + for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) + { + int32 parentIndex; + stream.ReadInt32(&parentIndex); + output.WriteInt32(parentIndex); + + Transform localTransform; + stream.Read(localTransform); + output.Write(localTransform); + + String name; + stream.ReadString(&name, 71); + output.WriteString(name, 71); + } + + int32 bonesCount; + stream.ReadInt32(&bonesCount); + output.WriteInt32(bonesCount); + + // For each bone + for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) + { + int32 parentIndex; + stream.ReadInt32(&parentIndex); + output.WriteInt32(parentIndex); + + int32 nodeIndex; + stream.ReadInt32(&nodeIndex); + output.WriteInt32(nodeIndex); + + Transform localTransform; + stream.Read(localTransform); + output.Write(localTransform); + + Matrix offsetMatrix; + stream.ReadBytes(&offsetMatrix, sizeof(Matrix)); + output.WriteBytes(&offsetMatrix, sizeof(Matrix)); + } + } + + // Retargeting + { + output.WriteInt32(0); + } + + // Save new data + if (stream.GetPosition() != stream.GetLength()) + { + LOG(Error, "Invalid position after upgrading skinned model header data."); + return true; + } + if (context.AllocateChunk(0)) + return true; + context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition()); + } + + // Rewrite meshes data chunks + for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) + { + const int32 chunkIndex = lodIndex + 1; + const auto srcData = context.Input.Header.Chunks[chunkIndex]; + if (srcData == nullptr || srcData->IsMissing()) + { + LOG(Warning, "Missing skinned model LOD meshes data chunk"); + return true; + } + MemoryReadStream stream(srcData->Get(), srcData->Size()); + MemoryWriteStream output(srcData->Size()); + + // Mesh Data Version + output.WriteByte(1); + + for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++) + { + uint32 vertices; + stream.ReadUint32(&vertices); + output.WriteUint32(vertices); + uint32 triangles; + stream.ReadUint32(&triangles); + output.WriteUint32(triangles); + uint16 blendShapesCount; + stream.ReadUint16(&blendShapesCount); + output.WriteUint16(blendShapesCount); + for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++) + { + output.WriteBool(stream.ReadBool()); + uint32 minVertexIndex, maxVertexIndex; + stream.ReadUint32(&minVertexIndex); + output.WriteUint32(minVertexIndex); + stream.ReadUint32(&maxVertexIndex); + output.WriteUint32(maxVertexIndex); + uint32 blendShapeVertices; + stream.ReadUint32(&blendShapeVertices); + output.WriteUint32(blendShapeVertices); + const uint32 blendShapeDataSize = blendShapeVertices * sizeof(BlendShapeVertex); + const auto blendShapeData = stream.Move(blendShapeDataSize); + output.WriteBytes(blendShapeData, blendShapeDataSize); + } + const uint32 indicesCount = triangles * 3; + const bool use16BitIndexBuffer = indicesCount <= MAX_uint16; + const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32); + if (vertices == 0 || triangles == 0) + return true; + const auto vb0 = stream.Move(vertices); + output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType)); + const auto ib = stream.Move(indicesCount * ibStride); + output.WriteBytes(ib, indicesCount * ibStride); + } + + // Save new data + if (stream.GetPosition() != stream.GetLength()) + { + LOG(Error, "Invalid position after upgrading skinned model LOD meshes data."); + return true; + } + if (context.AllocateChunk(chunkIndex)) + return true; + context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition()); + } + + return false; + } static bool Upgrade_5_To_30(AssetMigrationContext& context) { // [Deprecated in v1.10] @@ -73,7 +308,7 @@ PRAGMA_DISABLE_DEPRECATION_WARNINGS // LODs stream.Read(lodsCount); output.Write(lodsCount); - CHECK_RETURN(materialSlotsCount <= 6, true); + CHECK_RETURN(lodsCount <= 6, true); meshesCountPerLod.Resize(lodsCount); for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++) { From 011abe4ac0a81049cff13d77fa4a60a24c92768e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:49:17 +0100 Subject: [PATCH 148/215] Fix error when connecting nodes in Visject --- Source/Editor/Surface/VisjectSurface.Connecting.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 8a74ef8e6..fbbfa2053 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -60,12 +60,12 @@ namespace FlaxEditor.Surface if (toIsReference && !fromIsReference) { var toTypeName = to.TypeName; - return from.TypeName == toTypeName.Substring(0, toTypeName.Length - 1); + return !string.IsNullOrEmpty(toTypeName) && from.TypeName == toTypeName.Substring(0, toTypeName.Length - 1); } if (!toIsReference && fromIsReference) { var fromTypeName = from.TypeName; - return to.TypeName == fromTypeName.Substring(0, fromTypeName.Length - 1); + return !string.IsNullOrEmpty(fromTypeName) && to.TypeName == fromTypeName.Substring(0, fromTypeName.Length - 1); } // Implicit casting is supported for object reference to test whenever it is valid From 86444aa5f3b53834bed0c2802774b969b67ffcdc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:49:40 +0100 Subject: [PATCH 149/215] Fix `TypeUtils.GetTypeName` for nested generic types --- Source/Engine/Utilities/TypeUtils.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Source/Engine/Utilities/TypeUtils.cs b/Source/Engine/Utilities/TypeUtils.cs index abbf4051f..f5ff90c18 100644 --- a/Source/Engine/Utilities/TypeUtils.cs +++ b/Source/Engine/Utilities/TypeUtils.cs @@ -23,6 +23,33 @@ namespace FlaxEngine.Utilities var sb = new StringBuilder(); sb.Append(type.Namespace); sb.Append('.'); + var parent = type.DeclaringType; + if (parent != null) + { + // Find outer types in which this one is nested + var count = 0; + while (parent != null) + { + count++; + parent = parent.DeclaringType; + } + var path = new Type[count]; + parent = type.DeclaringType; + count = 0; + while (parent != null) + { + path[count++] = parent; + parent = parent.DeclaringType; + } + + // Insert type nesting into typename path (from the back to front) + for (int i = count - 1; i >= 0; i--) + { + parent = path[i]; + sb.Append(parent.Name); + sb.Append('+'); + } + } sb.Append(type.Name); sb.Append('['); var genericArgs = type.GetGenericArguments(); From 18fd68db2586d318135ca1490fa507a01d718741 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:50:47 +0100 Subject: [PATCH 150/215] Add `SpawnOptions` container for more robust prefabs spawning --- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 39 +++---- Source/Engine/Level/Prefabs/PrefabManager.cpp | 100 +++++++++++++----- Source/Engine/Level/Prefabs/PrefabManager.h | 24 +++++ 3 files changed, 113 insertions(+), 50 deletions(-) diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 4d100773c..f8dcde410 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -181,6 +181,18 @@ public: obj->SetParent(nullptr); obj->DeleteObject(); } + + static void SerializeObjects(const ActorsCache::SceneObjectsListType& sceneObjects, JsonWriter& writer) + { + PROFILE_CPU(); + writer.StartArray(); + for (int32 i = 0; i < sceneObjects.Count(); i++) + { + SceneObject* obj = sceneObjects[i]; + writer.SceneObject(obj); + } + writer.EndArray(); + } }; void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor) @@ -235,14 +247,7 @@ void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabIns // Serialize tmpBuffer.Clear(); CompactJsonWriter writerObj(tmpBuffer); - JsonWriter& writer = writerObj; - writer.StartArray(); - for (int32 i = 0; i < sceneObjects->Count(); i++) - { - SceneObject* obj = sceneObjects.Value->At(i); - writer.SceneObject(obj); - } - writer.EndArray(); + SerializeObjects(*sceneObjects.Value, writerObj); // Parse json to get DOM { @@ -746,14 +751,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr rapidjson_flax::StringBuffer dataBuffer; { CompactJsonWriter writerObj(dataBuffer); - JsonWriter& writer = writerObj; - writer.StartArray(); - for (int32 i = 0; i < targetObjects->Count(); i++) - { - SceneObject* obj = targetObjects.Value->At(i); - writer.SceneObject(obj); - } - writer.EndArray(); + PrefabInstanceData::SerializeObjects(*targetObjects.Value, writerObj); } // Parse json document and modify serialized data to extract only modified properties @@ -1079,14 +1077,7 @@ bool Prefab::UpdateInternal(const Array& defaultInstanceObjects, r { tmpBuffer.Clear(); PrettyJsonWriter writerObj(tmpBuffer); - JsonWriter& writer = writerObj; - writer.StartArray(); - for (int32 i = 0; i < defaultInstanceObjects.Count(); i++) - { - auto obj = defaultInstanceObjects.At(i); - writer.SceneObject(obj); - } - writer.EndArray(); + PrefabInstanceData::SerializeObjects(defaultInstanceObjects, writerObj); } LOG(Info, "Updating prefab data"); diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 251fabe09..d4e4774d7 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -40,7 +40,9 @@ PrefabManagerService PrefabManagerServiceInstance; Actor* PrefabManager::SpawnPrefab(Prefab* prefab) { Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; - return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr); + SpawnOptions options; + options.Parent = parent; + return SpawnPrefab(prefab, options); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position) @@ -69,20 +71,39 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform& transform) { - return SpawnPrefab(prefab, transform, parent, nullptr); + SpawnOptions options; + options.Transform = &transform; + options.Parent = parent; + return SpawnPrefab(prefab, options); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent) { - return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr); + SpawnOptions options; + options.Parent = parent; + return SpawnPrefab(prefab, options); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization) { - return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization); + SpawnOptions options; + options.Parent = parent; + options.ObjectsCache = objectsCache; + options.WithSync = withSynchronization; + return SpawnPrefab(prefab, options); } Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization) +{ + SpawnOptions options; + options.Transform = &transform; + options.Parent = parent; + options.ObjectsCache = objectsCache; + options.WithSync = withSynchronization; + return SpawnPrefab(prefab, options); +} + +Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options) { PROFILE_CPU_NAMED("Prefab.Spawn"); if (prefab == nullptr) @@ -111,15 +132,27 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac sceneObjects->Resize(dataCount); CollectionPoolCache::ScopeCache modifier = Cache::ISerializeModifier.Get(); modifier->EngineBuild = prefab->DataEngineBuild; - for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++) modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count()); + if (options.IDs) { - modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New()); + for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++) + { + Guid prefabObjectId = prefab->ObjectsIds[i]; + Guid id; + if (!options.IDs->TryGet(prefabObjectId, id)) + id = Guid::New(); + modifier->IdsMapping.Add(id, id); + } } - if (objectsCache) + else { - objectsCache->Clear(); - objectsCache->SetCapacity(prefab->ObjectsDataCache.Capacity()); + for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++) + modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New()); + } + if (options.ObjectsCache) + { + options.ObjectsCache->Clear(); + options.ObjectsCache->SetCapacity(prefab->ObjectsDataCache.Count()); } auto& data = *prefab->Data; SceneObjectsFactory::Context context(modifier.Value); @@ -139,7 +172,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac SceneObjectsFactory::HandleObjectDeserializationError(stream); } SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); - if (withSynchronization) + if (options.WithSync) { // Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it) // TODO: resave and force sync prefabs during game cooking so this step could be skipped in game @@ -157,7 +190,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac Scripting::ObjectsLookupIdMapping.Set(prevIdMapping); // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) - if (withSynchronization) + if (options.WithSync) { // TODO: resave and force sync scenes during game cooking so this step could be skipped in game SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); @@ -190,13 +223,13 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac // Prepare parent linkage for prefab root actor if (root->_parent) root->_parent->Children.Remove(root); - root->_parent = parent; - if (parent) - parent->Children.Add(root); + root->_parent = options.Parent; + if (options.Parent) + options.Parent->Children.Add(root); // Move root to the right location - if (transform.Translation != Vector3::Minimum) - root->SetTransform(transform); + if (options.Transform) + root->SetTransform(*options.Transform); // Link actors hierarchy for (int32 i = 0; i < sceneObjects->Count(); i++) @@ -268,24 +301,39 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac } // Link objects to prefab (only deserialized from prefab data) - for (int32 i = 0; i < dataCount; i++) + if (options.WithLink) { - auto& stream = data[i]; - SceneObject* obj = sceneObjects->At(i); - if (!obj) - continue; + for (int32 i = 0; i < dataCount; i++) + { + auto& stream = data[i]; + SceneObject* obj = sceneObjects->At(i); + if (!obj) + continue; - const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID"); - if (objectsCache) - objectsCache->Add(prefabObjectId, obj); - obj->LinkPrefab(prefabId, prefabObjectId); + const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID"); + if (options.ObjectsCache) + options.ObjectsCache->Add(prefabObjectId, obj); + obj->LinkPrefab(prefabId, prefabObjectId); + } + } + else if (options.ObjectsCache) + { + for (int32 i = 0; i < dataCount; i++) + { + auto& stream = data[i]; + SceneObject* obj = sceneObjects->At(i); + if (!obj) + continue; + const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID"); + options.ObjectsCache->Add(prefabObjectId, obj); + } } // Update transformations root->OnTransformChanged(); // Spawn if need to - if (parent && parent->IsDuringPlay()) + if (options.Parent && options.Parent->IsDuringPlay()) { // Begin play SceneBeginData beginData; diff --git a/Source/Engine/Level/Prefabs/PrefabManager.h b/Source/Engine/Level/Prefabs/PrefabManager.h index ba5b49ef3..0e84d829f 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.h +++ b/Source/Engine/Level/Prefabs/PrefabManager.h @@ -102,6 +102,30 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager /// The created actor (root) or null if failed. static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization = true); + struct FLAXENGINE_API SpawnOptions + { + // Spawn transformation. + const Transform* Transform = nullptr; + // The parent actor to add spawned object instance. Can be null to just deserialize contents of the prefab. + Actor* Parent = nullptr; + // Custom objects mapping (maps prefab objects into spawned objects). + const Dictionary* IDs = nullptr; + // Output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script). + Dictionary* ObjectsCache = nullptr; + // if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications. + bool WithSync = true; + // True if linked spawned prefab objects with the source prefab, otherwise links will be valid only for nested prefab objects. + bool WithLink = true; + }; + + /// + /// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay). + /// + /// The prefab asset. + /// The spawn options container. + /// The created actor (root) or null if failed. + static Actor* SpawnPrefab(Prefab* prefab, const SpawnOptions& options); + #if USE_EDITOR /// From 65a689f8ab47dde79dbbba630b3398e43d841c46 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:51:08 +0100 Subject: [PATCH 151/215] Add logging first scene asset id in cooked game --- Source/Engine/Engine/Base/GameBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index 2ac8d213c..ad45d0848 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -268,8 +268,8 @@ void GameBaseImpl::OnSplashScreenEnd() MainRenderTask::Instance->PostRender.Unbind(&OnPostRender); // Load the first scene - LOG(Info, "Loading the first scene"); const auto sceneId = FirstScene ? FirstScene.GetID() : Guid::Empty; + LOG(Info, "Loading the first scene ({})", sceneId); FirstScene = nullptr; if (Level::LoadSceneAsync(sceneId)) { From 6a66a944d210645d5760589a23e8e8b61f11fe36 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:51:35 +0100 Subject: [PATCH 152/215] Add total mesh vertex stride display in editor panel --- Source/Editor/Windows/Assets/ModelBaseWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index af9b4ea14..52dcff08a 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -271,6 +271,7 @@ namespace FlaxEditor.Windows.Assets foreach (var e in slotElements) group.Label($" {e.Type}, {e.Format} ({PixelFormatExtensions.SizeInBytes(e.Format)} bytes), offset {e.Offset}").Label.Height = height; } + group.Label($"{vertexLayout.Stride} bytes per-vertex"); } } proxy.OnGeneral(layout); From 67f12596e2404c4f0a5e86c25f1259613281bbb8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:52:12 +0100 Subject: [PATCH 153/215] Fix `LoadAssetTask` to properly dereference asset loading task pointer when asset init fails --- .../Content/Loading/Tasks/LoadAssetTask.h | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 24bd66119..3f211b3c7 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -26,18 +26,7 @@ public: ~LoadAssetTask() { - auto asset = Asset.Get(); - if (asset) - { - asset->Locker.Lock(); - if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this) - { - Platform::AtomicStore(&asset->_loadState, (int64)Asset::LoadState::LoadFailed); - Platform::AtomicStore(&asset->_loadingTask, 0); - LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed)); - } - asset->Locker.Unlock(); - } + DereferenceAsset(); } public: @@ -69,15 +58,7 @@ protected: void OnFail() override { - auto asset = Asset.Get(); - if (asset) - { - Asset = nullptr; - asset->Locker.Lock(); - if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this) - Platform::AtomicStore(&asset->_loadingTask, 0); - asset->Locker.Unlock(); - } + DereferenceAsset(true); // Base ContentLoadTask::OnFail(); @@ -85,18 +66,38 @@ protected: void OnEnd() override { - auto asset = Asset.Get(); - if (asset) - { - Asset = nullptr; - asset->Locker.Lock(); - if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this) - Platform::AtomicStore(&asset->_loadingTask, 0); - asset->Locker.Unlock(); - asset = nullptr; - } + DereferenceAsset(); // Base ContentLoadTask::OnEnd(); } + +private: + void DereferenceAsset(bool failed = false) + { + auto asset = Asset.Get(); + if (asset) + { + asset->Locker.Lock(); + Task* task = (Task*)Platform::AtomicRead(&asset->_loadingTask); + if (task) + { + do + { + if (task == this) + { + Platform::AtomicStore(&asset->_loadingTask, 0); + if (failed) + { + Platform::AtomicStore(&asset->_loadState, (int64)Asset::LoadState::LoadFailed); + LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed)); + } + break; + } + task = task->GetContinueWithTask(); + } while (task); + } + asset->Locker.Unlock(); + } + } }; From 7d0804af91e7fe489efb906fe1ef313601b5f55e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 20 Jan 2025 23:53:13 +0100 Subject: [PATCH 154/215] Add content deprecation upgrades support to prefabs and scenes when loading levels --- Source/Engine/Level/Level.cpp | 42 ++++++++++++++++++- Source/Engine/Level/Level.h | 2 +- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 44 ++++++++++++++++++++ Source/Engine/Level/Prefabs/Prefab.h | 13 ++++-- Source/Engine/Level/Scene/Scene.cpp | 2 + Source/Engine/Level/SceneObjectsFactory.cpp | 14 +++++++ Source/Engine/Level/SceneObjectsFactory.h | 3 ++ 7 files changed, 113 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index f24a853e3..e1636ea35 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -7,6 +7,7 @@ #include "SceneObjectsFactory.h" #include "Scene/Scene.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Core/ObjectsRemovalService.h" @@ -827,7 +828,7 @@ bool Level::loadScene(JsonAsset* sceneAsset) return true; } - return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild); + return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath()); } bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) @@ -866,11 +867,14 @@ bool Level::loadScene(rapidjson_flax::Document& document, Scene** outScene) return loadScene(data->value, saveEngineBuild, outScene); } -bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene) +bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath) { PROFILE_CPU_NAMED("Level.LoadScene"); if (outScene) *outScene = nullptr; +#if USE_EDITOR + ContentDeprecated::Clear(); +#endif LOG(Info, "Loading scene..."); Stopwatch stopwatch; _lastSceneLoadTime = DateTime::Now(); @@ -1002,6 +1006,9 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou if (context.Async) { ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize) +#if USE_EDITOR + volatile int64 deprecated = 0; +#endif JobSystem::Execute([&](int32 i) { i++; // Start from 1. at index [0] was scene @@ -1011,9 +1018,17 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou auto& idMapping = Scripting::ObjectsLookupIdMapping.Get(); idMapping = &context.GetModifier()->IdsMapping; SceneObjectsFactory::Deserialize(context, obj, data[i]); +#if USE_EDITOR + if (ContentDeprecated::Clear()) + Platform::InterlockedIncrement(&deprecated); +#endif idMapping = nullptr; } }, dataCount - 1); +#if USE_EDITOR + if (deprecated != 0) + ContentDeprecated::Mark(); +#endif ScenesLock.Lock(); } else @@ -1103,6 +1118,28 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou LOG(Info, "Scene loaded in {0}ms", stopwatch.GetMilliseconds()); if (outScene) *outScene = scene; + +#if USE_EDITOR + // Resave assets that use deprecated data format + for (auto& e : context.DeprecatedPrefabs) + { + AssetReference prefab = e.Item; + LOG(Info, "Resaving asset '{}' that uses deprecated data format", prefab->GetPath()); + if (prefab->Resave()) + { + LOG(Error, "Failed to resave asset '{}'", prefab->GetPath()); + } + } + if (ContentDeprecated::Clear() && assetPath) + { + LOG(Info, "Resaving asset '{}' that uses deprecated data format", *assetPath); + if (saveScene(scene, *assetPath)) + { + LOG(Error, "Failed to resave asset '{}'", *assetPath); + } + } +#endif + return false; } @@ -1125,6 +1162,7 @@ bool LevelImpl::saveScene(Scene* scene) bool LevelImpl::saveScene(Scene* scene, const String& path) { + PROFILE_CPU_NAMED("Level.SaveScene"); ASSERT(scene && EnumHasNoneFlags(scene->Flags, ObjectFlags::WasMarkedToDelete)); auto sceneId = scene->GetID(); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index b9c6b698a..2b3022d5b 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -551,5 +551,5 @@ private: static bool loadScene(JsonAsset* sceneAsset); static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr); static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr); - static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr); + static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr); }; diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index f8dcde410..895096f73 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -732,6 +732,50 @@ bool Prefab::ApplyAll(Actor* targetActor) return false; } +bool Prefab::Resave() +{ + if (OnCheckSave()) + return true; + PROFILE_CPU_NAMED("Prefab.Resave"); + ScopeLock lock(Locker); + + Dictionary objectIds; + objectIds.EnsureCapacity(ObjectsIds.Count()); + for (int32 i = 0; i < ObjectsIds.Count(); i++) + { + Guid id = ObjectsIds[i]; + objectIds.Add(id, id); + } + PrefabManager::SpawnOptions options; + options.WithLink = false; + options.IDs = &objectIds; + auto instance = PrefabManager::SpawnPrefab(this, options); + if (instance == nullptr) + return true; + + // Serialize to json data + CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); + SceneQuery::GetAllSerializableSceneObjects(instance, *sceneObjects.Value); + rapidjson_flax::StringBuffer dataBuffer; + { + PrettyJsonWriter writerObj(dataBuffer); + PrefabInstanceData::SerializeObjects(*sceneObjects.Value, writerObj); + } + + // Remove temporary objects + instance->DeleteObject(); + instance = nullptr; + + // Save to file + if (CreateJson::Create(GetPath(), dataBuffer, TypeName)) + { + LOG(Warning, "Failed to serialize prefab data to the asset."); + return true; + } + + return false; +} + bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData) { PROFILE_CPU_NAMED("Prefab.Apply"); diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index 5b7de8637..7ac3ea406 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -10,7 +10,7 @@ class Actor; class SceneObject; /// -/// Json asset that stores the collection of scene objects including actors and scripts. In general it can serve as any grouping of scene objects (for example a level) or be used as a form of a template instantiated and reused throughout the scene. +/// Json asset that stores the collection of scene objects including actors and scripts. In general, it can serve as any grouping of scene objects (for example a level) or be used as a form of a template instantiated and reused throughout the scene. /// /// API_CLASS(NoSpawn) class FLAXENGINE_API Prefab : public JsonAssetBase @@ -74,11 +74,16 @@ public: /// /// Applies the difference from the prefab object instance, saves the changes and synchronizes them with the active instances of the prefab asset. /// - /// - /// Applies all the changes from not only the given actor instance but all actors created within that prefab instance. - /// + /// Applies all the changes from not only the given actor instance but all actors created within that prefab instance. /// The root actor of spawned prefab instance to use as modified changes sources. + /// True if failed, otherwise false. bool ApplyAll(Actor* targetActor); + + /// + /// Resaves the prefab asset to the file by serializing default instance in the latest format and defaults. + /// + /// True if failed, otherwise false. + API_FUNCTION() bool Resave(); #endif private: diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index f562dfbe4..7ef8eb40b 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -5,6 +5,7 @@ #include "Engine/Level/Level.h" #include "Engine/Content/AssetInfo.h" #include "Engine/Content/Content.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Content/Factories/JsonAssetFactory.h" #include "Engine/Physics/Colliders/MeshCollider.h" #include "Engine/Level/Actors/StaticModel.h" @@ -306,6 +307,7 @@ void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) if (e != stream.MemberEnd()) { // Upgrade from old single hidden navmesh data into NavMesh actors on a scene + MARK_CONTENT_DEPRECATED(); AssetReference dataAsset; Serialization::Deserialize(e->value, dataAsset, modifier); const auto settings = NavigationSettings::Get(); diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 520154b28..fabf73aff 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -19,6 +19,7 @@ #include "Engine/Threading/Threading.h" #include "Engine/Level/Scripts/MissingScript.h" #endif +#include "Engine/Content/Deprecated.h" #include "Engine/Level/Scripts/ModelPrefab.h" #if USE_EDITOR @@ -199,6 +200,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D else { // [Deprecated: 18.07.2019 expires 18.07.2020] + MARK_CONTENT_DEPRECATED(); const auto typeIdMember = stream.FindMember("TypeID"); if (typeIdMember == stream.MemberEnd()) { @@ -286,7 +288,19 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria // Deserialize prefab data (recursive prefab loading to support nested prefabs) const auto prevVersion = modifier->EngineBuild; modifier->EngineBuild = prefab->DataEngineBuild; +#if USE_EDITOR + bool prevDeprecated = ContentDeprecated::Clear(); +#endif Deserialize(context, obj, *(ISerializable::DeserializeStream*)prefabData); +#if USE_EDITOR + if (ContentDeprecated::Clear(prevDeprecated)) + { + // Prefab contains deprecated data format + context.Locker.Lock(); + context.DeprecatedPrefabs.Add(prefab); + context.Locker.Unlock(); + } +#endif modifier->EngineBuild = prevVersion; } diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index e749375b2..15369c707 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -31,6 +31,9 @@ public: Dictionary ObjectToInstance; CriticalSection Locker; ThreadLocal Modifiers; +#if USE_EDITOR + HashSet DeprecatedPrefabs; +#endif Context(ISerializeModifier* modifier); ~Context(); From fe8f862b163f8211fcd8824df69a98d277440ffc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 00:04:03 +0100 Subject: [PATCH 155/215] Update engine assets --- Content/Editor/Camera/M_Camera.flax | 4 ++-- Content/Editor/CubeTexturePreviewMaterial.flax | 4 ++-- Content/Editor/DefaultFontMaterial.flax | 4 ++-- Content/Editor/Gizmo/Material.flax | 4 ++-- Content/Editor/Gizmo/MaterialWire.flax | 4 ++-- Content/Editor/Gizmo/MaterialWireFocus.flax | 4 ++-- Content/Editor/Gizmo/WireBox.flax | 4 ++-- Content/Editor/Highlight Material.flax | 4 ++-- Content/Editor/Icons/AudioListener.flax | 4 ++-- Content/Editor/Icons/AudioSource.flax | 4 ++-- Content/Editor/Icons/Decal.flax | 4 ++-- Content/Editor/Icons/DirectionalLight.flax | 4 ++-- Content/Editor/Icons/EnvironmentProbe.flax | 4 ++-- Content/Editor/Icons/IconsMaterial.flax | 4 ++-- Content/Editor/Icons/ParticleEffect.flax | 4 ++-- Content/Editor/Icons/PointLight.flax | 4 ++-- Content/Editor/Icons/SceneAnimationPlayer.flax | 4 ++-- Content/Editor/Icons/SkyLight.flax | 4 ++-- Content/Editor/Icons/Skybox.flax | 4 ++-- Content/Editor/IesProfilePreviewMaterial.flax | 4 ++-- Content/Editor/Particles/Particle Material Preview.flax | 4 ++-- Content/Editor/Primitives/Capsule.flax | 4 ++-- Content/Editor/SpriteMaterial.flax | 4 ++-- Content/Editor/Terrain/Circle Brush Material.flax | 2 +- Content/Editor/Terrain/Highlight Terrain Material.flax | 4 ++-- Content/Editor/TexturePreviewMaterial.flax | 2 +- Content/Engine/DefaultDeformableMaterial.flax | 4 ++-- Content/Engine/WhiteMaterial.flax | 4 ++-- 28 files changed, 54 insertions(+), 54 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index f2fa86005..4f330b51d 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abee67ec702a0c51b20383420a78b7b37c7c09a828bb3a54edb260a9ca9fd2b3 -size 28611 +oid sha256:9b36e082905708236b5d27f7ab61b230bc496788e799a52f6fa5d816053a3818 +size 28734 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index ee4d8c479..a476e2582 100644 --- a/Content/Editor/CubeTexturePreviewMaterial.flax +++ b/Content/Editor/CubeTexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5b4bfc128caf88f79e0a9b94fb3826ff19bfc6dbee5d07a8937717ed2101692 -size 29786 +oid sha256:e934a377645658a27cf0f98c302001e8567daf912399f34b3c233fdc0b000a90 +size 30326 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index 1b7142da5..c155358c8 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b68e47fa6052bfc2813ee919c239228e4871e18e0c8216ba04301283b8c3477b -size 28686 +oid sha256:efb05443172c8cfb69df8f15f9b4f00d1c6d38d55e470f28fa6fff0920ac4ccd +size 28828 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index 29fb07078..3f159230e 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c676cb17f94be1f86c9a60d45da718a7c00649955fb00fa80b8917d4e0b93c3c -size 31296 +oid sha256:ba255b7ea8df7bad961bfb56dde20e48a4c6bee0df5a56ed6d232f77caeae394 +size 31846 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 9976f7b0d..b24e61be0 100644 --- a/Content/Editor/Gizmo/MaterialWire.flax +++ b/Content/Editor/Gizmo/MaterialWire.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6db773836280d041aa67ff94f73383a86cf1e0169cedc698c4e88a6557807553 -size 29894 +oid sha256:eb7a45405f6c1f6f914c56dc5d16e37f270f04668402f9d7c6f65ed66cc0c624 +size 30543 diff --git a/Content/Editor/Gizmo/MaterialWireFocus.flax b/Content/Editor/Gizmo/MaterialWireFocus.flax index 4310dcfec..b042fab2c 100644 --- a/Content/Editor/Gizmo/MaterialWireFocus.flax +++ b/Content/Editor/Gizmo/MaterialWireFocus.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1822666601676f9a8da90ed139a36ab80950972098a4ac3981139392b40002c2 -size 434 +oid sha256:3f49170f829a9b2cb9d9c7680d199f6271e810dd1a181c61e0d83fa62988ba12 +size 535 diff --git a/Content/Editor/Gizmo/WireBox.flax b/Content/Editor/Gizmo/WireBox.flax index b8140c8fd..01e8cac75 100644 --- a/Content/Editor/Gizmo/WireBox.flax +++ b/Content/Editor/Gizmo/WireBox.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebdbfcb90d6b3df803294ff601a2765b27cbee83caf9753a0e3ba6c8b888a44a -size 7874 +oid sha256:6b5cd7a5eb1d50d8c600d74e2a96024930716e50c9d26b33bab5f85dc5c3d1fe +size 8024 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index e8bae9708..c84b741d4 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89d4b1de21cb5aafbf8de151372339d77c2225d0402cc8d965a9c9826baa5afa -size 29089 +oid sha256:ae62550a068f3f307e33badd8bbab9176e458c22720a4ae7f3fe793b9c823be3 +size 29237 diff --git a/Content/Editor/Icons/AudioListener.flax b/Content/Editor/Icons/AudioListener.flax index 1d05d75a7..4b9ecb1df 100644 --- a/Content/Editor/Icons/AudioListener.flax +++ b/Content/Editor/Icons/AudioListener.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:420c596ce4719b05d63cdf8ee4491993ae4346e0dd98e438d5b4de949311e1fe -size 466 +oid sha256:2d9f3b36a78d558c8430f2b14420711d708d0633874a183b9eaab5f3963de8d5 +size 567 diff --git a/Content/Editor/Icons/AudioSource.flax b/Content/Editor/Icons/AudioSource.flax index 1e7d99187..c4245c82d 100644 --- a/Content/Editor/Icons/AudioSource.flax +++ b/Content/Editor/Icons/AudioSource.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ef14e409196318a53050981f91890e62d9f387f9d3440034f4fb6dcfea7f364 -size 466 +oid sha256:fc6c177b3537b1aae695cbaf256adf07b2891c91ae3e48286e547e851c6cd0bc +size 567 diff --git a/Content/Editor/Icons/Decal.flax b/Content/Editor/Icons/Decal.flax index ecd65a17d..c69cb7a19 100644 --- a/Content/Editor/Icons/Decal.flax +++ b/Content/Editor/Icons/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd9d7b7a55f7ecfde753d0e1b253c3768b25cc7b65ca840647212a8870de6917 -size 466 +oid sha256:2dd18babe45190ee2820995e7282e8a365e0e74cf275f2da2ee8c91bdb348a97 +size 567 diff --git a/Content/Editor/Icons/DirectionalLight.flax b/Content/Editor/Icons/DirectionalLight.flax index b879061cc..37a60e882 100644 --- a/Content/Editor/Icons/DirectionalLight.flax +++ b/Content/Editor/Icons/DirectionalLight.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b2530bc1db1a640d7ac09124e6c1c7f1f6d1d917b38347c57df78b0e6ba0596 -size 303 +oid sha256:920bccc12a9bb65eefa1f1fe6acc1c87c949acb81a2ec19f45a0770e9c38c1a8 +size 567 diff --git a/Content/Editor/Icons/EnvironmentProbe.flax b/Content/Editor/Icons/EnvironmentProbe.flax index c56740de7..80660e942 100644 --- a/Content/Editor/Icons/EnvironmentProbe.flax +++ b/Content/Editor/Icons/EnvironmentProbe.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd025e805204377e3d7aea2ecccdaeec21561f4885195d70daa662654afa21ad -size 303 +oid sha256:245ec4045f64984aaf58d2442e949ae7ad6500f3631216bf67b0142d5fb7df66 +size 567 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index a7da1de3f..8a43d39b1 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3d78e259c9692d9d358c9ca3baa7d948a1e76647674b25e496d75a7f1355e29 -size 29017 +oid sha256:4eeca9088f6676fd983d9bdcb14220ea6b18d662291b4051a0f8a8ead77da2da +size 29154 diff --git a/Content/Editor/Icons/ParticleEffect.flax b/Content/Editor/Icons/ParticleEffect.flax index de63fb220..91236b94a 100644 --- a/Content/Editor/Icons/ParticleEffect.flax +++ b/Content/Editor/Icons/ParticleEffect.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d36cb8f0b9d9d94be7fa9f568f97ccf23a893f7b804146cf7965ab30cf157088 -size 466 +oid sha256:7b830f15153d9c86dc0bfc9c982f41a95e360727423716d6ea3b5c57e8739a8d +size 567 diff --git a/Content/Editor/Icons/PointLight.flax b/Content/Editor/Icons/PointLight.flax index 977162666..53ade9dfa 100644 --- a/Content/Editor/Icons/PointLight.flax +++ b/Content/Editor/Icons/PointLight.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c13708632656b7794753803c021a285ed4023b0bc1561cb14e0ba28fe0271480 -size 303 +oid sha256:c98e9d6fba236f325f6979803b6c6d37ba5d21b92f5639338423cf13c17be0db +size 567 diff --git a/Content/Editor/Icons/SceneAnimationPlayer.flax b/Content/Editor/Icons/SceneAnimationPlayer.flax index 5064558d1..1126aa672 100644 --- a/Content/Editor/Icons/SceneAnimationPlayer.flax +++ b/Content/Editor/Icons/SceneAnimationPlayer.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab77a2e8aeeba97aba519f5dfee5250e41d210d6020cb61d19ae4c7f48ccd0b9 -size 466 +oid sha256:96cb14e43719a7645084f322edcb47aaea0a0044be6b9300c8761fb34d33417d +size 567 diff --git a/Content/Editor/Icons/SkyLight.flax b/Content/Editor/Icons/SkyLight.flax index 0570b5b09..c5f845ec3 100644 --- a/Content/Editor/Icons/SkyLight.flax +++ b/Content/Editor/Icons/SkyLight.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c587e885e3fb0bdf6bd4a9fbd9bf8c746aae149c894009ebd5496f100290388 -size 303 +oid sha256:f8ef3f16cb2482196da99ac5ee14dbc2ebe477f0bdc636e62f8f4577bc417a48 +size 567 diff --git a/Content/Editor/Icons/Skybox.flax b/Content/Editor/Icons/Skybox.flax index 13ac1472d..56ac934d6 100644 --- a/Content/Editor/Icons/Skybox.flax +++ b/Content/Editor/Icons/Skybox.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:354283ae32bd759458179ed437ca0bd9be12062c5b8a3aa4020c4bc1ac83092c -size 303 +oid sha256:b7cf0288c069f20e48b553515e10417994aedc924dd47469634b61615021c8bc +size 567 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index 8a59e01f6..914b15c39 100644 --- a/Content/Editor/IesProfilePreviewMaterial.flax +++ b/Content/Editor/IesProfilePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc1e903d13905455ec7c6dfb04bd0920093e0fdd57c7243810c0c9ef7177df7d -size 18205 +oid sha256:67c9eb02ebe38f551a03da83ebcbe7d32a14beb1022a2f6a709975cef36621ca +size 20271 diff --git a/Content/Editor/Particles/Particle Material Preview.flax b/Content/Editor/Particles/Particle Material Preview.flax index da372b102..1f18aaeaa 100644 --- a/Content/Editor/Particles/Particle Material Preview.flax +++ b/Content/Editor/Particles/Particle Material Preview.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ae6968de38564e575a4df314a2453a5d5adc1b3ef93ebd916a87b8d25bb6932 -size 1628 +oid sha256:88de448e023b35296531fff817d4b95200bac97fbd5c927ee6a5e5a815323978 +size 2110 diff --git a/Content/Editor/Primitives/Capsule.flax b/Content/Editor/Primitives/Capsule.flax index 6ff0daed9..d851342bc 100644 --- a/Content/Editor/Primitives/Capsule.flax +++ b/Content/Editor/Primitives/Capsule.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:071d1e65b4bbaea07f9d0b7b681c7c43ff141704f98c78927af656917aba6551 -size 31478 +oid sha256:6fca37ec1a8a9035c590bc645003e20df3eb3a9f65913df68f89d58ef65694a2 +size 31528 diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax index 6446ea7fc..146d6db57 100644 --- a/Content/Editor/SpriteMaterial.flax +++ b/Content/Editor/SpriteMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3b4a3e52999678654341698e9192b52545c45466727b4adaa1997f29ea088fd -size 29159 +oid sha256:2e1c9a11ed04ed150bed712abe1cd716cc0f30828057c80a2a7a82a0c74246da +size 29703 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index 830793653..0b2395146 100644 --- a/Content/Editor/Terrain/Circle Brush Material.flax +++ b/Content/Editor/Terrain/Circle Brush Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64dc3a4e39d42de0e7ff71d31676a2e15a2f6d66d37013799bc7a3cda48ec175 +oid sha256:8af68b6133e5e64d422719958d10bd63c899329592c955134bf2f96fbc2fbaed size 27875 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 44c0d46e6..de34b92b3 100644 --- a/Content/Editor/Terrain/Highlight Terrain Material.flax +++ b/Content/Editor/Terrain/Highlight Terrain Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a566b0589214c88d8947fc1df5f25837721836758aa773b6f308e269639c6951 -size 21256 +oid sha256:d31e39fb4cbb543a960f8df0d38ae67c5e206f96e1e05390522dce63c99950f3 +size 21378 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 0a9184d34..7962f68e9 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf0dffaeb28d26a77ead3cf5d01096b90e100c1a686dd969a636207b727d3b5c +oid sha256:d7314eb673ab9949fa79b3030488e7f5303f9b6578333f5dce8e6edf3c8b3954 size 10570 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax index ed3edfcdc..3b9be24c0 100644 --- a/Content/Engine/DefaultDeformableMaterial.flax +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bda2374d296e4e80213b16f42e11a26198fd4bcd5db6cd04f3fa56d419ff3e68 -size 18514 +oid sha256:9b4aad4972750f4a633b0bd7bd4c416258b8d5996df0c4ffb864c327c9e92584 +size 18515 diff --git a/Content/Engine/WhiteMaterial.flax b/Content/Engine/WhiteMaterial.flax index fdbeb3873..64ccd4bcb 100644 --- a/Content/Engine/WhiteMaterial.flax +++ b/Content/Engine/WhiteMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:379d175e7be56c7741df5ed0a69ff1375dbd6cc4cfe228179401fd40f3b28404 -size 461 +oid sha256:5778a7c9133d946207e32e07914b79849822fa79f4a213b84667e8ee9a7b16c6 +size 583 From 0b24c501617bd333b3bacac6bf65aca0da1c690e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 10:18:57 +0100 Subject: [PATCH 156/215] Expose `ContentDeprecated` to scripting (editor-only) --- Source/Engine/Content/Deprecated.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Content/Deprecated.h b/Source/Engine/Content/Deprecated.h index 92cd3aa44..bb4a6fa40 100644 --- a/Source/Engine/Content/Deprecated.h +++ b/Source/Engine/Content/Deprecated.h @@ -6,14 +6,16 @@ #if USE_EDITOR -// Utility for marking content as deprecated when loading it in Editor. Used to auto-upgrade (by resaving) data during development in editor or during game cooking. -class FLAXENGINE_API ContentDeprecated +// Editor-only utility for marking content as deprecated during load. Used to auto-upgrade (by resaving) data during development in editor or during game cooking. +API_CLASS(Static) class FLAXENGINE_API ContentDeprecated { + DECLARE_SCRIPTING_TYPE_MINIMAL(ContentDeprecated); + public: // Marks content as deprecated (for the current thread). - static void Mark(); + API_FUNCTION() static void Mark(); // Reads and clears deprecation flag (for the current thread). - static bool Clear(bool newValue = false); + API_FUNCTION() static bool Clear(bool newValue = false); }; // Marks content as deprecated (for the current thread). From 9fd98c8e12df522c63da4893660e7051a53b97f7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 11:40:01 +0100 Subject: [PATCH 157/215] Optimize blend weights to use 8-bit per bone (instead of 16-bit) --- Source/Engine/Content/Assets/ModelBase.cpp | 19 +++++++++++++++---- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 40971bae0..dd2d970d0 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -666,6 +666,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l const bool useSeparatePositions = !isSkinned; const bool useSeparateColors = !isSkinned; PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt; + PixelFormat blendWeightsFormat = PixelFormat::R8G8B8A8_UNorm; for (const Int4& indices : mesh.BlendIndices) { if (indices.MaxValue() > MAX_uint8) @@ -705,7 +706,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l if (isSkinned) { vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, blendIndicesFormat }); - vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float }); + vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, blendWeightsFormat }); } if (!useSeparateColors && hasColors) vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm }); @@ -784,7 +785,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W); stream.Write(blendIndicesEnc); } - else + else //if (blendWeightsFormat == PixelFormat::R16G16B16A16_UInt) { // 16-bit indices const uint16 blendIndicesEnc[4] = { (uint16)blendIndices.X, (uint16)blendIndices.Y, (uint16)blendIndices.Z, (uint16)blendIndices.W }; @@ -795,8 +796,18 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l case VertexElement::Types::BlendWeights: { const Float4 blendWeights = mesh.BlendWeights.Get()[vertex]; - const Half4 blendWeightsEnc(blendWeights); - stream.Write(blendWeightsEnc); + if (blendWeightsFormat == PixelFormat::R8G8B8A8_UNorm) + { + // 8-bit weights + const Color32 blendWeightsEnc(blendWeights); + stream.Write(blendWeightsEnc); + } + else //if (blendWeightsFormat == PixelFormat::R16G16B16A16_Float) + { + // 16-bit weights + const Half4 blendWeightsEnc(blendWeights); + stream.Write(blendWeightsEnc); + } break; } case VertexElement::Types::TexCoord0: diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 94d0b0f4f..ea1c2c852 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -62,7 +62,7 @@ namespace vb0elements.Add({ VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm }); } vb0elements.Add({ VertexElement::Types::BlendIndices, 0, 0, 0, PixelFormat::R8G8B8A8_UInt }); - vb0elements.Add({ VertexElement::Types::BlendWeights, 0, 0, 0, PixelFormat::R16G16B16A16_Float }); + vb0elements.Add({ VertexElement::Types::BlendWeights, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm }); if (uvs) vb0elements.Add({ VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float }); if (colors) From 3b5f953b3d41de0c2c06b652fad2424b17eb4d3e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 11:52:27 +0100 Subject: [PATCH 158/215] Add failing Windows Test CI if `FlaxTests.exe` fails --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b828974a0..91561b78b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,6 +75,10 @@ jobs: - name: Test run: | .\Binaries\Editor\Win64\Development\FlaxTests.exe + IF NOT %ERRORLEVEL% == 0 ( + echo FlaxTests.exe failed with exit code %ERRORLEVEL% + exit 1 + ) dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests From d8dcec42d543efd28632708648e6de1db68c58c7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 12:28:35 +0100 Subject: [PATCH 159/215] Add failing Windows Test CI if `FlaxTests.exe` fails --- .github/workflows/tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91561b78b..893585647 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,10 +75,7 @@ jobs: - name: Test run: | .\Binaries\Editor\Win64\Development\FlaxTests.exe - IF NOT %ERRORLEVEL% == 0 ( - echo FlaxTests.exe failed with exit code %ERRORLEVEL% - exit 1 - ) + if not %errorlevel% == 0 exit %errorlevel% dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests From c3ad937d3fc631da793d555abae17107f73c2418 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 13:12:04 +0100 Subject: [PATCH 160/215] Add failing Windows Test CI if `FlaxTests.exe` fails --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 893585647..67709be46 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,9 +73,11 @@ jobs: .\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo - name: Test + shell: pwsh run: | + $ErrorActionPreference = "Stop" .\Binaries\Editor\Win64\Development\FlaxTests.exe - if not %errorlevel% == 0 exit %errorlevel% + if(!$?) { Exit LastExitCode } dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests From 236b8d5667f0900d38437a64c4724205af0300b7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 14:01:23 +0100 Subject: [PATCH 161/215] Add failing Windows Test CI if `FlaxTests.exe` fails --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 67709be46..7c28121fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,7 +77,7 @@ jobs: run: | $ErrorActionPreference = "Stop" .\Binaries\Editor\Win64\Development\FlaxTests.exe - if(!$?) { Exit LastExitCode } + if(!$?) { Write-Host "Tests failed with exit code $LastExitCode" -ForegroundColor Red; Exit $LastExitCode } dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests From b247070840b7a4fe99024e7dd435e2328a6c772c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 15:38:14 +0100 Subject: [PATCH 162/215] Fix regression in `Dictionary` capacity and use similar improvement in `HashSet` --- Source/Engine/Core/Collections/Dictionary.h | 4 ++-- Source/Engine/Core/Collections/HashSet.h | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 90c87d8e8..01b33cf2d 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -407,7 +407,7 @@ public: Compact(); // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + 1 + _deletedCount); + EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); // Find location of the item or place to insert it FindPositionResult pos; @@ -940,7 +940,7 @@ private: Compact(); // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + 1 + _deletedCount); + EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); // Find location of the item or place to insert it FindPositionResult pos; diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index aa61354fd..838db7a35 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -470,8 +470,9 @@ public: /// /// The minimum required capacity. /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. - void EnsureCapacity(const int32 minCapacity, const bool preserveContents = true) + void EnsureCapacity(int32 minCapacity, const bool preserveContents = true) { + minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE; if (_size >= minCapacity) return; int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); @@ -734,7 +735,7 @@ private: Compact(); // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount); + EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); // Find location of the item or place to insert it FindPositionResult pos; From 344d17714c09e840cb707a16a313f0fc2174a6dd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 15:39:21 +0100 Subject: [PATCH 163/215] Fix crash when using `ref struct` reflection in managed api --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 71caee1e9..e0900f663 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -340,6 +340,8 @@ namespace FlaxEngine.Interop { Type type = Unsafe.As(typeHandle.Target); var fields = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (type.IsValueType && !type.IsEnum && !type.IsPrimitive && type.GetCustomAttribute() != null) + fields = Array.Empty(); // ref struct are not supported NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf()); for (int i = 0; i < fields.Length; i++) From 682ca9e95314b7aa1c201fa7186cfae42394da1d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 21:38:02 +0100 Subject: [PATCH 164/215] Add progress indicator in editor for assets loading --- .../Editor/Modules/ProgressReportingModule.cs | 6 +++ .../Progress/Handlers/LoadAssetsProgress.cs | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Source/Editor/Progress/Handlers/LoadAssetsProgress.cs diff --git a/Source/Editor/Modules/ProgressReportingModule.cs b/Source/Editor/Modules/ProgressReportingModule.cs index 7ff284431..a1160aba5 100644 --- a/Source/Editor/Modules/ProgressReportingModule.cs +++ b/Source/Editor/Modules/ProgressReportingModule.cs @@ -56,6 +56,11 @@ namespace FlaxEditor.Modules /// public readonly GenerateScriptsProjectFilesProgress GenerateScriptsProjectFiles = new GenerateScriptsProjectFilesProgress(); + /// + /// The assets loading progress handler. + /// + public readonly LoadAssetsProgress LoadAssets = new LoadAssetsProgress(); + /// /// Gets the first active handler. /// @@ -80,6 +85,7 @@ namespace FlaxEditor.Modules RegisterHandler(CodeEditorOpen); RegisterHandler(NavMeshBuilding); RegisterHandler(GenerateScriptsProjectFiles); + RegisterHandler(LoadAssets); } /// diff --git a/Source/Editor/Progress/Handlers/LoadAssetsProgress.cs b/Source/Editor/Progress/Handlers/LoadAssetsProgress.cs new file mode 100644 index 000000000..dbac821e1 --- /dev/null +++ b/Source/Editor/Progress/Handlers/LoadAssetsProgress.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +namespace FlaxEditor.Progress.Handlers +{ + /// + /// Loading assets progress reporting handler. + /// + /// + public sealed class LoadAssetsProgress : ProgressHandler + { + private int _loadingAssetsCount; + + /// + /// Initializes a new instance of the class. + /// + public LoadAssetsProgress() + { + Editor.Instance.EditorUpdate += OnEditorUpdate; + } + + private void OnEditorUpdate() + { + var contentStats = FlaxEngine.Content.Stats; + if (_loadingAssetsCount == contentStats.LoadingAssetsCount) + return; + + if (contentStats.LoadingAssetsCount == 0) + OnEnd(); + else if (_loadingAssetsCount == 0) + OnStart(); + if (contentStats.LoadingAssetsCount > 0) + { + var progress = (float)contentStats.LoadedAssetsCount / contentStats.AssetsCount; + var text = contentStats.LoadingAssetsCount == 1 ? "Loading 1 asset..." : $"Loading {contentStats.LoadingAssetsCount} assets..."; + OnUpdate(progress, text); + } + _loadingAssetsCount = contentStats.LoadingAssetsCount; + } + } +} From 8736d4c91ab2c742deab60987f9fad4f6521a536 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 22:47:44 +0100 Subject: [PATCH 165/215] Remove not needed warning log in Editor on fresh project --- Source/Editor/Editor.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index e9ee68d88..f915bfde6 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -79,10 +79,6 @@ bool Editor::CheckProjectUpgrade() Delete(file); } } - else - { - LOG(Warning, "Missing version cache file"); - } // Check if project is in the old, deprecated layout if (EditorImpl::IsOldProjectXmlFormat) From 4cf22c3a1fb695bbdd1281646ccd6ea958d64c6b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 23:14:00 +0100 Subject: [PATCH 166/215] Fix material to run shader generation when using null rendering backend --- Source/Engine/Content/Assets/Material.cpp | 37 ++++------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index f7d800034..444b72230 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -138,36 +138,6 @@ Asset::LoadResult Material::load() } #undef IS_GPU_NOT_READY - // Special case for Null renderer - if (IsNullRenderer()) - { - // Hack loading - MemoryReadStream shaderCacheStream(nullptr, 0); - _materialShader = MaterialShader::CreateDummy(shaderCacheStream, _shaderHeader.Material.Info); - if (_materialShader == nullptr) - { - LOG(Warning, "Cannot load material."); - return LoadResult::Failed; - } - materialParamsChunk = GetChunk(SHADER_FILE_CHUNK_MATERIAL_PARAMS); - if (materialParamsChunk != nullptr && materialParamsChunk->IsLoaded()) - { - MemoryReadStream materialParamsStream(materialParamsChunk->Get(), materialParamsChunk->Size()); - if (Params.Load(&materialParamsStream)) - { - LOG(Warning, "Cannot load material parameters."); - return LoadResult::Failed; - } - } - else - { - // Don't use parameters - Params.Dispose(); - } - ParamsChanged(); - return LoadResult::Ok; - } - // If engine was compiled with shaders compiling service: // - Material should be changed in need to convert it to the newer version (via Visject Surface) // Shader should be recompiled if shader source code has been modified @@ -320,7 +290,12 @@ Asset::LoadResult Material::load() // Load shader cache (it may call compilation or gather cached data) ShaderCacheResult shaderCache; - if (LoadShaderCache(shaderCache)) + if (IsNullRenderer()) + { + MemoryReadStream shaderCacheStream(nullptr, 0); + _materialShader = MaterialShader::CreateDummy(shaderCacheStream, _shaderHeader.Material.Info); + } + else if (LoadShaderCache(shaderCache)) { LOG(Error, "Cannot load \'{0}\' shader cache.", ToString()); #if 1 From d1cc8da841ddebbfea2d49ee7e6850c256e18ba1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jan 2025 23:46:32 +0100 Subject: [PATCH 167/215] Remove not needed warning log in Editor on fresh project --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index e04f31af6..67a9e5f4f 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -137,10 +137,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) if (!FileSystem::DirectoryExists(CacheFolder)) FileSystem::CreateDirectory(CacheFolder); if (!FileSystem::FileExists(HeaderFilePath)) - { - LOG(Warning, "Missing incremental build cooking assets cache."); return; - } auto file = FileReadStream::Open(HeaderFilePath); if (file == nullptr) From 326bc498b89b1e33a989b53f8fac5e8db2da1fa7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 22 Jan 2025 00:01:25 +0100 Subject: [PATCH 168/215] Add more logging for cooking process --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 17 ++++++++++++++--- Source/Engine/Content/BinaryAsset.cpp | 2 ++ Source/Engine/Content/Storage/FlaxStorage.cpp | 6 +++--- .../Graphics/Shaders/Cache/ShaderAssetBase.cpp | 3 ++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 67a9e5f4f..b9085da0d 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -362,7 +362,7 @@ bool CookAssetsStep::ProcessDefaultAsset(AssetCookData& options) bool CookAssetsStep::Process(CookingData& data, CacheData& cache, Asset* asset) { - // Validate asset + PROFILE_CPU_ASSET(asset); if (asset->IsVirtual()) { // Virtual assets are not included into the build @@ -790,7 +790,10 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, BinaryAsset* a // Prepare asset data AssetInitData initData; if (asset->Storage->LoadAssetHeader(asset->GetID(), initData)) + { + LOG(Warning, "Failed to load asset {} header from storage '{}'", asset->GetID(), asset->Storage->GetPath()); return true; + } initData.Header.UnlinkChunks(); initData.Metadata.Release(); for (auto& e : initData.Dependencies) @@ -1162,7 +1165,7 @@ bool CookAssetsStep::Perform(CookingData& data) assetRef = Content::LoadAsync(assetId); if (assetRef == nullptr) { - data.Error(TEXT("Failed to load asset included in build.")); + LOG(Error, "Failed to load asset {} included in build", assetId); return true; } e.Info.TypeName = assetRef->GetTypeName(); @@ -1170,6 +1173,7 @@ bool CookAssetsStep::Perform(CookingData& data) // Cook asset if (Process(data, cache, assetRef.Get())) { + LOG(Error, "Failed to process asset {}", assetRef->ToString()); cache.Save(data); return true; } @@ -1202,10 +1206,17 @@ bool CookAssetsStep::Perform(CookingData& data) // Copy file if (!FileSystem::FileExists(cookedPath) || FileSystem::GetFileLastEditTime(cookedPath) >= FileSystem::GetFileLastEditTime(filePath)) { - if (FileSystem::CreateDirectory(StringUtils::GetDirectoryName(cookedPath))) + const String cookedFolder = StringUtils::GetDirectoryName(cookedPath); + if (FileSystem::CreateDirectory(cookedFolder)) + { + LOG(Error, "Failed to create directory '{}'", cookedFolder); return true; + } if (FileSystem::CopyFile(cookedPath, filePath)) + { + LOG(Error, "Failed to copy file from '{}' to '{}'", filePath, cookedPath); return true; + } } // Count stats of file extension diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 6af0bd22f..deb39856e 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -314,6 +314,8 @@ bool BinaryAsset::SaveAsset(const StringView& path, AssetInitData& data, bool si bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool silentMode) { + PROFILE_CPU(); + // Ensure path is in a valid format String pathNorm(path); ContentStorageManager::FormatPath(pathNorm); diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 2786b0618..007238889 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -1090,7 +1090,7 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) stream->ReadInt32(&chunkIndex); if (chunkIndex < -1 || chunkIndex >= _chunks.Count()) { - LOG(Warning, "Invalid chunks mapping."); + LOG(Warning, "Invalid chunks mapping ({} -> {}).", i, chunkIndex); return true; } data.Header.Chunks[i] = chunkIndex == INVALID_INDEX ? nullptr : _chunks[chunkIndex]; @@ -1147,7 +1147,7 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) stream->ReadInt32(&chunkIndex); if (chunkIndex < -1 || chunkIndex >= _chunks.Count()) { - LOG(Warning, "Invalid chunks mapping."); + LOG(Warning, "Invalid chunks mapping ({} -> {}).", i, chunkIndex); return true; } data.Header.Chunks[i] = chunkIndex == INVALID_INDEX ? nullptr : _chunks[chunkIndex]; @@ -1202,7 +1202,7 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) stream->ReadInt32(&chunkIndex); if (chunkIndex < -1 || chunkIndex >= _chunks.Count()) { - LOG(Warning, "Invalid chunks mapping."); + LOG(Warning, "Invalid chunks mapping ({} -> {}).", i, chunkIndex); return true; } data.Header.Chunks[i] = chunkIndex == INVALID_INDEX ? nullptr : _chunks[chunkIndex]; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index ecb727c11..813ec9a0c 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -11,6 +11,7 @@ #include "Engine/Content/Deprecated.h" #include "Engine/Serialization/MemoryReadStream.h" #endif +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/ShadowsOfMordor/AtlasChartsPacker.h" ShaderStorage::CachingMode ShaderStorage::CurrentCachingMode = @@ -157,7 +158,7 @@ bool IsValidShaderCache(DataContainer& shaderCache, Array& include bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) { - // Prepare + PROFILE_CPU(); auto parent = GetShaderAsset(); const ShaderProfile shaderProfile = GPUDevice::Instance->GetShaderProfile(); const int32 cacheChunkIndex = GetCacheChunkIndex(shaderProfile); From af416fe0c8b27604612ff33866872cbff87dd1fe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 22 Jan 2025 21:24:49 +0100 Subject: [PATCH 169/215] Fix packaging issues to properly read asset data after it's serialized with a new format --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 10 ++ Source/Engine/Content/BinaryAsset.cpp | 1 + Source/Engine/Content/Storage/AssetHeader.h | 44 +---- Source/Engine/Content/Storage/FlaxFile.h | 5 +- Source/Engine/Content/Storage/FlaxPackage.h | 5 +- Source/Engine/Content/Storage/FlaxStorage.cpp | 167 ++++++++++++++---- Source/Engine/Content/Storage/FlaxStorage.h | 40 +++-- 7 files changed, 181 insertions(+), 91 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index b9085da0d..0ba6ce861 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -368,11 +368,21 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, Asset* asset) // Virtual assets are not included into the build return false; } + const bool wasLoaded = asset->IsLoaded(); if (asset->WaitForLoaded()) { LOG(Error, "Failed to load asset \'{0}\'", asset->ToString()); return true; } + if (!wasLoaded) + { + // HACK: give some time to resave any old assets in Asset::onLoad after it's loaded + // This assumes that if Load Thread enters Asset::Save then it will get asset lock and hold it until asset is saved + // So we can take the same lock to wait for save end but first we need to wait for it to get that lock + // (in future try to handle it in a better way) + Platform::Sleep(5); + } + ScopeLock lock(asset->Locker); // Switch based on an asset type const auto asBinaryAsset = dynamic_cast(asset); diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index deb39856e..a78618678 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -372,6 +372,7 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool const auto locks = storage->_chunksLock; storage->_chunksLock = 0; result = storage->Save(data, silentMode); + ASSERT(storage->_chunksLock == 0); storage->_chunksLock = locks; } else diff --git a/Source/Engine/Content/Storage/AssetHeader.h b/Source/Engine/Content/Storage/AssetHeader.h index ad5c8efb9..f8cfeac75 100644 --- a/Source/Engine/Content/Storage/AssetHeader.h +++ b/Source/Engine/Content/Storage/AssetHeader.h @@ -20,7 +20,7 @@ typedef uint16 AssetChunksFlag; #define ALL_ASSET_CHUNKS MAX_uint16 /// -/// Flax Asset header +/// Flax asset file header. /// struct FLAXENGINE_API AssetHeader { @@ -51,34 +51,6 @@ public: } public: - /// - /// Gets the chunks. - /// - /// The output data. - template - void GetChunks(Array& output) const - { - for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) - { - if (Chunks[i] != nullptr) - output.Add(Chunks[i]); - } - } - - /// - /// Gets the chunks that are loaded. - /// - /// The output data. - template - void GetLoadedChunks(Array& output) const - { - for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) - { - if (Chunks[i] != nullptr && Chunks[i]->IsLoaded()) - output.Add(Chunks[i]); - } - } - /// /// Gets the amount of created asset chunks. /// @@ -95,14 +67,13 @@ public: void UnlinkChunks(); /// - /// Gets string with a human-readable info about that header + /// Gets string with a human-readable info about that header. /// - /// Header info string String ToString() const; }; /// -/// Flax Asset header data +/// Flax asset header with data. /// struct FLAXENGINE_API AssetInitData { @@ -137,12 +108,5 @@ public: /// /// Gets the hash code. /// - uint32 GetHashCode() const - { - // Note: do not use Metadata/Dependencies because it may not be loaded (it's optional) - uint32 hashCode = GetHash(Header.ID); - hashCode = (hashCode * 397) ^ SerializedVersion; - hashCode = (hashCode * 397) ^ CustomData.Length(); - return hashCode; - } + uint32 GetHashCode() const; }; diff --git a/Source/Engine/Content/Storage/FlaxFile.h b/Source/Engine/Content/Storage/FlaxFile.h index 9a1da5244..b2fbac034 100644 --- a/Source/Engine/Content/Storage/FlaxFile.h +++ b/Source/Engine/Content/Storage/FlaxFile.h @@ -27,7 +27,10 @@ public: bool HasAsset(const Guid& id) const override; bool HasAsset(const AssetInfo& info) const override; int32 GetEntriesCount() const override; - void GetEntry(int32 index, Entry& output) const override; + void GetEntry(int32 index, Entry& value) const override; +#if USE_EDITOR + void SetEntry(int32 index, const Entry& value) override; +#endif void GetEntries(Array& output) const override; void Dispose() override; diff --git a/Source/Engine/Content/Storage/FlaxPackage.h b/Source/Engine/Content/Storage/FlaxPackage.h index 11637c1b1..3afc0cdb8 100644 --- a/Source/Engine/Content/Storage/FlaxPackage.h +++ b/Source/Engine/Content/Storage/FlaxPackage.h @@ -28,7 +28,10 @@ public: bool HasAsset(const Guid& id) const override; bool HasAsset(const AssetInfo& info) const override; int32 GetEntriesCount() const override; - void GetEntry(int32 index, Entry& output) const override; + void GetEntry(int32 index, Entry& value) const override; +#if USE_EDITOR + void SetEntry(int32 index, const Entry& value) override; +#endif void GetEntries(Array& output) const override; void Dispose() override; diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 007238889..88c9a0d34 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -49,6 +49,15 @@ String AssetHeader::ToString() const return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount()); } +uint32 AssetInitData::GetHashCode() const +{ + // Do not use Metadata/Dependencies because it may not be loaded (it's optional) + uint32 hashCode = GetHash(Header.ID); + hashCode = (hashCode * 397) ^ SerializedVersion; + hashCode = (hashCode * 397) ^ CustomData.Length(); + return hashCode; +} + void FlaxChunk::RegisterUsage() { LastAccessTime = Platform::GetTimeSeconds(); @@ -605,6 +614,65 @@ bool FlaxStorage::Reload() return failed; } +bool FlaxStorage::ReloadSilent() +{ + ScopeLock lock(_loadLocker); + if (!IsLoaded()) + return false; + + // Open file + auto stream = OpenFile(); + if (stream == nullptr) + return true; + + // Magic Code + uint32 magicCode; + stream->ReadUint32(&magicCode); + if (magicCode != MagicCode) + { + LOG(Warning, "Invalid asset magic code in {0}", ToString()); + return true; + } + + // Version + uint32 version; + stream->ReadUint32(&version); + if (version == 9) + { + _version = version; + + // Custom storage data + CustomData customData; + stream->Read(customData); + + // Entries + int32 assetsCount; + stream->ReadInt32(&assetsCount); + if (assetsCount != GetEntriesCount()) + { + LOG(Warning, "Incorrect amount of assets ({} instead of {}) saved in {}", assetsCount, GetEntriesCount(), ToString()); + return true; + } + for (int32 i = 0; i < assetsCount; i++) + { + SerializedEntryV9 se; + stream->ReadBytes(&se, sizeof(se)); + Entry e(se.ID, se.TypeName.Data, se.Address); + SetEntry(i, e); + } + + // TODO: load chunks metadata? compare against loaded chunks? + } + else + { + // Fallback to full reload + LOG(Warning, "Fallback to full reload of {0}", ToString()); + Reload(); + } + + return false; +} + #endif bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data) @@ -809,7 +877,7 @@ FlaxChunk* FlaxStorage::AllocateChunk() #if USE_EDITOR -bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode, const CustomData* customData) +bool FlaxStorage::Create(const StringView& path, Span assets, bool silentMode, const CustomData* customData) { PROFILE_CPU(); ZoneText(*path, path.Length()); @@ -824,24 +892,32 @@ bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int3 return true; // Create package - bool result = Create(stream, data, dataCount, customData); + bool result = Create(stream, assets, customData); // Close file Delete(stream); - // Reload storage container (only if not in silent mode) - if (storage && !silentMode) + // Reload storage container + if (storage && storage->IsLoaded()) { - storage->Reload(); + if (silentMode) + { + // Silent data-only reload (loaded chunks location in file has been updated) + storage->ReloadSilent(); + } + else + { + // Full reload + storage->Reload(); + } } return result; } -bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 dataCount, const CustomData* customData) +bool FlaxStorage::Create(WriteStream* stream, Span assets, const CustomData* customData) { - // Validate inputs - if (data == nullptr || dataCount <= 0) + if (assets.Get() == nullptr || assets.Length() <= 0) { LOG(Warning, "Cannot create new package. No assets to write."); return true; @@ -849,27 +925,31 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d // Prepare data Array> entries; - entries.Resize(dataCount); + entries.Resize(assets.Length()); Array chunks; - - // Get all chunks - for (int32 i = 0; i < dataCount; i++) - data[i].Header.GetLoadedChunks(chunks); - int32 chunksCount = chunks.Count(); + for (const AssetInitData& asset : assets) + { + for (FlaxChunk* chunk : asset.Header.Chunks) + { + if (chunk && chunk->IsLoaded()) + chunks.Add(chunk); + } + } + const int32 chunksCount = chunks.Count(); // TODO: sort chunks by size? smaller ones first? // Calculate start address of the first asset header location // 0 -> Header -> Entries Count -> Entries -> Chunks Count -> Chunk Locations int32 currentAddress = sizeof(Header) + sizeof(int32) - + sizeof(SerializedEntryV9) * dataCount + + sizeof(SerializedEntryV9) * assets.Length() + sizeof(int32) + (sizeof(FlaxChunk::Location) + sizeof(int32)) * chunksCount; // Initialize entries offsets in the file - for (int32 i = 0; i < dataCount; i++) + for (int32 i = 0; i < assets.Length(); i++) { - auto& asset = data[i]; + const AssetInitData& asset = assets[i]; entries[i] = SerializedEntryV9(asset.Header.ID, asset.Header.TypeName, currentAddress); // Move forward by asset header data size @@ -934,8 +1014,8 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d stream->Write(mainHeader); // Write asset entries - stream->WriteInt32(dataCount); - stream->WriteBytes(entries.Get(), sizeof(SerializedEntryV9) * dataCount); + stream->WriteInt32(assets.Length()); + stream->WriteBytes(entries.Get(), sizeof(SerializedEntryV9) * assets.Length()); // Write chunk locations and meta stream->WriteInt32(chunksCount); @@ -948,20 +1028,18 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d } #if ASSETS_LOADING_EXTRA_VERIFICATION - // Check calculated position of first asset header - if (dataCount > 0 && stream->GetPosition() != entries[0].Address) + if (assets.Length() > 0 && stream->GetPosition() != entries[0].Address) { LOG(Warning, "Error while asset header location computation."); return true; } - #endif // Write asset headers - for (int32 i = 0; i < dataCount; i++) + for (int32 i = 0; i < assets.Length(); i++) { - auto& header = data[i]; + const AssetInitData& header = assets[i]; // ID stream->Write(header.Header.ID); @@ -974,9 +1052,10 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d stream->WriteUint32(header.SerializedVersion); // Chunks mapping - for (FlaxChunk* chunk : header.Header.Chunks) + for (const FlaxChunk* chunk : header.Header.Chunks) { const int32 index = chunks.Find(chunk); + ASSERT_LOW_LAYER(index >= -1 && index <= ALL_ASSET_CHUNKS); stream->WriteInt32(index); } @@ -998,14 +1077,12 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d } #if ASSETS_LOADING_EXTRA_VERIFICATION - // Check calculated position of first asset chunk if (chunksCount > 0 && stream->GetPosition() != chunks[0]->LocationInFile.Address) { LOG(Warning, "Error while asset data chunk location computation."); return true; } - #endif // Write chunks data @@ -1033,7 +1110,7 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d return false; } -bool FlaxStorage::Save(AssetInitData& data, bool silentMode) +bool FlaxStorage::Save(const AssetInitData& data, bool silentMode) { // Check if can modify the storage if (!AllowDataModifications()) @@ -1475,12 +1552,21 @@ int32 FlaxFile::GetEntriesCount() const return _asset.ID.IsValid() ? 1 : 0; } -void FlaxFile::GetEntry(int32 index, Entry& output) const +void FlaxFile::GetEntry(int32 index, Entry& value) const { ASSERT(index == 0); - output = _asset; + value = _asset; } +#if USE_EDITOR + +void FlaxFile::SetEntry(int32 index, const Entry& value) +{ + ASSERT(index == 0); + _asset = value; +} +#endif + void FlaxFile::GetEntries(Array& output) const { if (_asset.ID.IsValid()) @@ -1544,19 +1630,36 @@ int32 FlaxPackage::GetEntriesCount() const return _entries.Count(); } -void FlaxPackage::GetEntry(int32 index, Entry& output) const +void FlaxPackage::GetEntry(int32 index, Entry& value) const { ASSERT(index >= 0 && index < _entries.Count()); for (auto i = _entries.Begin(); i.IsNotEnd(); ++i) { if (index-- <= 0) { - output = i->Value; + value = i->Value; return; } } } +#if USE_EDITOR + +void FlaxPackage::SetEntry(int32 index, const Entry& value) +{ + ASSERT(index >= 0 && index < _entries.Count()); + for (auto i = _entries.Begin(); i.IsNotEnd(); ++i) + { + if (index-- <= 0) + { + i->Value = value; + return; + } + } +} + +#endif + void FlaxPackage::GetEntries(Array& output) const { _entries.GetValues(output); diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 09e8c0bdd..0d5965311 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -313,8 +313,17 @@ public: /// Gets the asset entry at given index. /// /// The asset index. - /// The output. - virtual void GetEntry(int32 index, Entry& output) const = 0; + /// The result. + virtual void GetEntry(int32 index, Entry& value) const = 0; + +#if USE_EDITOR + /// + /// Sets the asset entry at given index. + /// + /// The asset index. + /// The input value. + virtual void SetEntry(int32 index, const Entry& value) = 0; +#endif /// /// Gets all the entries in the storage. @@ -424,62 +433,58 @@ public: public: #if USE_EDITOR - /// /// Saves the specified asset data to the storage container. /// /// The data to save. /// In silent mode don't reload opened storage container that is using target file. /// True if cannot save, otherwise false - bool Save(AssetInitData& data, bool silentMode = false); + bool Save(const AssetInitData& data, bool silentMode = false); /// /// Creates new FlaxFile using specified asset data. /// /// The file path. - /// The data to write. + /// The asset data to write. /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false - FORCE_INLINE static bool Create(const StringView& path, const AssetInitData& data, bool silentMode = false, const CustomData* customData = nullptr) + FORCE_INLINE static bool Create(const StringView& path, const AssetInitData& asset, bool silentMode = false, const CustomData* customData = nullptr) { - return Create(path, &data, 1, silentMode, customData); + return Create(path, ToSpan(&asset, 1), silentMode, customData); } /// /// Creates new FlaxFile using specified assets data. /// /// The file path. - /// The data to write. + /// The assets data to write. /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false - FORCE_INLINE static bool Create(const StringView& path, const Array& data, bool silentMode = false, const CustomData* customData = nullptr) + FORCE_INLINE static bool Create(const StringView& path, const Array& assets, bool silentMode = false, const CustomData* customData = nullptr) { - return Create(path, data.Get(), data.Count(), silentMode, customData); + return Create(path, ToSpan(assets), silentMode, customData); } /// /// Creates new FlaxFile using specified assets data. /// /// The file path. - /// The data to write. - /// The data size. + /// The assets data to write. /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false - static bool Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode = false, const CustomData* customData = nullptr); + static bool Create(const StringView& path, Span assets, bool silentMode = false, const CustomData* customData = nullptr); /// /// Creates new FlaxFile using specified assets data. /// /// The output stream. - /// The data to write. - /// The data size. + /// The assets data to write. /// Custom options. /// True if cannot create package, otherwise false - static bool Create(WriteStream* stream, const AssetInitData* data, int32 dataCount, const CustomData* customData = nullptr); - + static bool Create(WriteStream* stream, Span assets, const CustomData* customData = nullptr); #endif protected: @@ -488,4 +493,5 @@ protected: virtual void AddEntry(Entry& e) = 0; FileReadStream* OpenFile(); virtual bool GetEntry(const Guid& id, Entry& e) = 0; + bool ReloadSilent(); }; From f5280eab740b1a0a58fb276c457bac8d5548c36c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 23 Jan 2025 14:44:11 +0100 Subject: [PATCH 170/215] Refactor and improve collections code #3043 --- Source/Editor/Editor.cpp | 2 +- Source/Editor/Scripting/ScriptsBuilder.cpp | 2 +- Source/Engine/Core/Collections/Array.h | 82 ++++--------- Source/Engine/Core/Collections/BitArray.h | 8 +- Source/Engine/Core/Collections/Dictionary.h | 125 +++++++++++--------- Source/Engine/Core/Collections/HashSet.h | 113 ++++++++++-------- Source/Engine/Core/Memory/Allocation.h | 111 +++++++++-------- Source/Engine/Core/Memory/AllocationUtils.h | 24 ++++ Source/Engine/Core/Memory/Memory.h | 35 +++--- Source/Engine/Core/Templates.h | 6 + Source/Engine/Debug/DebugCommands.cpp | 2 +- Source/Engine/Renderer/RenderListBuffer.h | 29 +---- Source/Engine/Scripting/Runtime/DotNet.cpp | 6 +- 13 files changed, 277 insertions(+), 268 deletions(-) create mode 100644 Source/Engine/Core/Memory/AllocationUtils.h diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index f915bfde6..a62799097 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -608,7 +608,7 @@ int32 Editor::LoadProduct() // Validate project min supported version (older engine may try to load newer project) // Special check if project specifies only build number, then major/minor fields are set to 0 const auto engineVersion = FLAXENGINE_VERSION; - for (auto e : projects) + for (const auto& e : projects) { const auto project = e.Item; if (project->MinEngineVersion > engineVersion || diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index ac0f5438d..404add5f3 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -582,7 +582,7 @@ bool ScriptsBuilderService::Init() auto project = Editor::Project; HashSet projects; project->GetAllProjects(projects); - for (auto e : projects) + for (const auto& e : projects) { ProjectInfo* project = e.Item; if (project->Name == TEXT("Flax")) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 4b9511671..b8e86bb19 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -6,6 +6,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Memory/AllocationUtils.h" /// /// Template for dynamic array with variable capacity. @@ -25,22 +26,9 @@ private: int32 _capacity; AllocationData _allocation; - FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, const int32 fromCount, const int32 fromCapacity) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - to.Swap(from); - else - { - to.Allocate(fromCapacity); - Memory::MoveItems(to.Get(), from.Get(), fromCount); - Memory::DestructItems(from.Get(), fromCount); - from.Free(); - } - } - public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// FORCE_INLINE Array() : _count(0) @@ -49,10 +37,10 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. - explicit Array(const int32 capacity) + /// The number of elements that can be added without a need to allocate more memory. + FORCE_INLINE explicit Array(const int32 capacity) : _count(0) , _capacity(capacity) { @@ -61,21 +49,7 @@ public: } /// - /// Initializes a new instance of the class. - /// - /// The initial values defined in the array. - Array(std::initializer_list initList) - { - _count = _capacity = static_cast(initList.size()); - if (_count > 0) - { - _allocation.Allocate(_count); - Memory::ConstructItems(_allocation.Get(), initList.begin(), _count); - } - } - - /// - /// Initializes a new instance of the class. + /// Initializes by copying elements. /// /// The initial data. /// The amount of items. @@ -91,38 +65,25 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by copying listed elements. /// - /// The other collection to copy. - Array(const Array& other) + /// The initial values defined in the array. + FORCE_INLINE Array(std::initializer_list initList) + : Array(initList.begin(), (int32)initList.size()) { - _count = _capacity = other._count; - if (_capacity > 0) - { - _allocation.Allocate(_capacity); - Memory::ConstructItems(_allocation.Get(), other.Get(), other._count); - } } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// The other collection to copy. - /// The additionally amount of items to add to the add. - Array(const Array& other, int32 extraSize) + FORCE_INLINE Array(const Array& other) + : Array(other.Get(), other.Count()) { - ASSERT(extraSize >= 0); - _count = _capacity = other._count + extraSize; - if (_capacity > 0) - { - _allocation.Allocate(_capacity); - Memory::ConstructItems(_allocation.Get(), other.Get(), other._count); - Memory::ConstructItems(_allocation.Get() + other._count, extraSize); - } } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// The other collection to copy. template @@ -138,7 +99,7 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by moving the content of the other collection. /// /// The other collection to move. Array(Array&& other) noexcept @@ -147,7 +108,7 @@ public: _capacity = other._capacity; other._count = 0; other._capacity = 0; - MoveToEmpty(_allocation, other._allocation, _count, _capacity); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _count, _capacity); } /// @@ -204,7 +165,7 @@ public: _capacity = other._capacity; other._count = 0; other._capacity = 0; - MoveToEmpty(_allocation, other._allocation, _count, _capacity); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _count, _capacity); } return *this; } @@ -377,10 +338,13 @@ public: /// /// Clear the collection without changing its capacity. /// - FORCE_INLINE void Clear() + void Clear() { - Memory::DestructItems(_allocation.Get(), _count); - _count = 0; + if (_count != 0) + { + Memory::DestructItems(_allocation.Get(), _count); + _count = 0; + } } /// diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h index 0a748f4bd..799feb64d 100644 --- a/Source/Engine/Core/Collections/BitArray.h +++ b/Source/Engine/Core/Collections/BitArray.h @@ -34,7 +34,7 @@ private: public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// FORCE_INLINE BitArray() : _count(0) @@ -43,9 +43,9 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. + /// The number of elements that can be added without a need to allocate more memory. explicit BitArray(const int32 capacity) : _count(0) , _capacity(capacity) @@ -55,7 +55,7 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// The other collection to copy. BitArray(const BitArray& other) noexcept diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 01b33cf2d..0b717dc52 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -4,6 +4,7 @@ #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Memory/AllocationUtils.h" #include "Engine/Core/Collections/BucketState.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Config.h" @@ -25,6 +26,7 @@ public: struct Bucket { friend Dictionary; + friend Memory; /// The key. KeyType Key; @@ -34,6 +36,57 @@ public: private: BucketState _state; + Bucket() + : _state(BucketState::Empty) + { + } + + Bucket(Bucket&& other) noexcept + { + _state = other._state; + if (other._state == BucketState::Occupied) + { + Memory::MoveItems(&Key, &other.Key, 1); + Memory::MoveItems(&Value, &other.Value, 1); + other._state = BucketState::Empty; + } + } + + Bucket& operator=(Bucket&& other) noexcept + { + if (this != &other) + { + if (_state == BucketState::Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } + _state = other._state; + if (other._state == BucketState::Occupied) + { + Memory::MoveItems(&Key, &other.Key, 1); + Memory::MoveItems(&Value, &other.Value, 1); + other._state = BucketState::Empty; + } + } + return *this; + } + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + Bucket(const Bucket&) = delete; + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + Bucket& operator=(const Bucket&) = delete; + + ~Bucket() + { + if (_state == BucketState::Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } + } + FORCE_INLINE void Free() { if (_state == BucketState::Occupied) @@ -104,46 +157,19 @@ private: int32 _size = 0; AllocationData _allocation; - FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, const int32 fromSize) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - to.Swap(from); - else - { - to.Allocate(fromSize); - Bucket* toData = to.Get(); - Bucket* fromData = from.Get(); - for (int32 i = 0; i < fromSize; i++) - { - Bucket& fromBucket = fromData[i]; - if (fromBucket.IsOccupied()) - { - Bucket& toBucket = toData[i]; - Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1); - Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1); - toBucket._state = BucketState::Occupied; - Memory::DestructItem(&fromBucket.Key); - Memory::DestructItem(&fromBucket.Value); - fromBucket._state = BucketState::Empty; - } - } - from.Free(); - } - } - public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// Dictionary() { } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. - explicit Dictionary(const int32 capacity) + /// The number of elements that can be added without a need to allocate more memory. + FORCE_INLINE explicit Dictionary(const int32 capacity) { SetCapacity(capacity); } @@ -160,11 +186,11 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// Other collection to copy Dictionary(const Dictionary& other) @@ -201,7 +227,7 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } return *this; } @@ -536,25 +562,16 @@ public: /// Enables preserving collection contents during resizing. void SetCapacity(int32 capacity, const bool preserveContents = true) { - if (capacity == Capacity()) + if (capacity == _size) return; ASSERT(capacity >= 0); AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); const int32 oldSize = _size; const int32 oldElementsCount = _elementsCount; _deletedCount = _elementsCount = 0; if (capacity != 0 && (capacity & (capacity - 1)) != 0) - { - // Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - capacity++; - } + capacity = AllocationUtils::AlignToPowerOf2(capacity); if (capacity) { _allocation.Allocate(capacity); @@ -574,11 +591,9 @@ public: { FindPosition(oldBucket.Key, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1); - Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1); - bucket->_state = BucketState::Occupied; - ++_elementsCount; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); + _elementsCount++; } } } @@ -968,7 +983,7 @@ private: { // Rebuild entire table completely AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); _allocation.Allocate(_size); Bucket* data = _allocation.Get(); for (int32 i = 0; i < _size; i++) @@ -982,10 +997,8 @@ private: { FindPosition(oldBucket.Key, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1); - Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1); - bucket->_state = BucketState::Occupied; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); } } for (int32 i = 0; i < _size; i++) diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 838db7a35..711e65dca 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -4,6 +4,7 @@ #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Memory/AllocationUtils.h" #include "Engine/Core/Collections/BucketState.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Config.h" @@ -24,6 +25,7 @@ public: struct Bucket { friend HashSet; + friend Memory; /// The item. T Item; @@ -31,6 +33,51 @@ public: private: BucketState _state; + Bucket() + : _state(BucketState::Empty) + { + } + + Bucket(Bucket&& other) noexcept + { + _state = other._state; + if (other._state == BucketState::Occupied) + { + Memory::MoveItems(&Item, &other.Item, 1); + other._state = BucketState::Empty; + } + } + + Bucket& operator=(Bucket&& other) noexcept + { + if (this != &other) + { + if (_state == BucketState::Occupied) + { + Memory::DestructItem(&Item); + } + _state = other._state; + if (other._state == BucketState::Occupied) + { + Memory::MoveItems(&Item, &other.Item, 1); + other._state = BucketState::Empty; + } + } + return *this; + } + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + Bucket(const Bucket&) = delete; + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + Bucket& operator=(const Bucket&) = delete; + + ~Bucket() + { + if (_state == BucketState::Occupied) + Memory::DestructItem(&Item); + } + FORCE_INLINE void Free() { if (_state == BucketState::Occupied) @@ -87,44 +134,19 @@ private: int32 _size = 0; AllocationData _allocation; - FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, const int32 fromSize) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - to.Swap(from); - else - { - to.Allocate(fromSize); - Bucket* toData = to.Get(); - Bucket* fromData = from.Get(); - for (int32 i = 0; i < fromSize; ++i) - { - Bucket& fromBucket = fromData[i]; - if (fromBucket.IsOccupied()) - { - Bucket& toBucket = toData[i]; - Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1); - toBucket._state = BucketState::Occupied; - Memory::DestructItem(&fromBucket.Item); - fromBucket._state = BucketState::Empty; - } - } - from.Free(); - } - } - public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// HashSet() { } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. - explicit HashSet(const int32 capacity) + /// The number of elements that can be added without a need to allocate more memory. + FORCE_INLINE explicit HashSet(const int32 capacity) { SetCapacity(capacity); } @@ -141,11 +163,11 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// Other collection to copy HashSet(const HashSet& other) @@ -182,7 +204,7 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } return *this; } @@ -413,25 +435,16 @@ public: /// Enable/disable preserving collection contents during resizing void SetCapacity(int32 capacity, const bool preserveContents = true) { - if (capacity == Capacity()) + if (capacity == _size) return; ASSERT(capacity >= 0); AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); const int32 oldSize = _size; const int32 oldElementsCount = _elementsCount; _deletedCount = _elementsCount = 0; if (capacity != 0 && (capacity & (capacity - 1)) != 0) - { - // Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - capacity++; - } + capacity = AllocationUtils::AlignToPowerOf2(capacity); if (capacity) { _allocation.Allocate(capacity); @@ -451,9 +464,8 @@ public: { FindPosition(oldBucket.Item, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1); - bucket->_state = BucketState::Occupied; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); _elementsCount++; } } @@ -764,7 +776,7 @@ private: { // Rebuild entire table completely AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); _allocation.Allocate(_size); Bucket* data = _allocation.Get(); for (int32 i = 0; i < _size; ++i) @@ -778,9 +790,8 @@ private: { FindPosition(oldBucket.Item, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1); - bucket->_state = BucketState::Occupied; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); } } for (int32 i = 0; i < _size; ++i) diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h index 5fce2ad73..7ce02ca90 100644 --- a/Source/Engine/Core/Memory/Allocation.h +++ b/Source/Engine/Core/Memory/Allocation.h @@ -5,6 +5,39 @@ #include "Memory.h" #include "Engine/Core/Core.h" +namespace AllocationUtils +{ + // Rounds up the input value to the next power of 2 to be used as bigger memory allocation block. Handles overflow. + inline int32 RoundUpToPowerOf2(int32 capacity) + { + // Reference: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + capacity--; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + uint64 capacity64 = (uint64)(capacity + 1) * 2; + if (capacity64 > MAX_int32) + capacity64 = MAX_int32; + return (int32)capacity64; + } + + // Aligns the input value to the next power of 2 to be used as bigger memory allocation block. + inline int32 AlignToPowerOf2(int32 capacity) + { + // Reference: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + capacity--; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + capacity++; + return capacity; + } +} + /// /// The memory allocation policy that uses inlined memory of the fixed size (no resize support, does not use heap allocations at all). /// @@ -47,16 +80,12 @@ public: FORCE_INLINE void Allocate(const int32 capacity) { -#if ENABLE_ASSERTION_LOW_LAYERS - ASSERT(capacity <= Capacity); -#endif + ASSERT_LOW_LAYER(capacity <= Capacity); } FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount) { -#if ENABLE_ASSERTION_LOW_LAYERS - ASSERT(capacity <= Capacity); -#endif + ASSERT_LOW_LAYER(capacity <= Capacity); } FORCE_INLINE void Free() @@ -71,7 +100,7 @@ public: }; /// -/// The memory allocation policy that uses default heap allocator. +/// The memory allocation policy that uses default heap allocation. /// class HeapAllocation { @@ -109,33 +138,17 @@ public: if (capacity < minCapacity) capacity = minCapacity; if (capacity < 8) - { capacity = 8; - } else - { - // Round up to the next power of 2 and multiply by 2 (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - uint64 capacity64 = (uint64)(capacity + 1) * 2; - if (capacity64 > MAX_int32) - capacity64 = MAX_int32; - capacity = (int32)capacity64; - } + capacity = AllocationUtils::RoundUpToPowerOf2(capacity); return capacity; } FORCE_INLINE void Allocate(const int32 capacity) { -#if ENABLE_ASSERTION_LOW_LAYERS - ASSERT(!_data); -#endif + ASSERT_LOW_LAYER(!_data); _data = static_cast(Allocator::Allocate(capacity * sizeof(T))); -#if !BUILD_RELEASE +#if ENABLE_ASSERTION if (!_data) OUT_OF_MEMORY; #endif @@ -144,7 +157,7 @@ public: FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount) { T* newData = capacity != 0 ? static_cast(Allocator::Allocate(capacity * sizeof(T))) : nullptr; -#if !BUILD_RELEASE +#if ENABLE_ASSERTION if (!newData && capacity != 0) OUT_OF_MEMORY; #endif @@ -176,7 +189,7 @@ public: /// /// The memory allocation policy that uses inlined memory of the fixed size and supports using additional allocation to increase its capacity (eg. via heap allocation). /// -template +template class InlinedAllocation { public: @@ -186,11 +199,11 @@ public: class alignas(sizeof(void*)) Data { private: - typedef typename OtherAllocator::template Data OtherData; + typedef typename FallbackAllocation::template Data FallbackData; - bool _useOther = false; + bool _useFallback = false; byte _data[Capacity * sizeof(T)]; - OtherData _other; + FallbackData _fallback; public: FORCE_INLINE Data() @@ -203,25 +216,25 @@ public: FORCE_INLINE T* Get() { - return _useOther ? _other.Get() : reinterpret_cast(_data); + return _useFallback ? _fallback.Get() : reinterpret_cast(_data); } FORCE_INLINE const T* Get() const { - return _useOther ? _other.Get() : reinterpret_cast(_data); + return _useFallback ? _fallback.Get() : reinterpret_cast(_data); } FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity) const { - return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity); + return minCapacity <= Capacity ? Capacity : _fallback.CalculateCapacityGrow(capacity, minCapacity); } FORCE_INLINE void Allocate(int32 capacity) { if (capacity > Capacity) { - _useOther = true; - _other.Allocate(capacity); + _useFallback = true; + _fallback.Allocate(capacity); } } @@ -232,32 +245,32 @@ public: // Check if the new allocation will fit into inlined storage if (capacity <= Capacity) { - if (_useOther) + if (_useFallback) { // Move the items from other allocation to the inlined storage - Memory::MoveItems(data, _other.Get(), newCount); + Memory::MoveItems(data, _fallback.Get(), newCount); // Free the other allocation - Memory::DestructItems(_other.Get(), oldCount); - _other.Free(); - _useOther = false; + Memory::DestructItems(_fallback.Get(), oldCount); + _fallback.Free(); + _useFallback = false; } } else { - if (_useOther) + if (_useFallback) { // Resize other allocation - _other.Relocate(capacity, oldCount, newCount); + _fallback.Relocate(capacity, oldCount, newCount); } else { // Allocate other allocation - _other.Allocate(capacity); - _useOther = true; + _fallback.Allocate(capacity); + _useFallback = true; // Move the items from the inlined storage to the other allocation - Memory::MoveItems(_other.Get(), data, newCount); + Memory::MoveItems(_fallback.Get(), data, newCount); Memory::DestructItems(data, oldCount); } } @@ -265,10 +278,10 @@ public: FORCE_INLINE void Free() { - if (_useOther) + if (_useFallback) { - _useOther = false; - _other.Free(); + _useFallback = false; + _fallback.Free(); } } diff --git a/Source/Engine/Core/Memory/AllocationUtils.h b/Source/Engine/Core/Memory/AllocationUtils.h new file mode 100644 index 000000000..236d7bae1 --- /dev/null +++ b/Source/Engine/Core/Memory/AllocationUtils.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Allocation.h" +#include "Engine/Core/Templates.h" + +namespace AllocationUtils +{ + // Moves the data from the source allocation to the destination allocation. + template + inline void MoveToEmpty(typename AllocationType::template Data& to, typename AllocationType::template Data& from, const int32 fromCount, const int32 fromCapacity) + { + if IF_CONSTEXPR (AllocationType::HasSwap) + to.Swap(from); + else + { + to.Allocate(fromCapacity); + Memory::MoveItems(to.Get(), from.Get(), fromCount); + Memory::DestructItems(from.Get(), fromCount); + from.Free(); + } + } +} diff --git a/Source/Engine/Core/Memory/Memory.h b/Source/Engine/Core/Memory/Memory.h index 484731c70..c8ac9edb4 100644 --- a/Source/Engine/Core/Memory/Memory.h +++ b/Source/Engine/Core/Memory/Memory.h @@ -91,15 +91,16 @@ namespace AllocatorExt } } -namespace Memory +class Memory { +public: /// /// Constructs the item in the memory. /// /// The optimized version is noop. /// The address of the memory location to construct. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItem(T* dst) { new(dst) T(); } @@ -110,7 +111,7 @@ namespace Memory /// The optimized version is noop. /// The address of the memory location to construct. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItem(T* dst) { // More undefined behavior! No more clean memory! More performance! Yay! //Platform::MemoryClear(dst, sizeof(T)); @@ -123,7 +124,7 @@ namespace Memory /// The address of the first memory location to construct. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) { while (count--) { @@ -139,7 +140,7 @@ namespace Memory /// The address of the first memory location to construct. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) { // More undefined behavior! No more clean memory! More performance! Yay! //Platform::MemoryClear(dst, count * sizeof(T)); @@ -153,7 +154,7 @@ namespace Memory /// The address of the first memory location to pass to the constructor. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) { while (count--) { @@ -171,7 +172,7 @@ namespace Memory /// The address of the first memory location to pass to the constructor. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) { Platform::MemoryCopy(dst, src, count * sizeof(U)); } @@ -182,7 +183,7 @@ namespace Memory /// The optimized version is noop. /// The address of the memory location to destruct. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItem(T* dst) { dst->~T(); } @@ -193,7 +194,7 @@ namespace Memory /// The optimized version is noop. /// The address of the memory location to destruct. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItem(T* dst) { } @@ -204,7 +205,7 @@ namespace Memory /// The address of the first memory location to destruct. /// The number of element to destruct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) { while (count--) { @@ -220,7 +221,7 @@ namespace Memory /// The address of the first memory location to destruct. /// The number of element to destruct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) { } @@ -232,7 +233,7 @@ namespace Memory /// The address of the first memory location to assign from. /// The number of element to assign. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) { while (count--) { @@ -250,7 +251,7 @@ namespace Memory /// The address of the first memory location to assign from. /// The number of element to assign. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) { Platform::MemoryCopy(dst, src, count * sizeof(T)); } @@ -263,11 +264,11 @@ namespace Memory /// The address of the first memory location to pass to the move constructor. /// The number of element to move. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type MoveItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type MoveItems(T* dst, U* src, int32 count) { while (count--) { - new(dst) T((T&&)*src); + new(dst) T((U&&)*src); ++(T*&)dst; ++src; } @@ -281,11 +282,11 @@ namespace Memory /// The address of the first memory location to pass to the move constructor. /// The number of element to move. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type MoveItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type MoveItems(T* dst, U* src, int32 count) { Platform::MemoryCopy(dst, src, count * sizeof(U)); } -} +}; /// /// Creates a new object of the given type. diff --git a/Source/Engine/Core/Templates.h b/Source/Engine/Core/Templates.h index 462fa5024..a87166e1d 100644 --- a/Source/Engine/Core/Templates.h +++ b/Source/Engine/Core/Templates.h @@ -234,6 +234,12 @@ struct TIsCopyConstructible enum { Value = __is_constructible(T, typename TAddLValueReference::Type>::Type) }; }; +template +struct TIsMoveConstructible +{ + enum { Value = __is_constructible(T, typename TAddRValueReference::Type) }; +}; + //////////////////////////////////////////////////////////////////////////////////// // Checks if a type has a trivial copy constructor. diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 1dabec081..a871dc813 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -198,7 +198,7 @@ namespace const MClass* attribute = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEngine.DebugCommand"); ASSERT_LOW_LAYER(attribute); const auto& classes = managedModule->Assembly->GetClasses(); - for (auto e : classes) + for (const auto& e : classes) { MClass* mclass = e.Value; if (mclass->IsGeneric() || diff --git a/Source/Engine/Renderer/RenderListBuffer.h b/Source/Engine/Renderer/RenderListBuffer.h index e6a22949a..955da31dd 100644 --- a/Source/Engine/Renderer/RenderListBuffer.h +++ b/Source/Engine/Renderer/RenderListBuffer.h @@ -30,7 +30,7 @@ private: public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// FORCE_INLINE RenderListBuffer() : _count(0) @@ -39,19 +39,7 @@ public: } /// - /// Initializes a new instance of the class. - /// - /// The initial capacity. - explicit RenderListBuffer(const int32 capacity) - : _count(0) - , _capacity(capacity) - { - if (capacity > 0) - _allocation.Allocate(capacity); - } - - /// - /// Initializes a new instance of the class. + /// Initializes by copying elements. /// /// The initial data. /// The amount of items. @@ -369,17 +357,6 @@ private: { // Ensure there is a slack for others threads to reduce resize counts in highly multi-threaded environment constexpr int32 slack = PLATFORM_THREADS_LIMIT * 8; - int32 capacity = count + slack; - { - // Round up to the next power of 2 and multiply by 2 - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - capacity = (capacity + 1) * 2; - } - return capacity; + return AllocationUtils::RoundUpToPowerOf2(count + slack); } }; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index ddafab614..30b26760f 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -335,17 +335,17 @@ void MCore::UnloadEngine() void MCore::ReloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) - for (auto e : CachedClassHandles) + for (const auto& e : CachedClassHandles) { e.Value->_hasCachedAttributes = false; e.Value->_attributes.Clear(); } - for (auto e : CachedAssemblyHandles) + for (const auto& e : CachedAssemblyHandles) { MAssembly* a = e.Value; if (!a->IsLoaded() || !a->_hasCachedClasses) continue; - for (auto q : a->GetClasses()) + for (const auto& q : a->GetClasses()) { MClass* c = q.Value; c->_hasCachedAttributes = false; From c1339765b7507615b74a17a970ac0475c678aa9c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 23 Jan 2025 21:32:09 +0100 Subject: [PATCH 171/215] Fix parsing nested template types in scripting type inheritance --- .../Bindings/BindingsGenerator.Parsing.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 98675bf6f..b7ab7cb9b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -552,15 +552,35 @@ namespace Flax.Build.Bindings if (token.Type == TokenType.LeftAngleBracket) { inheritType.GenericArgs = new List(); - while (true) + var argStack = new Stack(); + argStack.Push(inheritType); + TypeInfo lastArg = null; + while (argStack.Count > 0) { token = context.Tokenizer.NextToken(); if (token.Type == TokenType.RightAngleBracket) - break; + { + // Go up + argStack.Pop(); + lastArg = argStack.Peek(); + continue; + } if (token.Type == TokenType.Comma) continue; if (token.Type == TokenType.Identifier) - inheritType.GenericArgs.Add(new TypeInfo { Type = token.Value }); + { + var arg = new TypeInfo { Type = token.Value }; + var parent = argStack.Peek(); + if (parent.GenericArgs == null) + parent.GenericArgs = new List(); + parent.GenericArgs.Add(arg); + lastArg = arg; + } + else if (token.Type == TokenType.LeftAngleBracket) + { + // Go down + argStack.Push(lastArg); + } else throw new ParseException(ref context, "Incorrect inheritance"); } From 61a731704decc6f13df605c956a08544d9513fde Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 23 Jan 2025 21:40:52 +0100 Subject: [PATCH 172/215] Fix const-correctness in dictionary iterator access --- Source/Engine/Content/Assets/VisualScript.cpp | 4 ++-- Source/Engine/Content/Assets/VisualScript.h | 4 ++-- Source/Engine/Renderer/ShadowsPass.cpp | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index f5ff9ca58..968cedfe4 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -2142,7 +2142,7 @@ const Variant& VisualScript::GetScriptInstanceParameterValue(const StringView& n return Variant::Null; } -void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value) const +void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value) { CHECK(instance); for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++) @@ -2163,7 +2163,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip LOG(Warning, "Failed to set {0} parameter '{1}'", ToString(), name); } -void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value) const +void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value) { CHECK(instance); for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++) diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 9e9de1007..e52f6c3df 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -217,7 +217,7 @@ public: /// The parameter name. /// The object instance. /// The property value to set. - API_FUNCTION() void SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value) const; + API_FUNCTION() void SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value); /// /// Sets the value of the Visual Script parameter of the given instance. @@ -225,7 +225,7 @@ public: /// The parameter name. /// The object instance. /// The property value to set. - void SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value) const; + void SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value); /// /// Tries to find the method matching the given properties. Doesn't check base classes but just this script. diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 06b48d3c6..92fd0a886 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -72,7 +72,7 @@ struct ShadowAtlasLightTile Matrix WorldToShadow; float FramesToUpdate; // Amount of frames (with fraction) until the next shadow update can happen bool SkipUpdate; - bool HasStaticGeometry; + mutable bool HasStaticGeometry; Viewport CachedViewport; // The viewport used the last time to render shadow to the atlas void FreeDynamic(ShadowsCustomBuffer* buffer); @@ -190,7 +190,7 @@ struct ShadowAtlasLight uint8 TilesNeeded; uint8 TilesCount; bool HasStaticShadowContext; - StaticStates StaticState; + mutable StaticStates StaticState; BoundingSphere Bounds; float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance, Distance, TileBorder; Float4 CascadeSplits; @@ -1393,7 +1393,7 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch) bool renderedAny = false; for (auto& e : shadows.Lights) { - ShadowAtlasLight& atlasLight = e.Value; + const ShadowAtlasLight& atlasLight = e.Value; if (!atlasLight.HasStaticShadowContext || atlasLight.ContextCount == 0) continue; int32 contextIndex = 0; @@ -1403,7 +1403,7 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch) // Check for any static geometry to use in static shadow map for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++) { - ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex]; + const ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex]; contextIndex++; // Skip dynamic context auto& shadowContextStatic = renderContextBatch.Contexts[atlasLight.ContextIndex + contextIndex++]; if (!shadowContextStatic.List->DrawCallsLists[(int32)DrawCallsListType::Depth].IsEmpty() || !shadowContextStatic.List->ShadowDepthDrawCallsList.IsEmpty()) @@ -1419,7 +1419,7 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch) contextIndex = 0; for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++) { - ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex]; + const ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex]; if (!tile.RectTile) break; if (!tile.StaticRectTile) @@ -1472,13 +1472,13 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch) context->SetRenderTarget(shadows.ShadowMapAtlas->View(), (GPUTextureView*)nullptr); for (auto& e : shadows.Lights) { - ShadowAtlasLight& atlasLight = e.Value; + const ShadowAtlasLight& atlasLight = e.Value; if (atlasLight.ContextCount == 0) continue; int32 contextIndex = 0; for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++) { - ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex]; + const ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex]; if (!tile.RectTile) break; if (tile.SkipUpdate) From 38f74c8cf73d3fbfe89714c44064dc775bb157f9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 23 Jan 2025 23:59:31 +0100 Subject: [PATCH 173/215] Refactor `Dictionary` and `HashSet` to use shared base class Add const iterators --- Source/Engine/Core/Collections/BucketState.h | 15 - Source/Engine/Core/Collections/Dictionary.h | 884 +++++++------------ Source/Engine/Core/Collections/HashSet.h | 799 +++++++---------- Source/Engine/Core/Collections/HashSetBase.h | 400 +++++++++ Source/Engine/Core/Types/BaseTypes.h | 2 + 5 files changed, 1024 insertions(+), 1076 deletions(-) delete mode 100644 Source/Engine/Core/Collections/BucketState.h create mode 100644 Source/Engine/Core/Collections/HashSetBase.h diff --git a/Source/Engine/Core/Collections/BucketState.h b/Source/Engine/Core/Collections/BucketState.h deleted file mode 100644 index 16cbdb845..000000000 --- a/Source/Engine/Core/Collections/BucketState.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Core/Types/BaseTypes.h" - -/// -/// Tells if the object is occupied, and if not, if the bucket is a subject of compaction. -/// -enum class BucketState : byte -{ - Empty = 0, - Deleted = 1, - Occupied = 2, -}; diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 0b717dc52..b70ac85e8 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -2,12 +2,144 @@ #pragma once -#include "Engine/Core/Memory/Memory.h" -#include "Engine/Core/Memory/Allocation.h" -#include "Engine/Core/Memory/AllocationUtils.h" -#include "Engine/Core/Collections/BucketState.h" -#include "Engine/Core/Collections/HashFunctions.h" -#include "Engine/Core/Collections/Config.h" +#include "HashSetBase.h" + +/// +/// Describes single portion of space for the key and value pair in a hash map. +/// +template +struct DictionaryBucket +{ + friend Memory; + friend HashSetBase; + friend Dictionary; + + /// The key. + KeyType Key; + /// The value. + ValueType Value; + +private: + HashSetBucketState _state; + + DictionaryBucket() + : _state(HashSetBucketState::Empty) + { + } + + DictionaryBucket(DictionaryBucket&& other) noexcept + { + _state = other._state; + if (other._state == HashSetBucketState::Occupied) + { + Memory::MoveItems(&Key, &other.Key, 1); + Memory::MoveItems(&Value, &other.Value, 1); + other._state = HashSetBucketState::Empty; + } + } + + DictionaryBucket& operator=(DictionaryBucket&& other) noexcept + { + if (this != &other) + { + if (_state == HashSetBucketState::Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } + _state = other._state; + if (other._state == HashSetBucketState::Occupied) + { + Memory::MoveItems(&Key, &other.Key, 1); + Memory::MoveItems(&Value, &other.Value, 1); + other._state = HashSetBucketState::Empty; + } + } + return *this; + } + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + DictionaryBucket(const DictionaryBucket&) = delete; + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + DictionaryBucket& operator=(const DictionaryBucket&) = delete; + + ~DictionaryBucket() + { + if (_state == HashSetBucketState::Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } + } + + FORCE_INLINE void Free() + { + if (_state == HashSetBucketState::Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } + _state = HashSetBucketState::Empty; + } + + FORCE_INLINE void Delete() + { + ASSERT(IsOccupied()); + _state = HashSetBucketState::Deleted; + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } + + template + FORCE_INLINE void Occupy(const KeyComparableType& key) + { + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItem(&Value); + _state = HashSetBucketState::Occupied; + } + + template + FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value) + { + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItems(&Value, &value, 1); + _state = HashSetBucketState::Occupied; + } + + template + FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value) + { + Memory::ConstructItems(&Key, &key, 1); + Memory::MoveItems(&Value, &value, 1); + _state = HashSetBucketState::Occupied; + } + + FORCE_INLINE bool IsEmpty() const + { + return _state == HashSetBucketState::Empty; + } + + FORCE_INLINE bool IsDeleted() const + { + return _state == HashSetBucketState::Deleted; + } + + FORCE_INLINE bool IsOccupied() const + { + return _state == HashSetBucketState::Occupied; + } + + FORCE_INLINE bool IsNotOccupied() const + { + return _state != HashSetBucketState::Occupied; + } + + FORCE_INLINE const KeyType& GetKey() const + { + return Key; + } +}; /// /// Template for unordered dictionary with mapped key with value pairs. @@ -16,146 +148,12 @@ /// The type of the values in the dictionary. /// The type of memory allocator. template -API_CLASS(InBuild) class Dictionary +API_CLASS(InBuild) class Dictionary : public HashSetBase> { friend Dictionary; public: - /// - /// Describes single portion of space for the key and value pair in a hash map. - /// - struct Bucket - { - friend Dictionary; - friend Memory; - - /// The key. - KeyType Key; - /// The value. - ValueType Value; - - private: - BucketState _state; - - Bucket() - : _state(BucketState::Empty) - { - } - - Bucket(Bucket&& other) noexcept - { - _state = other._state; - if (other._state == BucketState::Occupied) - { - Memory::MoveItems(&Key, &other.Key, 1); - Memory::MoveItems(&Value, &other.Value, 1); - other._state = BucketState::Empty; - } - } - - Bucket& operator=(Bucket&& other) noexcept - { - if (this != &other) - { - if (_state == BucketState::Occupied) - { - Memory::DestructItem(&Key); - Memory::DestructItem(&Value); - } - _state = other._state; - if (other._state == BucketState::Occupied) - { - Memory::MoveItems(&Key, &other.Key, 1); - Memory::MoveItems(&Value, &other.Value, 1); - other._state = BucketState::Empty; - } - } - return *this; - } - - /// Copying a bucket is useless, because a key must be unique in the dictionary. - Bucket(const Bucket&) = delete; - - /// Copying a bucket is useless, because a key must be unique in the dictionary. - Bucket& operator=(const Bucket&) = delete; - - ~Bucket() - { - if (_state == BucketState::Occupied) - { - Memory::DestructItem(&Key); - Memory::DestructItem(&Value); - } - } - - FORCE_INLINE void Free() - { - if (_state == BucketState::Occupied) - { - Memory::DestructItem(&Key); - Memory::DestructItem(&Value); - } - _state = BucketState::Empty; - } - - FORCE_INLINE void Delete() - { - _state = BucketState::Deleted; - Memory::DestructItem(&Key); - Memory::DestructItem(&Value); - } - - template - FORCE_INLINE void Occupy(const KeyComparableType& key) - { - Memory::ConstructItems(&Key, &key, 1); - Memory::ConstructItem(&Value); - _state = BucketState::Occupied; - } - - template - FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value) - { - Memory::ConstructItems(&Key, &key, 1); - Memory::ConstructItems(&Value, &value, 1); - _state = BucketState::Occupied; - } - - template - FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value) - { - Memory::ConstructItems(&Key, &key, 1); - Memory::MoveItems(&Value, &value, 1); - _state = BucketState::Occupied; - } - - FORCE_INLINE bool IsEmpty() const - { - return _state == BucketState::Empty; - } - - FORCE_INLINE bool IsDeleted() const - { - return _state == BucketState::Deleted; - } - - FORCE_INLINE bool IsOccupied() const - { - return _state == BucketState::Occupied; - } - - FORCE_INLINE bool IsNotOccupied() const - { - return _state != BucketState::Occupied; - } - }; - - using AllocationData = typename AllocationType::template Data; - -private: - int32 _elementsCount = 0; - int32 _deletedCount = 0; - int32 _size = 0; - AllocationData _allocation; + typedef DictionaryBucket Bucket; + typedef HashSetBase Base; public: /// @@ -171,7 +169,7 @@ public: /// The number of elements that can be added without a need to allocate more memory. FORCE_INLINE explicit Dictionary(const int32 capacity) { - SetCapacity(capacity); + Base::SetCapacity(capacity); } /// @@ -180,13 +178,7 @@ public: /// The other collection to move. Dictionary(Dictionary&& other) noexcept { - _elementsCount = other._elementsCount; - _deletedCount = other._deletedCount; - _size = other._size; - other._elementsCount = 0; - other._deletedCount = 0; - other._size = 0; - AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); + Base::MoveToEmpty(MoveTemp(other)); } /// @@ -219,15 +211,9 @@ public: { if (this != &other) { - Clear(); - _allocation.Free(); - _elementsCount = other._elementsCount; - _deletedCount = other._deletedCount; - _size = other._size; - other._elementsCount = 0; - other._deletedCount = 0; - other._size = 0; - AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); + Base::Clear(); + Base::_allocation.Free(); + Base::MoveToEmpty(MoveTemp(other)); } return *this; } @@ -237,113 +223,129 @@ public: /// ~Dictionary() { - Clear(); } public: /// - /// Gets the amount of the elements in the collection. + /// The read-only dictionary collection iterator. /// - FORCE_INLINE int32 Count() const - { - return _elementsCount; - } - - /// - /// Gets the amount of the elements that can be contained by the collection. - /// - FORCE_INLINE int32 Capacity() const - { - return _size; - } - - /// - /// Returns true if collection is empty. - /// - FORCE_INLINE bool IsEmpty() const - { - return _elementsCount == 0; - } - - /// - /// Returns true if collection has one or more elements. - /// - FORCE_INLINE bool HasItems() const - { - return _elementsCount != 0; - } - -public: - /// - /// The Dictionary collection iterator. - /// - struct Iterator + struct ConstIterator : Base::IteratorBase { friend Dictionary; - private: - Dictionary* _collection; - int32 _index; - public: - Iterator(Dictionary* collection, const int32 index) - : _collection(collection) - , _index(index) + ConstIterator(const Dictionary* collection, const int32 index) + : Base::IteratorBase(collection, index) { } - Iterator(Dictionary const* collection, const int32 index) - : _collection(const_cast(collection)) - , _index(index) + ConstIterator() + : Base::IteratorBase(nullptr, -1) + { + } + + ConstIterator(const ConstIterator& i) + : Base::IteratorBase(i._collection, i._index) + { + } + + ConstIterator(ConstIterator&& i) noexcept + : Base::IteratorBase(i._collection, i._index) + { + } + + public: + FORCE_INLINE bool operator!() const + { + return !(bool)*this; + } + + FORCE_INLINE bool operator==(const ConstIterator& v) const + { + return this->_index == v._index && this->_collection == v._collection; + } + + FORCE_INLINE bool operator!=(const ConstIterator& v) const + { + return this->_index != v._index || this->_collection != v._collection; + } + + ConstIterator& operator=(const ConstIterator& v) + { + this->_collection = v._collection; + this->_index = v._index; + return *this; + } + + ConstIterator& operator=(ConstIterator&& v) noexcept + { + this->_collection = v._collection; + this->_index = v._index; + return *this; + } + + ConstIterator& operator++() + { + this->Next(); + return *this; + } + + ConstIterator operator++(int) const + { + ConstIterator i = *this; + i.Next(); + return i; + } + + ConstIterator& operator--() + { + this->Prev(); + return *this; + } + + ConstIterator operator--(int) const + { + ConstIterator i = *this; + i.Prev(); + return i; + } + }; + + /// + /// The dictionary collection iterator. + /// + struct Iterator : Base::IteratorBase + { + friend Dictionary; + public: + Iterator(Dictionary* collection, const int32 index) + : Base::IteratorBase(collection, index) { } Iterator() - : _collection(nullptr) - , _index(-1) + : Base::IteratorBase(nullptr, -1) { } Iterator(const Iterator& i) - : _collection(i._collection) - , _index(i._index) + : Base::IteratorBase(i._collection, i._index) { } Iterator(Iterator&& i) noexcept - : _collection(i._collection) - , _index(i._index) + : Base::IteratorBase(i._collection, i._index) { } public: - FORCE_INLINE int32 Index() const - { - return _index; - } - - FORCE_INLINE bool IsEnd() const - { - return _index == _collection->_size; - } - - FORCE_INLINE bool IsNotEnd() const - { - return _index != _collection->_size; - } - FORCE_INLINE Bucket& operator*() const { - return _collection->_allocation.Get()[_index]; + return ((Dictionary*)this->_collection)->_allocation.Get()[this->_index]; } FORCE_INLINE Bucket* operator->() const { - return &_collection->_allocation.Get()[_index]; - } - - FORCE_INLINE explicit operator bool() const - { - return _index >= 0 && _index < _collection->_size; + return &((Dictionary*)this->_collection)->_allocation.Get()[this->_index]; } FORCE_INLINE bool operator!() const @@ -353,68 +355,51 @@ public: FORCE_INLINE bool operator==(const Iterator& v) const { - return _index == v._index && _collection == v._collection; + return this->_index == v._index && this->_collection == v._collection; } FORCE_INLINE bool operator!=(const Iterator& v) const { - return _index != v._index || _collection != v._collection; + return this->_index != v._index || this->_collection != v._collection; } Iterator& operator=(const Iterator& v) { - _collection = v._collection; - _index = v._index; + this->_collection = v._collection; + this->_index = v._index; return *this; } Iterator& operator=(Iterator&& v) noexcept { - _collection = v._collection; - _index = v._index; + this->_collection = v._collection; + this->_index = v._index; return *this; } Iterator& operator++() { - const int32 capacity = _collection->_size; - if (_index != capacity) - { - const Bucket* data = _collection->_allocation.Get(); - do - { - ++_index; - } - while (_index != capacity && data[_index].IsNotOccupied()); - } + this->Next(); return *this; } Iterator operator++(int) const { Iterator i = *this; - ++i; + i.Next(); return i; } Iterator& operator--() { - if (_index > 0) - { - const Bucket* data = _collection->_allocation.Get(); - do - { - --_index; - } - while (_index > 0 && data[_index].IsNotOccupied()); - } + this->Prev(); return *this; } Iterator operator--(int) const { Iterator i = *this; - --i; + i.Prev(); return i; } }; @@ -428,27 +413,10 @@ public: template ValueType& At(const KeyComparableType& key) { - // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) - if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) - Compact(); - - // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); - - // Find location of the item or place to insert it - FindPositionResult pos; - FindPosition(key, pos); - - // Check if that key has been already added - if (pos.ObjectIndex != -1) - return _allocation.Get()[pos.ObjectIndex].Value; - - // Insert - ASSERT(pos.FreeSlotIndex != -1); - ++_elementsCount; - Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; - bucket.Occupy(key); - return bucket.Value; + Bucket* bucket = Base::OnAdd(key, false); + if (bucket->_state != HashSetBucketState::Occupied) + bucket->Occupy(key); + return bucket->Value; } /// @@ -459,10 +427,10 @@ public: template const ValueType& At(const KeyComparableType& key) const { - FindPositionResult pos; - FindPosition(key, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); ASSERT(pos.ObjectIndex != -1); - return _allocation.Get()[pos.ObjectIndex].Value; + return Base::_allocation.Get()[pos.ObjectIndex].Value; } /// @@ -496,13 +464,11 @@ public: template bool TryGet(const KeyComparableType& key, ValueType& result) const { - if (IsEmpty()) - return false; - FindPositionResult pos; - FindPosition(key, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); if (pos.ObjectIndex == -1) return false; - result = _allocation.Get()[pos.ObjectIndex].Value; + result = Base::_allocation.Get()[pos.ObjectIndex].Value; return true; } @@ -514,30 +480,14 @@ public: template ValueType* TryGet(const KeyComparableType& key) const { - if (IsEmpty()) - return nullptr; - FindPositionResult pos; - FindPosition(key, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); if (pos.ObjectIndex == -1) return nullptr; - return const_cast(&_allocation.Get()[pos.ObjectIndex].Value); //TODO This one is problematic. I think this entire method should be removed. + return const_cast(&Base::_allocation.Get()[pos.ObjectIndex].Value); } public: - /// - /// Clears the collection but without changing its capacity (all inserted elements: keys and values will be removed). - /// - void Clear() - { - if (_elementsCount + _deletedCount != 0) - { - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; i++) - data[i].Free(); - _elementsCount = _deletedCount = 0; - } - } - /// /// Clears the collection and delete value objects. /// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method. @@ -552,91 +502,7 @@ public: if (i->Value) ::Delete(i->Value); } - Clear(); - } - - /// - /// Changes the capacity of the collection. - /// - /// The new capacity. - /// Enables preserving collection contents during resizing. - void SetCapacity(int32 capacity, const bool preserveContents = true) - { - if (capacity == _size) - return; - ASSERT(capacity >= 0); - AllocationData oldAllocation; - AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); - const int32 oldSize = _size; - const int32 oldElementsCount = _elementsCount; - _deletedCount = _elementsCount = 0; - if (capacity != 0 && (capacity & (capacity - 1)) != 0) - capacity = AllocationUtils::AlignToPowerOf2(capacity); - if (capacity) - { - _allocation.Allocate(capacity); - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < capacity; i++) - data[i]._state = BucketState::Empty; - } - _size = capacity; - Bucket* oldData = oldAllocation.Get(); - if (oldElementsCount != 0 && capacity != 0 && preserveContents) - { - FindPositionResult pos; - for (int32 i = 0; i < oldSize; i++) - { - Bucket& oldBucket = oldData[i]; - if (oldBucket.IsOccupied()) - { - FindPosition(oldBucket.Key, pos); - ASSERT(pos.FreeSlotIndex != -1); - Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; - bucket = MoveTemp(oldBucket); - _elementsCount++; - } - } - } - if (oldElementsCount != 0) - { - for (int32 i = 0; i < oldSize; i++) - oldData[i].Free(); - } - } - - /// - /// Ensures that collection has given capacity. - /// - /// The minimum required items capacity. - /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. - void EnsureCapacity(int32 minCapacity, const bool preserveContents = true) - { - minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE; - if (_size >= minCapacity) - return; - int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); - if (capacity < DICTIONARY_DEFAULT_CAPACITY) - capacity = DICTIONARY_DEFAULT_CAPACITY; - SetCapacity(capacity, preserveContents); - } - - /// - /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange. - /// - /// The other collection. - void Swap(Dictionary& other) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - { - ::Swap(_elementsCount, other._elementsCount); - ::Swap(_deletedCount, other._deletedCount); - ::Swap(_size, other._size); - _allocation.Swap(other._allocation); - } - else - { - ::Swap(other, *this); - } + Base::Clear(); } public: @@ -649,7 +515,7 @@ public: template FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value) { - Bucket* bucket = OnAdd(key); + Bucket* bucket = Base::OnAdd(key); bucket->Occupy(key, value); return bucket; } @@ -663,7 +529,7 @@ public: template FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value) { - Bucket* bucket = OnAdd(key); + Bucket* bucket = Base::OnAdd(key); bucket->Occupy(key, MoveTemp(value)); return bucket; } @@ -672,7 +538,7 @@ public: /// Add pair element to the collection. /// /// Iterator with key and value. - void Add(const Iterator& i) + DEPRECATED("Use Add with separate Key and Value from iterator.") void Add(const Iterator& i) { ASSERT(i._collection != this && i); const Bucket& bucket = *i; @@ -687,15 +553,13 @@ public: template bool Remove(const KeyComparableType& key) { - if (IsEmpty()) - return false; - FindPositionResult pos; - FindPosition(key, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); if (pos.ObjectIndex != -1) { - _allocation.Get()[pos.ObjectIndex].Delete(); - --_elementsCount; - ++_deletedCount; + Base::_allocation.Get()[pos.ObjectIndex].Delete(); + --Base::_elementsCount; + ++Base::_deletedCount; return true; } return false; @@ -711,10 +575,9 @@ public: ASSERT(i._collection == this); if (i) { - ASSERT(_allocation.Get()[i._index].IsOccupied()); - _allocation.Get()[i._index].Delete(); - --_elementsCount; - ++_deletedCount; + Base::_allocation.Get()[i._index].Delete(); + --Base::_elementsCount; + ++Base::_deletedCount; return true; } return false; @@ -746,15 +609,28 @@ public: /// The key to find. /// The iterator for the found element or End if cannot find it. template - Iterator Find(const KeyComparableType& key) const + Iterator Find(const KeyComparableType& key) { - if (IsEmpty()) + if (Base::IsEmpty()) return End(); - FindPositionResult pos; - FindPosition(key, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End(); } + /// + /// Finds the element with given key in the collection. + /// + /// The key to find. + /// The iterator for the found element or End if cannot find it. + template + ConstIterator Find(const KeyComparableType& key) const + { + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); + return pos.ObjectIndex != -1 ? ConstIterator(this, pos.ObjectIndex) : End(); + } + /// /// Checks if given key is in a collection. /// @@ -763,10 +639,8 @@ public: template bool ContainsKey(const KeyComparableType& key) const { - if (IsEmpty()) - return false; - FindPositionResult pos; - FindPosition(key, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(key, pos); return pos.ObjectIndex != -1; } @@ -777,10 +651,10 @@ public: /// True if value has been found in a collection, otherwise false. bool ContainsValue(const ValueType& value) const { - if (HasItems()) + if (Base::HasItems()) { - const Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; ++i) + const Bucket* data = Base::_allocation.Get(); + for (int32 i = 0; i < Base::_size; ++i) { if (data[i].IsOccupied() && data[i].Value == value) return true; @@ -797,10 +671,10 @@ public: /// True if value has been found, otherwise false. bool KeyOf(const ValueType& value, KeyType* key) const { - if (HasItems()) + if (Base::HasItems()) { - const Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; ++i) + const Bucket* data = Base::_allocation.Get(); + for (int32 i = 0; i < Base::_size; ++i) { if (data[i].IsOccupied() && data[i].Value == value) { @@ -821,10 +695,15 @@ public: void Clone(const Dictionary& other) { // TODO: if both key and value are POD types then use raw memory copy for buckets - Clear(); - SetCapacity(other.Capacity(), false); - for (Iterator i = other.Begin(); i != other.End(); ++i) - Add(i); + Base::Clear(); + Base::SetCapacity(other.Capacity(), false); + for (ConstIterator i = other.Begin(); i != other.End(); ++i) + { + const Bucket& bucket = *i; + Add(bucket.Key, bucket.Value); + } + ASSERT(Base::Count() == other.Count()); + ASSERT(Base::Capacity() == other.Capacity()); } /// @@ -834,7 +713,7 @@ public: template void GetKeys(Array& result) const { - for (Iterator i = Begin(); i.IsNotEnd(); ++i) + for (ConstIterator i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Key); } @@ -845,21 +724,33 @@ public: template void GetValues(Array& result) const { - for (Iterator i = Begin(); i.IsNotEnd(); ++i) + for (ConstIterator i = Begin(); i.IsNotEnd(); ++i) result.Add(i->Value); } public: - Iterator Begin() const + Iterator Begin() { Iterator i(this, -1); ++i; return i; } - Iterator End() const + Iterator End() { - return Iterator(this, _size); + return Iterator(this, Base::_size); + } + + ConstIterator Begin() const + { + ConstIterator i(this, -1); + ++i; + return i; + } + + ConstIterator End() const + { + return ConstIterator(this, Base::_size); } Iterator begin() @@ -871,139 +762,18 @@ public: FORCE_INLINE Iterator end() { - return Iterator(this, _size); + return Iterator(this, Base::_size); } - Iterator begin() const + ConstIterator begin() const { - Iterator i(this, -1); + ConstIterator i(this, -1); ++i; return i; } - FORCE_INLINE Iterator end() const + FORCE_INLINE ConstIterator end() const { - return Iterator(this, _size); - } - -private: - /// - /// The result container of the dictionary item lookup searching. - /// - struct FindPositionResult - { - int32 ObjectIndex; - int32 FreeSlotIndex; - }; - - /// - /// Returns a pair of positions: 1st where the object is, 2nd where - /// it would go if you wanted to insert it. 1st is -1 - /// if object is not found; 2nd is -1 if it is. - /// Note: because of deletions where-to-insert is not trivial: it's the - /// first deleted bucket we see, as long as we don't find the key later - /// - /// The ky to find. - /// The pair of values: where the object is and where it would go if you wanted to insert it. - template - void FindPosition(const KeyComparableType& key, FindPositionResult& result) const - { - ASSERT(_size); - const int32 tableSizeMinusOne = _size - 1; - int32 bucketIndex = GetHash(key) & tableSizeMinusOne; - int32 insertPos = -1; - int32 checksCount = 0; - const Bucket* data = _allocation.Get(); - result.FreeSlotIndex = -1; - while (checksCount < _size) - { - // Empty bucket - const Bucket& bucket = data[bucketIndex]; - if (bucket.IsEmpty()) - { - // Found place to insert - result.ObjectIndex = -1; - result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos; - return; - } - // Deleted bucket - if (bucket.IsDeleted()) - { - // Keep searching but mark to insert - if (insertPos == -1) - insertPos = bucketIndex; - } - // Occupied bucket by target key - else if (bucket.Key == key) - { - // Found key - result.ObjectIndex = bucketIndex; - return; - } - ++checksCount; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, checksCount)) & tableSizeMinusOne; - } - result.ObjectIndex = -1; - result.FreeSlotIndex = insertPos; - } - - template - Bucket* OnAdd(const KeyComparableType& key) - { - // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) - if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) - Compact(); - - // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); - - // Find location of the item or place to insert it - FindPositionResult pos; - FindPosition(key, pos); - - // Ensure key is unknown - ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary."); - - // Insert - ASSERT(pos.FreeSlotIndex != -1); - _elementsCount++; - return &_allocation.Get()[pos.FreeSlotIndex]; - } - - void Compact() - { - if (_elementsCount == 0) - { - // Fast path if it's empty - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; i++) - data[i]._state = BucketState::Empty; - } - else - { - // Rebuild entire table completely - AllocationData oldAllocation; - AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); - _allocation.Allocate(_size); - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; i++) - data[i]._state = BucketState::Empty; - Bucket* oldData = oldAllocation.Get(); - FindPositionResult pos; - for (int32 i = 0; i < _size; i++) - { - Bucket& oldBucket = oldData[i]; - if (oldBucket.IsOccupied()) - { - FindPosition(oldBucket.Key, pos); - ASSERT(pos.FreeSlotIndex != -1); - Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; - bucket = MoveTemp(oldBucket); - } - } - for (int32 i = 0; i < _size; i++) - oldData[i].Free(); - } - _deletedCount = 0; + return ConstIterator(this, Base::_size); } }; diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 711e65dca..51886d843 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -2,12 +2,122 @@ #pragma once -#include "Engine/Core/Memory/Memory.h" -#include "Engine/Core/Memory/Allocation.h" -#include "Engine/Core/Memory/AllocationUtils.h" -#include "Engine/Core/Collections/BucketState.h" -#include "Engine/Core/Collections/HashFunctions.h" -#include "Engine/Core/Collections/Config.h" +#include "HashSetBase.h" + +/// +/// Describes single portion of space for the item in a hash set. +/// +template +struct HashSetBucket +{ + friend Memory; + friend HashSetBase; + friend HashSet; + + /// The item. + T Item; + +private: + HashSetBucketState _state; + + HashSetBucket() + : _state(HashSetBucketState::Empty) + { + } + + HashSetBucket(HashSetBucket&& other) noexcept + { + _state = other._state; + if (other._state == HashSetBucketState::Occupied) + { + Memory::MoveItems(&Item, &other.Item, 1); + other._state = HashSetBucketState::Empty; + } + } + + HashSetBucket& operator=(HashSetBucket&& other) noexcept + { + if (this != &other) + { + if (_state == HashSetBucketState::Occupied) + { + Memory::DestructItem(&Item); + } + _state = other._state; + if (other._state == HashSetBucketState::Occupied) + { + Memory::MoveItems(&Item, &other.Item, 1); + other._state = HashSetBucketState::Empty; + } + } + return *this; + } + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + HashSetBucket(const HashSetBucket&) = delete; + + /// Copying a bucket is useless, because a key must be unique in the dictionary. + HashSetBucket& operator=(const HashSetBucket&) = delete; + + ~HashSetBucket() + { + if (_state == HashSetBucketState::Occupied) + Memory::DestructItem(&Item); + } + + FORCE_INLINE void Free() + { + if (_state == HashSetBucketState::Occupied) + Memory::DestructItem(&Item); + _state = HashSetBucketState::Empty; + } + + FORCE_INLINE void Delete() + { + ASSERT(IsOccupied()); + _state = HashSetBucketState::Deleted; + Memory::DestructItem(&Item); + } + + template + FORCE_INLINE void Occupy(const ItemType& item) + { + Memory::ConstructItems(&Item, &item, 1); + _state = HashSetBucketState::Occupied; + } + + template + FORCE_INLINE void Occupy(ItemType&& item) + { + Memory::MoveItems(&Item, &item, 1); + _state = HashSetBucketState::Occupied; + } + + FORCE_INLINE bool IsEmpty() const + { + return _state == HashSetBucketState::Empty; + } + + FORCE_INLINE bool IsDeleted() const + { + return _state == HashSetBucketState::Deleted; + } + + FORCE_INLINE bool IsOccupied() const + { + return _state == HashSetBucketState::Occupied; + } + + FORCE_INLINE bool IsNotOccupied() const + { + return _state != HashSetBucketState::Occupied; + } + + FORCE_INLINE const T& GetKey() const + { + return Item; + } +}; /// /// Template for unordered set of values (without duplicates with O(1) lookup access). @@ -15,124 +125,12 @@ /// The type of elements in the set. /// The type of memory allocator. template -API_CLASS(InBuild) class HashSet +API_CLASS(InBuild) class HashSet : public HashSetBase> { friend HashSet; public: - /// - /// Describes single portion of space for the item in a hash map. - /// - struct Bucket - { - friend HashSet; - friend Memory; - - /// The item. - T Item; - - private: - BucketState _state; - - Bucket() - : _state(BucketState::Empty) - { - } - - Bucket(Bucket&& other) noexcept - { - _state = other._state; - if (other._state == BucketState::Occupied) - { - Memory::MoveItems(&Item, &other.Item, 1); - other._state = BucketState::Empty; - } - } - - Bucket& operator=(Bucket&& other) noexcept - { - if (this != &other) - { - if (_state == BucketState::Occupied) - { - Memory::DestructItem(&Item); - } - _state = other._state; - if (other._state == BucketState::Occupied) - { - Memory::MoveItems(&Item, &other.Item, 1); - other._state = BucketState::Empty; - } - } - return *this; - } - - /// Copying a bucket is useless, because a key must be unique in the dictionary. - Bucket(const Bucket&) = delete; - - /// Copying a bucket is useless, because a key must be unique in the dictionary. - Bucket& operator=(const Bucket&) = delete; - - ~Bucket() - { - if (_state == BucketState::Occupied) - Memory::DestructItem(&Item); - } - - FORCE_INLINE void Free() - { - if (_state == BucketState::Occupied) - Memory::DestructItem(&Item); - _state = BucketState::Empty; - } - - FORCE_INLINE void Delete() - { - _state = BucketState::Deleted; - Memory::DestructItem(&Item); - } - - template - FORCE_INLINE void Occupy(const ItemType& item) - { - Memory::ConstructItems(&Item, &item, 1); - _state = BucketState::Occupied; - } - - template - FORCE_INLINE void Occupy(ItemType&& item) - { - Memory::MoveItems(&Item, &item, 1); - _state = BucketState::Occupied; - } - - FORCE_INLINE bool IsEmpty() const - { - return _state == BucketState::Empty; - } - - FORCE_INLINE bool IsDeleted() const - { - return _state == BucketState::Deleted; - } - - FORCE_INLINE bool IsOccupied() const - { - return _state == BucketState::Occupied; - } - - FORCE_INLINE bool IsNotOccupied() const - { - return _state != BucketState::Occupied; - } - }; - - using AllocationData = typename AllocationType::template Data; - -private: - int32 _elementsCount = 0; - int32 _deletedCount = 0; - int32 _size = 0; - AllocationData _allocation; + typedef HashSetBucket Bucket; + typedef HashSetBase Base; public: /// @@ -148,7 +146,7 @@ public: /// The number of elements that can be added without a need to allocate more memory. FORCE_INLINE explicit HashSet(const int32 capacity) { - SetCapacity(capacity); + Base::SetCapacity(capacity); } /// @@ -157,13 +155,7 @@ public: /// The other collection to move. HashSet(HashSet&& other) noexcept { - _elementsCount = other._elementsCount; - _deletedCount = other._deletedCount; - _size = other._size; - other._elementsCount = 0; - other._deletedCount = 0; - other._size = 0; - AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); + Base::MoveToEmpty(MoveTemp(other)); } /// @@ -196,15 +188,9 @@ public: { if (this != &other) { - Clear(); - _allocation.Free(); - _elementsCount = other._elementsCount; - _deletedCount = other._deletedCount; - _size = other._size; - other._elementsCount = 0; - other._deletedCount = 0; - other._size = 0; - AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); + Base::Clear(); + Base::_allocation.Free(); + Base::MoveToEmpty(MoveTemp(other)); } return *this; } @@ -214,113 +200,129 @@ public: /// ~HashSet() { - Clear(); } public: /// - /// Gets the amount of the elements in the collection. + /// The read-only hash set collection iterator. /// - FORCE_INLINE int32 Count() const - { - return _elementsCount; - } - - /// - /// Gets the amount of the elements that can be contained by the collection. - /// - FORCE_INLINE int32 Capacity() const - { - return _size; - } - - /// - /// Returns true if collection is empty. - /// - FORCE_INLINE bool IsEmpty() const - { - return _elementsCount == 0; - } - - /// - /// Returns true if collection has one or more elements. - /// - FORCE_INLINE bool HasItems() const - { - return _elementsCount != 0; - } - -public: - /// - /// The hash set collection iterator. - /// - struct Iterator + struct ConstIterator : Base::IteratorBase { friend HashSet; - private: - HashSet* _collection; - int32 _index; - public: - Iterator(HashSet* collection, const int32 index) - : _collection(collection) - , _index(index) + ConstIterator(const HashSet* collection, const int32 index) + : Base::IteratorBase(collection, index) { } - Iterator(const HashSet* collection, const int32 index) - : _collection(const_cast(collection)) - , _index(index) + ConstIterator() + : Base::IteratorBase(nullptr, -1) + { + } + + ConstIterator(const ConstIterator& i) + : Base::IteratorBase(i._collection, i._index) + { + } + + ConstIterator(ConstIterator&& i) noexcept + : Base::IteratorBase(i._collection, i._index) + { + } + + public: + FORCE_INLINE bool operator!() const + { + return !(bool)*this; + } + + FORCE_INLINE bool operator==(const ConstIterator& v) const + { + return this->_index == v._index && this->_collection == v._collection; + } + + FORCE_INLINE bool operator!=(const ConstIterator& v) const + { + return this->_index != v._index || this->_collection != v._collection; + } + + ConstIterator& operator=(const ConstIterator& v) + { + this->_collection = v._collection; + this->_index = v._index; + return *this; + } + + ConstIterator& operator=(ConstIterator&& v) noexcept + { + this->_collection = v._collection; + this->_index = v._index; + return *this; + } + + ConstIterator& operator++() + { + this->Next(); + return *this; + } + + ConstIterator operator++(int) const + { + ConstIterator i = *this; + i.Next(); + return i; + } + + ConstIterator& operator--() + { + this->Prev(); + return *this; + } + + ConstIterator operator--(int) const + { + ConstIterator i = *this; + i.Prev(); + return i; + } + }; + + /// + /// The hash set collection iterator. + /// + struct Iterator : Base::IteratorBase + { + friend HashSet; + public: + Iterator(HashSet* collection, const int32 index) + : Base::IteratorBase(collection, index) { } Iterator() - : _collection(nullptr) - , _index(-1) + : Base::IteratorBase(nullptr, -1) { } Iterator(const Iterator& i) - : _collection(i._collection) - , _index(i._index) + : Base::IteratorBase(i._collection, i._index) { } Iterator(Iterator&& i) noexcept - : _collection(i._collection) - , _index(i._index) + : Base::IteratorBase(i._collection, i._index) { } public: - FORCE_INLINE int32 Index() const - { - return _index; - } - - FORCE_INLINE bool IsEnd() const - { - return _index == _collection->_size; - } - - FORCE_INLINE bool IsNotEnd() const - { - return _index != _collection->_size; - } - FORCE_INLINE Bucket& operator*() const { - return _collection->_allocation.Get()[_index]; + return ((HashSet*)this->_collection)->_allocation.Get()[this->_index]; } FORCE_INLINE Bucket* operator->() const { - return &_collection->_allocation.Get()[_index]; - } - - FORCE_INLINE explicit operator bool() const - { - return _index >= 0 && _index < _collection->_size; + return &((HashSet*)this->_collection)->_allocation.Get()[this->_index]; } FORCE_INLINE bool operator!() const @@ -330,87 +332,56 @@ public: FORCE_INLINE bool operator==(const Iterator& v) const { - return _index == v._index && _collection == v._collection; + return this->_index == v._index && this->_collection == v._collection; } FORCE_INLINE bool operator!=(const Iterator& v) const { - return _index != v._index || _collection != v._collection; + return this->_index != v._index || this->_collection != v._collection; } Iterator& operator=(const Iterator& v) { - _collection = v._collection; - _index = v._index; + this->_collection = v._collection; + this->_index = v._index; return *this; } Iterator& operator=(Iterator&& v) noexcept { - _collection = v._collection; - _index = v._index; + this->_collection = v._collection; + this->_index = v._index; return *this; } Iterator& operator++() { - const int32 capacity = _collection->_size; - if (_index != capacity) - { - const Bucket* data = _collection->_allocation.Get(); - do - { - ++_index; - } - while (_index != capacity && data[_index].IsNotOccupied()); - } + this->Next(); return *this; } - Iterator operator++(int) + Iterator operator++(int) const { Iterator i = *this; - ++i; + i.Next(); return i; } Iterator& operator--() { - if (_index > 0) - { - const Bucket* data = _collection->_allocation.Get(); - do - { - --_index; - } - while (_index > 0 && data[_index].IsNotOccupied()); - } + this->Prev(); return *this; } Iterator operator--(int) { Iterator i = *this; - --i; + i.Prev(); return i; } }; - + public: - /// - /// Removes all elements from the collection. - /// - void Clear() - { - if (_elementsCount + _deletedCount != 0) - { - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; i++) - data[i].Free(); - _elementsCount = _deletedCount = 0; - } - } - /// /// Clears the collection and delete value objects. /// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method. @@ -425,93 +396,7 @@ public: if (i->Item) ::Delete(i->Item); } - Clear(); - } - - /// - /// Changes capacity of the collection - /// - /// New capacity - /// Enable/disable preserving collection contents during resizing - void SetCapacity(int32 capacity, const bool preserveContents = true) - { - if (capacity == _size) - return; - ASSERT(capacity >= 0); - AllocationData oldAllocation; - AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); - const int32 oldSize = _size; - const int32 oldElementsCount = _elementsCount; - _deletedCount = _elementsCount = 0; - if (capacity != 0 && (capacity & (capacity - 1)) != 0) - capacity = AllocationUtils::AlignToPowerOf2(capacity); - if (capacity) - { - _allocation.Allocate(capacity); - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < capacity; i++) - data[i]._state = BucketState::Empty; - } - _size = capacity; - Bucket* oldData = oldAllocation.Get(); - if (oldElementsCount != 0 && capacity != 0 && preserveContents) - { - FindPositionResult pos; - for (int32 i = 0; i < oldSize; i++) - { - Bucket& oldBucket = oldData[i]; - if (oldBucket.IsOccupied()) - { - FindPosition(oldBucket.Item, pos); - ASSERT(pos.FreeSlotIndex != -1); - Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; - bucket = MoveTemp(oldBucket); - _elementsCount++; - } - } - } - if (oldElementsCount != 0) - { - for (int32 i = 0; i < oldSize; i++) - oldData[i].Free(); - } - } - - /// - /// Ensures that collection has given capacity. - /// - /// The minimum required capacity. - /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. - void EnsureCapacity(int32 minCapacity, const bool preserveContents = true) - { - minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE; - if (_size >= minCapacity) - return; - int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); - if (capacity < DICTIONARY_DEFAULT_CAPACITY) - capacity = DICTIONARY_DEFAULT_CAPACITY; - SetCapacity(capacity, preserveContents); - } - - /// - /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange. - /// - /// The other collection. - void Swap(HashSet& other) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - { - ::Swap(_elementsCount, other._elementsCount); - ::Swap(_deletedCount, other._deletedCount); - ::Swap(_size, other._size); - _allocation.Swap(other._allocation); - } - else - { - HashSet tmp = MoveTemp(other); - other = *this; - *this = MoveTemp(tmp); - } + Base::Clear(); } public: @@ -523,7 +408,7 @@ public: template bool Add(const ItemType& item) { - Bucket* bucket = OnAdd(item); + Bucket* bucket = Base::OnAdd(item, false); if (bucket) bucket->Occupy(item); return bucket != nullptr; @@ -536,7 +421,7 @@ public: /// True if element has been added to the collection, otherwise false if the element is already present. bool Add(T&& item) { - Bucket* bucket = OnAdd(item); + Bucket* bucket = Base::OnAdd(item, false); if (bucket) bucket->Occupy(MoveTemp(item)); return bucket != nullptr; @@ -546,7 +431,7 @@ public: /// Add element at iterator to the collection /// /// Iterator with item to add - void Add(const Iterator& i) + DEPRECATED("Use Add with separate Key and Value from iterator.") void Add(const Iterator& i) { ASSERT(i._collection != this && i); const Bucket& bucket = *i; @@ -561,15 +446,13 @@ public: template bool Remove(const ItemType& item) { - if (IsEmpty()) - return false; - FindPositionResult pos; - FindPosition(item, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(item, pos); if (pos.ObjectIndex != -1) { - _allocation.Get()[pos.ObjectIndex].Delete(); - --_elementsCount; - ++_deletedCount; + Base::_allocation.Get()[pos.ObjectIndex].Delete(); + --Base::_elementsCount; + ++Base::_deletedCount; return true; } return false; @@ -585,10 +468,9 @@ public: ASSERT(i._collection == this); if (i) { - ASSERT(_allocation.Get()[i._index].IsOccupied()); - _allocation.Get()[i._index].Delete(); - --_elementsCount; - ++_deletedCount; + Base::_allocation.Get()[i._index].Delete(); + --Base::_elementsCount; + ++Base::_deletedCount; return true; } return false; @@ -601,15 +483,26 @@ public: /// Item to find /// Iterator for the found element or End if cannot find it template - Iterator Find(const ItemType& item) const + Iterator Find(const ItemType& item) { - if (IsEmpty()) - return End(); - FindPositionResult pos; - FindPosition(item, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(item, pos); return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End(); } + /// + /// Find element with given item in the collection + /// + /// Item to find + /// Iterator for the found element or End if cannot find it + template + ConstIterator Find(const ItemType& item) const + { + typename Base::FindPositionResult pos; + Base::FindPosition(item, pos); + return pos.ObjectIndex != -1 ? ConstIterator(this, pos.ObjectIndex) : End(); + } + /// /// Determines whether a collection contains the specified element. /// @@ -618,39 +511,60 @@ public: template bool Contains(const ItemType& item) const { - if (IsEmpty()) - return false; - FindPositionResult pos; - FindPosition(item, pos); + typename Base::FindPositionResult pos; + Base::FindPosition(item, pos); return pos.ObjectIndex != -1; } public: /// - /// Clones other collection into this + /// Clones other collection into this. /// - /// Other collection to clone + /// The other collection to clone. void Clone(const HashSet& other) { - Clear(); - SetCapacity(other.Capacity(), false); - for (Iterator i = other.Begin(); i != other.End(); ++i) - Add(i); - ASSERT(Count() == other.Count()); - ASSERT(Capacity() == other.Capacity()); + Base::Clear(); + Base::SetCapacity(other.Capacity(), false); + for (ConstIterator i = other.Begin(); i != other.End(); ++i) + Add(i->Item); + ASSERT(Base::Count() == other.Count()); + ASSERT(Base::Capacity() == other.Capacity()); + } + + /// + /// Gets the items collection to the output array (will contain unique items). + /// + /// The result. + template + void GetItems(Array& result) const + { + for (ConstIterator i = Begin(); i.IsNotEnd(); ++i) + result.Add(i->Item); } public: - Iterator Begin() const + Iterator Begin() { Iterator i(this, -1); ++i; return i; } - Iterator End() const + Iterator End() { - return Iterator(this, _size); + return Iterator(this, Base::_size); + } + + ConstIterator Begin() const + { + ConstIterator i(this, -1); + ++i; + return i; + } + + ConstIterator End() const + { + return ConstIterator(this, Base::_size); } Iterator begin() @@ -662,141 +576,18 @@ public: FORCE_INLINE Iterator end() { - return Iterator(this, _size); + return Iterator(this, Base::_size); } - Iterator begin() const + ConstIterator begin() const { - Iterator i(this, -1); + ConstIterator i(this, -1); ++i; return i; } - FORCE_INLINE Iterator end() const + FORCE_INLINE ConstIterator end() const { - return Iterator(this, _size); - } - -private: - /// - /// The result container of the set item lookup searching. - /// - struct FindPositionResult - { - int32 ObjectIndex; - int32 FreeSlotIndex; - }; - - /// - /// Returns a pair of positions: 1st where the object is, 2nd where - /// it would go if you wanted to insert it. 1st is -1 - /// if object is not found; 2nd is -1 if it is. - /// Note: because of deletions where-to-insert is not trivial: it's the - /// first deleted bucket we see, as long as we don't find the item later - /// - /// The item to find - /// Pair of values: where the object is and where it would go if you wanted to insert it - template - void FindPosition(const ItemType& item, FindPositionResult& result) const - { - ASSERT(_size); - const int32 tableSizeMinusOne = _size - 1; - int32 bucketIndex = GetHash(item) & tableSizeMinusOne; - int32 insertPos = -1; - int32 numChecks = 0; - const Bucket* data = _allocation.Get(); - result.FreeSlotIndex = -1; - while (numChecks < _size) - { - // Empty bucket - const Bucket& bucket = data[bucketIndex]; - if (bucket.IsEmpty()) - { - // Found place to insert - result.ObjectIndex = -1; - result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos; - return; - } - // Deleted bucket - if (bucket.IsDeleted()) - { - // Keep searching but mark to insert - if (insertPos == -1) - insertPos = bucketIndex; - } - // Occupied bucket by target item - else if (bucket.Item == item) - { - // Found item - result.ObjectIndex = bucketIndex; - return; - } - - numChecks++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, numChecks)) & tableSizeMinusOne; - } - result.ObjectIndex = -1; - result.FreeSlotIndex = insertPos; - } - - template - Bucket* OnAdd(const ItemType& key) - { - // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) - if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) - Compact(); - - // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); - - // Find location of the item or place to insert it - FindPositionResult pos; - FindPosition(key, pos); - - // Check if object has been already added - if (pos.ObjectIndex != -1) - return nullptr; - - // Insert - ASSERT(pos.FreeSlotIndex != -1); - ++_elementsCount; - return &_allocation.Get()[pos.FreeSlotIndex]; - } - - void Compact() - { - if (_elementsCount == 0) - { - // Fast path if it's empty - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; ++i) - data[i]._state = BucketState::Empty; - } - else - { - // Rebuild entire table completely - AllocationData oldAllocation; - AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); - _allocation.Allocate(_size); - Bucket* data = _allocation.Get(); - for (int32 i = 0; i < _size; ++i) - data[i]._state = BucketState::Empty; - Bucket* oldData = oldAllocation.Get(); - FindPositionResult pos; - for (int32 i = 0; i < _size; ++i) - { - Bucket& oldBucket = oldData[i]; - if (oldBucket.IsOccupied()) - { - FindPosition(oldBucket.Item, pos); - ASSERT(pos.FreeSlotIndex != -1); - Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; - bucket = MoveTemp(oldBucket); - } - } - for (int32 i = 0; i < _size; ++i) - oldData[i].Free(); - } - _deletedCount = 0; + return ConstIterator(this, Base::_size); } }; diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h new file mode 100644 index 000000000..0ad2d7035 --- /dev/null +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -0,0 +1,400 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Memory/Memory.h" +#include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Memory/AllocationUtils.h" +#include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Collections/Config.h" + +/// +/// Tells if the object is occupied, and if not, if the bucket is a subject of compaction. +/// +enum class HashSetBucketState : byte +{ + Empty = 0, + Deleted = 1, + Occupied = 2, +}; + +/// +/// Base class for unordered set of values (without duplicates with O(1) lookup access). +/// +/// The type of bucket structure that stores element data and state. +/// The type of memory allocator. +template +class HashSetBase +{ + friend HashSetBase; + +public: + // Type of allocation data used to store hash set buckets. + using AllocationData = typename AllocationType::template Data; + +protected: + int32 _elementsCount = 0; + int32 _deletedCount = 0; + int32 _size = 0; + AllocationData _allocation; + + HashSetBase() + { + } + + void MoveToEmpty(HashSetBase&& other) + { + _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; + _size = other._size; + other._elementsCount = 0; + other._deletedCount = 0; + other._size = 0; + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); + } + + ~HashSetBase() + { + Clear(); + } + +public: + /// + /// Gets the amount of the elements in the collection. + /// + FORCE_INLINE int32 Count() const + { + return _elementsCount; + } + + /// + /// Gets the amount of the elements that can be contained by the collection. + /// + FORCE_INLINE int32 Capacity() const + { + return _size; + } + + /// + /// Returns true if collection is empty. + /// + FORCE_INLINE bool IsEmpty() const + { + return _elementsCount == 0; + } + + /// + /// Returns true if collection has one or more elements. + /// + FORCE_INLINE bool HasItems() const + { + return _elementsCount != 0; + } + +public: + /// + /// Removes all elements from the collection. + /// + void Clear() + { + if (_elementsCount + _deletedCount != 0) + { + BucketType* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i].Free(); + _elementsCount = _deletedCount = 0; + } + } + + /// + /// Changes the capacity of the collection. + /// + /// The new capacity. + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void SetCapacity(int32 capacity, const bool preserveContents = true) + { + if (capacity == _size) + return; + ASSERT(capacity >= 0); + AllocationData oldAllocation; + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); + const int32 oldSize = _size; + const int32 oldElementsCount = _elementsCount; + _deletedCount = _elementsCount = 0; + if (capacity != 0 && (capacity & (capacity - 1)) != 0) + capacity = AllocationUtils::AlignToPowerOf2(capacity); + if (capacity) + { + _allocation.Allocate(capacity); + BucketType* data = _allocation.Get(); + for (int32 i = 0; i < capacity; i++) + data[i]._state = HashSetBucketState::Empty; + } + _size = capacity; + BucketType* oldData = oldAllocation.Get(); + if (oldElementsCount != 0 && capacity != 0 && preserveContents) + { + FindPositionResult pos; + for (int32 i = 0; i < oldSize; i++) + { + BucketType& oldBucket = oldData[i]; + if (oldBucket.IsOccupied()) + { + FindPosition(oldBucket.GetKey(), pos); + ASSERT(pos.FreeSlotIndex != -1); + BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); + _elementsCount++; + } + } + } + if (oldElementsCount != 0) + { + for (int32 i = 0; i < oldSize; i++) + oldData[i].Free(); + } + } + + /// + /// Ensures that collection has given capacity. + /// + /// The minimum required capacity. + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void EnsureCapacity(int32 minCapacity, const bool preserveContents = true) + { + minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE; + if (_size >= minCapacity) + return; + int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + if (capacity < DICTIONARY_DEFAULT_CAPACITY) + capacity = DICTIONARY_DEFAULT_CAPACITY; + SetCapacity(capacity, preserveContents); + } + + /// + /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange. + /// + /// The other collection. + void Swap(HashSetBase& other) + { + if IF_CONSTEXPR (AllocationType::HasSwap) + { + ::Swap(_elementsCount, other._elementsCount); + ::Swap(_deletedCount, other._deletedCount); + ::Swap(_size, other._size); + _allocation.Swap(other._allocation); + } + else + { + ::Swap(other, *this); + } + } + +public: + /// + /// The collection iterator base implementation. + /// + struct IteratorBase + { + protected: + const HashSetBase* _collection; + int32 _index; + + IteratorBase(const HashSetBase* collection, const int32 index) + : _collection(collection) + , _index(index) + { + } + + void Next() + { + const int32 capacity = _collection->_size; + if (_index != capacity) + { + const BucketType* data = _collection->_allocation.Get(); + do + { + ++_index; + } + while (_index != capacity && data[_index].IsNotOccupied()); + } + } + + void Prev() + { + if (_index > 0) + { + const BucketType* data = _collection->_allocation.Get(); + do + { + --_index; + } + while (_index > 0 && data[_index].IsNotOccupied()); + } + } + + public: + FORCE_INLINE int32 Index() const + { + return _index; + } + + FORCE_INLINE bool IsEnd() const + { + return _index == _collection->_size; + } + + FORCE_INLINE bool IsNotEnd() const + { + return _index != _collection->_size; + } + + FORCE_INLINE const BucketType& operator*() const + { + return _collection->_allocation.Get()[_index]; + } + + FORCE_INLINE const BucketType* operator->() const + { + return &_collection->_allocation.Get()[_index]; + } + + FORCE_INLINE explicit operator bool() const + { + return _index >= 0 && _index < _collection->_size; + } + }; + +protected: + /// + /// The result container of the set item lookup searching. + /// + struct FindPositionResult + { + int32 ObjectIndex; + int32 FreeSlotIndex; + }; + + /// + /// Returns a pair of positions: 1st where the object is, 2nd where it would go if you wanted to insert it. + /// 1st is -1 if object is not found; 2nd is -1 if it is. + /// Because of deletions where-to-insert is not trivial: it's the first deleted bucket we see, as long as we don't find the item later. + /// + /// The key to find + /// A pair of values: where the object is and where it would go if you wanted to insert it + template + void FindPosition(const KeyComparableType& key, FindPositionResult& result) const + { + result.FreeSlotIndex = -1; + if (_size == 0) + { + result.ObjectIndex = -1; + return; + } + const int32 tableSizeMinusOne = _size - 1; + int32 bucketIndex = GetHash(key) & tableSizeMinusOne; + int32 insertPos = -1; + int32 checksCount = 0; + const BucketType* data = _allocation.Get(); + while (checksCount < _size) + { + // Empty bucket + const BucketType& bucket = data[bucketIndex]; + if (bucket.IsEmpty()) + { + // Found place to insert + result.ObjectIndex = -1; + result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos; + return; + } + // Deleted bucket + if (bucket.IsDeleted()) + { + // Keep searching but mark to insert + if (insertPos == -1) + insertPos = bucketIndex; + } + // Occupied bucket by target item + else if (bucket.GetKey() == key) + { + // Found item + result.ObjectIndex = bucketIndex; + return; + } + + // Move to the next bucket + checksCount++; + bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, checksCount)) & tableSizeMinusOne; + } + result.ObjectIndex = -1; + result.FreeSlotIndex = insertPos; + } + + template + BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true) + { + // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) + if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) + Compact(); + + // Ensure to have enough memory for the next item (in case of new element insertion) + EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); + + // Find location of the item or place to insert it + FindPositionResult pos; + FindPosition(key, pos); + + // Check if object has been already added + if (pos.ObjectIndex != -1) + { + if (checkUnique) + { + Platform::CheckFailed("That key has been already added to the collection.", __FILE__, __LINE__); + return nullptr; + } + return &_allocation.Get()[pos.ObjectIndex]; + } + + // Insert + ASSERT(pos.FreeSlotIndex != -1); + ++_elementsCount; + return &_allocation.Get()[pos.FreeSlotIndex]; + } + + void Compact() + { + if (_elementsCount == 0) + { + // Fast path if it's empty + BucketType* data = _allocation.Get(); + for (int32 i = 0; i < _size; ++i) + data[i]._state = HashSetBucketState::Empty; + } + else + { + // Rebuild entire table completely + AllocationData oldAllocation; + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); + _allocation.Allocate(_size); + BucketType* data = _allocation.Get(); + for (int32 i = 0; i < _size; ++i) + data[i]._state = HashSetBucketState::Empty; + BucketType* oldData = oldAllocation.Get(); + FindPositionResult pos; + for (int32 i = 0; i < _size; ++i) + { + BucketType& oldBucket = oldData[i]; + if (oldBucket.IsOccupied()) + { + FindPosition(oldBucket.GetKey(), pos); + ASSERT(pos.FreeSlotIndex != -1); + BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); + } + } + for (int32 i = 0; i < _size; ++i) + oldData[i].Free(); + } + _deletedCount = 0; + } +}; diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index 64db93be9..3273abeeb 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -83,6 +83,8 @@ template class Pair; template class Dictionary; +template +class HashSet; template class Function; template From dffc6ea24dc7ad556db4c13e62495b703e4ab331 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 00:05:07 +0100 Subject: [PATCH 174/215] Move hash set related configs into proper header file and rename those --- Source/Engine/Core/Collections/Config.h | 27 ------------- Source/Engine/Core/Collections/HashSetBase.h | 42 ++++++++++++++++---- Source/Engine/Tests/TestCollections.cpp | 20 +++++----- 3 files changed, 45 insertions(+), 44 deletions(-) delete mode 100644 Source/Engine/Core/Collections/Config.h diff --git a/Source/Engine/Core/Collections/Config.h b/Source/Engine/Core/Collections/Config.h deleted file mode 100644 index c0346319b..000000000 --- a/Source/Engine/Core/Collections/Config.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Platform/Defines.h" - -/// -/// Default capacity for the dictionaries (amount of space for the elements). -/// -#ifndef DICTIONARY_DEFAULT_CAPACITY -#if PLATFORM_DESKTOP -#define DICTIONARY_DEFAULT_CAPACITY 256 -#else -#define DICTIONARY_DEFAULT_CAPACITY 64 -#endif -#endif - -/// -/// Default slack space divider for the dictionaries. -/// -#define DICTIONARY_DEFAULT_SLACK_SCALE 3 - -/// -/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size). -/// -#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks) -//#define DICTIONARY_PROB_FUNC(size, numChecks) (1) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 0ad2d7035..4b28016af 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -6,7 +6,35 @@ #include "Engine/Core/Memory/Allocation.h" #include "Engine/Core/Memory/AllocationUtils.h" #include "Engine/Core/Collections/HashFunctions.h" -#include "Engine/Core/Collections/Config.h" + +/// +/// Default capacity for the hash set collections (minimum initial amount of space for the elements). +/// +#ifndef HASH_SET_DEFAULT_CAPACITY +#if PLATFORM_DESKTOP +#define HASH_SET_DEFAULT_CAPACITY 256 +#else +#define HASH_SET_DEFAULT_CAPACITY 64 +#endif +#endif + +/// +/// Default slack space divider for the hash sets. +/// +#define HASH_SET_DEFAULT_SLACK_SCALE 3 + +/// +/// Function for hash set that tells how change hash index during iteration (size param is a buckets table size). +/// +#define HASH_SET_PROB_FUNC(size, numChecks) (numChecks) +//#define HASH_SET_PROB_FUNC(size, numChecks) (1) + +// [Deprecated in v1.10] Use HASH_SET_DEFAULT_CAPACITY +#define DICTIONARY_DEFAULT_CAPACITY HASH_SET_DEFAULT_CAPACITY +// [Deprecated in v1.10] Use HASH_SET_DEFAULT_SLACK_SCALE +#define DICTIONARY_DEFAULT_SLACK_SCALE HASH_SET_DEFAULT_SLACK_SCALE +// [Deprecated in v1.10] Use HASH_SET_PROB_FUNC +#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks) HASH_SET_PROB_FUNC(size, numChecks) /// /// Tells if the object is occupied, and if not, if the bucket is a subject of compaction. @@ -162,12 +190,12 @@ public: /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. void EnsureCapacity(int32 minCapacity, const bool preserveContents = true) { - minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE; + minCapacity *= HASH_SET_DEFAULT_SLACK_SCALE; if (_size >= minCapacity) return; int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); - if (capacity < DICTIONARY_DEFAULT_CAPACITY) - capacity = DICTIONARY_DEFAULT_CAPACITY; + if (capacity < HASH_SET_DEFAULT_CAPACITY) + capacity = HASH_SET_DEFAULT_CAPACITY; SetCapacity(capacity, preserveContents); } @@ -324,7 +352,7 @@ protected: // Move to the next bucket checksCount++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, checksCount)) & tableSizeMinusOne; + bucketIndex = (bucketIndex + HASH_SET_PROB_FUNC(_size, checksCount)) & tableSizeMinusOne; } result.ObjectIndex = -1; result.FreeSlotIndex = insertPos; @@ -334,11 +362,11 @@ protected: BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true) { // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) - if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) + if (_deletedCount > _size / HASH_SET_DEFAULT_SLACK_SCALE) Compact(); // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE); + EnsureCapacity(((_elementsCount + 1) * HASH_SET_DEFAULT_SLACK_SCALE + _deletedCount) / HASH_SET_DEFAULT_SLACK_SCALE); // Find location of the item or place to insert it FindPositionResult pos; diff --git a/Source/Engine/Tests/TestCollections.cpp b/Source/Engine/Tests/TestCollections.cpp index f9b0e47ca..522c3008d 100644 --- a/Source/Engine/Tests/TestCollections.cpp +++ b/Source/Engine/Tests/TestCollections.cpp @@ -185,8 +185,8 @@ TEST_CASE("HashSet") SECTION("Test Allocators") { HashSet a1; - HashSet> a2; - HashSet> a3; + HashSet> a2; + HashSet> a3; for (int32 i = 0; i < 7; i++) { a1.Add(i); @@ -236,7 +236,7 @@ TEST_CASE("HashSet") { HashSet a1; a1.Add(1); - CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY); } SECTION("Test Add/Remove") @@ -248,7 +248,7 @@ TEST_CASE("HashSet") a1.Remove(i); } CHECK(a1.Count() == 0); - CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY); a1.Clear(); for (int32 i = 1; i <= 10; i++) a1.Add(-i); @@ -258,7 +258,7 @@ TEST_CASE("HashSet") a1.Remove(i); } CHECK(a1.Count() == 10); - CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY); } } @@ -267,8 +267,8 @@ TEST_CASE("Dictionary") SECTION("Test Allocators") { Dictionary a1; - Dictionary> a2; - Dictionary> a3; + Dictionary> a2; + Dictionary> a3; for (int32 i = 0; i < 7; i++) { a1.Add(i, i); @@ -322,7 +322,7 @@ TEST_CASE("Dictionary") { Dictionary a1; a1.Add(1, 1); - CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY); } SECTION("Test Add/Remove") @@ -334,7 +334,7 @@ TEST_CASE("Dictionary") a1.Remove(i); } CHECK(a1.Count() == 0); - CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY); a1.Clear(); for (int32 i = 1; i <= 10; i++) a1.Add(-i, -i); @@ -344,6 +344,6 @@ TEST_CASE("Dictionary") a1.Remove(i); } CHECK(a1.Count() == 10); - CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY); } } From 80a44b5f5c12f4493071b7efa086b2f7eac001bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 11:08:31 +0100 Subject: [PATCH 175/215] Remove old code --- Source/Engine/Animations/Graph/AnimGraph.h | 16 ------------- Source/Engine/Core/Templates.h | 2 ++ .../Graphics/Materials/MaterialParams.h | 17 -------------- Source/Engine/Graphics/Models/MaterialSlot.h | 9 -------- Source/Engine/Graphics/Models/Mesh.h | 10 -------- Source/Engine/Graphics/Models/SkinnedMesh.h | 11 --------- .../DirectX/DX11/GPUTextureDX11.h | 23 ------------------- .../DirectX/DX12/GPUTextureDX12.h | 17 -------------- .../GraphicsDevice/Vulkan/GPUTextureVulkan.h | 16 ------------- Source/Engine/Particles/ParticleEffect.h | 16 ------------- Source/Engine/Particles/ParticlesSimulation.h | 16 ------------- Source/Engine/Visject/ShaderGraph.h | 16 ------------- Source/Engine/Visject/VisjectGraph.h | 16 ------------- 13 files changed, 2 insertions(+), 183 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index deca7bb5f..e1967f1ad 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -169,22 +169,6 @@ DECLARE_ENUM_OPERATORS(AnimGraphStateTransition::FlagTypes); API_CLASS() class AnimGraphParameter : public VisjectGraphParameter { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(AnimGraphParameter, VisjectGraphParameter); -public: - AnimGraphParameter(const AnimGraphParameter& other) - : AnimGraphParameter() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - AnimGraphParameter& operator=(const AnimGraphParameter& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } }; /// diff --git a/Source/Engine/Core/Templates.h b/Source/Engine/Core/Templates.h index a87166e1d..88c3c0165 100644 --- a/Source/Engine/Core/Templates.h +++ b/Source/Engine/Core/Templates.h @@ -234,6 +234,8 @@ struct TIsCopyConstructible enum { Value = __is_constructible(T, typename TAddLValueReference::Type>::Type) }; }; +// Checks if a type has a move constructor. + template struct TIsMoveConstructible { diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index 2092c5c49..bf3d61d29 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -198,23 +198,6 @@ private: ScriptingObjectReference _asGPUTexture; String _name; -public: - MaterialParameter(const MaterialParameter& other) - : MaterialParameter() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - MaterialParameter& operator=(const MaterialParameter& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } - public: /// /// Gets the parameter ID (not the parameter instance Id but the original parameter ID). diff --git a/Source/Engine/Graphics/Models/MaterialSlot.h b/Source/Engine/Graphics/Models/MaterialSlot.h index 6d08939b3..8a2281d5f 100644 --- a/Source/Engine/Graphics/Models/MaterialSlot.h +++ b/Source/Engine/Graphics/Models/MaterialSlot.h @@ -28,13 +28,4 @@ API_CLASS(NoSpawn) class FLAXENGINE_API MaterialSlot : public ScriptingObject /// The slot name. /// API_FIELD() String Name; - -public: - MaterialSlot(const MaterialSlot& other) - : MaterialSlot() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } }; diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 7ca50a9c2..a10e36797 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -16,16 +16,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase); -public: - Mesh(const Mesh& other) - : Mesh() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - -public: /// /// Gets the model owning this mesh. /// diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index 5080024f9..0f407bcc2 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -12,16 +12,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public MeshBase { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, MeshBase); -public: - SkinnedMesh(const SkinnedMesh& other) - : SkinnedMesh() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - -public: /// /// Gets the skinned model owning this mesh. /// @@ -48,7 +38,6 @@ public: /// True if cannot load data, otherwise false. DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer); -public: /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). /// [Deprecated in v1.10] diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h index 6cc2124f9..0d5ba2264 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTextureDX11.h @@ -24,33 +24,10 @@ private: ID3D11UnorderedAccessView* _uav = nullptr; public: - - /// - /// Initializes a new instance of the class. - /// GPUTextureViewDX11() { } - GPUTextureViewDX11(const GPUTextureViewDX11& other) - : GPUTextureViewDX11() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - GPUTextureViewDX11& operator=(const GPUTextureViewDX11& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } - - /// - /// Finalizes an instance of the class. - /// ~GPUTextureViewDX11() { Release(); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h index c89ad2cb8..884500151 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h @@ -21,27 +21,10 @@ private: DescriptorHeapWithSlotsDX12::Slot _rtv, _srv, _dsv, _uav; public: - GPUTextureViewDX12() { } - GPUTextureViewDX12(const GPUTextureViewDX12& other) - : GPUTextureViewDX12() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - GPUTextureViewDX12& operator=(const GPUTextureViewDX12& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } - ~GPUTextureViewDX12() { Release(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h index 46da296d6..e2ea01dd6 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h @@ -19,22 +19,6 @@ public: { } - GPUTextureViewVulkan(const GPUTextureViewVulkan& other) - : GPUTextureViewVulkan() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - GPUTextureViewVulkan& operator=(const GPUTextureViewVulkan& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } - #if !BUILD_RELEASE ~GPUTextureViewVulkan() { diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index 1e2136ff9..7317f176b 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -32,22 +32,6 @@ public: { } - ParticleEffectParameter(const ParticleEffectParameter& other) - : ParticleEffectParameter() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - ParticleEffectParameter& operator=(const ParticleEffectParameter& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } - /// /// Returns true if parameter object handle is valid. /// diff --git a/Source/Engine/Particles/ParticlesSimulation.h b/Source/Engine/Particles/ParticlesSimulation.h index ec2bd5e05..acf5c5bbf 100644 --- a/Source/Engine/Particles/ParticlesSimulation.h +++ b/Source/Engine/Particles/ParticlesSimulation.h @@ -21,22 +21,6 @@ public: : GraphParameter(SpawnParams(Guid::New(), TypeInitializer)) { } - - ParticleSystemParameter(const ParticleSystemParameter& other) - : ParticleSystemParameter() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - ParticleSystemParameter& operator=(const ParticleSystemParameter& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } }; /// diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index 1a871cf4f..8bdc15eb7 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -94,22 +94,6 @@ public: : GraphParameter(SpawnParams(Guid::New(), TypeInitializer)) { } - - ShaderGraphParameter(const ShaderGraphParameter& other) - : ShaderGraphParameter() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - ShaderGraphParameter& operator=(const ShaderGraphParameter& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } }; template, class BoxType = ShaderGraphBox, class ParameterType = ShaderGraphParameter> diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 3a2e6439d..0f91ff4ab 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -103,22 +103,6 @@ public: API_CLASS() class FLAXENGINE_API VisjectGraphParameter : public GraphParameter { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter); -public: - VisjectGraphParameter(const VisjectGraphParameter& other) - : VisjectGraphParameter() - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - } - - VisjectGraphParameter& operator=(const VisjectGraphParameter& other) - { -#if !BUILD_RELEASE - CRASH; // Not used -#endif - return *this; - } }; template, class BoxType = VisjectGraphBox, class ParameterType = VisjectGraphParameter> From ae74d49b0909c369e59887d3bb4fe4c935bcda4a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 12:24:52 +0100 Subject: [PATCH 176/215] Fix bug --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index b7ab7cb9b..74383bc6b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -562,7 +562,7 @@ namespace Flax.Build.Bindings { // Go up argStack.Pop(); - lastArg = argStack.Peek(); + lastArg = argStack.Count != 0 ? argStack.Peek() : null; continue; } if (token.Type == TokenType.Comma) From fa2f2e31043d35cca0db760e80180412cabccd52 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 12:53:56 +0100 Subject: [PATCH 177/215] Fix Linux build --- Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp b/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp index 4c688732c..016b95b51 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystemWatcher.cpp @@ -217,7 +217,7 @@ LinuxFileSystemWatcher::~LinuxFileSystemWatcher() FileSystemWatchers::Thread = nullptr; close(FileSystemWatchers::WacherFileDescriptor); FileSystemWatchers::WacherFileDescriptor = 0; - for (auto e : FileSystemWatchers::Watchers) + for (auto& e : FileSystemWatchers::Watchers) Delete(e.Value.Second.Second); FileSystemWatchers::Watchers.Clear(); } From cf40facefeba75cb682a1e35e81656856c83ce03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 20:07:12 +0100 Subject: [PATCH 178/215] Add engine fatal error types handling Add general out-of-memory handling Add safety memory buffer for crash or out of memory handling Refactor Globals exit/error state to be in Engine class --- .../Content/Upgraders/BinaryAssetUpgrader.h | 9 +- .../AssetsImportingManager.cpp | 9 +- Source/Engine/Core/Log.cpp | 2 + Source/Engine/Core/Memory/Allocation.h | 10 --- Source/Engine/Engine/Engine.cpp | 32 ++++--- Source/Engine/Engine/Engine.h | 30 +++++-- Source/Engine/Engine/Globals.cpp | 2 + Source/Engine/Engine/Globals.h | 49 +++++++---- .../Engine/Graphics/Async/GPUTasksContext.cpp | 6 +- Source/Engine/Networking/NetworkStream.cpp | 4 - .../Engine/Platform/Android/AndroidDefines.h | 1 + Source/Engine/Platform/Base/PlatformBase.cpp | 85 ++++++++++++------- Source/Engine/Platform/Base/PlatformBase.h | 44 +++++----- Source/Engine/Platform/Unix/UnixPlatform.cpp | 2 + .../Engine/Platform/Win32/Win32Platform.cpp | 2 + .../Platform/Windows/WindowsPlatform.cpp | 4 +- Source/Engine/Platform/iOS/iOSDefines.h | 1 + .../Serialization/MemoryWriteStream.cpp | 25 +----- .../Engine/ShadowsOfMordor/Builder.DoWork.cpp | 3 +- Source/Engine/Video/Video.cpp | 5 -- 20 files changed, 166 insertions(+), 159 deletions(-) diff --git a/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h b/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h index 9cf459bfb..97bcf1c31 100644 --- a/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/BinaryAssetUpgrader.h @@ -46,14 +46,7 @@ public: } // Create new chunk - const auto chunk = New(); - Output.Header.Chunks[index] = chunk; - - if (chunk == nullptr) - { - OUT_OF_MEMORY; - } - + Output.Header.Chunks[index] = New(); return false; } }; diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index b689cdc62..a37738c55 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -151,14 +151,7 @@ bool CreateAssetContext::AllocateChunk(int32 index) } // Create new chunk - const auto chunk = New(); - Data.Header.Chunks[index] = chunk; - - if (chunk == nullptr) - { - OUT_OF_MEMORY; - } - + Data.Header.Chunks[index] = New(); return false; } diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index a9d3268dc..fb4091572 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -146,8 +146,10 @@ void Log::Logger::Write(const StringView& msg) #endif } +#if !BUILD_RELEASE // Send message to platform logging Platform::Log(msg); +#endif // Write message to log file constexpr int32 LogMaxWriteSize = 1 * 1024 * 1024; // 1GB diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h index 7ce02ca90..fa09e9765 100644 --- a/Source/Engine/Core/Memory/Allocation.h +++ b/Source/Engine/Core/Memory/Allocation.h @@ -148,27 +148,17 @@ public: { ASSERT_LOW_LAYER(!_data); _data = static_cast(Allocator::Allocate(capacity * sizeof(T))); -#if ENABLE_ASSERTION - if (!_data) - OUT_OF_MEMORY; -#endif } FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount) { T* newData = capacity != 0 ? static_cast(Allocator::Allocate(capacity * sizeof(T))) : nullptr; -#if ENABLE_ASSERTION - if (!newData && capacity != 0) - OUT_OF_MEMORY; -#endif - if (oldCount) { if (newCount > 0) Memory::MoveItems(newData, _data, newCount); Memory::DestructItems(_data, oldCount); } - Allocator::Free(_data); _data = newData; } diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 7fea9cd6d..17dc8d333 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -71,6 +71,9 @@ Action Engine::Draw; Action Engine::Pause; Action Engine::Unpause; Action Engine::RequestingExit; +FatalErrorType Engine::FatalError = FatalErrorType::None; +bool Engine::IsRequestingExit = false; +int32 Engine::ExitCode = 0; Window* Engine::MainWindow = nullptr; int32 Engine::Main(const Char* cmdLine) @@ -201,7 +204,7 @@ int32 Engine::Main(const Char* cmdLine) PROFILE_CPU_NAMED("Platform.Tick"); Platform::Tick(); } - + // Update game logic if (Time::OnBeginUpdate(time)) { @@ -236,12 +239,13 @@ int32 Engine::Main(const Char* cmdLine) FileSystem::DeleteDirectory(Globals::TemporaryFolder); } - return Globals::ExitCode; + return ExitCode; } -void Engine::Exit(int32 exitCode) +void Engine::Exit(int32 exitCode, FatalErrorType error) { ASSERT(IsInMainThread()); + FatalError = error; // Call on exit event OnExit(); @@ -250,23 +254,23 @@ void Engine::Exit(int32 exitCode) exit(exitCode); } -void Engine::RequestExit(int32 exitCode) +void Engine::RequestExit(int32 exitCode, FatalErrorType error) { - if (Globals::IsRequestingExit) + if (IsRequestingExit) return; #if USE_EDITOR // Send to editor (will leave play mode if need to) - if (Editor::Managed->OnAppExit()) - { - Globals::IsRequestingExit = true; - Globals::ExitCode = exitCode; - RequestingExit(); - } -#else + if (!Editor::Managed->OnAppExit()) + return; +#endif + IsRequestingExit = true; + ExitCode = exitCode; + PRAGMA_DISABLE_DEPRECATION_WARNINGS; Globals::IsRequestingExit = true; Globals::ExitCode = exitCode; + PRAGMA_ENABLE_DEPRECATION_WARNINGS; + FatalError = error; RequestingExit(); -#endif } void Engine::OnFixedUpdate() @@ -407,7 +411,7 @@ bool Engine::IsReady() bool Engine::ShouldExit() { - return Globals::IsRequestingExit; + return IsRequestingExit; } bool Engine::IsEditor() diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 2f1728aa2..692ff494b 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -14,9 +14,9 @@ class JsonAsset; /// API_CLASS(Static) class FLAXENGINE_API Engine { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(Engine); -public: + DECLARE_SCRIPTING_TYPE_NO_SPAWN(Engine); +public: /// /// The engine start time (local time). /// @@ -38,7 +38,6 @@ public: API_FIELD(ReadOnly) static uint64 FrameCount; public: - /// /// Event called on engine fixed update. /// @@ -84,8 +83,22 @@ public: /// API_EVENT() static Action RequestingExit; -public: + /// + /// The current state of the fatal error. Set to None if no error occurred yet. + /// + API_FIELD(ReadOnly) static FatalErrorType FatalError; + /// + /// Flags set to true if engine needs to be closed (exit is pending). Use FatalError to determinate the exit reason (specific error or normal shutdown). + /// + API_FIELD(ReadOnly) static bool IsRequestingExit; + + /// + /// The current process exit code (pending to return). + /// + static int32 ExitCode; + +public: /// /// The main engine function (must be called from platform specific entry point). /// @@ -97,16 +110,17 @@ public: /// Exits the engine. /// /// The exit code. - API_FUNCTION(Attributes="DebugCommand") static void Exit(int32 exitCode = -1); + /// The fatal error type (or None on graceful exit). + API_FUNCTION(Attributes="DebugCommand") static void Exit(int32 exitCode = -1, FatalErrorType error = FatalErrorType::None); /// /// Requests normal engine exit. /// /// The exit code. - API_FUNCTION() static void RequestExit(int32 exitCode = 0); + /// The fatal error type (or None on graceful exit). + API_FUNCTION() static void RequestExit(int32 exitCode = 0, FatalErrorType error = FatalErrorType::None); public: - /// /// Fixed update callback used by the physics simulation (fixed stepping). /// @@ -138,7 +152,6 @@ public: static void OnExit(); public: - // Returns true if engine is running without main window (aka headless mode). API_PROPERTY() static bool IsHeadless(); @@ -184,7 +197,6 @@ public: API_PROPERTY() static bool HasGameViewportFocus(); private: - static void OnPause(); static void OnUnpause(); }; diff --git a/Source/Engine/Engine/Globals.cpp b/Source/Engine/Engine/Globals.cpp index 5aed2a958..fe38e9f93 100644 --- a/Source/Engine/Engine/Globals.cpp +++ b/Source/Engine/Engine/Globals.cpp @@ -18,9 +18,11 @@ String Globals::ProjectContentFolder; #if USE_MONO String Globals::MonoPath; #endif +PRAGMA_DISABLE_DEPRECATION_WARNINGS; bool Globals::FatalErrorOccurred; bool Globals::IsRequestingExit; int32 Globals::ExitCode; +PRAGMA_ENABLE_DEPRECATION_WARNINGS; uint64 Globals::MainThreadID; String Globals::EngineVersion(TEXT(FLAXENGINE_VERSION_TEXT)); int32 Globals::EngineBuildNumber = FLAXENGINE_VERSION_BUILD; diff --git a/Source/Engine/Engine/Globals.h b/Source/Engine/Engine/Globals.h index f204aff29..a51fb035b 100644 --- a/Source/Engine/Engine/Globals.h +++ b/Source/Engine/Engine/Globals.h @@ -10,8 +10,9 @@ /// API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Globals { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); +public: // Paths // Main engine directory path. @@ -34,7 +35,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); API_FIELD(ReadOnly) static String BinariesFolder; #if USE_EDITOR - // Project specific cache folder path (editor-only). API_FIELD(ReadOnly) static String ProjectCacheFolder; @@ -43,43 +43,60 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals); // Game source code directory path (editor-only). API_FIELD(ReadOnly) static String ProjectSourceFolder; - #endif - // Project content directory path + // Project content directory path. API_FIELD(ReadOnly) static String ProjectContentFolder; #if USE_MONO - // Mono library folder path + // Mono library folder path. API_FIELD(ReadOnly) static String MonoPath; #endif +public: // State - // True if fatal error occurred (engine is exiting) - static bool FatalErrorOccurred; + // True if fatal error occurred (engine is exiting). + // [Deprecated in v1.10] + static DEPRECATED("Use Engine::FatalError instead.") bool FatalErrorOccurred; - // True if engine needs to be closed - static bool IsRequestingExit; + // True if engine needs to be closed. + // [Deprecated in v1.10] + static DEPRECATED("Use Engine::IsRequestingExit instead.") bool IsRequestingExit; /// - /// True if engine needs to be closed + /// Flags set to true if engine needs to be closed (exit is pending). + /// [Deprecated in v1.10] /// - API_PROPERTY() FORCE_INLINE static bool GetIsRequestingExit() { return IsRequestingExit; } + API_PROPERTY() DEPRECATED("Use Engine::IsRequestingExit instead.") FORCE_INLINE static bool GetIsRequestingExit() + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS; + return IsRequestingExit; + PRAGMA_ENABLE_DEPRECATION_WARNINGS; + } /// - /// True if fatal error occurred (engine is exiting) + /// Flags set to true if fatal error occurred (engine is exiting). + /// [Deprecated in v1.10] /// - API_PROPERTY() FORCE_INLINE static bool GetFatalErrorOccurred() { return FatalErrorOccurred; } + API_PROPERTY() DEPRECATED("Use Engine::FatalError instead.") FORCE_INLINE static bool GetFatalErrorOccurred() + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS; + return FatalErrorOccurred; + PRAGMA_ENABLE_DEPRECATION_WARNINGS; + } - // Exit code - static int32 ExitCode; + // Process exit code (pending to return). + // [Deprecated in v1.10] + static DEPRECATED("Use Engine::ExitCode instead.") int32 ExitCode; +public: // Threading - // Main Engine thread id + // Main Engine thread id. API_FIELD(ReadOnly) static uint64 MainThreadID; +public: // Config /// diff --git a/Source/Engine/Graphics/Async/GPUTasksContext.cpp b/Source/Engine/Graphics/Async/GPUTasksContext.cpp index e0ee8cae1..f285886f6 100644 --- a/Source/Engine/Graphics/Async/GPUTasksContext.cpp +++ b/Source/Engine/Graphics/Async/GPUTasksContext.cpp @@ -5,7 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Threading/Threading.h" -#include "Engine/Engine/Globals.h" +#include "Engine/Engine/Engine.h" #define GPU_TASKS_USE_DEDICATED_CONTEXT 0 @@ -37,7 +37,7 @@ GPUTasksContext::~GPUTasksContext() auto task = tasks[i]; if (task->GetSyncPoint() <= _currentSyncPoint && task->GetState() != TaskState::Finished) { - if (!Globals::IsRequestingExit) + if (!Engine::IsRequestingExit) LOG(Warning, "{0} has been canceled before a sync", task->ToString()); task->CancelSync(); } @@ -62,7 +62,7 @@ void GPUTasksContext::OnCancelSync(GPUTask* task) _tasksDone.Remove(task); - if (!Globals::IsRequestingExit) + if (!Engine::IsRequestingExit) LOG(Warning, "{0} has been canceled before a sync", task->ToString()); } diff --git a/Source/Engine/Networking/NetworkStream.cpp b/Source/Engine/Networking/NetworkStream.cpp index fe597a848..1cbff2d1e 100644 --- a/Source/Engine/Networking/NetworkStream.cpp +++ b/Source/Engine/Networking/NetworkStream.cpp @@ -247,10 +247,6 @@ void NetworkStream::WriteBytes(const void* data, uint32 bytes) while (newLength < position + bytes) newLength *= 2; byte* newBuf = (byte*)Allocator::Allocate(newLength); - if (newBuf == nullptr) - { - OUT_OF_MEMORY; - } if (_buffer && _length) Platform::MemoryCopy(newBuf, _buffer, _length); if (_allocated) diff --git a/Source/Engine/Platform/Android/AndroidDefines.h b/Source/Engine/Platform/Android/AndroidDefines.h index 5cf857976..6fef09129 100644 --- a/Source/Engine/Platform/Android/AndroidDefines.h +++ b/Source/Engine/Platform/Android/AndroidDefines.h @@ -32,6 +32,7 @@ #endif #define PLATFORM_TYPE PlatformType::Android #define PLATFORM_CACHE_LINE_SIZE 64 +#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE (64ull * 1024) // 64 kB #define USE_MONO_AOT_MODE MONO_AOT_MODE_NONE diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 93b06b821..31830060f 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -49,6 +49,7 @@ float PlatformBase::CustomDpiScale = 1.0f; Array> PlatformBase::Users; Delegate PlatformBase::UserAdded; Delegate PlatformBase::UserRemoved; +void* OutOfMemoryBuffer = nullptr; const Char* ToString(NetworkConnectionType value) { @@ -150,6 +151,12 @@ bool PlatformBase::Init() srand((unsigned int)Platform::GetTimeCycles()); + // Preallocate safety dynamic buffer to be released before Out Of Memory reporting to ensure code can properly execute +#ifndef PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE +#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE (1ull * 1024 * 1024) // 1 MB +#endif + OutOfMemoryBuffer = Allocator::Allocate(PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE); + return false; } @@ -188,6 +195,8 @@ void PlatformBase::BeforeExit() void PlatformBase::Exit() { + Allocator::Free(OutOfMemoryBuffer); + OutOfMemoryBuffer = nullptr; } #if COMPILE_WITH_PROFILER @@ -257,25 +266,35 @@ bool PlatformBase::Is64BitApp() #endif } -void PlatformBase::Fatal(const Char* msg, void* context) +void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error) { // Check if is already during fatal state - if (Globals::FatalErrorOccurred) + if (Engine::FatalError != FatalErrorType::None) { // Just send one more error to the log and back LOG(Error, "Error after fatal error: {0}", msg); return; } + // Free OOM safety buffer + Allocator::Free(OutOfMemoryBuffer); + OutOfMemoryBuffer = nullptr; + // Set flags + PRAGMA_DISABLE_DEPRECATION_WARNINGS; Globals::FatalErrorOccurred = true; Globals::IsRequestingExit = true; - Globals::ExitCode = -1; + Globals::ExitCode = -Math::Max((int32)error, 1); + PRAGMA_ENABLE_DEPRECATION_WARNINGS; + Engine::IsRequestingExit = true; + Engine::ExitCode = -Math::Max((int32)error, 1); + Engine::FatalError = error; Engine::RequestingExit(); // Collect crash info (platform-dependant implementation that might collect stack trace and/or create memory dump) { // Log separation for crash info + LOG_FLUSH(); Log::Logger::WriteFloor(); LOG(Error, ""); LOG(Error, "Critical error! Reason: {0}", msg); @@ -358,11 +377,11 @@ void PlatformBase::Fatal(const Char* msg, void* context) // Only main thread can call exit directly if (IsInMainThread()) { - Engine::Exit(-1); + Engine::Exit(Engine::ExitCode, error); } } -void PlatformBase::Error(const Char* msg) +void PlatformBase::Error(const StringView& msg) { #if PLATFORM_HAS_HEADLESS_MODE if (CommandLine::Options.Headless.IsTrue()) @@ -372,7 +391,7 @@ void PlatformBase::Error(const Char* msg) ansi += PLATFORM_LINE_TERMINATOR; printf("Error: %s\n", ansi.Get()); #else - std::cout << "Error: " << msg << std::endl; + std::cout << "Error: " << *msg << std::endl; #endif } else @@ -382,12 +401,12 @@ void PlatformBase::Error(const Char* msg) } } -void PlatformBase::Warning(const Char* msg) +void PlatformBase::Warning(const StringView& msg) { #if PLATFORM_HAS_HEADLESS_MODE if (CommandLine::Options.Headless.IsTrue()) { - std::cout << "Warning: " << msg << std::endl; + std::cout << "Warning: " << *msg << std::endl; } else #endif @@ -396,12 +415,12 @@ void PlatformBase::Warning(const Char* msg) } } -void PlatformBase::Info(const Char* msg) +void PlatformBase::Info(const StringView& msg) { #if PLATFORM_HAS_HEADLESS_MODE if (CommandLine::Options.Headless.IsTrue()) { - std::cout << "Info: " << msg << std::endl; + std::cout << "Info: " << *msg << std::endl; } else #endif @@ -410,24 +429,9 @@ void PlatformBase::Info(const Char* msg) } } -void PlatformBase::Fatal(const StringView& msg) +void PlatformBase::Fatal(const StringView& msg, FatalErrorType error) { - Fatal(*msg); -} - -void PlatformBase::Error(const StringView& msg) -{ - Error(*msg); -} - -void PlatformBase::Warning(const StringView& msg) -{ - Warning(*msg); -} - -void PlatformBase::Info(const StringView& msg) -{ - Info(*msg); + Fatal(msg, nullptr, error); } void PlatformBase::Log(const StringView& msg) @@ -443,28 +447,43 @@ void PlatformBase::Crash(int32 line, const char* file) { const StringAsUTF16<256> fileUTF16(file); const String msg = String::Format(TEXT("Fatal crash!\nFile: {0}\nLine: {1}"), fileUTF16.Get(), line); - LOG_STR(Fatal, msg); + LOG_STR(Error, msg); + Fatal(msg, nullptr, FatalErrorType::Assertion); } void PlatformBase::OutOfMemory(int32 line, const char* file) { - const StringAsUTF16<256> fileUTF16(file); - const String msg = String::Format(TEXT("Out of memory error!\nFile: {0}\nLine: {1}"), fileUTF16.Get(), line); - LOG_STR(Fatal, msg); + fmt_flax::allocator allocator; + fmt_flax::memory_buffer buffer(allocator); + static_assert(fmt::inline_buffer_size > 300, "Update stack buffer to prevent dynamic memory allocation on Out Of Memory."); + if (file) + { + const StringAsUTF16<256> fileUTF16(file); + fmt_flax::format(buffer, TEXT("Out of memory error!\nFile: {0}\nLine: {1}"), fileUTF16.Get(), line); + } + else + { + fmt_flax::format(buffer, TEXT("Out of memory error!")); + } + const StringView msg(buffer.data(), (int32)buffer.size()); + LOG_STR(Error, msg); + Fatal(msg, nullptr, FatalErrorType::OutOfMemory); } void PlatformBase::MissingCode(int32 line, const char* file, const char* info) { const StringAsUTF16<256> fileUTF16(file); const String msg = String::Format(TEXT("TODO: {0}\nFile: {1}\nLine: {2}"), String(info), fileUTF16.Get(), line); - LOG_STR(Fatal, msg); + LOG_STR(Error, msg); + Fatal(msg, nullptr, FatalErrorType::Assertion); } void PlatformBase::Assert(const char* message, const char* file, int line) { const StringAsUTF16<256> fileUTF16(file); const String msg = String::Format(TEXT("Assertion failed!\nFile: {0}\nLine: {1}\n\nExpression: {2}"), fileUTF16.Get(), line, String(message)); - LOG_STR(Fatal, msg); + LOG_STR(Error, msg); + Fatal(msg, nullptr, FatalErrorType::Assertion); } void PlatformBase::CheckFailed(const char* message, const char* file, int line) diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index d5ff4c59f..e34bf3015 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -125,6 +125,23 @@ enum class ThreadPriority extern FLAXENGINE_API const Char* ToString(ThreadPriority value); +/// +/// Possible fatal error types that cause engine exit. +/// +API_ENUM() enum class FatalErrorType +{ + // No fatal error set. + None, + // Not defined or custom error. + Unknown, + // Runtime exception caught by the handler (eg. stack overflow, invalid memory address access). + Exception, + // Data assertion failed (eg. invalid value or code usage). + Assertion, + // Program run out of memory to allocate. + OutOfMemory, +}; + API_INJECT_CODE(cpp, "#include \"Engine/Platform/Platform.h\""); /// @@ -457,32 +474,15 @@ public: /// /// The message content. /// The platform-dependent context for the stack trace collecting (eg. platform exception info). - static void Fatal(const Char* msg, void* context = nullptr); + /// The fatal error type. + API_FUNCTION() static void Fatal(const StringView& msg, void* context, FatalErrorType error = FatalErrorType::Unknown); - /// - /// Shows the error message to the user. - /// - /// The message content. - static void Error(const Char* msg); - - /// - /// Shows the warning message to the user. - /// - /// The message content. - static void Warning(const Char* msg); - - /// - /// Shows the information message to the user. - /// - /// The message content. - static void Info(const Char* msg); - -public: /// /// Shows the fatal error message to the user. /// /// The message content. - API_FUNCTION() static void Fatal(const StringView& msg); + /// The fatal error type. + API_FUNCTION() static void Fatal(const StringView& msg, FatalErrorType error = FatalErrorType::Unknown); /// /// Shows the error message to the user. @@ -527,7 +527,7 @@ public: /// /// The source line. /// The source file. - NO_RETURN static void OutOfMemory(int32 line, const char* file); + NO_RETURN static void OutOfMemory(int32 line = -1, const char* file = nullptr); /// /// Performs a fatal crash due to code not being implemented. diff --git a/Source/Engine/Platform/Unix/UnixPlatform.cpp b/Source/Engine/Platform/Unix/UnixPlatform.cpp index afa207d9c..5b065bfef 100644 --- a/Source/Engine/Platform/Unix/UnixPlatform.cpp +++ b/Source/Engine/Platform/Unix/UnixPlatform.cpp @@ -30,6 +30,8 @@ void* UnixPlatform::Allocate(uint64 size, uint64 alignment) // Calculate the offset and store it behind aligned pointer *((offset_t*)ptr - 1) = (offset_t)((uintptr_t)ptr - (uintptr_t)p); } + else + OutOfMemory(); #if COMPILE_WITH_PROFILER OnMemoryAlloc(ptr, size); #endif diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index a9aa784c2..5650a5839 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -259,6 +259,8 @@ void Win32Platform::Prefetch(void const* ptr) void* Win32Platform::Allocate(uint64 size, uint64 alignment) { void* ptr = _aligned_malloc((size_t)size, (size_t)alignment); + if (!ptr) + OutOfMemory(); #if COMPILE_WITH_PROFILER OnMemoryAlloc(ptr, size); #endif diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 57d46b1f8..41ea7a340 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -282,7 +282,7 @@ long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep) } // Skip if engine already crashed - if (Globals::FatalErrorOccurred) + if (Engine::FatalError != FatalErrorType::None) return EXCEPTION_CONTINUE_SEARCH; // Get exception info @@ -326,7 +326,7 @@ long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep) } // Crash engine - Platform::Fatal(errorMsg.Get(), ep); + Platform::Fatal(errorMsg.Get(), ep, FatalErrorType::Exception); return EXCEPTION_CONTINUE_SEARCH; } diff --git a/Source/Engine/Platform/iOS/iOSDefines.h b/Source/Engine/Platform/iOS/iOSDefines.h index f9482acbc..fb4ec876b 100644 --- a/Source/Engine/Platform/iOS/iOSDefines.h +++ b/Source/Engine/Platform/iOS/iOSDefines.h @@ -13,6 +13,7 @@ #define PLATFORM_ARCH ArchitectureType::ARM64 #define PLATFORM_CACHE_LINE_SIZE 128 #define PLATFORM_DEBUG_BREAK __builtin_trap() +#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE (64ull * 1024) // 64 kB // Use AOT for Mono #define USE_MONO_AOT 1 diff --git a/Source/Engine/Serialization/MemoryWriteStream.cpp b/Source/Engine/Serialization/MemoryWriteStream.cpp index 27047e3fd..77358b116 100644 --- a/Source/Engine/Serialization/MemoryWriteStream.cpp +++ b/Source/Engine/Serialization/MemoryWriteStream.cpp @@ -14,18 +14,7 @@ MemoryWriteStream::MemoryWriteStream() MemoryWriteStream::MemoryWriteStream(uint32 capacity) : _capacity(capacity) { - if (capacity > 0) - { - _buffer = (byte*)Allocator::Allocate(capacity); - if (_buffer == nullptr) - { - OUT_OF_MEMORY; - } - } - else - { - _buffer = nullptr; - } + _buffer = capacity > 0 ? (byte*)Allocator::Allocate(capacity) : nullptr; _position = _buffer; } @@ -46,10 +35,6 @@ void* MemoryWriteStream::Move(uint32 bytes) while (newCapacity < position + bytes) newCapacity *= 2; byte* newBuf = (byte*)Allocator::Allocate(newCapacity); - if (newBuf == nullptr) - { - OUT_OF_MEMORY; - } Platform::MemoryCopy(newBuf, _buffer, _capacity); Allocator::Free(_buffer); @@ -73,10 +58,6 @@ void MemoryWriteStream::Reset(uint32 capacity) { Allocator::Free(_buffer); _buffer = (byte*)Allocator::Allocate(capacity); - if (_buffer == nullptr) - { - OUT_OF_MEMORY; - } _capacity = capacity; } @@ -142,10 +123,6 @@ void MemoryWriteStream::WriteBytes(const void* data, uint32 bytes) while (newCapacity < position + bytes) newCapacity *= 2; byte* newBuf = (byte*)Allocator::Allocate(newCapacity); - if (newBuf == nullptr) - { - OUT_OF_MEMORY; - } Platform::MemoryCopy(newBuf, _buffer, _capacity); Allocator::Free(_buffer); diff --git a/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp b/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp index 4ac46d67c..2ae617070 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp @@ -4,6 +4,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Engine/Engine.h" #include "Engine/Level/Actors/BoxBrush.h" #include "Engine/Level/SceneQuery.h" #include "Engine/Renderer/Renderer.h" @@ -414,7 +415,7 @@ int32 ShadowsOfMordor::Builder::doWork() // Clear _wasBuildCalled = false; IsBakingLightmaps = false; - if (!Globals::FatalErrorOccurred) + if (Engine::FatalError == FatalErrorType::None) deleteState(); // Release scenes data diff --git a/Source/Engine/Video/Video.cpp b/Source/Engine/Video/Video.cpp index cb2881923..2b3ff8efb 100644 --- a/Source/Engine/Video/Video.cpp +++ b/Source/Engine/Video/Video.cpp @@ -328,11 +328,6 @@ void VideoBackendPlayer::UpdateVideoFrame(Span data, TimeSpan time, TimeSp if (VideoFrameMemory.Length() < (int32)slicePitch) { VideoFrameMemory.Allocate(slicePitch); - if (VideoFrameMemory.IsInvalid()) - { - OUT_OF_MEMORY; - return; - } } Platform::MemoryCopy(VideoFrameMemory.Get(), data.Get(), slicePitch); From b36e55446fc7c849bf9f28ee53ba50e194810521 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 20:15:17 +0100 Subject: [PATCH 179/215] Add `Engine::ReportCrash` event for custom crash reporting or handling --- Source/Engine/Engine/Engine.cpp | 1 + Source/Engine/Engine/Engine.h | 5 +++++ Source/Engine/Platform/Base/PlatformBase.cpp | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 17dc8d333..92e29c78e 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -71,6 +71,7 @@ Action Engine::Draw; Action Engine::Pause; Action Engine::Unpause; Action Engine::RequestingExit; +Delegate Engine::ReportCrash; FatalErrorType Engine::FatalError = FatalErrorType::None; bool Engine::IsRequestingExit = false; int32 Engine::ExitCode = 0; diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 692ff494b..1ae590d28 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -83,6 +83,11 @@ public: /// API_EVENT() static Action RequestingExit; + /// + /// The custom handler for engine crash handling and reporting. Can be used to override default message box with a custom one or send crash report to telemetry service. Args are: error message and context pointer (for stack-trace access). + /// + API_EVENT() static Delegate ReportCrash; + /// /// The current state of the fatal error. Set to None if no error occurred yet. /// diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 31830060f..6770cb9eb 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -372,7 +372,10 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er } // Show error message - Error(msg); + if (Engine::ReportCrash.IsBinded()) + Engine::ReportCrash(msg, context); + else + Error(msg); // Only main thread can call exit directly if (IsInMainThread()) From 342f3543ca0a7f954bbad270341c5d10ab87b7c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 20:15:32 +0100 Subject: [PATCH 180/215] Fix `LOG_FLUSH` to flush only when automatic mode is disabled --- Source/Engine/Core/Log.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Log.h b/Source/Engine/Core/Log.h index 9e313a158..0b5fe7c18 100644 --- a/Source/Engine/Core/Log.h +++ b/Source/Engine/Core/Log.h @@ -21,10 +21,11 @@ #define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str) #if LOG_ENABLE_AUTO_FLUSH +// Noop as log is auto-flushed on write +#define LOG_FLUSH() +#else // Flushes the log file buffer #define LOG_FLUSH() Log::Logger::Flush() -#else -#define LOG_FLUSH() #endif /// From 0b48a274e511a644a93afdced77426feb2b41dd6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 22:54:56 +0100 Subject: [PATCH 181/215] Fix compilation --- Source/Engine/Engine/Globals.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Engine/Globals.h b/Source/Engine/Engine/Globals.h index a51fb035b..65d0d668a 100644 --- a/Source/Engine/Engine/Globals.h +++ b/Source/Engine/Engine/Globals.h @@ -58,17 +58,17 @@ public: // True if fatal error occurred (engine is exiting). // [Deprecated in v1.10] - static DEPRECATED("Use Engine::FatalError instead.") bool FatalErrorOccurred; + DEPRECATED("Use Engine::FatalError instead.") static bool FatalErrorOccurred; // True if engine needs to be closed. // [Deprecated in v1.10] - static DEPRECATED("Use Engine::IsRequestingExit instead.") bool IsRequestingExit; + DEPRECATED("Use Engine::IsRequestingExit instead.") static bool IsRequestingExit; /// /// Flags set to true if engine needs to be closed (exit is pending). /// [Deprecated in v1.10] /// - API_PROPERTY() DEPRECATED("Use Engine::IsRequestingExit instead.") FORCE_INLINE static bool GetIsRequestingExit() + API_PROPERTY() DEPRECATED("Use Engine::IsRequestingExit instead.") static bool GetIsRequestingExit() { PRAGMA_DISABLE_DEPRECATION_WARNINGS; return IsRequestingExit; @@ -79,7 +79,7 @@ public: /// Flags set to true if fatal error occurred (engine is exiting). /// [Deprecated in v1.10] /// - API_PROPERTY() DEPRECATED("Use Engine::FatalError instead.") FORCE_INLINE static bool GetFatalErrorOccurred() + API_PROPERTY() DEPRECATED("Use Engine::FatalError instead.") static bool GetFatalErrorOccurred() { PRAGMA_DISABLE_DEPRECATION_WARNINGS; return FatalErrorOccurred; @@ -88,7 +88,7 @@ public: // Process exit code (pending to return). // [Deprecated in v1.10] - static DEPRECATED("Use Engine::ExitCode instead.") int32 ExitCode; + DEPRECATED("Use Engine::ExitCode instead.") static int32 ExitCode; public: // Threading From d4c72487cdc00896f053b2b1a5e70571620ac830 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 22:55:21 +0100 Subject: [PATCH 182/215] Fix various asset windows shutdown when editor is disposing during crash --- .../Editor/Windows/Assets/AnimationGraphWindow.cs | 3 +-- Source/Editor/Windows/Assets/AnimationWindow.cs | 6 ++++-- Source/Editor/Windows/Assets/AudioClipWindow.cs | 6 ++++-- Source/Editor/Windows/Assets/BehaviorTreeWindow.cs | 4 ++-- Source/Editor/Windows/Assets/CollisionDataWindow.cs | 2 ++ Source/Editor/Windows/Assets/JsonAssetWindow.cs | 6 ++++-- Source/Editor/Windows/Assets/ModelBaseWindow.cs | 6 ++++-- .../Editor/Windows/Assets/ParticleSystemWindow.cs | 6 ++++-- .../Editor/Windows/Assets/PrefabWindow.Hierarchy.cs | 6 ++++-- Source/Editor/Windows/Assets/PrefabWindow.cs | 6 ++++-- .../Editor/Windows/Assets/SceneAnimationWindow.cs | 13 +++++++++---- Source/Editor/Windows/Assets/SkinnedModelWindow.cs | 2 ++ Source/Editor/Windows/Assets/TextureWindow.cs | 6 ++++-- Source/Editor/Windows/Assets/VideoWindow.cs | 4 ++-- Source/Editor/Windows/Assets/VisualScriptWindow.cs | 6 ++++-- 15 files changed, 54 insertions(+), 28 deletions(-) diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index 1cf03f062..aacf1aa4e 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -441,6 +441,7 @@ namespace FlaxEditor.Windows.Assets { if (IsDisposing) return; + base.OnDestroy(); Animations.DebugFlow -= OnDebugFlow; _properties = null; @@ -448,8 +449,6 @@ namespace FlaxEditor.Windows.Assets _debugPicker = null; _showNodesButton = null; _previewTab = null; - - base.OnDestroy(); } /// diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 058bf3622..c65b8caab 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -438,6 +438,10 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + if (_undo != null) { _undo.Enabled = false; @@ -454,8 +458,6 @@ namespace FlaxEditor.Windows.Assets _saveButton = null; _undoButton = null; _redoButton = null; - - base.OnDestroy(); } } } diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs index 35072620b..5336b2e3c 100644 --- a/Source/Editor/Windows/Assets/AudioClipWindow.cs +++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs @@ -289,6 +289,10 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + if (_previewSource) { _preview.Source = null; @@ -297,8 +301,6 @@ namespace FlaxEditor.Windows.Assets _previewSource = null; } FlaxEngine.Object.Destroy(ref _previewScene); - - base.OnDestroy(); } /// diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 3d6399365..0df851f5b 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -571,14 +571,14 @@ namespace FlaxEditor.Windows.Assets { if (IsDisposing) return; + base.OnDestroy(); + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _undo.Enabled = false; _nodePropertiesEditor.Deselect(); _knowledgePropertiesEditor.Deselect(); _undo.Clear(); _behaviorPicker = null; - - base.OnDestroy(); } /// diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index 7cddd8eed..b15478b43 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -307,6 +307,8 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; base.OnDestroy(); Object.Destroy(ref _collisionWiresShowActor); diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index dc1e1e71f..5bc6e62c5 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -320,14 +320,16 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + if (_isRegisteredForScriptsReload) { _isRegisteredForScriptsReload = false; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; } _typeText = null; - - base.OnDestroy(); } } } diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 52dcff08a..6e26a4bb1 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -90,11 +90,13 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + Presenter.Deselect(); Presenter = null; Proxy = null; - - base.OnDestroy(); } } diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 2708f8c87..7e9968060 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -569,6 +569,10 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + if (_undo != null) _undo.Enabled = false; _propertiesEditor?.Deselect(); @@ -579,8 +583,6 @@ namespace FlaxEditor.Windows.Assets _saveButton = null; _undoButton = null; _redoButton = null; - - base.OnDestroy(); } } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 6a786cb3a..0e8f1c3ab 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -251,6 +251,10 @@ namespace FlaxEditor.Windows.Assets public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + _window = null; _dragAssets = null; _dragActorType = null; @@ -258,8 +262,6 @@ namespace FlaxEditor.Windows.Assets _dragScriptItems = null; _dragHandlers?.Clear(); _dragHandlers = null; - - base.OnDestroy(); } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 7ee8ecff0..5efc56726 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -536,14 +536,16 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + Editor.Prefabs.PrefabApplied -= OnPrefabApplied; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; _undo.Dispose(); Graph.Dispose(); - - base.OnDestroy(); } /// diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index 20219a079..65007b0c7 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -594,7 +594,12 @@ namespace FlaxEditor.Windows.Assets public override void OnDestroy() { + if (IsDisposing) + return; CancelRendering(); + + base.OnDestroy(); + _window.Timeline.Enabled = true; _window.Timeline.Visible = true; _window._toolstrip.Enabled = true; @@ -602,8 +607,6 @@ namespace FlaxEditor.Windows.Assets _window = null; _presenter = null; _options = null; - - base.OnDestroy(); } } @@ -1030,6 +1033,10 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + Level.ActorDeleted -= OnActorDeleted; if (_previewButton.Checked) @@ -1056,8 +1063,6 @@ namespace FlaxEditor.Windows.Assets _redoButton = null; _renderButton = null; _previewPlayerPicker = null; - - base.OnDestroy(); } } } diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index f54fe9d73..f2d3bdf03 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -734,6 +734,8 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; base.OnDestroy(); Object.Destroy(ref _highlightActor); diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 8084847e4..b5f428285 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -184,11 +184,13 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + Presenter.Deselect(); Presenter = null; Proxy = null; - - base.OnDestroy(); } } diff --git a/Source/Editor/Windows/Assets/VideoWindow.cs b/Source/Editor/Windows/Assets/VideoWindow.cs index 958608914..36f119458 100644 --- a/Source/Editor/Windows/Assets/VideoWindow.cs +++ b/Source/Editor/Windows/Assets/VideoWindow.cs @@ -199,12 +199,12 @@ namespace FlaxEditor.Windows.Assets { if (IsDisposing) return; + base.OnDestroy(); + _videoPlayer.Stop(); Object.Destroy(ref _videoPlayer); _item.RemoveReference(this); _item = null; - - base.OnDestroy(); } /// diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index f41936dd0..c218fe0d8 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -1373,13 +1373,15 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; + base.OnDestroy(); + _undo.Enabled = false; _propertiesEditor.Deselect(); _undo.Clear(); _debugObjectPicker = null; _debugToolstripControls = null; - - base.OnDestroy(); } /// From d501018febe0455541192a3db788277f86eff404 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 24 Jan 2025 22:56:52 +0100 Subject: [PATCH 183/215] Add improved GPU crashes reporting --- Source/Engine/Core/Math/Math.h | 6 +- Source/Engine/Graphics/GPUDevice.cpp | 32 ++++++- Source/Engine/Graphics/GPUDevice.h | 2 + .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 66 ++++++++++--- .../GraphicsDevice/DirectX/RenderToolsDX.h | 95 +++---------------- .../Vulkan/RenderToolsVulkan.cpp | 45 ++++----- .../GraphicsDevice/Vulkan/RenderToolsVulkan.h | 16 +--- Source/Engine/Platform/Base/PlatformBase.cpp | 4 +- Source/Engine/Platform/Base/PlatformBase.h | 6 ++ 9 files changed, 132 insertions(+), 140 deletions(-) diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h index 2ce9e0d7d..f88b60ab0 100644 --- a/Source/Engine/Core/Math/Math.h +++ b/Source/Engine/Core/Math/Math.h @@ -604,7 +604,7 @@ namespace Math template static T AlignUpWithMask(T value, T mask) { - return (T)(value + mask & ~mask); + return (T)((value + mask) & ~mask); } template @@ -623,7 +623,7 @@ namespace Math static T AlignUp(T value, T alignment) { T mask = alignment - 1; - return (T)(value + mask & ~mask); + return (T)((value + mask) & ~mask); } /// @@ -648,7 +648,7 @@ namespace Math template static bool IsAligned(T value, T alignment) { - return 0 == (value & alignment - 1); + return 0 == (value & (alignment - 1)); } template diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index ad87261af..52a752b4a 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -9,6 +9,7 @@ #include "RenderTools.h" #include "Graphics.h" #include "Shaders/GPUShader.h" +#include "Shaders/GPUVertexLayout.h" #include "Async/DefaultGPUTasksExecutor.h" #include "Async/GPUTasksManager.h" #include "Engine/Core/Log.h" @@ -26,8 +27,6 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/Enums.h" -#include "Shaders/GPUVertexLayout.h" - GPUResourcePropertyBase::~GPUResourcePropertyBase() { const auto e = _resource; @@ -311,6 +310,16 @@ struct GPUDevice::PrivateData GPUDevice* GPUDevice::Instance = nullptr; +void GPUDevice::OnRequestingExit() +{ + if (Engine::FatalError != FatalErrorType::GPUCrash && + Engine::FatalError != FatalErrorType::GPUHang && + Engine::FatalError != FatalErrorType::GPUOutOfMemory) + return; + // TODO: get and log actual GPU memory used by the engine (API-specific) + DumpResourcesToLog(); +} + GPUDevice::GPUDevice(RendererType type, ShaderProfile profile) : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) , _state(DeviceState::Missing) @@ -354,6 +363,7 @@ bool GPUDevice::Init() LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory)); if (!Limits.HasCompute) LOG(Warning, "Compute Shaders are not supported"); + Engine::RequestingExit.Bind(this); return false; } @@ -450,9 +460,23 @@ void GPUDevice::DumpResourcesToLog() const output.AppendLine(); output.AppendLine(); + const bool printTypes[(int32)GPUResourceType::MAX] = + { + true, // RenderTarget + true, // Texture + true, // CubeTexture + true, // VolumeTexture + true, // Buffer + true, // Shader + false, // PipelineState + false, // Descriptor + false, // Query + false, // Sampler + }; for (int32 typeIndex = 0; typeIndex < (int32)GPUResourceType::MAX; typeIndex++) { const auto type = static_cast(typeIndex); + const auto printType = printTypes[typeIndex]; output.AppendFormat(TEXT("Group: {0}s"), ScriptingEnum::ToString(type)); output.AppendLine(); @@ -462,12 +486,12 @@ void GPUDevice::DumpResourcesToLog() const for (int32 i = 0; i < _resources.Count(); i++) { const GPUResource* resource = _resources[i]; - if (resource->GetResourceType() == type) + if (resource->GetResourceType() == type && resource->GetMemoryUsage() != 0) { count++; memUsage += resource->GetMemoryUsage(); auto str = resource->ToString(); - if (str.HasChars()) + if (str.HasChars() && printType) { output.Append(TEXT('\t')); output.Append(str); diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index 9fdba7885..7b8791850 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -97,6 +97,8 @@ protected: Array _resources; CriticalSection _resourcesLock; + void OnRequestingExit(); + protected: /// /// Initializes a new instance of the class. diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index d64df3857..90c7e6a74 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -130,7 +130,7 @@ DXGI_FORMAT RenderToolsDX::ToDxgiFormat(PixelFormat format) return PixelFormatToDXGIFormat[(int32)format]; } -const Char* RenderToolsDX::GetFeatureLevelString(const D3D_FEATURE_LEVEL featureLevel) +const Char* RenderToolsDX::GetFeatureLevelString(D3D_FEATURE_LEVEL featureLevel) { switch (featureLevel) { @@ -159,11 +159,24 @@ const Char* RenderToolsDX::GetFeatureLevelString(const D3D_FEATURE_LEVEL feature } } -String RenderToolsDX::GetD3DErrorString(HRESULT errorCode) +uint32 RenderToolsDX::CountAdapterOutputs(IDXGIAdapter* adapter) { - StringBuilder sb(256); + uint32 count = 0; + while (true) + { + IDXGIOutput* output; + HRESULT hr = adapter->EnumOutputs(count, &output); + if (FAILED(hr)) + { + break; + } + count++; + } + return count; +} - // Switch error code +void FormatD3DErrorString(HRESULT errorCode, StringBuilder& sb, HRESULT& removedReason) +{ #define D3DERR(x) case x: sb.Append(TEXT(#x)); break switch (errorCode) { @@ -184,6 +197,8 @@ String RenderToolsDX::GetD3DErrorString(HRESULT errorCode) // DirectX 11 D3DERR(D3D11_ERROR_FILE_NOT_FOUND); D3DERR(D3D11_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS); + D3DERR(D3D11_ERROR_TOO_MANY_UNIQUE_VIEW_OBJECTS); + D3DERR(D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD); // DirectX 12 //D3DERR(D3D12_ERROR_FILE_NOT_FOUND); @@ -221,22 +236,20 @@ String RenderToolsDX::GetD3DErrorString(HRESULT errorCode) #endif default: - { sb.AppendFormat(TEXT("0x{0:x}"), static_cast(errorCode)); - } break; } #undef D3DERR if (errorCode == DXGI_ERROR_DEVICE_REMOVED || errorCode == DXGI_ERROR_DEVICE_RESET || errorCode == DXGI_ERROR_DRIVER_INTERNAL_ERROR) { - HRESULT reason = S_OK; - const RendererType rendererType = GPUDevice::Instance ? GPUDevice::Instance->GetRendererType() : RendererType::Unknown; - void* nativePtr = GPUDevice::Instance ? GPUDevice::Instance->GetNativePtr() : nullptr; + GPUDevice* device = GPUDevice::Instance; + const RendererType rendererType = device ? device->GetRendererType() : RendererType::Unknown; + void* nativePtr = device ? device->GetNativePtr() : nullptr; #if GRAPHICS_API_DIRECTX12 if (rendererType == RendererType::DirectX12 && nativePtr) { - reason = ((ID3D12Device*)nativePtr)->GetDeviceRemovedReason(); + removedReason = ((ID3D12Device*)nativePtr)->GetDeviceRemovedReason(); } #endif #if GRAPHICS_API_DIRECTX11 @@ -244,11 +257,11 @@ String RenderToolsDX::GetD3DErrorString(HRESULT errorCode) rendererType == RendererType::DirectX10_1 || rendererType == RendererType::DirectX10) && nativePtr) { - reason = ((ID3D11Device*)nativePtr)->GetDeviceRemovedReason(); + removedReason = ((ID3D11Device*)nativePtr)->GetDeviceRemovedReason(); } #endif const Char* reasonStr = nullptr; - switch (reason) + switch (removedReason) { case DXGI_ERROR_DEVICE_HUNG: reasonStr = TEXT("HUNG"); @@ -269,8 +282,35 @@ String RenderToolsDX::GetD3DErrorString(HRESULT errorCode) if (reasonStr != nullptr) sb.AppendFormat(TEXT(", Device Removed Reason: {0}"), reasonStr); } +} - return sb.ToString(); +void RenderToolsDX::LogD3DResult(HRESULT result, const char* file, uint32 line, bool fatal) +{ + ASSERT_LOW_LAYER(FAILED(result)); + + // Process error and format message + StringBuilder sb; + HRESULT removedReason = S_OK; + sb.Append(TEXT("DirectX error: ")); + FormatD3DErrorString(result, sb, removedReason); + if (file) + sb.Append(TEXT(" at ")).Append(file).Append(':').Append(line); + const StringView msg(sb.ToStringView()); + + // Handle error + FatalErrorType errorType = FatalErrorType::None; + if (result == E_OUTOFMEMORY) + errorType = FatalErrorType::GPUOutOfMemory; + else if (removedReason != S_OK) + { + errorType = FatalErrorType::GPUCrash; + if (removedReason == DXGI_ERROR_DEVICE_HUNG) + errorType = FatalErrorType::GPUHang; + } + if (errorType != FatalErrorType::None) + Platform::Fatal(msg, nullptr, errorType); + else + Log::Logger::Write(fatal ? LogType::Fatal : LogType::Error, msg); } LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& semanticIndex) diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h index b34f2794d..b57f6f316 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.h @@ -17,12 +17,6 @@ namespace RenderToolsDX { #if GRAPHICS_API_DIRECTX11 - - /// - /// Converts Flax GPUResourceUsage to DirectX 11 resource D3D11_USAGE enum type. - /// - /// The Flax resource usage. - /// The DirectX 11 resource usage. inline D3D11_USAGE ToD3D11Usage(const GPUResourceUsage usage) { switch (usage) @@ -37,11 +31,6 @@ namespace RenderToolsDX } } - /// - /// Gets the cpu access flags from the resource usage. - /// - /// The Flax resource usage. - /// The DirectX 11 resource CPU usage. inline UINT GetDX11CpuAccessFlagsFromUsage(const GPUResourceUsage usage) { switch (usage) @@ -56,82 +45,26 @@ namespace RenderToolsDX return 0; } } - #endif - /// - /// Converts Flax Pixel Format to the DXGI Format. - /// - /// The Flax Pixel Format. - /// The DXGI Format - extern DXGI_FORMAT ToDxgiFormat(PixelFormat format); - - // Aligns location to the next multiple of align value. - template - T Align(T location, T align) - { - ASSERT(!((0 == align) || (align & (align - 1)))); - return ((location + (align - 1)) & ~(align - 1)); - } - - extern const Char* GetFeatureLevelString(const D3D_FEATURE_LEVEL featureLevel); - // Calculate a subresource index for a texture FORCE_INLINE uint32 CalcSubresourceIndex(uint32 mipSlice, uint32 arraySlice, uint32 mipLevels) { return mipSlice + arraySlice * mipLevels; } - inline uint32 CountAdapterOutputs(IDXGIAdapter* adapter) - { - uint32 count = 0; - while (true) - { - IDXGIOutput* output; - HRESULT hr = adapter->EnumOutputs(count, &output); - if (FAILED(hr)) - { - break; - } - count++; - } - return count; - } - - extern String GetD3DErrorString(HRESULT errorCode); - - inline void ValidateD3DResult(HRESULT result, const char* file = "", uint32 line = 0) - { - ASSERT(FAILED(result)); - const String& errorString = GetD3DErrorString(result); - LOG(Fatal, "DirectX error: {0} at {1}:{2}", errorString, String(file), line); - } - - inline void LogD3DResult(HRESULT result, const char* file = "", uint32 line = 0) - { - ASSERT(FAILED(result)); - const String& errorString = GetD3DErrorString(result); - LOG(Error, "DirectX error: {0} at {1}:{2}", errorString, String(file), line); - } - + DXGI_FORMAT ToDxgiFormat(PixelFormat format); + const Char* GetFeatureLevelString(D3D_FEATURE_LEVEL featureLevel); + uint32 CountAdapterOutputs(IDXGIAdapter* adapter); + void LogD3DResult(HRESULT result, const char* file = nullptr, uint32 line = 0, bool fatal = false); LPCSTR GetVertexInputSemantic(VertexElement::Types type, UINT& semanticIndex); }; -#if GPU_ENABLE_ASSERTION - // DirectX results validation -#define VALIDATE_DIRECTX_CALL(x) { HRESULT result = x; if (FAILED(result)) RenderToolsDX::ValidateD3DResult(result, __FILE__, __LINE__); } +#define VALIDATE_DIRECTX_CALL(x) { HRESULT result = x; if (FAILED(result)) RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__, true); } #define LOG_DIRECTX_RESULT(result) if (FAILED(result)) RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__) #define LOG_DIRECTX_RESULT_WITH_RETURN(result, returnValue) if (FAILED(result)) { RenderToolsDX::LogD3DResult(result, __FILE__, __LINE__); return returnValue; } -#else - -#define VALIDATE_DIRECTX_CALL(x) x -#define LOG_DIRECTX_RESULT(result) if(FAILED(result)) RenderToolsDX::LogD3DResult(result) -#define LOG_DIRECTX_RESULT_WITH_RETURN(result, returnValue) if(FAILED(result)) { RenderToolsDX::LogD3DResult(result); return returnValue; } - -#endif - #if GPU_ENABLE_DIAGNOSTICS || COMPILE_WITH_SHADER_COMPILER || GPU_ENABLE_RESOURCE_NAMING #include "Engine/Utilities/StringConverter.h" @@ -165,15 +98,15 @@ inline void SetDebugObjectName(IDXGIObject* resource, const char (&name)[NameLen #if GRAPHICS_API_DIRECTX11 template -inline void SetDebugObjectName(ID3D10DeviceChild* resource, const char(&name)[NameLength]) +inline void SetDebugObjectName(ID3D10DeviceChild* resource, const char (&name)[NameLength]) { - SetDebugObjectName(resource, name, NameLength - 1); + SetDebugObjectName(resource, name, NameLength - 1); } template -inline void SetDebugObjectName(ID3D11DeviceChild* resource, const char(&name)[NameLength]) +inline void SetDebugObjectName(ID3D11DeviceChild* resource, const char (&name)[NameLength]) { - SetDebugObjectName(resource, name, NameLength - 1); + SetDebugObjectName(resource, name, NameLength - 1); } #endif @@ -196,15 +129,15 @@ inline void SetDebugObjectName(ID3D12DeviceChild* resource, const char (&name)[N #if GRAPHICS_API_DIRECTX11 template -inline void SetDebugObjectName(ID3D10Resource* resource, const char(&name)[NameLength]) +inline void SetDebugObjectName(ID3D10Resource* resource, const char (&name)[NameLength]) { - resource->SetPrivateData(WKPDID_D3DDebugObjectName, NameLength - 1, name); + resource->SetPrivateData(WKPDID_D3DDebugObjectName, NameLength - 1, name); } template -inline void SetDebugObjectName(ID3D11Resource* resource, const char(&name)[NameLength]) +inline void SetDebugObjectName(ID3D11Resource* resource, const char (&name)[NameLength]) { - resource->SetPrivateData(WKPDID_D3DDebugObjectName, NameLength - 1, name); + resource->SetPrivateData(WKPDID_D3DDebugObjectName, NameLength - 1, name); } #endif @@ -241,7 +174,7 @@ inline void SetDebugObjectName(T* resource, const Char* data, UINT size) #else char* ansi = (char*)Allocator::Allocate(size + 1); StringUtils::ConvertUTF162ANSI(data, ansi, size); - ansi[size] ='\0'; + ansi[size] = '\0'; SetDebugObjectName(resource, ansi, size); Allocator::Free(ansi); #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index 7ee231472..f403b991b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -180,7 +180,7 @@ void RenderToolsVulkan::SetObjectName(VkDevice device, uint64 objectHandle, VkOb String RenderToolsVulkan::GetVkErrorString(VkResult result) { - StringBuilder sb(256); + StringBuilder sb(64); // Switch error code switch (result) @@ -228,33 +228,30 @@ String RenderToolsVulkan::GetVkErrorString(VkResult result) return sb.ToString(); } -void RenderToolsVulkan::ValidateVkResult(VkResult result, const char* file, uint32 line) +void RenderToolsVulkan::LogVkResult(VkResult result, const char* file, uint32 line, bool fatal) { - // Ensure result if invalid ASSERT(result != VK_SUCCESS); - // Get error string - const String& errorString = GetVkErrorString(result); + // Process error and format message + StringBuilder sb; + sb.Append(TEXT("Vulkan error: ")); + sb.Append(GetVkErrorString(result)); + if (file) + sb.Append(TEXT(" at ")).Append(file).Append(':').Append(line); + const StringView msg(sb.ToStringView()); - // Send error - LOG(Fatal, "Vulkan error: {0} at {1}:{2}", errorString, String(file), line); -} - -void RenderToolsVulkan::LogVkResult(VkResult result, const char* file, uint32 line) -{ - // Ensure result if invalid - ASSERT(result != VK_SUCCESS); - - // Get error string - const String& errorString = GetVkErrorString(result); - - // Send error - LOG(Error, "Vulkan error: {0} at {1}:{2}", errorString, String(file), line); -} - -void RenderToolsVulkan::LogVkResult(VkResult result) -{ - LogVkResult(result, "", 0); + // Handle error + FatalErrorType errorType = FatalErrorType::None; + if (result == VK_ERROR_OUT_OF_HOST_MEMORY || result == VK_ERROR_OUT_OF_DEVICE_MEMORY || result == VK_ERROR_OUT_OF_POOL_MEMORY) + errorType = FatalErrorType::GPUOutOfMemory; + else if (result == VK_TIMEOUT) + errorType = FatalErrorType::GPUHang; + else if (result == VK_ERROR_DEVICE_LOST || result == VK_ERROR_SURFACE_LOST_KHR || result == VK_ERROR_MEMORY_MAP_FAILED) + errorType = FatalErrorType::GPUCrash; + if (errorType != FatalErrorType::None) + Platform::Fatal(msg, nullptr, errorType); + else + Log::Logger::Write(fatal ? LogType::Fatal : LogType::Error, msg); } bool RenderToolsVulkan::HasExtension(const Array& extensions, const char* name) diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index b09f2a72f..8d5295db0 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -11,21 +11,13 @@ #if GRAPHICS_API_VULKAN -#if GPU_ENABLE_ASSERTION - -// Vulkan results validation -#define VALIDATE_VULKAN_RESULT(x) { VkResult result = x; if (result != VK_SUCCESS) RenderToolsVulkan::ValidateVkResult(result, __FILE__, __LINE__); } +#define VALIDATE_VULKAN_RESULT(x) { VkResult result = x; if (result != VK_SUCCESS) RenderToolsVulkan::LogVkResult(result, __FILE__, __LINE__, true); } #define LOG_VULKAN_RESULT(result) if (result != VK_SUCCESS) RenderToolsVulkan::LogVkResult(result, __FILE__, __LINE__) #define LOG_VULKAN_RESULT_WITH_RETURN(result) if (result != VK_SUCCESS) { RenderToolsVulkan::LogVkResult(result, __FILE__, __LINE__); return true; } +#if GPU_ENABLE_ASSERTION #define VK_SET_DEBUG_NAME(device, handle, type, name) RenderToolsVulkan::SetObjectName(device->Device, (uint64)handle, type, name) - #else - -#define VALIDATE_VULKAN_RESULT(x) x -#define LOG_VULKAN_RESULT(result) if (result != VK_SUCCESS) RenderToolsVulkan::LogVkResult(result) -#define LOG_VULKAN_RESULT_WITH_RETURN(result) if (result != VK_SUCCESS) { RenderToolsVulkan::LogVkResult(result); return true; } #define VK_SET_DEBUG_NAME(device, handle, type, name) - #endif /// @@ -46,9 +38,7 @@ public: #endif static String GetVkErrorString(VkResult result); - static void ValidateVkResult(VkResult result, const char* file, uint32 line); - static void LogVkResult(VkResult result, const char* file, uint32 line); - static void LogVkResult(VkResult result); + static void LogVkResult(VkResult result, const char* file = nullptr, uint32 line = 0, bool fatal = false); static inline VkPipelineStageFlags GetBufferBarrierFlags(VkAccessFlags accessFlags) { diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 6770cb9eb..da394d2fd 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -348,8 +348,8 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er LOG(Error, "Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedPhysicalMemory), (int32)(100 * memoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); LOG(Error, "Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedVirtualMemory), (int32)(100 * memoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); const ProcessMemoryStats processMemoryStats = Platform::GetProcessMemoryStats(); - LOG(Error, "Process Used Physical Memory: {0}", Utilities::BytesToText(processMemoryStats.UsedPhysicalMemory)); - LOG(Error, "Process Used Virtual Memory: {0}", Utilities::BytesToText(processMemoryStats.UsedVirtualMemory)); + LOG(Error, "Process Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedPhysicalMemory), (int32)(100 * processMemoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); + LOG(Error, "Process Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedVirtualMemory), (int32)(100 * processMemoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); } } if (Log::Logger::LogFilePath.HasChars()) diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index e34bf3015..670251d10 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -140,6 +140,12 @@ API_ENUM() enum class FatalErrorType Assertion, // Program run out of memory to allocate. OutOfMemory, + // The graphics device crashed, has been removed or restarted. + GPUCrash, + // The graphics device stopped responding (eg. incorrect rendering code or bug in driver). + GPUHang, + // The graphics device run out of video memory to allocate. + GPUOutOfMemory, }; API_INJECT_CODE(cpp, "#include \"Engine/Platform/Platform.h\""); From 6b6d808698ca4aa790eba7cec42100b90a6363f9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Feb 2025 09:20:06 +0100 Subject: [PATCH 184/215] Fix warning --- Source/Engine/Core/Types/Variant.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index c1677942a..41b626667 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -3948,7 +3948,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Float4: return Variant(Float4(*(Double2*)v.AsData, 0.0f, 0.0f)); case VariantType::Color: - return Variant(Color(((Double2*)v.AsData)->X, ((Double2*)v.AsData)->Y, 0.0f, 0.0f)); + return Variant(Color((float)((Double2*)v.AsData)->X, (float)((Double2*)v.AsData)->Y, 0.0f, 0.0f)); case VariantType::Double3: return Variant(Double3(*(Double2*)v.AsData, 0.0)); case VariantType::Double4: @@ -3984,7 +3984,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) case VariantType::Float4: return Variant(Float4(*(Double3*)v.AsData, 0.0f)); case VariantType::Color: - return Variant(Color(((Double3*)v.AsData)->X, ((Double3*)v.AsData)->Y, ((Double3*)v.AsData)->Z, 0.0f)); + return Variant(Color((float)((Double3*)v.AsData)->X, (float)((Double3*)v.AsData)->Y, (float)((Double3*)v.AsData)->Z, 0.0f)); case VariantType::Double2: return Variant(Double2(*(Double3*)v.AsData)); case VariantType::Double4: From 4aab1be3d60cd1732636c50ad367e4c935ab4617 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Feb 2025 09:20:48 +0100 Subject: [PATCH 185/215] Bump up materials version after merge with `master` --- Source/Engine/Graphics/Materials/MaterialShader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 99a97c8fa..d7549fed5 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 172 +#define MATERIAL_GRAPH_VERSION 173 class Material; class GPUShader; From ce335a0fe40d0f5efaa8d7e9a01ff5ac59d7f042 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Feb 2025 09:52:49 +0100 Subject: [PATCH 186/215] Use new api for span from stream --- Source/Engine/Level/Actor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index b21afd12f..47d3fb22d 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1999,7 +1999,7 @@ Actor* Actor::Clone() // Deserialize objects Array output; - if (FromBytes(ToSpan(stream.GetHandle(), (int32)stream.GetPosition()), output, modifier.Value) || output.IsEmpty()) + if (FromBytes(ToSpan(stream), output, modifier.Value) || output.IsEmpty()) return nullptr; return output[0]; } From 754d321ef09619ed6b65945b531e4aa88b2c5000 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 24 Feb 2025 23:28:35 +0100 Subject: [PATCH 187/215] Fix crash on incorrect index buffer size when building mesh buffers via `MeshAccessor` #3246 --- Source/Engine/Graphics/Models/MeshBase.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index 857a85fff..229edde7c 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -207,7 +207,7 @@ bool MeshAccessor::UpdateMesh(MeshBase* mesh, bool calculateBounds) { ibData = _data[IB].Get(); use16BitIndexBuffer = _formats[IB] == PixelFormat::R16_UInt; - triangles = _data[IB].Length() / PixelFormatExtensions::SizeInBytes(_formats[IB]); + triangles = _data[IB].Length() / PixelFormatExtensions::SizeInBytes(_formats[IB]) / 3; } if (mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout)) @@ -340,6 +340,11 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array= vbData.Count(), true); + const uint32 indices = triangles * 3; + if (use16BitIndexBuffer) + { + CHECK_RETURN(indices <= MAX_uint16, true); + } ASSERT(_model); GPUBuffer* vertexBuffer0 = nullptr; GPUBuffer* vertexBuffer1 = nullptr; @@ -369,7 +374,7 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const ArrayCreateBuffer(MESH_BUFFER_NAME(".IB")); - if (indexBuffer->Init(GPUBufferDescription::Index(use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32), triangles * 3, ibData))) + if (indexBuffer->Init(GPUBufferDescription::Index(use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32), indices, ibData))) goto ERROR_LOAD_END; // Init collision proxy From 21047ac8ac6ae7905ecf43dbe6d5887148f026fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 24 Feb 2025 23:46:44 +0100 Subject: [PATCH 188/215] Upgrade `CollisionCooking` to use new `MeshAccessor` --- Source/Engine/Graphics/Models/MeshAccessor.h | 10 +++ Source/Engine/Graphics/Models/MeshBase.cpp | 90 ++++++++++++++++++++ Source/Engine/Physics/CollisionCooking.cpp | 26 ++---- 3 files changed, 107 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Graphics/Models/MeshAccessor.h b/Source/Engine/Graphics/Models/MeshAccessor.h index 3fc7f689f..29ec406f2 100644 --- a/Source/Engine/Graphics/Models/MeshAccessor.h +++ b/Source/Engine/Graphics/Models/MeshAccessor.h @@ -89,6 +89,16 @@ public: // Check input data and stream type with IsLinear before calling. void SetLinear(const void* data); + + // Copies the contents of the input data span into the elements of this stream. + void Set(Span src); + void Set(Span src); + void Set(Span src); + + // Copies the contents of this stream into a destination data span. + void CopyTo(Span dst) const; + void CopyTo(Span dst) const; + void CopyTo(Span dst) const; }; private: diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index 229edde7c..5c38dd24b 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -89,6 +89,96 @@ void MeshAccessor::Stream::SetLinear(const void* data) Platform::MemoryCopy(_data.Get(), data, _data.Length()); } +void MeshAccessor::Stream::Set(Span src) +{ + const int32 count = GetCount(); + ASSERT(src.Length() >= count); + if (IsLinear(PixelFormat::R32G32_Float)) + { + Platform::MemoryCopy(_data.Get(), src.Get(), _data.Length()); + } + else + { + for (int32 i = 0; i < count; i++) + _sampler.Write(_data.Get() + i * _stride, Float4(src.Get()[i], 0, 0)); + } +} + +void MeshAccessor::Stream::Set(Span src) +{ + const int32 count = GetCount(); + ASSERT(src.Length() >= count); + if (IsLinear(PixelFormat::R32G32B32_Float)) + { + Platform::MemoryCopy(_data.Get(), src.Get(), _data.Length()); + } + else + { + for (int32 i = 0; i < count; i++) + _sampler.Write(_data.Get() + i * _stride, Float4(src.Get()[i], 0)); + } +} + +inline void MeshAccessor::Stream::Set(Span<::Color> src) +{ + const int32 count = GetCount(); + ASSERT(src.Length() >= count); + if (IsLinear(PixelFormat::R32G32B32A32_Float)) + { + Platform::MemoryCopy(_data.Get(), src.Get(), _data.Length()); + } + else + { + for (int32 i = 0; i < count; i++) + _sampler.Write(_data.Get() + i * _stride, Float4(src.Get()[i])); + } +} + +void MeshAccessor::Stream::CopyTo(Span dst) const +{ + const int32 count = GetCount(); + ASSERT(dst.Length() >= count); + if (IsLinear(PixelFormat::R32G32_Float)) + { + Platform::MemoryCopy(dst.Get(), _data.Get(), _data.Length()); + } + else + { + for (int32 i = 0; i < count; i++) + dst.Get()[i] = Float2(_sampler.Read(_data.Get() + i * _stride)); + } +} + +void MeshAccessor::Stream::CopyTo(Span dst) const +{ + const int32 count = GetCount(); + ASSERT(dst.Length() >= count); + if (IsLinear(PixelFormat::R32G32B32_Float)) + { + Platform::MemoryCopy(dst.Get(), _data.Get(), _data.Length()); + } + else + { + for (int32 i = 0; i < count; i++) + dst.Get()[i] = Float3(_sampler.Read(_data.Get() + i * _stride)); + } +} + +void MeshAccessor::Stream::CopyTo(Span<::Color> dst) const +{ + const int32 count = GetCount(); + ASSERT(dst.Length() >= count); + if (IsLinear(PixelFormat::R32G32B32A32_Float)) + { + Platform::MemoryCopy(dst.Get(), _data.Get(), _data.Length()); + } + else + { + for (int32 i = 0; i < count; i++) + dst.Get()[i] = ::Color(_sampler.Read(_data.Get() + i * _stride)); + } +} + bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span buffers) { CHECK_RETURN(mesh, true); diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 97b01c70f..cbfb91e5a 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -4,8 +4,10 @@ #include "CollisionCooking.h" #include "Engine/Threading/Task.h" +#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Async/GPUTask.h" #include "Engine/Graphics/Models/MeshBase.h" +#include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Threading/Threading.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Log.h" @@ -216,21 +218,11 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali const int32 vertexCount = vertexCounts[i]; if (vertexCount == 0) continue; - const int32 vStride = vData.Length() / vertexCount; - if (vStride == sizeof(Float3)) - Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, vData.Get(), vertexCount * sizeof(Float3)); - else - { - // This assumes that each vertex structure contains position as Float3 in the beginning - auto dst = finalVertexData.Get() + firstVertexIndex; - auto src = vData.Get(); - for (int32 j = 0; j < vertexCount; j++) - { - *dst++ = *(Float3*)src; - src += vStride; - - } - } + MeshAccessor accessor; + if (accessor.LoadBuffer(MeshBufferType::Vertex0, Span(vData), mesh.GetVertexBuffer(0)->GetVertexLayout())) + continue; + auto positionStream = accessor.Position(); + positionStream.CopyTo(Span(finalVertexData.Get() + firstVertexIndex, vertexCount)); vertexCounter += vertexCount; if (needIndexBuffer) @@ -242,9 +234,7 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali auto dst = finalIndexData.Get() + indexCounter; auto src = iData.Get(); for (int32 j = 0; j < indexCount; j++) - { *dst++ = firstVertexIndex + *src++; - } indexCounter += indexCount; } else @@ -252,9 +242,7 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali auto dst = finalIndexData.Get() + indexCounter; auto src = iData.Get(); for (int32 j = 0; j < indexCount; j++) - { *dst++ = firstVertexIndex + *src++; - } indexCounter += indexCount; } } From 953cbb6e405f3db4df937e8546d71af6ef190d0c Mon Sep 17 00:00:00 2001 From: Muzz Date: Tue, 25 Feb 2025 14:02:15 +1000 Subject: [PATCH 189/215] Advanced Bloom commit. --- .../Engine/Graphics/PostProcessSettings.cpp | 7 +- Source/Engine/Graphics/PostProcessSettings.h | 346 ++++++++++-------- Source/Engine/Renderer/PostProcessingPass.cpp | 213 ++++++----- Source/Engine/Renderer/PostProcessingPass.h | 27 +- Source/Shaders/PostProcessing.shader | 279 ++++++++++++-- 5 files changed, 567 insertions(+), 305 deletions(-) diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index 738cc4505..e717559f0 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -42,10 +42,13 @@ void BloomSettings::BlendWith(BloomSettings& other, float weight) BLEND_BOOL(Enabled); BLEND_FLOAT(Intensity); BLEND_FLOAT(Threshold); - BLEND_FLOAT(BlurSigma); - BLEND_FLOAT(Limit); + BLEND_FLOAT(ThresholdKnee); + BLEND_FLOAT(Clamp); + BLEND_FLOAT(BaseMix); + BLEND_FLOAT(HighMix); } + void ToneMappingSettings::BlendWith(ToneMappingSettings& other, float weight) { const bool isHalf = weight >= 0.5f; diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index dc3895954..f2297f240 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -236,43 +236,43 @@ API_STRUCT() struct FLAXENGINE_API AmbientOcclusionSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - AmbientOcclusionSettingsOverride OverrideFlags = Override::None; + AmbientOcclusionSettingsOverride OverrideFlags = Override::None; /// /// Enable/disable ambient occlusion effect. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Enabled)") - bool Enabled = true; + bool Enabled = true; /// /// Ambient occlusion intensity. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Intensity)") - float Intensity = 0.8f; + float Intensity = 0.8f; /// /// Ambient occlusion power. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Power)") - float Power = 0.75f; + float Power = 0.75f; /// /// Ambient occlusion check range radius. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Radius)") - float Radius = 0.7f; + float Radius = 0.7f; /// /// Ambient occlusion fade out end distance from camera (in world units). /// API_FIELD(Attributes="Limit(0.0f), EditorOrder(4), PostProcessSetting((int)AmbientOcclusionSettingsOverride.FadeOutDistance)") - float FadeOutDistance = 5000.0f; + float FadeOutDistance = 5000.0f; /// /// Ambient occlusion fade distance (in world units). Defines the size of the effect fade from fully visible to fully invisible at FadeOutDistance. /// API_FIELD(Attributes="Limit(0.0f), EditorOrder(5), PostProcessSetting((int)AmbientOcclusionSettingsOverride.FadeDistance)") - float FadeDistance = 500.0f; + float FadeDistance = 500.0f; public: /// @@ -342,43 +342,43 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - GlobalIlluminationSettingsOverride OverrideFlags = Override::None; + GlobalIlluminationSettingsOverride OverrideFlags = Override::None; /// /// The Global Illumination mode to use. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Mode)") - GlobalIlluminationMode Mode = GlobalIlluminationMode::None; + GlobalIlluminationMode Mode = GlobalIlluminationMode::None; /// /// Global Illumination indirect lighting intensity scale. Can be used to boost or reduce GI effect. /// API_FIELD(Attributes="EditorOrder(10), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 1.0f; /// /// Global Illumination infinite indirect lighting bounce intensity scale. Can be used to boost or reduce GI effect for the light bouncing on the surfaces. /// API_FIELD(Attributes="EditorOrder(11), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.BounceIntensity)") - float BounceIntensity = 1.0f; + float BounceIntensity = 1.0f; /// /// Defines how quickly GI blends between the current frame and the history buffer. Lower values update GI faster, but with more jittering and noise. If the camera in your game doesn't move much, we recommend values closer to 1. /// API_FIELD(Attributes="EditorOrder(20), Limit(0, 1), PostProcessSetting((int)GlobalIlluminationSettingsOverride.TemporalResponse)") - float TemporalResponse = 0.9f; + float TemporalResponse = 0.9f; /// /// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance. /// API_FIELD(Attributes="EditorOrder(30), Limit(1000), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Distance)") - float Distance = 20000.0f; + float Distance = 20000.0f; /// /// The irradiance lighting outside the GI range used as a fallback to prevent pure-black scene outside the Global Illumination range. /// API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)GlobalIlluminationSettingsOverride.FallbackIrradiance)") - Color FallbackIrradiance = Color::Black; + Color FallbackIrradiance = Color::Black; public: /// @@ -415,19 +415,29 @@ API_ENUM(Attributes="Flags") enum class BloomSettingsOverride : int32 Threshold = 1 << 2, /// - /// Overrides property. + /// Overrides property. /// - BlurSigma = 1 << 3, + ThresholdKnee = 1 << 3, /// - /// Overrides property. + /// Overrides property. /// - Limit = 1 << 4, + Clamp = 1 << 4, + + /// + /// Overrides property. + /// + BaseMix = 1 << 5, + + /// + /// Overrides property. + /// + HighMix = 1 << 6, /// /// All properties. /// - All = Enabled | Intensity | Threshold | BlurSigma | Limit, + All = Enabled | Intensity | Threshold | ThresholdKnee | Clamp | BaseMix | HighMix, }; /// @@ -443,37 +453,49 @@ API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - BloomSettingsOverride OverrideFlags = Override::None; + BloomSettingsOverride OverrideFlags = Override::None; /// /// If checked, bloom effect will be rendered. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)BloomSettingsOverride.Enabled)") - bool Enabled = true; + bool Enabled = true; /// - /// Bloom effect strength. Set a value of 0 to disabled it, while higher values increase the effect. + /// Overall bloom effect strength. Higher values create a stronger glow effect. /// - API_FIELD(Attributes="Limit(0, 20.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)") - float Intensity = 1.0f; + API_FIELD(Attributes "Limit(0, 100.0f, 0.001f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)") + float Intensity = 1.0f; /// - /// Minimum pixel brightness value to start blooming. Values below this threshold are skipped. + /// Luminance threshold where bloom begins. /// - API_FIELD(Attributes="Limit(0, 15.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)BloomSettingsOverride.Threshold)") - float Threshold = 3.0f; + API_FIELD(Attributes="Limit(0, 100.0f, 0.1f), EditorOrder(2), PostProcessSetting((int)BloomSettingsOverride.Threshold)") + float Threshold = 1.0f; /// - /// This affects the fall-off of the bloom. It's the standard deviation (sigma) used in the Gaussian blur formula when calculating the kernel of the bloom. + /// Controls the threshold rolloff curve. Higher values create a softer transition. /// - API_FIELD(Attributes="Limit(0, 20.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)BloomSettingsOverride.BlurSigma)") - float BlurSigma = 4.0f; + API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)BloomSettingsOverride.ThresholdKnee)") + float ThresholdKnee = 0.5f; /// - /// Bloom effect brightness limit. Pixels with higher luminance will be capped to this brightness level. + /// Maximum brightness limit for bloom highlights. /// - API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(4), PostProcessSetting((int)BloomSettingsOverride.Limit)") - float Limit = 10.0f; + API_FIELD(Attributes="Limit(0, 100.0f, 0.1f), EditorOrder(4), PostProcessSetting((int)BloomSettingsOverride.Clamp)") + float Clamp = 3.0f; + + /// + /// Base mip contribution for wider, softer bloom. + /// + API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)BloomSettingsOverride.BaseMix)") + float BaseMix = 0.6f; + + /// + /// High mip contribution for tighter, core bloom. + /// + API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)BloomSettingsOverride.HighMix)") + float HighMix = 1.0f; public: /// @@ -487,7 +509,7 @@ public: /// /// The structure members override flags. /// -API_ENUM(Attributes="Flags") enum class ToneMappingSettingsOverride : int32 +API_ENUM(Attributes ="Flags") enum class ToneMappingSettingsOverride : int32 { /// /// None properties. @@ -528,25 +550,25 @@ API_STRUCT() struct FLAXENGINE_API ToneMappingSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - ToneMappingSettingsOverride OverrideFlags = Override::None; + ToneMappingSettingsOverride OverrideFlags = Override::None; /// /// Adjusts the white balance in relation to the temperature of the light in the scene. When the light temperature and this one match the light will appear white. When a value is used that is higher than the light in the scene it will yield a "warm" or yellow color, and, conversely, if the value is lower, it would yield a "cool" or blue color. /// API_FIELD(Attributes="Limit(1500, 15000), EditorOrder(0), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTemperature)") - float WhiteTemperature = 6500.0f; + float WhiteTemperature = 6500.0f; /// /// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. /// API_FIELD(Attributes="Limit(-1, 1, 0.001f), EditorOrder(1), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTint)") - float WhiteTint = 0.0f; + float WhiteTint = 0.0f; /// /// The tone mapping mode to use for the color grading process. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)ToneMappingSettingsOverride.Mode)") - ToneMappingMode Mode = ToneMappingMode::ACES; + ToneMappingMode Mode = ToneMappingMode::ACES; public: /// @@ -706,7 +728,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - ColorGradingSettingsOverride OverrideFlags = Override::None; + ColorGradingSettingsOverride OverrideFlags = Override::None; // Global @@ -714,31 +736,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(0), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturation), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Saturation\")") - Float4 ColorSaturation = Float4::One; + Float4 ColorSaturation = Float4::One; /// /// Gets or sets the color contrast (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(1), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrast), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Contrast\")") - Float4 ColorContrast = Float4::One; + Float4 ColorContrast = Float4::One; /// /// Gets or sets the color gamma (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(2), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGamma), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Gamma\")") - Float4 ColorGamma = Float4::One; + Float4 ColorGamma = Float4::One; /// /// Gets or sets the color gain (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(3), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGain), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Gain\")") - Float4 ColorGain = Float4::One; + Float4 ColorGain = Float4::One; /// /// Gets or sets the color offset (applies globally to the whole image). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(4), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffset), Limit(-1, 1, 0.001f), EditorDisplay(\"Global\", \"Offset\")") - Float4 ColorOffset = Float4::Zero; + Float4 ColorOffset = Float4::Zero; // Shadows @@ -746,31 +768,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(5), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturationShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Saturation\")") - Float4 ColorSaturationShadows = Float4::One; + Float4 ColorSaturationShadows = Float4::One; /// /// Gets or sets the color contrast (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(6), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrastShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Contrast\")") - Float4 ColorContrastShadows = Float4::One; + Float4 ColorContrastShadows = Float4::One; /// /// Gets or sets the color gamma (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(7), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGammaShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Gamma\")") - Float4 ColorGammaShadows = Float4::One; + Float4 ColorGammaShadows = Float4::One; /// /// Gets or sets the color gain (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(8), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGainShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Gain\")") - Float4 ColorGainShadows = Float4::One; + Float4 ColorGainShadows = Float4::One; /// /// Gets or sets the color offset (applies to shadows only). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(9), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffsetShadows), Limit(-1, 1, 0.001f), EditorDisplay(\"Shadows\", \"Shadows Offset\")") - Float4 ColorOffsetShadows = Float4::Zero; + Float4 ColorOffsetShadows = Float4::Zero; // Midtones @@ -778,31 +800,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(10), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturationMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Saturation\")") - Float4 ColorSaturationMidtones = Float4::One; + Float4 ColorSaturationMidtones = Float4::One; /// /// Gets or sets the color contrast (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(11), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrastMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Contrast\")") - Float4 ColorContrastMidtones = Float4::One; + Float4 ColorContrastMidtones = Float4::One; /// /// Gets or sets the color gamma (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(12), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGammaMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Gamma\")") - Float4 ColorGammaMidtones = Float4::One; + Float4 ColorGammaMidtones = Float4::One; /// /// Gets or sets the color gain (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(13), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGainMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Gain\")") - Float4 ColorGainMidtones = Float4::One; + Float4 ColorGainMidtones = Float4::One; /// /// Gets or sets the color offset (applies to midtones only). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(14), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffsetMidtones), Limit(-1, 1, 0.001f), EditorDisplay(\"Midtones\", \"Midtones Offset\")") - Float4 ColorOffsetMidtones = Float4::Zero; + Float4 ColorOffsetMidtones = Float4::Zero; // Highlights @@ -810,31 +832,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(15), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturationHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Saturation\")") - Float4 ColorSaturationHighlights = Float4::One; + Float4 ColorSaturationHighlights = Float4::One; /// /// Gets or sets the color contrast (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(16), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrastHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Contrast\")") - Float4 ColorContrastHighlights = Float4::One; + Float4 ColorContrastHighlights = Float4::One; /// /// Gets or sets the color gamma (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(17), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGammaHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Gamma\")") - Float4 ColorGammaHighlights = Float4::One; + Float4 ColorGammaHighlights = Float4::One; /// /// Gets or sets the color gain (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(18), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGainHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Gain\")") - Float4 ColorGainHighlights = Float4::One; + Float4 ColorGainHighlights = Float4::One; /// /// Gets or sets the color offset (applies to highlights only). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(19), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffsetHighlights), Limit(-1, 1, 0.001f), EditorDisplay(\"Highlights\", \"Highlights Offset\")") - Float4 ColorOffsetHighlights = Float4::Zero; + Float4 ColorOffsetHighlights = Float4::Zero; // @@ -842,13 +864,13 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The shadows maximum value. Default is 0.09. /// API_FIELD(Attributes="Limit(-1, 1, 0.01f), EditorOrder(20), PostProcessSetting((int)ColorGradingSettingsOverride.ShadowsMax)") - float ShadowsMax = 0.09f; + float ShadowsMax = 0.09f; /// /// The highlights minimum value. Default is 0.5. /// API_FIELD(Attributes="Limit(-1, 1, 0.01f), EditorOrder(21), PostProcessSetting((int)ColorGradingSettingsOverride.HighlightsMin)") - float HighlightsMin = 0.5f; + float HighlightsMin = 0.5f; // @@ -856,13 +878,13 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The Lookup Table (LUT) used to perform color correction. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)") - SoftAssetReference LutTexture; + SoftAssetReference LutTexture; /// /// The LUT blending weight (normalized to range 0-1). Default is 1.0. /// API_FIELD(Attributes="Limit(0, 1, 0.01f), EditorOrder(23), PostProcessSetting((int)ColorGradingSettingsOverride.LutWeight)") - float LutWeight = 1.0f; + float LutWeight = 1.0f; public: /// @@ -947,61 +969,61 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - EyeAdaptationSettingsOverride OverrideFlags = Override::None; + EyeAdaptationSettingsOverride OverrideFlags = Override::None; /// /// The effect rendering mode used for the exposure processing. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)EyeAdaptationSettingsOverride.Mode)") - EyeAdaptationMode Mode = EyeAdaptationMode::AutomaticHistogram; + EyeAdaptationMode Mode = EyeAdaptationMode::AutomaticHistogram; /// /// The speed at which the exposure changes when the scene brightness moves from a dark area to a bright area (brightness goes up). /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedUp)") - float SpeedUp = 3.0f; + float SpeedUp = 3.0f; /// /// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down). /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)") - float SpeedDown = 10.0f; + float SpeedDown = 10.0f; /// /// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.). /// API_FIELD(Attributes="Limit(-100, 100, 0.01f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.PreExposure)") - float PreExposure = 0.0f; + float PreExposure = 0.0f; /// /// The post-exposure value applied to the scene color after performing post-processing (such as bloom, lens flares, etc.) but before color grading and tone mapping. /// API_FIELD(Attributes="Limit(-100, 100, 0.01f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.PostExposure)") - float PostExposure = 0.0f; + float PostExposure = 0.0f; /// /// The minimum brightness for the auto exposure which limits the lower brightness the eye can adapt within. /// API_FIELD(Attributes="Limit(0, 20.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)EyeAdaptationSettingsOverride.MinBrightness), EditorDisplay(null, \"Minimum Brightness\")") - float MinBrightness = 0.03f; + float MinBrightness = 0.03f; /// /// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")") - float MaxBrightness = 15.0f; + float MaxBrightness = 15.0f; /// /// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode. /// API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramLowPercent)") - float HistogramLowPercent = 70.0f; + float HistogramLowPercent = 70.0f; /// /// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode. /// API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)") - float HistogramHighPercent = 90.0f; + float HistogramHighPercent = 90.0f; public: /// @@ -1081,55 +1103,55 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - CameraArtifactsSettingsOverride OverrideFlags = Override::None; + CameraArtifactsSettingsOverride OverrideFlags = Override::None; /// /// Strength of the vignette effect. Value 0 hides it. /// API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)") - float VignetteIntensity = 0.4f; + float VignetteIntensity = 0.4f; /// /// Color of the vignette. /// API_FIELD(Attributes="EditorOrder(1), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteColor)") - Float3 VignetteColor = Float3(0, 0, 0.001f); + Float3 VignetteColor = Float3(0, 0, 0.001f); /// /// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. /// API_FIELD(Attributes="Limit(0.0001f, 2.0f, 0.001f), EditorOrder(2), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteShapeFactor)") - float VignetteShapeFactor = 0.125f; + float VignetteShapeFactor = 0.125f; /// /// Intensity of the grain filter. A value of 0 hides it. /// API_FIELD(Attributes="Limit(0.0f, 2.0f, 0.005f), EditorOrder(3), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainAmount)") - float GrainAmount = 0.006f; + float GrainAmount = 0.006f; /// /// Size of the grain particles. /// API_FIELD(Attributes="Limit(1.0f, 3.0f, 0.01f), EditorOrder(4), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainParticleSize)") - float GrainParticleSize = 1.6f; + float GrainParticleSize = 1.6f; /// /// Speed of the grain particle animation. /// API_FIELD(Attributes="Limit(0.0f, 10.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainSpeed)") - float GrainSpeed = 1.0f; + float GrainSpeed = 1.0f; /// /// Controls the chromatic aberration effect strength. A value of 0 hides it. /// API_FIELD(Attributes="Limit(0.0f, 1.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)CameraArtifactsSettingsOverride.ChromaticDistortion)") - float ChromaticDistortion = 0.0f; + float ChromaticDistortion = 0.0f; /// /// Screen tint color (the alpha channel defines the blending factor). /// API_FIELD(Attributes="DefaultValue(typeof(Color), \"0,0,0,0\"), EditorOrder(7), PostProcessSetting((int)CameraArtifactsSettingsOverride.ScreenFadeColor)") - Color ScreenFadeColor = Color::Transparent; + Color ScreenFadeColor = Color::Transparent; public: /// @@ -1229,79 +1251,79 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - LensFlaresSettingsOverride OverrideFlags = Override::None; + LensFlaresSettingsOverride OverrideFlags = Override::None; /// /// Strength of the effect. A value of 0 disables it. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)") - float Intensity = 0.5f; + float Intensity = 0.5f; /// /// Amount of lens flares ghosts. /// API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)") - int32 Ghosts = 4; + int32 Ghosts = 4; /// /// Lens flares halo width. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)") - float HaloWidth = 0.04f; + float HaloWidth = 0.04f; /// /// Lens flares halo intensity. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)") - float HaloIntensity = 0.5f; + float HaloIntensity = 0.5f; /// /// Ghost samples dispersal parameter. /// API_FIELD(Attributes="EditorOrder(4), PostProcessSetting((int)LensFlaresSettingsOverride.GhostDispersal)") - float GhostDispersal = 0.3f; + float GhostDispersal = 0.3f; /// /// Lens flares color distortion parameter. /// API_FIELD(Attributes="EditorOrder(5), PostProcessSetting((int)LensFlaresSettingsOverride.Distortion)") - float Distortion = 1.5f; + float Distortion = 1.5f; /// /// Input image brightness threshold. Added to input pixels. /// API_FIELD(Attributes="EditorOrder(6), PostProcessSetting((int)LensFlaresSettingsOverride.ThresholdBias)") - float ThresholdBias = -0.5f; + float ThresholdBias = -0.5f; /// /// Input image brightness threshold scale. Used to multiply input pixels. /// API_FIELD(Attributes="EditorOrder(7), PostProcessSetting((int)LensFlaresSettingsOverride.ThresholdScale)") - float ThresholdScale = 0.22f; + float ThresholdScale = 0.22f; /// /// Fullscreen lens dirt texture. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)") - SoftAssetReference LensDirt; + SoftAssetReference LensDirt; /// /// Fullscreen lens dirt intensity parameter. Allows tuning dirt visibility. /// API_FIELD(Attributes="Limit(0, 100, 0.01f), EditorOrder(9), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirtIntensity)") - float LensDirtIntensity = 1.0f; + float LensDirtIntensity = 1.0f; /// /// Custom lens color texture (1D) used for lens color spectrum. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)") - SoftAssetReference LensColor; + SoftAssetReference LensColor; /// /// Custom lens star texture sampled by lens flares. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)") - SoftAssetReference LensStar; + SoftAssetReference LensStar; public: /// @@ -1421,103 +1443,103 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - DepthOfFieldSettingsOverride OverrideFlags = Override::None; + DepthOfFieldSettingsOverride OverrideFlags = Override::None; /// /// If checked, the depth of field effect will be visible. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)DepthOfFieldSettingsOverride.Enabled)") - bool Enabled = false; + bool Enabled = false; /// /// The blur intensity in the out-of-focus areas. Allows reducing the blur amount by scaling down the Gaussian Blur radius. Normalized to range 0-1. /// API_FIELD(Attributes="Limit(0, 1, 0.01f), EditorOrder(1), PostProcessSetting((int)DepthOfFieldSettingsOverride.BlurStrength)") - float BlurStrength = 1.0f; + float BlurStrength = 1.0f; /// /// The distance in World Units from the camera that acts as the center of the region where the scene is perfectly in focus and no blurring occurs. /// API_FIELD(Attributes="Limit(0), EditorOrder(2), PostProcessSetting((int)DepthOfFieldSettingsOverride.FocalDistance)") - float FocalDistance = 1700.0f; + float FocalDistance = 1700.0f; /// /// The distance in World Units beyond the focal distance where the scene is perfectly in focus and no blurring occurs. /// API_FIELD(Attributes="Limit(0), EditorOrder(3), PostProcessSetting((int)DepthOfFieldSettingsOverride.FocalRegion)") - float FocalRegion = 3000.0f; + float FocalRegion = 3000.0f; /// /// The distance in World Units from the focal region on the side nearer to the camera over which the scene transitions from focused to blurred. /// API_FIELD(Attributes="Limit(0), EditorOrder(4), PostProcessSetting((int)DepthOfFieldSettingsOverride.NearTransitionRange)") - float NearTransitionRange = 300.0f; + float NearTransitionRange = 300.0f; /// /// The distance in World Units from the focal region on the side farther from the camera over which the scene transitions from focused to blurred. /// API_FIELD(Attributes="Limit(0), EditorOrder(5), PostProcessSetting((int)DepthOfFieldSettingsOverride.FarTransitionRange)") - float FarTransitionRange = 500.0f; + float FarTransitionRange = 500.0f; /// /// The distance in World Units which describes border after that there is no blur (useful to disable DoF on sky). Use 0 to disable that feature. /// API_FIELD(Attributes="Limit(0, float.MaxValue, 2), EditorOrder(6), PostProcessSetting((int)DepthOfFieldSettingsOverride.DepthLimit)") - float DepthLimit = 0.0f; + float DepthLimit = 0.0f; /// /// If checked, bokeh shapes will be rendered. /// API_FIELD(Attributes="EditorOrder(7), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehEnabled)") - bool BokehEnabled = true; + bool BokehEnabled = true; /// /// Controls size of the bokeh shapes. /// API_FIELD(Attributes="Limit(0, 200.0f, 0.1f), EditorOrder(8), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehSize)") - float BokehSize = 25.0f; + float BokehSize = 25.0f; /// /// Controls brightness of the bokeh shapes. Can be used to fade them or make more intense. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(9), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBrightness)") - float BokehBrightness = 1.0f; + float BokehBrightness = 1.0f; /// /// Defines the type of the bokeh shapes. /// API_FIELD(Attributes="EditorOrder(10), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShape)") - BokehShapeType BokehShape = BokehShapeType::Octagon; + BokehShapeType BokehShape = BokehShapeType::Octagon; /// /// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px). /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)") - SoftAssetReference BokehShapeCustom; + SoftAssetReference BokehShapeCustom; /// /// The minimum pixel brightness to create the bokeh. Pixels with lower brightness will be skipped. /// API_FIELD(Attributes="Limit(0, 10000.0f, 0.01f), EditorOrder(12), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBrightnessThreshold)") - float BokehBrightnessThreshold = 3.0f; + float BokehBrightnessThreshold = 3.0f; /// /// Depth of Field bokeh shape blur threshold. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.001f), EditorOrder(13), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBlurThreshold)") - float BokehBlurThreshold = 0.05f; + float BokehBlurThreshold = 0.05f; /// /// Controls bokeh shape brightness falloff. Higher values reduce bokeh visibility. /// API_FIELD(Attributes="Limit(0, 2.0f, 0.001f), EditorOrder(14), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehFalloff)") - float BokehFalloff = 0.5f; + float BokehFalloff = 0.5f; /// /// Controls bokeh shape generation for depth discontinuities. /// API_FIELD(Attributes="Limit(0, 5.0f, 0.001f), EditorOrder(15), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehDepthCutoff)") - float BokehDepthCutoff = 1.5f; + float BokehDepthCutoff = 1.5f; public: /// @@ -1577,31 +1599,31 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - MotionBlurSettingsOverride OverrideFlags = Override::None; + MotionBlurSettingsOverride OverrideFlags = Override::None; /// /// If checked, the motion blur effect will be rendered. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)MotionBlurSettingsOverride.Enabled)") - bool Enabled = true; + bool Enabled = true; /// /// The blur effect strength. A value of 0 disables it, while higher values increase the effect. /// API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)") - float Scale = 0.5f; + float Scale = 0.5f; /// /// The amount of sample points used during motion blur rendering. It affects blur quality and performance. /// API_FIELD(Attributes="Limit(4, 32, 0.1f), EditorOrder(2), PostProcessSetting((int)MotionBlurSettingsOverride.SampleCount)") - int32 SampleCount = 10; + int32 SampleCount = 10; /// /// The motion vectors texture resolution. Motion blur uses a per-pixel motion vector buffer that contains an objects movement information. Use a lower resolution to improve performance. /// API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)MotionBlurSettingsOverride.MotionVectorsResolution)") - ResolutionMode MotionVectorsResolution = ResolutionMode::Half; + ResolutionMode MotionVectorsResolution = ResolutionMode::Half; public: /// @@ -1721,103 +1743,103 @@ API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializabl /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - ScreenSpaceReflectionsSettingsOverride OverrideFlags = Override::None; + ScreenSpaceReflectionsSettingsOverride OverrideFlags = Override::None; /// /// The effect intensity (normalized to range [0;1]). Use 0 to disable it. /// API_FIELD(Attributes="Limit(0, 5.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 1.0f; /// /// The reflections tracing mode. /// API_FIELD(Attributes="EditorOrder(1), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TraceMode)") - ReflectionsTraceMode TraceMode = ReflectionsTraceMode::ScreenTracing; + ReflectionsTraceMode TraceMode = ReflectionsTraceMode::ScreenTracing; /// /// The depth buffer downscale option to optimize raycast performance. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.DepthResolution)") - ResolutionMode DepthResolution = ResolutionMode::Half; + ResolutionMode DepthResolution = ResolutionMode::Half; /// /// The raycast resolution. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RayTracePassResolution)") - ResolutionMode RayTracePassResolution = ResolutionMode::Half; + ResolutionMode RayTracePassResolution = ResolutionMode::Half; /// /// The reflection spread parameter. This value controls source roughness effect on reflections blur. Smaller values produce wider reflections spread but also introduce more noise. Higher values provide more mirror-like reflections. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(10), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.BRDFBias), EditorDisplay(null, \"BRDF Bias\")") - float BRDFBias = 0.82f; + float BRDFBias = 0.82f; /// /// The maximum amount of roughness a material must have to reflect the scene. For example, if this value is set to 0.4, only materials with a roughness value of 0.4 or below reflect the scene. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(15), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RoughnessThreshold)") - float RoughnessThreshold = 0.45f; + float RoughnessThreshold = 0.45f; /// /// The offset of the raycast origin. Lower values produce more correct reflection placement, but produce more artifacts. We recommend values of 0.3 or lower. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(20), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.WorldAntiSelfOcclusionBias)") - float WorldAntiSelfOcclusionBias = 0.1f; + float WorldAntiSelfOcclusionBias = 0.1f; /// /// The raycast resolution. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(25), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolvePassResolution)") - ResolutionMode ResolvePassResolution = ResolutionMode::Full; + ResolutionMode ResolvePassResolution = ResolutionMode::Full; /// /// The number of rays used to resolve the reflection color. Higher values provide better quality but reduce effect performance. Use value of 1 for the best performance at cost of quality. /// API_FIELD(Attributes="Limit(1, 8), EditorOrder(26), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolveSamples)") - int32 ResolveSamples = 4; + int32 ResolveSamples = 4; /// /// The point at which the far edges of the reflection begin to fade. Has no effect on performance. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.02f), EditorOrder(30), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.EdgeFadeFactor)") - float EdgeFadeFactor = 0.1f; + float EdgeFadeFactor = 0.1f; /// /// The effect fade out end distance from camera (in world units). /// API_FIELD(Attributes="Limit(0), EditorOrder(31), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.FadeOutDistance)") - float FadeOutDistance = 5000.0f; + float FadeOutDistance = 5000.0f; /// /// The effect fade distance (in world units). Defines the size of the effect fade from fully visible to fully invisible at FadeOutDistance. /// API_FIELD(Attributes="Limit(0), EditorOrder(32), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.FadeDistance)") - float FadeDistance = 500.0f; + float FadeDistance = 500.0f; /// /// "The input color buffer downscale mode that uses blurred mipmaps when resolving the reflection color. Produces more realistic results by blurring distant parts of reflections in rough (low-gloss) materials. It also improves performance on most platforms but uses more memory. /// API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.UseColorBufferMips), EditorDisplay(null, \"Use Color Buffer Mips\")") - bool UseColorBufferMips = true; + bool UseColorBufferMips = true; /// /// If checked, enables the temporal pass. Reduces noise, but produces an animated "jittering" effect that's sometimes noticeable. If disabled, the properties below have no effect. /// API_FIELD(Attributes="EditorOrder(50), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalEffect), EditorDisplay(null, \"Enable Temporal Effect\")") - bool TemporalEffect = true; + bool TemporalEffect = true; /// /// The intensity of the temporal effect. Lower values produce reflections faster, but more noise. /// API_FIELD(Attributes="Limit(0, 20.0f, 0.5f), EditorOrder(55), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalScale)") - float TemporalScale = 8.0f; + float TemporalScale = 8.0f; /// /// Defines how quickly reflections blend between the reflection in the current frame and the history buffer. Lower values produce reflections faster, but with more jittering. If the camera in your game doesn't move much, we recommend values closer to 1. /// API_FIELD(Attributes="Limit(0.05f, 1.0f, 0.01f), EditorOrder(60), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalResponse)") - float TemporalResponse = 0.8f; + float TemporalResponse = 0.8f; public: /// @@ -1902,61 +1924,61 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - AntiAliasingSettingsOverride OverrideFlags = Override::None; + AntiAliasingSettingsOverride OverrideFlags = Override::None; /// /// The anti-aliasing effect mode. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)AntiAliasingSettingsOverride.Mode)") - AntialiasingMode Mode = AntialiasingMode::FastApproximateAntialiasing; + AntialiasingMode Mode = AntialiasingMode::FastApproximateAntialiasing; /// /// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output. /// API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_JitterSpread = 1.0f; + float TAA_JitterSpread = 1.0f; /// /// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts. /// API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_Sharpness = 0.1f; + float TAA_Sharpness = 0.1f; /// /// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion. /// API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_StationaryBlending = 0.95f; + float TAA_StationaryBlending = 0.95f; /// /// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion. /// API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_MotionBlending = 0.85f; + float TAA_MotionBlending = 0.85f; /// /// The sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// - API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(10), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_SharpeningAmount), EditorDisplay(null, \"CAS Sharpening Amount\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_SharpeningAmount = 0.0f; + API_FIELD(Attributes="Limit(0, 10f, 0.001f), EditorOrder(10), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_SharpeningAmount), EditorDisplay(null, \"CAS Sharpening Amount\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_SharpeningAmount = 0.0f; /// /// The edge sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// - API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(11), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_EdgeSharpening), EditorDisplay(null, \"CAS Edge Sharpening\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_EdgeSharpening = 0.5f; + API_FIELD(Attributes="Limit(0, 10f, 0.001f), EditorOrder(11), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_EdgeSharpening), EditorDisplay(null, \"CAS Edge Sharpening\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_EdgeSharpening = 0.5f; /// /// The minimum edge threshold for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// - API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(12), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_MinEdgeThreshold), EditorDisplay(null, \"CAS Min Edge Threshold\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_MinEdgeThreshold = 0.03f; + API_FIELD(Attributes="Limit(0, 10f, 0.001f), EditorOrder(12), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_MinEdgeThreshold), EditorDisplay(null, \"CAS Min Edge Threshold\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_MinEdgeThreshold = 0.03f; /// /// The over-blur limit for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// - API_FIELD(Attributes = "Limit(0, 100f, 0.001f), EditorOrder(13), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_OverBlurLimit), EditorDisplay(null, \"CAS Over-blur Limit\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_OverBlurLimit = 1.0f; + API_FIELD(Attributes="Limit(0, 100f, 0.001f), EditorOrder(13), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_OverBlurLimit), EditorDisplay(null, \"CAS Over-blur Limit\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_OverBlurLimit = 1.0f; public: /// @@ -1979,7 +2001,7 @@ API_STRUCT() struct FLAXENGINE_API PostFxMaterialsSettings : ISerializable /// The post-process materials collection for rendering (fixed capacity). /// API_FIELD(Attributes="EditorDisplay(null, EditorDisplayAttribute.InlineStyle), Collection(MaxCount=8)") - Array, FixedAllocation> Materials; + Array, FixedAllocation> Materials; public: /// @@ -2001,79 +2023,79 @@ API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable /// The ambient occlusion effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Ambient Occlusion\"), EditorOrder(100), JsonProperty(\"AO\")") - AmbientOcclusionSettings AmbientOcclusion; + AmbientOcclusionSettings AmbientOcclusion; /// /// The global illumination effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Global Illumination\"), EditorOrder(150), JsonProperty(\"GI\")") - GlobalIlluminationSettings GlobalIllumination; + GlobalIlluminationSettings GlobalIllumination; /// /// The bloom effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Bloom\"), EditorOrder(200)") - BloomSettings Bloom; + BloomSettings Bloom; /// /// The tone mapping effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Tone Mapping\"), EditorOrder(300)") - ToneMappingSettings ToneMapping; + ToneMappingSettings ToneMapping; /// /// The color grading effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Color Grading\"), EditorOrder(400)") - ColorGradingSettings ColorGrading; + ColorGradingSettings ColorGrading; /// /// The eye adaptation effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Eye Adaptation\"), EditorOrder(500)") - EyeAdaptationSettings EyeAdaptation; + EyeAdaptationSettings EyeAdaptation; /// /// The camera artifacts effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Camera Artifacts\"), EditorOrder(600)") - CameraArtifactsSettings CameraArtifacts; + CameraArtifactsSettings CameraArtifacts; /// /// The lens flares effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Lens Flares\"), EditorOrder(700)") - LensFlaresSettings LensFlares; + LensFlaresSettings LensFlares; /// /// The depth of field effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Depth Of Field\"), EditorOrder(800)") - DepthOfFieldSettings DepthOfField; + DepthOfFieldSettings DepthOfField; /// /// The motion blur effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Motion Blur\"), EditorOrder(900)") - MotionBlurSettings MotionBlur; + MotionBlurSettings MotionBlur; /// /// The screen space reflections effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Screen Space Reflections\"), EditorOrder(1000), JsonProperty(\"SSR\")") - ScreenSpaceReflectionsSettings ScreenSpaceReflections; + ScreenSpaceReflectionsSettings ScreenSpaceReflections; /// /// The antialiasing effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Anti Aliasing\"), EditorOrder(1100), JsonProperty(\"AA\")") - AntiAliasingSettings AntiAliasing; + AntiAliasingSettings AntiAliasing; /// /// The PostFx materials rendering settings. /// API_FIELD(Attributes="EditorDisplay(\"PostFx Materials\"), NoAnimate, EditorOrder(1200)") - PostFxMaterialsSettings PostFxMaterials; + PostFxMaterialsSettings PostFxMaterials; public: /// diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 99831b430..f4d86ec21 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -11,8 +11,9 @@ PostProcessingPass::PostProcessingPass() : _shader(nullptr) - , _psThreshold(nullptr) - , _psScale(nullptr) + , _psBloomBrightPass(nullptr) + , _psBloomDownsample(nullptr) + , _psBloomDualFilterUpsample(nullptr) , _psBlurH(nullptr) , _psBlurV(nullptr) , _psGenGhosts(nullptr) @@ -30,8 +31,9 @@ String PostProcessingPass::ToString() const bool PostProcessingPass::Init() { // Create pipeline states - _psThreshold = GPUDevice::Instance->CreatePipelineState(); - _psScale = GPUDevice::Instance->CreatePipelineState(); + _psBloomBrightPass = GPUDevice::Instance->CreatePipelineState(); + _psBloomDownsample = GPUDevice::Instance->CreatePipelineState(); + _psBloomDualFilterUpsample = GPUDevice::Instance->CreatePipelineState(); _psBlurH = GPUDevice::Instance->CreatePipelineState(); _psBlurV = GPUDevice::Instance->CreatePipelineState(); _psGenGhosts = GPUDevice::Instance->CreatePipelineState(); @@ -69,16 +71,23 @@ bool PostProcessingPass::setupResources() // Create pipeline stages GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; - if (!_psThreshold->IsValid()) + if (!_psBloomBrightPass->IsValid()) { - psDesc.PS = shader->GetPS("PS_Threshold"); - if (_psThreshold->Init(psDesc)) + psDesc.PS = shader->GetPS("PS_BloomBrightPass"); + if (_psBloomBrightPass->Init(psDesc)) return true; } - if (!_psScale->IsValid()) + if (!_psBloomDownsample->IsValid()) { - psDesc.PS = shader->GetPS("PS_Scale"); - if (_psScale->Init(psDesc)) + psDesc.PS = shader->GetPS("PS_BloomDownsample"); + if (_psBloomDownsample->Init(psDesc)) + return true; + } + + if (!_psBloomDualFilterUpsample->IsValid()) + { + psDesc.PS = shader->GetPS("PS_BloomDualFilterUpsample"); + if (_psBloomDualFilterUpsample->Init(psDesc)) return true; } if (!_psBlurH->IsValid()) @@ -167,8 +176,9 @@ void PostProcessingPass::Dispose() RendererPass::Dispose(); // Cleanup - SAFE_DELETE_GPU_RESOURCE(_psThreshold); - SAFE_DELETE_GPU_RESOURCE(_psScale); + SAFE_DELETE_GPU_RESOURCE(_psBloomBrightPass); + SAFE_DELETE_GPU_RESOURCE(_psBloomDownsample); + SAFE_DELETE_GPU_RESOURCE(_psBloomDualFilterUpsample); SAFE_DELETE_GPU_RESOURCE(_psBlurH); SAFE_DELETE_GPU_RESOURCE(_psBlurV); SAFE_DELETE_GPU_RESOURCE(_psGenGhosts); @@ -179,13 +189,32 @@ void PostProcessingPass::Dispose() _defaultLensStar = nullptr; } +float CalculateBloomMipCount(int32 width, int32 height) +{ + // Calculate the smallest dimension + int32 minDimension = Math::Min(width, height); + + // Calculate how many times we can half the dimension until we hit a minimum size + // (e.g., 16x16 pixels as the smallest mip) + const int32 MIN_MIP_SIZE = 16; + float mipCount = 1.0f; + + while (minDimension > MIN_MIP_SIZE) { + minDimension /= 2; + mipCount += 1.0f; + } + + + return mipCount; +} + void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT) { PROFILE_GPU_CPU("Post Processing"); auto device = GPUDevice::Instance; auto context = device->GetMainContext(); auto& view = renderContext.View; - + context->ResetRenderTarget(); PostProcessSettings& settings = renderContext.List->Settings; @@ -204,8 +233,10 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, int32 h4 = h2 >> 1; int32 h8 = h4 >> 1; + int32 bloomMipCount = CalculateBloomMipCount(w1, h1); + // Ensure to have valid data and if at least one effect should be applied - if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass() || w8 == 0 || h8 ==0) + if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass() || w8 == 0 || h8 == 0) { // Resources are missing. Do not perform rendering. Just copy raw frame context->SetViewportAndScissors((float)output->Width(), (float)output->Height()); @@ -245,14 +276,18 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, } if (useBloom) { - data.BloomMagnitude = settings.Bloom.Intensity; + data.BloomIntensity = settings.Bloom.Intensity; + data.BloomClamp = settings.Bloom.Clamp; data.BloomThreshold = settings.Bloom.Threshold; - data.BloomBlurSigma = Math::Max(settings.Bloom.BlurSigma, 0.0001f); - data.BloomLimit = settings.Bloom.Limit; + data.BloomThresholdKnee = settings.Bloom.ThresholdKnee; + data.BloomBaseMix = settings.Bloom.BaseMix; + data.BloomHighMix = settings.Bloom.HighMix; + data.BloomMipCount = bloomMipCount; + data.BloomLayer = 0.0f; } else { - data.BloomMagnitude = 0; + data.BloomIntensity = 0; } if (useLensFlares) { @@ -298,99 +333,83 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, //////////////////////////////////////////////////////////////////////////////////// // Bloom - auto tempDesc = GPUTextureDescription::New2D(w2, h2, 0, output->Format(), GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews); - auto bloomTmp1 = RenderTargetPool::Get(tempDesc); - RENDER_TARGET_POOL_SET_NAME(bloomTmp1, "PostProcessing.Bloom"); - // TODO: bloomTmp2 could be quarter res because we don't use it's first mip - auto bloomTmp2 = RenderTargetPool::Get(tempDesc); - RENDER_TARGET_POOL_SET_NAME(bloomTmp2, "PostProcessing.Bloom"); + + auto tempDesc = GPUTextureDescription::New2D(w2, h2, bloomMipCount, output->Format(), GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews); + auto bloomBuffer1 = RenderTargetPool::Get(tempDesc); + RENDER_TARGET_POOL_SET_NAME(bloomBuffer1, "PostProcessing.Bloom"); + auto bloomBuffer2 = RenderTargetPool::Get(tempDesc); + RENDER_TARGET_POOL_SET_NAME(bloomBuffer2, "PostProcessing.Bloom"); + + for (int32 mip = 0; mip < bloomMipCount; mip++) + { + context->Clear(bloomBuffer1->View(0, mip), Color::Transparent); + context->Clear(bloomBuffer2->View(0, mip), Color::Transparent); + } // Check if use bloom if (useBloom) { - // Bloom Threshold and downscale to 1/2 - context->SetRenderTarget(bloomTmp1->View(0, 0)); + + context->SetRenderTarget(bloomBuffer1->View(0, 0)); context->SetViewportAndScissors((float)w2, (float)h2); context->BindSR(0, input->View()); - context->SetState(_psThreshold); + context->SetState(_psBloomBrightPass); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); - // Downscale to 1/4 - context->SetRenderTarget(bloomTmp1->View(0, 1)); - context->SetViewportAndScissors((float)w4, (float)h4); - context->BindSR(0, bloomTmp1->View(0, 0)); - context->SetState(_psScale); - context->DrawFullscreenTriangle(); - context->ResetRenderTarget(); - - // Downscale to 1/8 - context->SetRenderTarget(bloomTmp1->View(0, 2)); - context->SetViewportAndScissors((float)w8, (float)h8); - context->BindSR(0, bloomTmp1->View(0, 1)); - context->SetState(_psScale); - context->DrawFullscreenTriangle(); - context->ResetRenderTarget(); - - // TODO: perform blur when downscaling (13 tap) and when upscaling? (9 tap) - - // Gaussian Blur - GB_ComputeKernel(data.BloomBlurSigma, static_cast(w8), static_cast(h8)); - //int32 blurStages = (int)Rendering.Quality + 1; - int32 blurStages = 2; - for (int32 i = 0; i < blurStages; i++) + // Progressive downsamples + for (int32 mip = 1; mip < bloomMipCount; mip++) { - // Horizontal Bloom Blur - Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheH, sizeof(GaussianBlurCacheH)); - context->UpdateCB(cb1, &_gbData); - context->BindCB(1, cb1); - // - context->SetRenderTarget(bloomTmp2->View(0, 2)); - context->BindSR(0, bloomTmp1->View(0, 2)); - context->SetState(_psBlurH); + const float mipWidth = w2 >> mip; + const float mipHeight = h2 >> mip; + + context->SetRenderTarget(bloomBuffer1->View(0, mip)); + context->SetViewportAndScissors(mipWidth, mipHeight); + context->BindSR(0, bloomBuffer1->View(0, mip - 1)); + context->SetState(_psBloomDownsample); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); - // Vertical Bloom Blur - Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheV, sizeof(GaussianBlurCacheV)); - context->UpdateCB(cb1, &_gbData); - context->BindCB(1, cb1); - // - context->SetRenderTarget(bloomTmp1->View(0, 2)); - context->BindSR(0, bloomTmp2->View(0, 2)); - context->SetState(_psBlurV); + } + + // Progressive upsamples + for (int32 mip = bloomMipCount - 2; mip >= 0; mip--) + { + auto upscalebuffer = bloomBuffer2; + if (mip == bloomMipCount - 2) + { + // if it's the first, copy the chain over. + upscalebuffer = bloomBuffer1; + + } + + const float mipWidth = w2 >> mip; + const float mipHeight = h2 >> mip; + + data.BloomLayer = static_cast(mip); // Set the current mip as bloom layer + context->UpdateCB(cb0, &data); // Update the constant buffer + + context->SetRenderTarget(bloomBuffer2->View(0, mip)); + context->SetViewportAndScissors(mipWidth, mipHeight); + context->BindSR(0, upscalebuffer->View(0, mip + 1)); + context->BindSR(1, bloomBuffer1->View(0, mip + 1)); + context->SetState(_psBloomDualFilterUpsample); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); } - // Upscale to 1/4 (use second tmp target to cache that downscale thress data for lens flares) - context->SetRenderTarget(bloomTmp2->View(0, 1)); - context->SetViewportAndScissors((float)w4, (float)h4); - context->BindSR(0, bloomTmp1->View(0, 2)); - context->SetState(_psScale); - context->DrawFullscreenTriangle(); - context->ResetRenderTarget(); - - // Upscale to 1/2 - context->SetRenderTarget(bloomTmp1->View(0, 0)); - context->SetViewportAndScissors((float)w2, (float)h2); - context->BindSR(0, bloomTmp2->View(0, 1)); - context->SetState(_psScale); - context->DrawFullscreenTriangle(); - context->ResetRenderTarget(); - - // Set bloom - context->UnBindSR(0); - context->BindSR(2, bloomTmp1->View(0, 0)); + // Final composite + context->BindSR(1, bloomBuffer1); + context->BindSR(2, bloomBuffer2); } else { - // No bloom texture context->UnBindSR(2); } //////////////////////////////////////////////////////////////////////////////////// - // Lens Flares + //Lens Flares + // Check if use lens flares if (useLensFlares) @@ -399,10 +418,12 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, context->BindSR(5, getCustomOrDefault(settings.LensFlares.LensStar, _defaultLensStar, TEXT("Engine/Textures/DefaultLensStarburst"))); context->BindSR(6, getCustomOrDefault(settings.LensFlares.LensColor, _defaultLensColor, TEXT("Engine/Textures/DefaultLensColor"))); + // use bloomBuffer1's mip 1 as input for lens flares + // Render lens flares - context->SetRenderTarget(bloomTmp2->View(0, 1)); + context->SetRenderTarget(bloomBuffer2->View(0, 1)); context->SetViewportAndScissors((float)w4, (float)h4); - context->BindSR(3, bloomTmp1->View(0, 1)); + context->BindSR(3, bloomBuffer1->View(0, 1)); // Use mip 1 of bloomBuffer1 as source context->SetState(_psGenGhosts); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); @@ -415,8 +436,8 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheH, sizeof(GaussianBlurCacheH)); context->UpdateCB(cb1, &_gbData); context->BindCB(1, cb1); - context->SetRenderTarget(bloomTmp1->View(0, 1)); - context->BindSR(0, bloomTmp2->View(0, 1)); + context->SetRenderTarget(bloomBuffer1->View(0, 1)); + context->BindSR(0, bloomBuffer2->View(0, 1)); context->SetState(_psBlurH); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); @@ -425,14 +446,14 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheV, sizeof(GaussianBlurCacheV)); context->UpdateCB(cb1, &_gbData); context->BindCB(1, cb1); - context->SetRenderTarget(bloomTmp2->View(0, 1)); - context->BindSR(0, bloomTmp1->View(0, 1)); + context->SetRenderTarget(bloomBuffer2->View(0, 1)); + context->BindSR(0, bloomBuffer1->View(0, 1)); context->SetState(_psBlurV); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); // Set lens flares output - context->BindSR(3, bloomTmp2->View(0, 1)); + context->BindSR(3, bloomBuffer2->View(0, 1)); } else { @@ -482,6 +503,6 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, context->DrawFullscreenTriangle(); // Cleanup - RenderTargetPool::Release(bloomTmp1); - RenderTargetPool::Release(bloomTmp2); + RenderTargetPool::Release(bloomBuffer1); + RenderTargetPool::Release(bloomBuffer2); } diff --git a/Source/Engine/Renderer/PostProcessingPass.h b/Source/Engine/Renderer/PostProcessingPass.h index 22ef5eafc..b7d73a5fd 100644 --- a/Source/Engine/Renderer/PostProcessingPass.h +++ b/Source/Engine/Renderer/PostProcessingPass.h @@ -15,11 +15,16 @@ class PostProcessingPass : public RendererPass { private: - GPU_CB_STRUCT(Data { - float BloomLimit; - float BloomThreshold; - float BloomMagnitude; - float BloomBlurSigma; + GPU_CB_STRUCT(Data{ + float BloomIntensity; // Overall bloom strength multiplier + float BloomClamp; // Maximum brightness limit for bloom + float BloomThreshold; // Luminance threshold where bloom begins + float BloomThresholdKnee; // Controls the threshold rolloff curve + float BloomBaseMix; // Base mip contribution + float BloomHighMix; // High mip contribution + float BloomMipCount; + float BloomLayer; + Float3 VignetteColor; float VignetteShapeFactor; @@ -56,7 +61,7 @@ private: Matrix LensFlareStarMat; }); - GPU_CB_STRUCT(GaussianBlurData { + GPU_CB_STRUCT(GaussianBlurData{ Float2 Size; float Dummy3; float Dummy4; @@ -65,8 +70,9 @@ private: // Post Processing AssetReference _shader; - GPUPipelineState* _psThreshold; - GPUPipelineState* _psScale; + GPUPipelineState* _psBloomBrightPass; + GPUPipelineState* _psBloomDownsample; + GPUPipelineState* _psBloomDualFilterUpsample; GPUPipelineState* _psBlurH; GPUPipelineState* _psBlurV; GPUPipelineState* _psGenGhosts; @@ -115,8 +121,9 @@ private: #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { - _psThreshold->ReleaseGPU(); - _psScale->ReleaseGPU(); + _psBloomBrightPass->ReleaseGPU(); + _psBloomDownsample->ReleaseGPU(); + _psBloomDualFilterUpsample->ReleaseGPU(); _psBlurH->ReleaseGPU(); _psBlurV->ReleaseGPU(); _psGenGhosts->ReleaseGPU(); diff --git a/Source/Shaders/PostProcessing.shader b/Source/Shaders/PostProcessing.shader index c54337b2e..70f5c6a0c 100644 --- a/Source/Shaders/PostProcessing.shader +++ b/Source/Shaders/PostProcessing.shader @@ -36,10 +36,17 @@ META_CB_BEGIN(0, Data) -float BloomLimit; -float BloomThreshold; -float BloomMagnitude; -float BloomBlurSigma; +// Bloom parameters +float BloomIntensity; +float BloomClamp; +float BloomThreshold; +float BloomThresholdKnee; + +float BloomBaseMix; +float BloomHighMix; +float BloomMipCount; +float BloomLayer; + float3 VignetteColor; float VignetteShapeFactor; @@ -253,34 +260,228 @@ float2 coordRot(in float2 tc, in float angle) } // Uses a lower exposure to produce a value suitable for a bloom pass + META_PS(true, FEATURE_LEVEL_ES2) -float4 PS_Threshold(Quad_VS2PS input) : SV_Target +float4 PS_BloomBrightPass(Quad_VS2PS input) : SV_Target { - float4 color = Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); - return clamp(color - BloomThreshold, 0, BloomLimit); + // Get dimensions for precise texel calculations + uint width, height; + Input0.GetDimensions(width, height); + float2 texelSize = 1.0 / float2(width, height); + // Use fixed 13-tap sample pattern for initial bright pass + float3 color = 0; + float totalWeight = 0; + + // Center sample with high weight for energy preservation + float3 center = Input0.Sample(SamplerLinearClamp, input.TexCoord).rgb; + + // Apply Karis average to prevent bright pixels from dominating + float centerLuma = max(dot(center, float3(0.2126, 0.7152, 0.0722)), 0.0001); + center = center / (1.0 + centerLuma); + + float centerWeight = 4.0; + color += center * centerWeight; + totalWeight += centerWeight; + // Inner ring - fixed offset at 1.0 texel distance + UNROLL + for (int i = 0; i < 4; i++) + { + float angle = i * (PI / 2.0); + float2 offset = float2(cos(angle), sin(angle)) * texelSize; + float3 sample = Input0.Sample(SamplerLinearClamp, input.TexCoord + offset).rgb; + + // Apply Karis average + float sampleLuma = max(dot(sample, float3(0.2126, 0.7152, 0.0722)), 0.0001); + sample = sample / (1.0 + sampleLuma); + + float weight = 2.0; + color += sample * weight; + totalWeight += weight; + } + // Outer ring - fixed offset at 1.4142 texel distance (diagonal) + UNROLL + for (int j = 0; j < 8; j++) + { + float angle = j * (PI / 4.0); + float2 offset = float2(cos(angle), sin(angle)) * texelSize * 1.4142; + float3 sample = Input0.Sample(SamplerLinearClamp, input.TexCoord + offset).rgb; + + // Apply Karis average + float sampleLuma = max(dot(sample, float3(0.2126, 0.7152, 0.0722)), 0.0001); + sample = sample / (1.0 + sampleLuma); + + float weight = 1.0; + color += sample * weight; + totalWeight += weight; + } + color /= totalWeight; + + // Un-apply Karis average to maintain energy + float finalLuma = max(dot(color, float3(0.2126, 0.7152, 0.0722)), 0.0001); + color = color * (1.0 + finalLuma); + + // Apply threshold with quadratic rolloff for smoother transition + float luminance = dot(color, float3(0.2126, 0.7152, 0.0722)); + float threshold = max(BloomThreshold, 0.2); + float knee = threshold * BloomThresholdKnee; + float soft_max = threshold + knee; + + float contribution = 0; + if (luminance > threshold) { + if (luminance < soft_max) { + // Quadratic softening between threshold and (threshold + knee) + float x = (luminance - threshold) / knee; + contribution = x * x * 0.5; + } else { + // Full contribution above soft_max + contribution = luminance - threshold; + } + } + + float testc = BloomClamp; + float3 clamped = (color * contribution); + clamped.r = min(clamped.r, testc); + clamped.g = min(clamped.g, testc); + clamped.b = min(clamped.b, testc); + + // Store threshold result in alpha for downsample chain + return float4(clamped, luminance); } -// Uses hw bilinear filtering for upscaling or downscaling META_PS(true, FEATURE_LEVEL_ES2) -float4 PS_Scale(Quad_VS2PS input) : SV_Target +float4 PS_BloomDownsample(Quad_VS2PS input) : SV_Target { - // TODO: we could use quality switch for bloom effect + uint width, height; + Input0.GetDimensions(width, height); + float2 texelSize = 1.0 / float2(width, height); - return Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); - /* - float3 color; - // TODO: use gather for dx11 and dx12?? - color = Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0, int2( 0, 0)).rgb; - color += Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0, int2( 0, 1)).rgb; - color += Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0, int2( 0,-1)).rgb; - color += Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0, int2(-1, 0)).rgb; - color += Input0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0, int2( 1, 0)).rgb; - color *= (1.0f / 5.0f); + // 9-tap tent filter with fixed weights + float3 color = 0; + float totalWeight = 0; - return float4(color, 1); - */ + // Sample offsets (fixed) + const float2 offsets[9] = { + float2( 0, 0), // Center + float2(-1, -1), // Corners + float2( 1, -1), + float2(-1, 1), + float2( 1, 1), + float2( 0, -1), // Cross + float2(-1, 0), + float2( 1, 0), + float2( 0, 1) + }; + + // Sample weights (fixed) + const float weights[9] = { + 4.0, // Center + 1.0, // Corners + 1.0, + 1.0, + 1.0, + 2.0, // Cross + 2.0, + 2.0, + 2.0 + }; + + UNROLL + for (int i = 0; i < 9; i++) + { + float2 offset = offsets[i] * texelSize * 2.0; // Fixed scale factor for stability + float4 sample = Input0.Sample(SamplerLinearClamp, input.TexCoord + offset); + color += sample.rgb * weights[i]; + totalWeight += weights[i]; + } + + return float4(color / totalWeight, 1.0); } +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_BloomDualFilterUpsample(Quad_VS2PS input) : SV_Target +{ + float anisotropy = 1.0; + + uint width, height; + Input0.GetDimensions(width, height); + float2 texelSize = 1.0 / float2(width, height); + + // Maintain fixed scale through mip chain + float baseOffset = 1.0; + float offsetScale = (1.0) * baseOffset; + float3 color = 0; + float totalWeight = 0; + + // Center + float4 center = Input0.Sample(SamplerLinearClamp, input.TexCoord); + float centerWeight = 4.0; + color += center.rgb * centerWeight; + totalWeight += centerWeight; + + // Cross - fixed distance samples + float2 crossOffsets[4] = { + float2(offsetScale * anisotropy, 0), + float2(-offsetScale * anisotropy, 0), + float2(0, offsetScale), + float2(0, -offsetScale) + }; + + UNROLL + for (int i = 0; i < 4; i++) + { + float4 sample = Input0.Sample(SamplerLinearClamp, input.TexCoord + crossOffsets[i] * texelSize); + float weight = 2.0; + color += sample.rgb * weight; + totalWeight += weight; + } + + // Corners - fixed distance samples + float2 cornerOffsets[4] = { + float2(offsetScale * anisotropy, offsetScale), + float2(-offsetScale * anisotropy, offsetScale), + float2(offsetScale * anisotropy, -offsetScale), + float2(-offsetScale * anisotropy, -offsetScale) + }; + + UNROLL + for (int j = 0; j < 4; j++) + { + float4 sample = Input0.Sample(SamplerLinearClamp, input.TexCoord + cornerOffsets[j] * texelSize); + float weight = 1.0; + color += sample.rgb * weight; + totalWeight += weight; + } + + color /= totalWeight; + + uint width1, height1; + Input1.GetDimensions(width1, height1); + + // Calculate mip fade factor (0 = smallest mip, 1 = largest mip) + float mipFade = BloomLayer / (BloomMipCount - 1); + + // Muzz says: + // Lerp between your desired intensity values based on mip level + // setting both to 0.6 is a decent default, but playing with these numbers will let you dial in the blending between the lowest and highest mips. + // you can make some really ugly bloom if you go too far. + // note this does change the intensity of the bloom. + // This was my own invention + + float mipIntensity = lerp(BloomBaseMix, BloomHighMix, mipFade); + color *= mipIntensity; + + BRANCH + if (width1 > 0) + { + float3 previousMip = Input1.Sample(SamplerLinearClamp, input.TexCoord).rgb; + color += previousMip; + } + + return float4(color, 1.0); +} + + + // Horizontal gaussian blur META_PS(true, FEATURE_LEVEL_ES2) float4 PS_GaussainBlurH(Quad_VS2PS input) : SV_Target @@ -311,6 +512,8 @@ float4 PS_GaussainBlurV(Quad_VS2PS input) : SV_Target return color; } + + // Generate 'ghosts' for lens flare META_PS(true, FEATURE_LEVEL_ES2) float4 PS_Ghosts(Quad_VS2PS input) : SV_Target @@ -350,7 +553,8 @@ float4 PS_Ghosts(Quad_VS2PS input) : SV_Target Input3.Sample(SamplerLinearClamp, offset + ghostVecnNorm * distortion.r).r, Input3.Sample(SamplerLinearClamp, offset + ghostVecnNorm * distortion.g).g, Input3.Sample(SamplerLinearClamp, offset + ghostVecnNorm * distortion.b).b); - color = clamp(color + LensBias, 0, 10) * (LensScale * weight); + color = clamp((color * 1.0f) + LensBias, 0, 10) * (LensScale * weight); + // Accumulate color result += color; @@ -406,6 +610,8 @@ float nrand(float2 n) return frac(sin(dot(n.xy, float2(12.9898, 78.233)))* 43758.5453); } + + // Applies exposure, color grading and tone mapping to the input. // Combines it with the results of the bloom pass and other postFx. META_PS(true, FEATURE_LEVEL_ES2) @@ -452,7 +658,8 @@ float4 PS_Composite(Quad_VS2PS input) : SV_Target color = Input0.Sample(SamplerLinearClamp, uv); } - // Lens Flares + + // Lens Flaresf BRANCH if (LensFlareIntensity > 0) { @@ -470,19 +677,21 @@ float4 PS_Composite(Quad_VS2PS input) : SV_Target lensLight += lensFlares * 1.5f; color.rgb += lensFlares; } + - // Bloom - BRANCH - if (BloomMagnitude > 0) - { - // Sample the bloom - float3 bloom = Input2.SampleLevel(SamplerLinearClamp, uv, 0).rgb; - bloom = bloom * BloomMagnitude; +// In PS_Composite: +BRANCH +if (BloomIntensity > 0) +{ + // Sample the final bloom result + float3 bloom = Input2.Sample(SamplerLinearClamp, input.TexCoord).rgb; + bloom = bloom * BloomIntensity; - // Accumulate final bloom lght - lensLight += max(0, bloom * 3.0f + (- 1.0f * 3.0f)); - color.rgb += bloom; - } + + + lensLight += max(0, bloom * 3.0f + (- 1.0f * 3.0f)); + color.rgb += bloom; +} // Lens Dirt float3 lensDirt = LensDirt.SampleLevel(SamplerLinearClamp, uv, 0).rgb; From 1fac78f48f954b19a77cf2b98275e543e71e112a Mon Sep 17 00:00:00 2001 From: Muzz Date: Tue, 25 Feb 2025 14:49:59 +1000 Subject: [PATCH 190/215] attributes fix --- Source/Engine/Graphics/PostProcessSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index f2297f240..a8e73f752 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -464,7 +464,7 @@ API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable /// /// Overall bloom effect strength. Higher values create a stronger glow effect. /// - API_FIELD(Attributes "Limit(0, 100.0f, 0.001f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)") + API_FIELD(Attributes="Limit(0, 100.0f, 0.001f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)") float Intensity = 1.0f; /// From 7885590593e1e46441b469f5dada9720fe40207e Mon Sep 17 00:00:00 2001 From: Muzz Date: Fri, 28 Feb 2025 14:54:44 +0100 Subject: [PATCH 191/215] Merge branch 'Muzz-Triplanar-Features' into 1.10 --- Content/Editor/MaterialTemplates/Decal.shader | 6 + .../MaterialTemplates/Deformable.shader | 14 ++ Content/Editor/MaterialTemplates/GUI.shader | 6 + .../Editor/MaterialTemplates/Surface.shader | 24 ++ .../Editor/MaterialTemplates/Terrain.shader | 6 + Source/Editor/Surface/Archetypes/Material.cs | 66 ++++++ Source/Editor/Surface/Archetypes/Textures.cs | 52 ++++- Source/Editor/Surface/Archetypes/Tools.cs | 8 +- Source/Editor/Surface/Elements/FloatValue.cs | 5 + .../MaterialGenerator.Material.cpp | 131 +++++++++++ .../MaterialGenerator.Textures.cpp | 210 ++++++++++++++---- Source/Engine/Visject/ShaderStringBuilder.cpp | 34 +++ Source/Engine/Visject/ShaderStringBuilder.h | 21 ++ Source/Shaders/MaterialCommon.hlsl | 8 + 14 files changed, 536 insertions(+), 55 deletions(-) create mode 100644 Source/Engine/Visject/ShaderStringBuilder.cpp create mode 100644 Source/Engine/Visject/ShaderStringBuilder.h diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index 58ef9492c..06b44d498 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -83,6 +83,12 @@ float3 GetObjectSize(MaterialInput input) return float3(1, 1, 1); } +// Gets the current object scale (supports instancing) +float3 GetObjectScale(MaterialInput input) +{ + return float3(1, 1, 1); +} + // Get the current object random value supports instancing) float GetPerInstanceRandom(MaterialInput input) { diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader index 9ff0161eb..91f96607d 100644 --- a/Content/Editor/MaterialTemplates/Deformable.shader +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -207,6 +207,20 @@ float3 GetObjectSize(MaterialInput input) return GeometrySize * float3(world._m00, world._m11, world._m22); } +// Gets the current object scale (supports instancing) +float3 GetObjectScale(MaterialInput input) +{ + float4x4 world = input.Object.WorldMatrix; + + // Extract scale from the world matrix + float3 scale; + scale.x = length(float3(world._11, world._12, world._13)); + scale.y = length(float3(world._21, world._22, world._23)); + scale.z = length(float3(world._31, world._32, world._33)); + + return scale; +} + // Get the current object random value float GetPerInstanceRandom(MaterialInput input) { diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index 170d95f1a..fad7afa32 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -163,6 +163,12 @@ float3 GetObjectSize(MaterialInput input) return float3(1, 1, 1); } +// Gets the current object scale (supports instancing) +float3 GetObjectScale(MaterialInput input) +{ + return float3(1, 1, 1); +} + // Get the current object random value supports instancing) float GetPerInstanceRandom(MaterialInput input) { diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index b0465d2ff..ba3c36440 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -42,6 +42,12 @@ struct GeometryData nointerpolation uint ObjectIndex : TEXCOORD8; }; +float3 DecodeNormal(float4 normalMap) +{ + float2 xy = normalMap.rg * 2.0 - 1.0; + return float3(xy, sqrt(1.0 - saturate(dot(xy, xy)))); +} + // Interpolants passed from the vertex shader struct VertexOutput { @@ -232,6 +238,24 @@ float3 GetObjectSize(MaterialInput input) return input.Object.GeometrySize * float3(world._m00, world._m11, world._m22); } +// Gets the current object scale (supports instancing) +float3 GetObjectScale(MaterialInput input) +{ + float4x4 world = input.Object.WorldMatrix; + + // Get the squares of the scale factors + float scaleXSquared = dot(world[0].xyz, world[0].xyz); + float scaleYSquared = dot(world[1].xyz, world[1].xyz); + float scaleZSquared = dot(world[2].xyz, world[2].xyz); + + // Take square root to get actual scales + return float3( + sqrt(scaleXSquared), + sqrt(scaleYSquared), + sqrt(scaleZSquared) + ); +} + // Get the current object random value (supports instancing) float GetPerInstanceRandom(MaterialInput input) { diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index 9b654e8f0..abc444316 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -236,6 +236,12 @@ float3 GetObjectSize(MaterialInput input) return float3(1, 1, 1); } +// Gets the current object scale (supports instancing) +float3 GetObjectScale(MaterialInput input) +{ + return float3(1, 1, 1); +} + // Get the current object random value float GetPerInstanceRandom(MaterialInput input) { diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 9da0ec067..05f67c0e1 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -15,6 +15,29 @@ namespace FlaxEditor.Surface.Archetypes [HideInEditor] public static class Material { + /// + /// Blend modes (each enum item value maps to box ID). + /// + internal enum BlendMode + { + Normal, + Add, + Subtract, + Multiply, + Screen, + Overlay, + LinearBurn, + LinearLight, + Darken, + Lighten, + Difference, + Exclusion, + Divide, + HardLight, + PinLight, + HardMix + }; + /// /// Customized for main material node. /// @@ -1073,6 +1096,49 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4), ] }, + new NodeArchetype + { + TypeID = 50, + Title = "Shift HSV", + Description = "Modifies the HSV of a color, values are from -1:1, preserves alpha", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 80), + DefaultValues = + [ + 0.0f, // For Hue (index 0) + 0.0f, // For Sat (index 1) + 0.0f, // For Val (index 2) + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "RGBA", true, typeof(Float4), 0), // No default + NodeElementArchetype.Factory.Input(1, "Hue", true, typeof(float), 1, 0), // Uses DefaultValues[0] + NodeElementArchetype.Factory.Input(2, "Sat", true, typeof(float), 2, 1), // Uses DefaultValues[1] + NodeElementArchetype.Factory.Input(3, "Val", true, typeof(float), 3, 2), // Uses DefaultValues[2] + NodeElementArchetype.Factory.Output(0, "RGBA", typeof(Float4), 4), + ] + }, + new NodeArchetype + { + TypeID = 51, + Title = "Color Blend", + Description = "Blends two colors using various blend modes. Passes base alpha through.", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(180, 80), + DefaultValues = new object[] + { + BlendMode.Normal, // Default blend mode + 1.0f, // Default blend amount + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(1, "Base Color", true, typeof(Float4), 0), + NodeElementArchetype.Factory.Input(2, "Blend Color", true, typeof(Float4), 1), + NodeElementArchetype.Factory.Input(3, "Intensity", true, typeof(float), 2, 1), + NodeElementArchetype.Factory.Enum(0, 0, 120, 0, typeof(BlendMode)), // Blend mode selector + NodeElementArchetype.Factory.Output(0, "Result", typeof(Float4), 3), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index d941da939..6fe682197 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -402,21 +402,29 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 16, - Title = "World Triplanar Texture", - Description = "Projects a texture using world-space coordinates instead of UVs.", + Title = "Triplanar Texture", + Description = "Projects a texture using world-space coordinates with triplanar mapping.", Flags = NodeFlags.MaterialGraph, - Size = new Float2(240, 60), + Size = new Float2(280, 100), DefaultValues = new object[] { - 1.0f, - 1.0f + 1.0f, // Scale + 1.0f, // Blend + Float2.Zero, // Offset + 2, // Sampler + false, // Local }, Elements = new[] { NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0), - NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(Float4), 1, 0), + NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0), NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1), - NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 3) + NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2), + NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 5), + NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"), + NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)), + NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"), + NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4), } }, new NodeArchetype @@ -456,7 +464,35 @@ namespace FlaxEditor.Surface.Archetypes { NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0) } - } + }, + new NodeArchetype + { + TypeID = 23, + Title = "Triplanar Normal Map", + Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(280, 100), + DefaultValues = new object[] + { + 1.0f, // Scale + 1.0f, // Blend + Float2.Zero, // Offset + 2, // Sampler + false, // Local + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0), + NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0), + NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1), + NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2), + NodeElementArchetype.Factory.Output(0, "Vector", typeof(Float3), 5), + NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"), + NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)), + NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"), + NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index e08171ca2..4258b65cd 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -1499,12 +1499,12 @@ namespace FlaxEditor.Surface.Archetypes 2, // Stop 0 - 0.1f, - Color.CornflowerBlue, + 0.05f, + Color.Black, // Stop 1 - 0.9f, - Color.GreenYellow, + 0.95f, + Color.White, // Empty stops 2-7 0.0f, Color.Black, diff --git a/Source/Editor/Surface/Elements/FloatValue.cs b/Source/Editor/Surface/Elements/FloatValue.cs index 71ee26df9..d01abcf06 100644 --- a/Source/Editor/Surface/Elements/FloatValue.cs +++ b/Source/Editor/Surface/Elements/FloatValue.cs @@ -147,6 +147,11 @@ namespace FlaxEditor.Surface.Elements { value = (double)toSet; } + else if (parentNode.GroupArchetype.GroupID != 2) + { + // Per-component editing is used only by nodes from Constant group, otherwise use float + value = toSet; + } else if (value is Vector2 asVector2) { if (arch.BoxID == 0) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index f56f7843c..f220b49ce 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -4,6 +4,7 @@ #include "MaterialGenerator.h" #include "Engine/Content/Assets/MaterialFunction.h" +#include "Engine/Visject/ShaderStringBuilder.h" void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { @@ -643,6 +644,136 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, {1}, {2})"), innerMask.Value, outerMask.Value, mask.Value), node); break; } + // Shift HSV + case 50: + { + const auto color = tryGetValue(node->GetBox(0), Value::One).AsFloat4(); + if (!color.IsValid()) + { + value = Value::Zero; + break; + } + const auto hue = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat(); + const auto saturation = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); + const auto val = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat(); + auto result = writeLocal(Value::InitForZero(ValueType::Float4), node); + + const String hsvAdjust = ShaderStringBuilder() + .Code(TEXT(R"( + { + float3 rgb = %COLOR%.rgb; + float minc = min(min(rgb.r, rgb.g), rgb.b); + float maxc = max(max(rgb.r, rgb.g), rgb.b); + float delta = maxc - minc; + + float3 grb = float3(rgb.g - rgb.b, rgb.r - rgb.b, rgb.b - rgb.g); + float3 cmps = float3(maxc == rgb.r, maxc == rgb.g, maxc == rgb.b); + float h = dot(grb * rcp(delta), cmps); + h += 6.0 * (h < 0); + h = frac(h * (1.0/6.0) * step(0, delta) + %HUE% * 0.5); + + float s = saturate(delta * rcp(maxc + step(maxc, 0)) * (1.0 + %SATURATION%)); + float v = maxc * (1.0 + %VALUE%); + + float3 k = float3(1.0, 2.0 / 3.0, 1.0 / 3.0); + %RESULT% = float4(v * lerp(1.0, saturate(abs(frac(h + k) * 6.0 - 3.0) - 1.0), s), %COLOR%.a); + } + )")) + .Replace(TEXT("%COLOR%"), color.Value) + .Replace(TEXT("%HUE%"), hue.Value) + .Replace(TEXT("%SATURATION%"), saturation.Value) + .Replace(TEXT("%VALUE%"), val.Value) + .Replace(TEXT("%RESULT%"), result.Value) + .Build(); + _writer.Write(*hsvAdjust); + value = result; + break; + } + // Color Blend + case 51: + { + const auto baseColor = tryGetValue(node->GetBox(0), Value::One).AsFloat4(); + const auto blendColor = tryGetValue(node->GetBox(1), Value::One).AsFloat4(); + const auto blendAmount = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); + const auto blendMode = node->Values[0].AsInt; + auto result = writeLocal(Value::InitForZero(ValueType::Float4), node); + + String blendFormula; + switch (blendMode) + { + case 0: // Normal + blendFormula = TEXT("blend"); + break; + case 1: // Add + blendFormula = TEXT("base + blend"); + break; + case 2: // Subtract + blendFormula = TEXT("base - blend"); + break; + case 3: // Multiply + blendFormula = TEXT("base * blend"); + break; + case 4: // Screen + blendFormula = TEXT("1.0 - (1.0 - base) * (1.0 - blend)"); + break; + case 5: // Overlay + blendFormula = TEXT("base <= 0.5 ? 2.0 * base * blend : 1.0 - 2.0 * (1.0 - base) * (1.0 - blend)"); + break; + case 6: // Linear Burn + blendFormula = TEXT("base + blend - 1.0"); + break; + case 7: // Linear Light + blendFormula = TEXT("blend < 0.5 ? max(base + (2.0 * blend) - 1.0, 0.0) : min(base + 2.0 * (blend - 0.5), 1.0)"); + break; + case 8: // Darken + blendFormula = TEXT("min(base, blend)"); + break; + case 9: // Lighten + blendFormula = TEXT("max(base, blend)"); + break; + case 10: // Difference + blendFormula = TEXT("abs(base - blend)"); + break; + case 11: // Exclusion + blendFormula = TEXT("base + blend - (2.0 * base * blend)"); + break; + case 12: // Divide + blendFormula = TEXT("base / (blend + 0.000001)"); + break; + case 13: // Hard Light + blendFormula = TEXT("blend <= 0.5 ? 2.0 * base * blend : 1.0 - 2.0 * (1.0 - base) * (1.0 - blend)"); + break; + case 14: // Pin Light + blendFormula = TEXT("blend <= 0.5 ? min(base, 2.0 * blend) : max(base, 2.0 * (blend - 0.5))"); + break; + case 15: // Hard Mix + blendFormula = TEXT("step(1.0 - base, blend)"); + break; + default: + blendFormula = TEXT("blend"); + break; + } + + const String blendImpl = ShaderStringBuilder() + .Code(TEXT(R"( + { + float3 base = %BASE%.rgb; + float3 blend = %BLEND%.rgb; + float alpha = %BASE%.a; + float3 final = %BLEND_FORMULA%; + %RESULT% = float4(lerp(base, final, %AMOUNT%), alpha); + } + )")) + .Replace(TEXT("%BASE%"), baseColor.Value) + .Replace(TEXT("%BLEND%"), blendColor.Value) + .Replace(TEXT("%AMOUNT%"), blendAmount.Value) + .Replace(TEXT("%BLEND_FORMULA%"), *blendFormula) + .Replace(TEXT("%RESULT%"), result.Value) + .Build(); + _writer.Write(*blendImpl); + value = result; + break; + } default: break; } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 1ec9acb94..e155a576a 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -3,6 +3,26 @@ #if COMPILE_WITH_MATERIAL_GRAPH #include "MaterialGenerator.h" +#include "Engine/Visject/ShaderStringBuilder.h" + +namespace +{ + enum CommonSamplerType + { + LinearClamp = 0, + PointClamp = 1, + LinearWrap = 2, + PointWrap = 3, + TextureGroup = 4, + }; + const Char* SamplerNames[] + { + TEXT("SamplerLinearClamp"), + TEXT("SamplerPointClamp"), + TEXT("SamplerLinearWrap"), + TEXT("SamplerPointWrap"), + }; +}; MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture) { @@ -491,22 +511,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) // Procedural Texture Sample case 17: { - enum CommonSamplerType - { - LinearClamp = 0, - PointClamp = 1, - LinearWrap = 2, - PointWrap = 3, - TextureGroup = 4, - }; - const Char* SamplerNames[] - { - TEXT("SamplerLinearClamp"), - TEXT("SamplerPointClamp"), - TEXT("SamplerLinearWrap"), - TEXT("SamplerPointWrap"), - }; - // Get input boxes auto textureBox = node->GetBox(0); auto uvsBox = node->GetBox(1); @@ -645,9 +649,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) // Decode normal map vector if (isNormalMap) { - // TODO: maybe we could use helper function for UnpackNormalTexture() and unify unpacking? - _writer.Write(TEXT("\t{0}.xy = {0}.xy * 2.0 - 1.0;\n"), textureBox->Cache.Value); - _writer.Write(TEXT("\t{0}.z = sqrt(saturate(1.0 - dot({0}.xy, {0}.xy)));\n"), textureBox->Cache.Value); + _writer.Write(TEXT("\t{0}.xyz = UnpackNormalMap({0}.xy);\n"), textureBox->Cache.Value); } value = textureBox->Cache; @@ -690,12 +692,10 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = box == gradientBox ? gradient : distance; break; } - // World Triplanar Texture + // Triplanar Texture case 16: { auto textureBox = node->GetBox(0); - auto scaleBox = node->GetBox(1); - auto blendBox = node->GetBox(2); if (!textureBox->HasConnection()) { // No texture to sample @@ -704,28 +704,62 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } const bool canUseSample = CanUseSample(_treeType); const auto texture = eatBox(textureBox->GetParent(), textureBox->FirstConnection()); - const auto scale = tryGetValue(scaleBox, node->Values[0]).AsFloat4(); - const auto blend = tryGetValue(blendBox, node->Values[1]).AsFloat(); + const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat(); + const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); + const auto offset = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat2(); + const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false; + + const Char* samplerName; + const int32 samplerIndex = node->Values[3].AsInt; + if (samplerIndex == TextureGroup) + { + auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].AsInt); + samplerName = *textureGroupSampler.ShaderName; + } + else if (samplerIndex >= 0 && samplerIndex < ARRAY_COUNT(SamplerNames)) + { + samplerName = SamplerNames[samplerIndex]; + } + else + { + OnError(node, box, TEXT("Invalid texture sampler.")); + return; + } + auto result = writeLocal(Value::InitForZero(ValueType::Float4), node); - const String triplanarTexture = String::Format(TEXT( - " {{\n" - " float tiling = {1} * 0.001f;\n" - " float3 worldPos = (input.WorldPosition.xyz + GetLargeWorldsTileOffset(1.0f / tiling)) * tiling;\n" - " float3 normal = abs(input.TBN[2]);\n" - " normal = pow(normal, {2});\n" - " normal = normalize(normal);\n" - " {3} += {0}.{4}(SamplerLinearWrap, worldPos.yz{5}) * normal.x;\n" - " {3} += {0}.{4}(SamplerLinearWrap, worldPos.xz{5}) * normal.y;\n" - " {3} += {0}.{4}(SamplerLinearWrap, worldPos.xy{5}) * normal.z;\n" - " }}\n" - ), - texture.Value, // {0} - scale.Value, // {1} - blend.Value, // {2} - result.Value, // {3} - canUseSample ? TEXT("Sample") : TEXT("SampleLevel"), // {4} - canUseSample ? TEXT("") : TEXT(", 0") // {5} - ); + + const String triplanarTexture = ShaderStringBuilder() + .Code(TEXT(R"( + { + // Get world position and normal + float tiling = %SCALE% * 0.001f; + float3 position = ((%POSITION%) + GetLargeWorldsTileOffset(1.0f / tiling)) * tiling; + float3 normal = normalize(%NORMAL%); + + // Compute triplanar blend weights using power distribution + float3 blendWeights = pow(abs(normal), %BLEND%); + blendWeights /= dot(blendWeights, float3(1, 1, 1)); + + // Sample projections with proper scaling and offset + float4 xProjection = %TEXTURE%.%SAMPLE%(%SAMPLER%, position.yz + %OFFSET%%SAMPLE_ARGS%); + float4 yProjection = %TEXTURE%.%SAMPLE%(%SAMPLER%, position.xz + %OFFSET%%SAMPLE_ARGS%); + float4 zProjection = %TEXTURE%.%SAMPLE%(%SAMPLER%, position.xy + %OFFSET%%SAMPLE_ARGS%); + + // Blend projections using computed weights + %RESULT% = xProjection * blendWeights.x + yProjection * blendWeights.y + zProjection * blendWeights.z; + } +)")) + .Replace(TEXT("%TEXTURE%"), texture.Value) + .Replace(TEXT("%SCALE%"), scale.Value) + .Replace(TEXT("%BLEND%"), blend.Value) + .Replace(TEXT("%OFFSET%"), offset.Value) + .Replace(TEXT("%RESULT%"), result.Value) + .Replace(TEXT("%POSITION%"), local ? TEXT("TransformWorldVectorToLocal(input, input.WorldPosition - GetObjectPosition(input)) / GetObjectScale(input)") : TEXT("input.WorldPosition")) + .Replace(TEXT("%NORMAL%"), local ? TEXT("TransformWorldVectorToLocal(input, input.TBN[2])") : TEXT("input.TBN[2]")) + .Replace(TEXT("%SAMPLER%"), samplerName) + .Replace(TEXT("%SAMPLE%"), canUseSample ? TEXT("Sample") : TEXT("SampleLevel")) + .Replace(TEXT("%SAMPLE_ARGS%"), canUseSample ? TEXT("") : TEXT(", 0")) // Sample mip0 when cannot get auto ddx/ddy in Vertex Shader + .Build(); _writer.Write(*triplanarTexture); value = result; break; @@ -747,6 +781,96 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) value = output; break; } + // Triplanar Normal Map + case 23: + { + auto textureBox = node->GetBox(0); + if (!textureBox->HasConnection()) + { + // No texture to sample + value = Value::Zero; + break; + } + const bool canUseSample = CanUseSample(_treeType); + const auto texture = eatBox(textureBox->GetParent(), textureBox->FirstConnection()); + const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat(); + const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); + const auto offset = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat2(); + const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false; + + const Char* samplerName; + const int32 samplerIndex = node->Values[3].AsInt; + if (samplerIndex == TextureGroup) + { + auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].AsInt); + samplerName = *textureGroupSampler.ShaderName; + } + else if (samplerIndex >= 0 && samplerIndex < ARRAY_COUNT(SamplerNames)) + { + samplerName = SamplerNames[samplerIndex]; + } + else + { + OnError(node, box, TEXT("Invalid texture sampler.")); + return; + } + + auto result = writeLocal(Value::InitForZero(ValueType::Float3), node); + + // Reference: https://bgolus.medium.com/normal-mapping-for-a-triplanar-shader-10bf39dca05a + const String triplanarNormalMap = ShaderStringBuilder() + .Code(TEXT(R"( + { + // Get world position and normal + float tiling = %SCALE% * 0.001f; + float3 position = ((%POSITION%) + GetLargeWorldsTileOffset(1.0f / tiling)) * tiling; + float3 normal = normalize(%NORMAL%); + + // Compute triplanar blend weights using power distribution + float3 blendWeights = pow(abs(normal), %BLEND%); + blendWeights /= dot(blendWeights, float3(1, 1, 1)); + + // Unpack normal maps + float3 tnormalX = UnpackNormalMap(%TEXTURE%.%SAMPLE%(%SAMPLER%, position.yz + %OFFSET%%SAMPLE_ARGS%).rg); + float3 tnormalY = UnpackNormalMap(%TEXTURE%.%SAMPLE%(%SAMPLER%, position.xz + %OFFSET%%SAMPLE_ARGS%).rg); + float3 tnormalZ = UnpackNormalMap(%TEXTURE%.%SAMPLE%(%SAMPLER%, position.xy + %OFFSET%%SAMPLE_ARGS%).rg); + + // Apply proper whiteout blend + normal = normalize(input.TBN[2]); + float3 axisSign = sign(normal); + float2 sumX = tnormalX.xy + normal.zy; + float2 sumY = tnormalY.xy + normal.xz; + float2 sumZ = tnormalZ.xy + normal.xy; + tnormalX = float3(sumX, sqrt(1.0 - saturate(dot(sumX, sumX))) * axisSign.x); + tnormalY = float3(sumY, sqrt(1.0 - saturate(dot(sumY, sumY))) * axisSign.y); + tnormalZ = float3(sumZ, sqrt(1.0 - saturate(dot(sumZ, sumZ))) * axisSign.z); + + // Blend the normal maps using the blend weights + float3 blendedNormal = normalize( + tnormalX.zyx * blendWeights.x + + tnormalY.xzy * blendWeights.y + + tnormalZ.xyz * blendWeights.z + ); + + // Transform to tangent space + %RESULT% = normalize(TransformWorldVectorToTangent(input, blendedNormal)); + } +)")) + .Replace(TEXT("%TEXTURE%"), texture.Value) + .Replace(TEXT("%SCALE%"), scale.Value) + .Replace(TEXT("%BLEND%"), blend.Value) + .Replace(TEXT("%OFFSET%"), offset.Value) + .Replace(TEXT("%RESULT%"), result.Value) + .Replace(TEXT("%POSITION%"), local ? TEXT("TransformWorldVectorToLocal(input, input.WorldPosition - GetObjectPosition(input)) / GetObjectScale(input)") : TEXT("input.WorldPosition")) + .Replace(TEXT("%NORMAL%"), local ? TEXT("TransformWorldVectorToLocal(input, input.TBN[2])") : TEXT("input.TBN[2]")) + .Replace(TEXT("%SAMPLER%"), samplerName) + .Replace(TEXT("%SAMPLE%"), canUseSample ? TEXT("Sample") : TEXT("SampleLevel")) + .Replace(TEXT("%SAMPLE_ARGS%"), canUseSample ? TEXT("") : TEXT(", 0")) // Sample mip0 when cannot get auto ddx/ddy in Vertex Shader + .Build(); + _writer.Write(*triplanarNormalMap); + value = result; + break; + } default: break; } diff --git a/Source/Engine/Visject/ShaderStringBuilder.cpp b/Source/Engine/Visject/ShaderStringBuilder.cpp new file mode 100644 index 000000000..84657a292 --- /dev/null +++ b/Source/Engine/Visject/ShaderStringBuilder.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "ShaderStringBuilder.h" + +ShaderStringBuilder& ShaderStringBuilder::Code(const Char* shaderCode) +{ + _code = shaderCode; + return *this; +} + +ShaderStringBuilder& ShaderStringBuilder::Replace(const String& key, const String& value) +{ + _replacements.Add(Pair(key, value)); + return *this; +} + +String ShaderStringBuilder::Build() const +{ + String result = _code; + for (const auto& replacement : _replacements) + { + const auto& key = replacement.First; + const auto& value = replacement.Second; + int32 position = 0; + while ((position = result.Find(key)) != -1) + { + result = String::Format(TEXT("{0}{1}{2}"), + StringView(result.Get(), position), + value, + StringView(result.Get() + position + key.Length())); + } + } + return result; +} diff --git a/Source/Engine/Visject/ShaderStringBuilder.h b/Source/Engine/Visject/ShaderStringBuilder.h new file mode 100644 index 000000000..2492c91e9 --- /dev/null +++ b/Source/Engine/Visject/ShaderStringBuilder.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Array.h" + +// Helper utility for shader source code formatting. +class ShaderStringBuilder +{ +private: + String _code; + Array> _replacements; + +public: + ShaderStringBuilder& Code(const Char* shaderCode); + ShaderStringBuilder& Replace(const String& key, const String& value); + String Build() const; +}; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 1c77d85ae..b36c0a05a 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -261,6 +261,14 @@ struct GBufferOutput float4 RT3 : SV_Target4; }; +float3 UnpackNormalMap(float2 value) +{ + float3 normal; + normal.xy = value * 2.0 - 1.0; + normal.z = sqrt(saturate(1.0 - dot(normal.xy, normal.xy))); + return normal; +} + float3x3 CalcTangentBasis(float3 normal, float3 pos, float2 uv) { // References: From 7ccae0140b97fbbfe170e8c2cae5ddb88fd66dca Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 28 Feb 2025 15:18:29 +0100 Subject: [PATCH 192/215] Fix regression from #3214 on Deformable shader --- Content/Editor/MaterialTemplates/Deformable.shader | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader index 91f96607d..4de948d28 100644 --- a/Content/Editor/MaterialTemplates/Deformable.shader +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -210,7 +210,7 @@ float3 GetObjectSize(MaterialInput input) // Gets the current object scale (supports instancing) float3 GetObjectScale(MaterialInput input) { - float4x4 world = input.Object.WorldMatrix; + float4x4 world = WorldMatrix; // Extract scale from the world matrix float3 scale; From 72c3d2b94c5429b985bc99e205fe69165a885638 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 28 Feb 2025 15:19:47 +0100 Subject: [PATCH 193/215] Update engine materials --- Content/Editor/Camera/M_Camera.flax | 4 ++-- Content/Editor/CubeTexturePreviewMaterial.flax | 4 ++-- Content/Editor/DebugMaterials/DDGIDebugProbes.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Decal.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Particle.flax | 2 +- Content/Editor/DebugMaterials/SingleColor/Surface.flax | 4 ++-- .../Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax | 4 ++-- Content/Editor/DebugMaterials/SingleColor/Terrain.flax | 4 ++-- Content/Editor/DefaultFontMaterial.flax | 4 ++-- Content/Editor/Gizmo/FoliageBrushMaterial.flax | 4 ++-- Content/Editor/Gizmo/Material.flax | 4 ++-- Content/Editor/Gizmo/MaterialWire.flax | 4 ++-- Content/Editor/Gizmo/SelectionOutlineMaterial.flax | 2 +- Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax | 4 ++-- Content/Editor/Highlight Material.flax | 4 ++-- Content/Editor/Icons/IconsMaterial.flax | 4 ++-- Content/Editor/IesProfilePreviewMaterial.flax | 4 ++-- Content/Editor/Particles/Particle Material Color.flax | 4 ++-- Content/Editor/Particles/Smoke Material.flax | 2 +- Content/Editor/SpriteMaterial.flax | 4 ++-- Content/Editor/Terrain/Circle Brush Material.flax | 4 ++-- Content/Editor/Terrain/Highlight Terrain Material.flax | 4 ++-- Content/Editor/TexturePreviewMaterial.flax | 4 ++-- Content/Editor/Wires Debug Material.flax | 4 ++-- Content/Engine/DefaultDeformableMaterial.flax | 4 ++-- Content/Engine/DefaultMaterial.flax | 4 ++-- Content/Engine/DefaultRadialMenu.flax | 4 ++-- Content/Engine/DefaultTerrainMaterial.flax | 4 ++-- Content/Engine/SingleColorMaterial.flax | 4 ++-- Content/Engine/SkyboxMaterial.flax | 4 ++-- 30 files changed, 57 insertions(+), 57 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 4f330b51d..7ac9bab2d 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b36e082905708236b5d27f7ab61b230bc496788e799a52f6fa5d816053a3818 -size 28734 +oid sha256:4dc3a7d0a8287c7c2c26b7685b726583677f96e1fe86ccb5f8518cb189b51a92 +size 29407 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index a476e2582..b0b54b394 100644 --- a/Content/Editor/CubeTexturePreviewMaterial.flax +++ b/Content/Editor/CubeTexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e934a377645658a27cf0f98c302001e8567daf912399f34b3c233fdc0b000a90 -size 30326 +oid sha256:857edec8daa8bbd5bf16c839f65042df7a921154dcf48c1ac7a06aab4e40eab6 +size 30999 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index b0a5ad387..ee6bd8d7b 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce33227101e39743018e58f6b81a33b59eec684cd440b89fba2ba2be1dc6aad7 -size 39559 +oid sha256:fb32df5fa9255c8d27f968819ab7f59236640661c83ae33588733542ea635b0f +size 40232 diff --git a/Content/Editor/DebugMaterials/SingleColor/Decal.flax b/Content/Editor/DebugMaterials/SingleColor/Decal.flax index d8ed52acf..02126626b 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Decal.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Decal.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d398931a452215ce613378897906681eccb6def7e50f753f31de920b1f1f1b1a -size 7489 +oid sha256:fddc7cd2d8732f8eef008d0a2bdc47948b87a14849d9e6fabcfe60ddb218aa42 +size 7617 diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax index c9403453c..bbab3ee4b 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:893a53353feb0c2bf63cf33b6c061ed76771b9e959a904a01286c433a88251d8 +oid sha256:e06851af881852106892b2bd03df0127c84db3a601abd9d7aaac7baf40343369 size 32040 diff --git a/Content/Editor/DebugMaterials/SingleColor/Surface.flax b/Content/Editor/DebugMaterials/SingleColor/Surface.flax index e2c54a6b9..dd9785be9 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Surface.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Surface.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d901e8d5b1237dee6746306da3d8cdb0c33686630b2b4686dbaf8f818bc9901 -size 28507 +oid sha256:34080fbc40ed62bc5501969640403013291b3104dbb4374d9268994f3902e5a8 +size 29180 diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax index b82bca2cd..2ec83813f 100644 --- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax +++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cb52bea70277ccdc6a89cd1aba14e50d09f0842a6dc5b35baece3bf8d733492 -size 30692 +oid sha256:71acbd5242dd2d2f02554af0e5a95da9a5bd90d77aef80dc6fcdaefb88251d23 +size 31365 diff --git a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax index 1f954f5f8..ac31c7161 100644 --- a/Content/Editor/DebugMaterials/SingleColor/Terrain.flax +++ b/Content/Editor/DebugMaterials/SingleColor/Terrain.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73d390d83aa605c26d2879edc5faff503c1e29f2056b501a9753843eb7e29980 -size 21203 +oid sha256:dcc185a2ec2e02ee9f52c806ba3756827c6d12f586c91a12b44295a95dd093dc +size 21331 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index c155358c8..23676ca21 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efb05443172c8cfb69df8f15f9b4f00d1c6d38d55e470f28fa6fff0920ac4ccd -size 28828 +oid sha256:f1500a05e700f3b12d7e3984a978773c61f4c9ecc34ec96720fae724a87bab8e +size 29501 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index eb56b11e5..d88a8eea3 100644 --- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax +++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8b27ed5124e16059411584d56db41add0ca95523c3ac29daa588c0be3c351dc -size 36719 +oid sha256:10aff82173474030b7ce41b491c803cc0de265ea0317a3aca87102099d65ca79 +size 37392 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index 3f159230e..d7815f10f 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba255b7ea8df7bad961bfb56dde20e48a4c6bee0df5a56ed6d232f77caeae394 -size 31846 +oid sha256:3e68032a48b3196f73c614a6302223765485e8fa3e1e278a2395c9d9ae1c5064 +size 32519 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index b24e61be0..26f3956c5 100644 --- a/Content/Editor/Gizmo/MaterialWire.flax +++ b/Content/Editor/Gizmo/MaterialWire.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb7a45405f6c1f6f914c56dc5d16e37f270f04668402f9d7c6f65ed66cc0c624 -size 30543 +oid sha256:dbc08de78faa00aab330168dce166867d85dcae142081219d6a7eeb5f285ffd7 +size 31216 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index e63875b54..70a58006b 100644 --- a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax +++ b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09c7e36108cd272387d8fd7deb02488b633d01cc913e8e6422a52613593ea350 +oid sha256:de0b19b33a2aac03a0fb7beda935c8a22cbb506f9ca9383bcce3eaac858a54e3 size 16166 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index 7c8cb4abf..01ba9f46a 100644 --- a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax +++ b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:586b9ef40a3e65c0636126451b76ce171941437551417ac3e0520e16f82f114d -size 29620 +oid sha256:5a32d40e1f13606fea29e45cb0df1a45dbcc51eb329963d8db18ff64fa66bd4e +size 30293 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index c84b741d4..7c4be9cc2 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae62550a068f3f307e33badd8bbab9176e458c22720a4ae7f3fe793b9c823be3 -size 29237 +oid sha256:d1ec249ecd34cc66f9236431705adfaccff3e9ac5825ea72eb8b18449a96ccb7 +size 29910 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 8a43d39b1..27e1e992f 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4eeca9088f6676fd983d9bdcb14220ea6b18d662291b4051a0f8a8ead77da2da -size 29154 +oid sha256:652de424a7e366c1dcafd42cfe552f2d4f1327cd9ef033fb554d961e0a29ae2b +size 29827 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index 914b15c39..c23d9981d 100644 --- a/Content/Editor/IesProfilePreviewMaterial.flax +++ b/Content/Editor/IesProfilePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67c9eb02ebe38f551a03da83ebcbe7d32a14beb1022a2f6a709975cef36621ca -size 20271 +oid sha256:6388c4a40a0de927abdd532d5bfaff5b7c651f8daa434c6f04a0579e33a1015b +size 20399 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 5af02cba8..07d6d0587 100644 --- a/Content/Editor/Particles/Particle Material Color.flax +++ b/Content/Editor/Particles/Particle Material Color.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5788e73182bd74f8ae86dc6d1a3e3ce8770d770336a60d378b0adfb324db0275 -size 30279 +oid sha256:7c71a6394a3a0668f6d2b18281aaa36d83b1b150fd10a869edace21e1ffa5b07 +size 30361 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index aaebd3630..bd8c1c0e9 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c0f585b0808e5cfc7472ba2eda26ed71ce53bd584a52cc773038869f3673ef7 +oid sha256:6097a8ca31dbe7a985b5c512d2049d2d22c73175551965c75d6b360323505491 size 38427 diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax index 146d6db57..8d001b9c7 100644 --- a/Content/Editor/SpriteMaterial.flax +++ b/Content/Editor/SpriteMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e1c9a11ed04ed150bed712abe1cd716cc0f30828057c80a2a7a82a0c74246da -size 29703 +oid sha256:7ed88e5e8b513f7460e94942ad722d8b193c013434586f4382673f7dc3074e98 +size 30376 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index 0b2395146..f2f21d189 100644 --- a/Content/Editor/Terrain/Circle Brush Material.flax +++ b/Content/Editor/Terrain/Circle Brush Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8af68b6133e5e64d422719958d10bd63c899329592c955134bf2f96fbc2fbaed -size 27875 +oid sha256:9ab7e9c88c9896f37ac5a43efe176e13f98eafacc3117b49cd7173b31376b21d +size 28003 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index de34b92b3..d1e4226f5 100644 --- a/Content/Editor/Terrain/Highlight Terrain Material.flax +++ b/Content/Editor/Terrain/Highlight Terrain Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d31e39fb4cbb543a960f8df0d38ae67c5e206f96e1e05390522dce63c99950f3 -size 21378 +oid sha256:40e38d672c13c06a84366689b8ce2b346179b4aa0de3cf1fc6035b33ad036e4a +size 21506 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 7962f68e9..0bcb9b77f 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7314eb673ab9949fa79b3030488e7f5303f9b6578333f5dce8e6edf3c8b3954 -size 10570 +oid sha256:93b0220308d2a3051d7e449505ca18dab7a82d46bf6a86e4ecfb9b741909f91b +size 10698 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 77ca3d389..bdf2bde08 100644 --- a/Content/Editor/Wires Debug Material.flax +++ b/Content/Editor/Wires Debug Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b05087861471a54921da90e977ecb760ea4191d085270825efc4f20dbb7887ad -size 29089 +oid sha256:d7e22035ac54615fee78cad71d5e5268ff139811d4390fe9a5025ab4d234ff4e +size 29910 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax index 3b9be24c0..b9e972b63 100644 --- a/Content/Engine/DefaultDeformableMaterial.flax +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b4aad4972750f4a633b0bd7bd4c416258b8d5996df0c4ffb864c327c9e92584 -size 18515 +oid sha256:feac2f9ced2d8edf6a43f1c56cdfdc27bbd018a4c23a527a403c30bdb9217e63 +size 18922 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index 48aa481a7..a7949d435 100644 --- a/Content/Engine/DefaultMaterial.flax +++ b/Content/Engine/DefaultMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6f40053f6ff8060180cfb592abe35f973343611e4e123d3c834572862f9d5e0 -size 30532 +oid sha256:1db3d343643eec7c6f7c3ac33a205618480ce41b4b196ebfc6b63e00085a555f +size 31205 diff --git a/Content/Engine/DefaultRadialMenu.flax b/Content/Engine/DefaultRadialMenu.flax index 3ebe4072a..db233a854 100644 --- a/Content/Engine/DefaultRadialMenu.flax +++ b/Content/Engine/DefaultRadialMenu.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48836a2f237180c2f90cb51f54242790bddd558693d4782fe19ae3ed840623bf -size 20340 +oid sha256:a9e9e01ae0222d9dc0db7c51741c3e2c2595bb550fa9dd1665d11ce4fac0afc9 +size 20468 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index d18df0ee2..83e892360 100644 --- a/Content/Engine/DefaultTerrainMaterial.flax +++ b/Content/Engine/DefaultTerrainMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5dd522c412f91d76d979cf2886211824eafc526f48590203ec2b5c20f88394f -size 23500 +oid sha256:7c890a5b43c0158d1885c3905ab02c8b0ee36a558c4770d79892837960ee07e5 +size 23628 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index 3aa389842..515c48cea 100644 --- a/Content/Engine/SingleColorMaterial.flax +++ b/Content/Engine/SingleColorMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:214b54846d487f6e2264ddccbf02eb9e06522add8ff0742f617a748704757b54 -size 28708 +oid sha256:27acfeeafdb50abe76a2feee0616ea90ee5d4fd72c678244d80f15ed4a97894a +size 29381 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 1381ede4a..a8b1b327f 100644 --- a/Content/Engine/SkyboxMaterial.flax +++ b/Content/Engine/SkyboxMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b578128ade99102a95a983b1b54a8de504e56601bdcfca93306597dae881a38c -size 29906 +oid sha256:8e7d0fe3697a0b65eefb8756e0f9aa48bc2d505eb94710b5972097eb61b6b029 +size 30944 From 06de1c00413d2c5c8c6ca87464d0bd9cb4476054 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 5 Mar 2025 10:51:29 +0100 Subject: [PATCH 194/215] Add logging memoery usage by external apps on crash Game crash on out of memory might be caused by user opening too many external apps that use most of memory. --- Source/Engine/Platform/Base/PlatformBase.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index da394d2fd..860f72bc2 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -342,14 +342,20 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er LOG(Error, ""); } - // Log process memory stats + // Log memory stats { const MemoryStats memoryStats = Platform::GetMemoryStats(); - LOG(Error, "Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedPhysicalMemory), (int32)(100 * memoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); - LOG(Error, "Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedVirtualMemory), (int32)(100 * memoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); + LOG(Error, "Total Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedPhysicalMemory), (int32)(100 * memoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); + LOG(Error, "Total Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedVirtualMemory), (int32)(100 * memoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); const ProcessMemoryStats processMemoryStats = Platform::GetProcessMemoryStats(); LOG(Error, "Process Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedPhysicalMemory), (int32)(100 * processMemoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); LOG(Error, "Process Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedVirtualMemory), (int32)(100 * processMemoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); +#define GET_MEM(totalUsed, processUsed) totalUsed > processUsed ? totalUsed - processUsed : 0 + const uint64 externalUsedPhysical = GET_MEM(memoryStats.UsedPhysicalMemory, processMemoryStats.UsedPhysicalMemory); + const uint64 externalUsedVirtual = GET_MEM(memoryStats.UsedVirtualMemory, processMemoryStats.UsedVirtualMemory); +#undef GET_MEM + LOG(Error, "External Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedPhysical), (int32)(100 * externalUsedPhysical / memoryStats.TotalPhysicalMemory)); + LOG(Error, "External Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedVirtual), (int32)(100 * externalUsedVirtual / memoryStats.TotalVirtualMemory)); } } if (Log::Logger::LogFilePath.HasChars()) From 7809007a13d5ab507b0dfc96b78123b8ca67afa2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 5 Mar 2025 12:09:32 +0100 Subject: [PATCH 195/215] Fix deprecation api usage on game exit --- Source/Engine/Engine/Base/GameBase.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index ad45d0848..6bb0d482d 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -198,8 +198,7 @@ void GameBaseImpl::OnMainWindowClosed() Engine::MainWindow = nullptr; // Request engine exit - Globals::IsRequestingExit = true; - Engine::RequestingExit(); + Engine::RequestExit(0); } void GameBaseImpl::OnPostRender(GPUContext* context, RenderContext& renderContext) From 06729f6b62a2e504045dadf2dd1b3770915d5b3c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 5 Mar 2025 12:10:17 +0100 Subject: [PATCH 196/215] Add Program Size Memory access and log on start --- .../Platform/Android/AndroidPlatform.cpp | 5 ++++ Source/Engine/Platform/Base/PlatformBase.cpp | 15 ++++++++--- .../Engine/Platform/Linux/LinuxPlatform.cpp | 13 ++++------ Source/Engine/Platform/MemoryStats.h | 25 +++++++++++-------- .../Engine/Platform/Win32/Win32Platform.cpp | 13 ++++------ 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index c4ef0379c..404cf5476 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -283,6 +283,7 @@ namespace bool IsPaused = true; bool IsVibrating = false; int32 ScreenWidth = 0, ScreenHeight = 0; + uint64 ProgramSizeMemory = 0; Guid DeviceId; String AppPackageName, DeviceManufacturer, DeviceModel, DeviceBuildNumber; String SystemVersion, SystemLanguage, CacheDir, ExecutablePath; @@ -724,6 +725,7 @@ MemoryStats AndroidPlatform::GetMemoryStats() result.UsedPhysicalMemory = (totalPages - availablePages) * pageSize; result.TotalVirtualMemory = result.TotalPhysicalMemory; result.UsedVirtualMemory = result.UsedPhysicalMemory; + result.ProgramSizeMemory = ProgramSizeMemory; return result; } @@ -826,6 +828,9 @@ bool AndroidPlatform::Init() ClockSource = CLOCK_MONOTONIC; } + // Estimate program size by checking physical memory usage on start + ProgramSizeMemory = Platform::GetProcessMemoryStats().UsedPhysicalMemory; + // Set info about the CPU cpu_set_t cpus; CPU_ZERO(&cpus); diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 860f72bc2..40c017c6e 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -174,6 +174,7 @@ void PlatformBase::LogInfo() const MemoryStats memStats = Platform::GetMemoryStats(); LOG(Info, "Physical Memory: {0} total, {1} used ({2}%)", Utilities::BytesToText(memStats.TotalPhysicalMemory), Utilities::BytesToText(memStats.UsedPhysicalMemory), Utilities::RoundTo2DecimalPlaces((float)memStats.UsedPhysicalMemory * 100.0f / (float)memStats.TotalPhysicalMemory)); LOG(Info, "Virtual Memory: {0} total, {1} used ({2}%)", Utilities::BytesToText(memStats.TotalVirtualMemory), Utilities::BytesToText(memStats.UsedVirtualMemory), Utilities::RoundTo2DecimalPlaces((float)memStats.UsedVirtualMemory * 100.0f / (float)memStats.TotalVirtualMemory)); + LOG(Info, "Program Size: {0}", Utilities::BytesToText(memStats.ProgramSizeMemory)); LOG(Info, "Main thread id: 0x{0:x}, Process id: {1}", Globals::MainThreadID, Platform::GetCurrentProcessId()); LOG(Info, "Desktop size: {0}", Platform::GetDesktopSize()); @@ -345,15 +346,21 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er // Log memory stats { const MemoryStats memoryStats = Platform::GetMemoryStats(); - LOG(Error, "Total Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedPhysicalMemory), (int32)(100 * memoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); - LOG(Error, "Total Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedVirtualMemory), (int32)(100 * memoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); const ProcessMemoryStats processMemoryStats = Platform::GetProcessMemoryStats(); - LOG(Error, "Process Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedPhysicalMemory), (int32)(100 * processMemoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); - LOG(Error, "Process Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedVirtualMemory), (int32)(100 * processMemoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); #define GET_MEM(totalUsed, processUsed) totalUsed > processUsed ? totalUsed - processUsed : 0 const uint64 externalUsedPhysical = GET_MEM(memoryStats.UsedPhysicalMemory, processMemoryStats.UsedPhysicalMemory); const uint64 externalUsedVirtual = GET_MEM(memoryStats.UsedVirtualMemory, processMemoryStats.UsedVirtualMemory); #undef GET_MEM + + // Total memory usage + LOG(Error, "Total Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedPhysicalMemory), (int32)(100 * memoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); + LOG(Error, "Total Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(memoryStats.UsedVirtualMemory), (int32)(100 * memoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); + + // Engine memory usage + LOG(Error, "Process Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedPhysicalMemory), (int32)(100 * processMemoryStats.UsedPhysicalMemory / memoryStats.TotalPhysicalMemory)); + LOG(Error, "Process Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(processMemoryStats.UsedVirtualMemory), (int32)(100 * processMemoryStats.UsedVirtualMemory / memoryStats.TotalVirtualMemory)); + + // External apps memory usage LOG(Error, "External Used Physical Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedPhysical), (int32)(100 * externalUsedPhysical / memoryStats.TotalPhysicalMemory)); LOG(Error, "External Used Virtual Memory: {0} ({1}%)", Utilities::BytesToText(externalUsedVirtual), (int32)(100 * externalUsedVirtual / memoryStats.TotalVirtualMemory)); } diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 681b60c37..8c03fa164 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -57,6 +57,7 @@ CPUInfo UnixCpu; int ClockSource; +uint64 ProgramSizeMemory; Guid DeviceId; String UserLocale, ComputerName, HomeDir; byte MacAddress[6]; @@ -1773,32 +1774,25 @@ int32 LinuxPlatform::GetCacheLineSize() MemoryStats LinuxPlatform::GetMemoryStats() { - // Get memory usage const uint64 pageSize = getpagesize(); const uint64 totalPages = get_phys_pages(); const uint64 availablePages = get_avphys_pages(); - - // Fill result data MemoryStats result; result.TotalPhysicalMemory = totalPages * pageSize; result.UsedPhysicalMemory = (totalPages - availablePages) * pageSize; result.TotalVirtualMemory = result.TotalPhysicalMemory; result.UsedVirtualMemory = result.UsedPhysicalMemory; - + result.ProgramSizeMemory = ProgramSizeMemory; return result; } ProcessMemoryStats LinuxPlatform::GetProcessMemoryStats() { - // Get memory usage struct rusage usage; getrusage(RUSAGE_SELF, &usage); - - // Fill result data ProcessMemoryStats result; result.UsedPhysicalMemory = usage.ru_maxrss; result.UsedVirtualMemory = result.UsedPhysicalMemory; - return result; } @@ -1930,6 +1924,9 @@ bool LinuxPlatform::Init() ClockSource = CLOCK_MONOTONIC; } + // Estimate program size by checking physical memory usage on start + ProgramSizeMemory = Platform::GetProcessMemoryStats().UsedPhysicalMemory; + // Set info about the CPU cpu_set_t cpus; CPU_ZERO(&cpus); diff --git a/Source/Engine/Platform/MemoryStats.h b/Source/Engine/Platform/MemoryStats.h index b673c5138..75788d5db 100644 --- a/Source/Engine/Platform/MemoryStats.h +++ b/Source/Engine/Platform/MemoryStats.h @@ -7,45 +7,50 @@ /// /// Contains information about current memory usage and capacity. /// -API_STRUCT() struct MemoryStats +API_STRUCT(NoDefault) struct MemoryStats { -DECLARE_SCRIPTING_TYPE_MINIMAL(MemoryStats); + DECLARE_SCRIPTING_TYPE_MINIMAL(MemoryStats); /// /// Total amount of physical memory in bytes. /// - API_FIELD() uint64 TotalPhysicalMemory; + API_FIELD() uint64 TotalPhysicalMemory = 0; /// /// Amount of used physical memory in bytes. /// - API_FIELD() uint64 UsedPhysicalMemory; + API_FIELD() uint64 UsedPhysicalMemory = 0; /// /// Total amount of virtual memory in bytes. /// - API_FIELD() uint64 TotalVirtualMemory; + API_FIELD() uint64 TotalVirtualMemory = 0; /// /// Amount of used virtual memory in bytes. /// - API_FIELD() uint64 UsedVirtualMemory; + API_FIELD() uint64 UsedVirtualMemory = 0; + + /// + /// Amount of memory used initially by the program data (executable code, exclusive shared libraries and global static data sections). + /// + API_FIELD() uint64 ProgramSizeMemory = 0; }; /// /// Contains information about current memory usage by the process. /// -API_STRUCT() struct ProcessMemoryStats +API_STRUCT(NoDefault) struct ProcessMemoryStats { -DECLARE_SCRIPTING_TYPE_MINIMAL(ProcessMemoryStats); + DECLARE_SCRIPTING_TYPE_MINIMAL(ProcessMemoryStats); /// /// Amount of used physical memory in bytes. /// - API_FIELD() uint64 UsedPhysicalMemory; + API_FIELD() uint64 UsedPhysicalMemory = 0; /// /// Amount of used virtual memory in bytes. /// - API_FIELD() uint64 UsedVirtualMemory; + API_FIELD() uint64 UsedVirtualMemory = 0; }; diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 5650a5839..49548eadf 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -26,6 +26,7 @@ namespace { Guid DeviceId; CPUInfo CpuInfo; + uint64 ProgramSizeMemory; uint64 ClockFrequency; double CyclesToSeconds; WSAData WsaData; @@ -72,6 +73,9 @@ bool Win32Platform::Init() ClockFrequency = frequency.QuadPart; CyclesToSeconds = 1.0 / static_cast(frequency.QuadPart); + // Estimate program size by checking physical memory usage on start + ProgramSizeMemory = Platform::GetProcessMemoryStats().UsedPhysicalMemory; + // Count CPUs BOOL done = FALSE; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr; @@ -313,33 +317,26 @@ int32 Win32Platform::GetCacheLineSize() MemoryStats Win32Platform::GetMemoryStats() { - // Get memory stats MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); - - // Fill result data MemoryStats result; result.TotalPhysicalMemory = statex.ullTotalPhys; result.UsedPhysicalMemory = statex.ullTotalPhys - statex.ullAvailPhys; result.TotalVirtualMemory = statex.ullTotalVirtual; result.UsedVirtualMemory = statex.ullTotalVirtual - statex.ullAvailVirtual; - + result.ProgramSizeMemory = ProgramSizeMemory; return result; } ProcessMemoryStats Win32Platform::GetProcessMemoryStats() { - // Get memory stats PROCESS_MEMORY_COUNTERS_EX countersEx; countersEx.cb = sizeof(countersEx); GetProcessMemoryInfo(GetCurrentProcess(), (PPROCESS_MEMORY_COUNTERS)&countersEx, sizeof(countersEx)); - - // Fill result data ProcessMemoryStats result; result.UsedPhysicalMemory = countersEx.WorkingSetSize; result.UsedVirtualMemory = countersEx.PrivateUsage; - return result; } From 196ada571043c80731f093ad08b7c7abf8dab140 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 5 Mar 2025 13:43:15 +0100 Subject: [PATCH 197/215] Add Extra Development Memory access and log on start --- Source/Engine/Platform/Base/PlatformBase.cpp | 3 +++ Source/Engine/Platform/GDK/GDKPlatform.cpp | 1 - Source/Engine/Platform/MemoryStats.h | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 40c017c6e..c8de3c6cf 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -175,6 +175,9 @@ void PlatformBase::LogInfo() LOG(Info, "Physical Memory: {0} total, {1} used ({2}%)", Utilities::BytesToText(memStats.TotalPhysicalMemory), Utilities::BytesToText(memStats.UsedPhysicalMemory), Utilities::RoundTo2DecimalPlaces((float)memStats.UsedPhysicalMemory * 100.0f / (float)memStats.TotalPhysicalMemory)); LOG(Info, "Virtual Memory: {0} total, {1} used ({2}%)", Utilities::BytesToText(memStats.TotalVirtualMemory), Utilities::BytesToText(memStats.UsedVirtualMemory), Utilities::RoundTo2DecimalPlaces((float)memStats.UsedVirtualMemory * 100.0f / (float)memStats.TotalVirtualMemory)); LOG(Info, "Program Size: {0}", Utilities::BytesToText(memStats.ProgramSizeMemory)); +#if !BUILD_RELEASE && !PLATFORM_DESKTOP + LOG(Info, "Extra Development Memory: {0}", Utilities::BytesToText(memStats.ExtraDevelopmentMemory)); +#endif LOG(Info, "Main thread id: 0x{0:x}, Process id: {1}", Globals::MainThreadID, Platform::GetCurrentProcessId()); LOG(Info, "Desktop size: {0}", Platform::GetDesktopSize()); diff --git a/Source/Engine/Platform/GDK/GDKPlatform.cpp b/Source/Engine/Platform/GDK/GDKPlatform.cpp index 8d3bd935d..fe58c8c98 100644 --- a/Source/Engine/Platform/GDK/GDKPlatform.cpp +++ b/Source/Engine/Platform/GDK/GDKPlatform.cpp @@ -6,7 +6,6 @@ #include "Engine/Platform/Window.h" #include "Engine/Platform/CreateWindowSettings.h" #include "Engine/Platform/WindowsManager.h" -#include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/BatteryInfo.h" #include "Engine/Platform/Base/PlatformUtils.h" #include "Engine/Profiler/ProfilerCPU.h" diff --git a/Source/Engine/Platform/MemoryStats.h b/Source/Engine/Platform/MemoryStats.h index 75788d5db..ab2fb1b91 100644 --- a/Source/Engine/Platform/MemoryStats.h +++ b/Source/Engine/Platform/MemoryStats.h @@ -35,6 +35,11 @@ API_STRUCT(NoDefault) struct MemoryStats /// Amount of memory used initially by the program data (executable code, exclusive shared libraries and global static data sections). /// API_FIELD() uint64 ProgramSizeMemory = 0; + + /// + /// Amount of extra memory assigned by the platform for development. Only used on platforms with fixed memory and no paging. + /// + API_FIELD() uint64 ExtraDevelopmentMemory = 0; }; /// From 8e2f3ec0c0dee02a83c78c80bf246ddf34de4f10 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Mar 2025 00:36:39 +0100 Subject: [PATCH 198/215] Fix `Version` marshaling as parameter --- .../Engine/NativeInterop.Marshallers.cs | 11 +++ .../Engine/Engine/NativeInterop.Unmanaged.cs | 86 ------------------- Source/Engine/Scripting/Scripting.cs | 17 ++-- .../Bindings/BindingsGenerator.CSharp.cs | 9 ++ 4 files changed, 27 insertions(+), 96 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 82359e1b4..63c0f7bc8 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -225,6 +225,17 @@ namespace FlaxEngine.Interop public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.Free(unmanaged); } +#if FLAX_EDITOR + [HideInEditor] +#endif + [CustomMarshaller(typeof(Version), MarshalMode.Default, typeof(VersionMarshaller))] + public static class VersionMarshaller + { + public static Version ConvertToManaged(IntPtr unmanaged) => Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)); + public static IntPtr ConvertToUnmanaged(Version managed) => ManagedHandleMarshaller.ConvertToUnmanaged(managed); + public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.Free(unmanaged); + } + #if FLAX_EDITOR [HideInEditor] #endif diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index e0900f663..6c6858a20 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -56,90 +56,6 @@ namespace FlaxEngine.Interop internal uint setterAttributes; } - [StructLayout(LayoutKind.Explicit)] - public struct NativeVariant - { - [StructLayout(LayoutKind.Sequential)] - internal struct NativeVariantType - { - internal VariantUtils.VariantType types; - internal IntPtr TypeName; // char* - } - - [FieldOffset(0)] - NativeVariantType Type; - - [FieldOffset(8)] - byte AsBool; - - [FieldOffset(8)] - short AsInt16; - - [FieldOffset(8)] - ushort AsUint16; - - [FieldOffset(8)] - int AsInt; - - [FieldOffset(8)] - uint AsUint; - - [FieldOffset(8)] - long AsInt64; - - [FieldOffset(8)] - ulong AsUint64; - - [FieldOffset(8)] - float AsFloat; - - [FieldOffset(8)] - double AsDouble; - - [FieldOffset(8)] - IntPtr AsPointer; - - [FieldOffset(8)] - int AsData0; - - [FieldOffset(12)] - int AsData1; - - [FieldOffset(16)] - int AsData2; - - [FieldOffset(20)] - int AsData3; - - [FieldOffset(24)] - int AsData4; - - [FieldOffset(28)] - int AsData5; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NativeVersion - { - internal int _Major; - internal int _Minor; - internal int _Build; - internal int _Revision; - - internal NativeVersion(Version ver) - { - _Major = ver.Major; - _Minor = ver.Minor; - _Build = ver.Build; - _Revision = ver.Revision; - } - - internal Version GetVersion() - { - return new Version(_Major, _Minor, _Build, _Revision); - } - } - unsafe partial class NativeInterop { [LibraryImport("FlaxEngine", EntryPoint = "NativeInterop_CreateClass", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] @@ -1100,8 +1016,6 @@ namespace FlaxEngine.Interop { Type type = Unsafe.As(typeHandle.Target); Type nativeType = GetInternalType(type) ?? type; - if (nativeType == typeof(Version)) - nativeType = typeof(NativeVersion); int size; if (nativeType.IsClass) size = sizeof(IntPtr); diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 8faf52c2b..39fdba696 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -232,7 +232,13 @@ namespace FlaxEngine internal static ManagedHandle VersionToManaged(int major, int minor, int build, int revision) { - Version version = new Version(major, minor, Math.Max(build, 0), Math.Max(revision, 0)); + Version version; + if (revision >= 0) + version = new Version(major, minor, Math.Max(build, 0), revision); + else if (build >= 0) + version = new Version(major, minor, build); + else + version = new Version(major, minor); return ManagedHandle.Alloc(version); } @@ -245,15 +251,6 @@ namespace FlaxEngine return ManagedHandle.Alloc(new CultureInfo(lcid)); } - [StructLayout(LayoutKind.Sequential)] - internal struct VersionNative - { - public int Major; - public int Minor; - public int Build; - public int Revision; - } - internal static void VersionToNative(ManagedHandle versionHandle, ref int major, ref int minor, ref int build, ref int revision) { Version version = Unsafe.As(versionHandle.Target); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index b524e1648..72f052afa 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -615,6 +615,8 @@ namespace Flax.Build.Bindings returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; else if (returnValueType == "CultureInfo") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))"; + else if (returnValueType == "Version") + returnMarshalType = "MarshalUsing(typeof(VersionMarshaller))"; else if (functionInfo.ReturnType.Type == "Variant") // object returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))"; else if (FindApiTypeInfo(buildData, functionInfo.ReturnType, caller)?.IsInterface ?? false) @@ -676,6 +678,8 @@ namespace Flax.Build.Bindings parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; else if (parameterInfo.Type.Type == "CultureInfo") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))"; + else if (parameterInfo.Type.Type == "Version") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.VersionMarshaller))"; else if (parameterInfo.Type.Type == "Variant") // object parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))"; else if (parameterInfo.Type.Type == "MonoArray" || parameterInfo.Type.Type == "MArray") @@ -731,10 +735,15 @@ namespace Flax.Build.Bindings string parameterMarshalType = ""; if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") { + // TODO: make this code shared with MarshalUsing selection from the above if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer") parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")"; else if (parameterInfo.Type.Type == "Dictionary") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; + else if (parameterInfo.Type.Type == "CultureInfo") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))"; + else if (parameterInfo.Type.Type == "Version") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.VersionMarshaller))"; } if (nativeType == "System.Type") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; From 772f7f7fd246118f491400975ed6729de0e86076 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Mar 2025 09:00:33 +0100 Subject: [PATCH 199/215] Move `GPUDeviceDX::UpdateOutputs` to cpp file --- .../GraphicsDevice/DirectX/GPUDeviceDX.h | 161 +----------------- .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 155 +++++++++++++++++ 2 files changed, 157 insertions(+), 159 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h b/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h index cc6d123d1..61b61847e 100644 --- a/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h @@ -23,83 +23,15 @@ public: Array VideoModes; }; -namespace Windows -{ - typedef struct _devicemodeW - { - WCHAR dmDeviceName[32]; - WORD dmSpecVersion; - WORD dmDriverVersion; - WORD dmSize; - WORD dmDriverExtra; - DWORD dmFields; - - union - { - struct - { - short dmOrientation; - short dmPaperSize; - short dmPaperLength; - short dmPaperWidth; - short dmScale; - short dmCopies; - short dmDefaultSource; - short dmPrintQuality; - } DUMMYSTRUCTNAME; - - POINTL dmPosition; - - struct - { - POINTL dmPosition; - DWORD dmDisplayOrientation; - DWORD dmDisplayFixedOutput; - } DUMMYSTRUCTNAME2; - } DUMMYUNIONNAME; - - short dmColor; - short dmDuplex; - short dmYResolution; - short dmTTOption; - short dmCollate; - WCHAR dmFormName[32]; - WORD dmLogPixels; - DWORD dmBitsPerPel; - DWORD dmPelsWidth; - DWORD dmPelsHeight; - - union - { - DWORD dmDisplayFlags; - DWORD dmNup; - } DUMMYUNIONNAME2; - - DWORD dmDisplayFrequency; - DWORD dmICMMethod; - DWORD dmICMIntent; - DWORD dmMediaType; - DWORD dmDitherType; - DWORD dmReserved1; - DWORD dmReserved2; - DWORD dmPanningWidth; - DWORD dmPanningHeight; - } DEVMODEW, *PDEVMODEW, *NPDEVMODEW, *LPDEVMODEW; - - WIN_API BOOL WIN_API_CALLCONV EnumDisplaySettingsW(LPCWSTR lpszDeviceName, DWORD iModeNum, DEVMODEW* lpDevMode); -} - /// /// Base for all DirectX graphics devices. /// class GPUDeviceDX : public GPUDevice { protected: - GPUAdapterDX* _adapter; protected: - GPUDeviceDX(RendererType type, ShaderProfile profile, GPUAdapterDX* adapter) : GPUDevice(type, profile) , _adapter(adapter) @@ -107,11 +39,9 @@ protected: } public: - /// - /// Gets DirectX device feature level + /// Gets DirectX device feature level. /// - /// D3D feature level FORCE_INLINE D3D_FEATURE_LEVEL GetD3DFeatureLevel() const { return _adapter->MaxFeatureLevel; @@ -124,94 +54,7 @@ public: protected: - void UpdateOutputs(IDXGIAdapter* adapter) - { -#if PLATFORM_WINDOWS - // Collect output devices - uint32 outputIdx = 0; - ComPtr output; - DXGI_FORMAT defaultBackbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT); - Array modeDesc; - while (adapter->EnumOutputs(outputIdx, &output) != DXGI_ERROR_NOT_FOUND) - { - auto& outputDX11 = Outputs.AddOne(); - - outputDX11.Output = output; - output->GetDesc(&outputDX11.Desc); - - uint32 numModes = 0; - HRESULT hr = output->GetDisplayModeList(defaultBackbufferFormat, 0, &numModes, nullptr); - if (FAILED(hr)) - { - LOG(Warning, "Error while enumerating adapter output video modes."); - continue; - } - - modeDesc.Resize(numModes, false); - hr = output->GetDisplayModeList(defaultBackbufferFormat, 0, &numModes, modeDesc.Get()); - if (FAILED(hr)) - { - LOG(Warning, "Error while enumerating adapter output video modes."); - continue; - } - - for (auto& mode : modeDesc) - { - bool foundVideoMode = false; - for (auto& videoMode : outputDX11.VideoModes) - { - if (videoMode.Width == mode.Width && - videoMode.Height == mode.Height && - videoMode.RefreshRate.Numerator == mode.RefreshRate.Numerator && - videoMode.RefreshRate.Denominator == mode.RefreshRate.Denominator) - { - foundVideoMode = true; - break; - } - } - - if (!foundVideoMode) - { - outputDX11.VideoModes.Add(mode); - - // Collect only from the main monitor - if (Outputs.Count() == 1) - { - VideoOutputModes.Add({ mode.Width, mode.Height, (uint32)(mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator) }); - } - } - } - - // Get desktop display mode - HMONITOR hMonitor = outputDX11.Desc.Monitor; - MONITORINFOEX monitorInfo; - monitorInfo.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfo(hMonitor, &monitorInfo); - - Windows::DEVMODEW devMode; - devMode.dmSize = sizeof(Windows::DEVMODEW); - devMode.dmDriverExtra = 0; - Windows::EnumDisplaySettingsW(monitorInfo.szDevice, ((DWORD)-1), &devMode); - - DXGI_MODE_DESC currentMode; - currentMode.Width = devMode.dmPelsWidth; - currentMode.Height = devMode.dmPelsHeight; - bool useDefaultRefreshRate = 1 == devMode.dmDisplayFrequency || 0 == devMode.dmDisplayFrequency; - currentMode.RefreshRate.Numerator = useDefaultRefreshRate ? 0 : devMode.dmDisplayFrequency; - currentMode.RefreshRate.Denominator = useDefaultRefreshRate ? 0 : 1; - currentMode.Format = defaultBackbufferFormat; - currentMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; - currentMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; - - if (output->FindClosestMatchingMode(¤tMode, &outputDX11.DesktopViewMode, nullptr) != S_OK) - outputDX11.DesktopViewMode = currentMode; - - float refreshRate = outputDX11.DesktopViewMode.RefreshRate.Numerator / (float)outputDX11.DesktopViewMode.RefreshRate.Denominator; - LOG(Info, "Video output '{0}' {1}x{2} {3} Hz", outputDX11.Desc.DeviceName, devMode.dmPelsWidth, devMode.dmPelsHeight, refreshRate); - outputIdx++; - } -#endif - } + void UpdateOutputs(IDXGIAdapter* adapter); static RendererType getRendererType(GPUAdapterDX* adapter) { diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index 90c7e6a74..39a975b74 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -3,10 +3,77 @@ #if GRAPHICS_API_DIRECTX11 || GRAPHICS_API_DIRECTX12 #include "RenderToolsDX.h" +#include "GPUDeviceDX.h" #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Graphics/GPUDevice.h" #include +namespace Windows +{ + typedef struct _devicemodeW + { + WCHAR dmDeviceName[32]; + WORD dmSpecVersion; + WORD dmDriverVersion; + WORD dmSize; + WORD dmDriverExtra; + DWORD dmFields; + + union + { + struct + { + short dmOrientation; + short dmPaperSize; + short dmPaperLength; + short dmPaperWidth; + short dmScale; + short dmCopies; + short dmDefaultSource; + short dmPrintQuality; + } DUMMYSTRUCTNAME; + + POINTL dmPosition; + + struct + { + POINTL dmPosition; + DWORD dmDisplayOrientation; + DWORD dmDisplayFixedOutput; + } DUMMYSTRUCTNAME2; + } DUMMYUNIONNAME; + + short dmColor; + short dmDuplex; + short dmYResolution; + short dmTTOption; + short dmCollate; + WCHAR dmFormName[32]; + WORD dmLogPixels; + DWORD dmBitsPerPel; + DWORD dmPelsWidth; + DWORD dmPelsHeight; + + union + { + DWORD dmDisplayFlags; + DWORD dmNup; + } DUMMYUNIONNAME2; + + DWORD dmDisplayFrequency; + DWORD dmICMMethod; + DWORD dmICMIntent; + DWORD dmMediaType; + DWORD dmDitherType; + DWORD dmReserved1; + DWORD dmReserved2; + DWORD dmPanningWidth; + DWORD dmPanningHeight; + } DEVMODEW, *PDEVMODEW, *NPDEVMODEW, *LPDEVMODEW; + + WIN_API BOOL WIN_API_CALLCONV EnumDisplaySettingsW(LPCWSTR lpszDeviceName, DWORD iModeNum, DEVMODEW* lpDevMode); +} + // @formatter:off DXGI_FORMAT PixelFormatToDXGIFormat[110] = @@ -372,5 +439,93 @@ LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& se return ""; } } +void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter) +{ +#if PLATFORM_WINDOWS + // Collect output devices + uint32 outputIdx = 0; + ComPtr output; + DXGI_FORMAT defaultBackbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT); + Array modeDesc; + while (adapter->EnumOutputs(outputIdx, &output) != DXGI_ERROR_NOT_FOUND) + { + auto& outputDX11 = Outputs.AddOne(); + + outputDX11.Output = output; + output->GetDesc(&outputDX11.Desc); + + uint32 numModes = 0; + HRESULT hr = output->GetDisplayModeList(defaultBackbufferFormat, 0, &numModes, nullptr); + if (FAILED(hr)) + { + LOG(Warning, "Error while enumerating adapter output video modes."); + continue; + } + + modeDesc.Resize(numModes, false); + hr = output->GetDisplayModeList(defaultBackbufferFormat, 0, &numModes, modeDesc.Get()); + if (FAILED(hr)) + { + LOG(Warning, "Error while enumerating adapter output video modes."); + continue; + } + + for (auto& mode : modeDesc) + { + bool foundVideoMode = false; + for (auto& videoMode : outputDX11.VideoModes) + { + if (videoMode.Width == mode.Width && + videoMode.Height == mode.Height && + videoMode.RefreshRate.Numerator == mode.RefreshRate.Numerator && + videoMode.RefreshRate.Denominator == mode.RefreshRate.Denominator) + { + foundVideoMode = true; + break; + } + } + + if (!foundVideoMode) + { + outputDX11.VideoModes.Add(mode); + + // Collect only from the main monitor + if (Outputs.Count() == 1) + { + VideoOutputModes.Add({ mode.Width, mode.Height, (uint32)(mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator) }); + } + } + } + + // Get desktop display mode + HMONITOR hMonitor = outputDX11.Desc.Monitor; + MONITORINFOEX monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(hMonitor, &monitorInfo); + + Windows::DEVMODEW devMode; + devMode.dmSize = sizeof(Windows::DEVMODEW); + devMode.dmDriverExtra = 0; + Windows::EnumDisplaySettingsW(monitorInfo.szDevice, ((DWORD)-1), &devMode); + + DXGI_MODE_DESC currentMode; + currentMode.Width = devMode.dmPelsWidth; + currentMode.Height = devMode.dmPelsHeight; + bool useDefaultRefreshRate = 1 == devMode.dmDisplayFrequency || 0 == devMode.dmDisplayFrequency; + currentMode.RefreshRate.Numerator = useDefaultRefreshRate ? 0 : devMode.dmDisplayFrequency; + currentMode.RefreshRate.Denominator = useDefaultRefreshRate ? 0 : 1; + currentMode.Format = defaultBackbufferFormat; + currentMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + currentMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + + if (output->FindClosestMatchingMode(¤tMode, &outputDX11.DesktopViewMode, nullptr) != S_OK) + outputDX11.DesktopViewMode = currentMode; + + float refreshRate = outputDX11.DesktopViewMode.RefreshRate.Numerator / (float)outputDX11.DesktopViewMode.RefreshRate.Denominator; + LOG(Info, "Video output '{0}' {1}x{2} {3} Hz", outputDX11.Desc.DeviceName, devMode.dmPelsWidth, devMode.dmPelsHeight, refreshRate); + outputIdx++; + } +#endif +} #endif From cc7d88d4a9bdbc3a47b9ab349874a9d1952d28a0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Mar 2025 09:08:00 +0100 Subject: [PATCH 200/215] Fix `Platform::`GetCacheLineSize` to be deprecated in favor of existing `CPUInfo.CacheLineSize` --- Source/Engine/Platform/Android/AndroidPlatform.cpp | 5 ----- Source/Engine/Platform/Android/AndroidPlatform.h | 1 - Source/Engine/Platform/Apple/ApplePlatform.cpp | 5 ----- Source/Engine/Platform/Apple/ApplePlatform.h | 1 - Source/Engine/Platform/Base/PlatformBase.cpp | 5 +++++ Source/Engine/Platform/Base/PlatformBase.h | 3 ++- Source/Engine/Platform/Linux/LinuxPlatform.cpp | 5 ----- Source/Engine/Platform/Linux/LinuxPlatform.h | 1 - Source/Engine/Platform/Win32/Win32Platform.cpp | 5 ----- Source/Engine/Platform/Win32/Win32Platform.h | 1 - 10 files changed, 7 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index 404cf5476..466e72e0c 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -710,11 +710,6 @@ CPUInfo AndroidPlatform::GetCPUInfo() return AndroidCpu; } -int32 AndroidPlatform::GetCacheLineSize() -{ - return AndroidCpu.CacheLineSize; -} - MemoryStats AndroidPlatform::GetMemoryStats() { const uint64 pageSize = getpagesize(); diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h index 4d8955627..c300a0f2a 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.h +++ b/Source/Engine/Platform/Android/AndroidPlatform.h @@ -81,7 +81,6 @@ public: } static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentThreadID() diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 4844c41aa..6d9b19296 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -134,11 +134,6 @@ CPUInfo ApplePlatform::GetCPUInfo() return Cpu; } -int32 ApplePlatform::GetCacheLineSize() -{ - return Cpu.CacheLineSize; -} - MemoryStats ApplePlatform::GetMemoryStats() { MemoryStats result; diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index 9f0fddebf..6f16c07e8 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.h +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -67,7 +67,6 @@ public: } static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentThreadID(); diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index c8de3c6cf..fffffa5d0 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -270,6 +270,11 @@ bool PlatformBase::Is64BitApp() #endif } +int32 PlatformBase::GetCacheLineSize() +{ + return Platform::GetCPUInfo().CacheLineSize; +} + void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error) { // Check if is already during fatal state diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index 670251d10..f6f56c4b7 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -380,9 +380,10 @@ public: /// /// Gets the CPU cache line size. + /// [Deprecated in v1.10] /// /// The cache line size. - API_PROPERTY() static int32 GetCacheLineSize() = delete; + API_PROPERTY() DEPRECATED("Use CPUInfo.CacheLineSize instead") static int32 GetCacheLineSize(); /// /// Gets the current memory stats. diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 8c03fa164..354560c34 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1767,11 +1767,6 @@ CPUInfo LinuxPlatform::GetCPUInfo() return UnixCpu; } -int32 LinuxPlatform::GetCacheLineSize() -{ - return UnixCpu.CacheLineSize; -} - MemoryStats LinuxPlatform::GetMemoryStats() { const uint64 pageSize = getpagesize(); diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 10c81f436..864e27812 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -95,7 +95,6 @@ public: } static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentThreadID() diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 49548eadf..eedd36c7a 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -310,11 +310,6 @@ CPUInfo Win32Platform::GetCPUInfo() return CpuInfo; } -int32 Win32Platform::GetCacheLineSize() -{ - return CpuInfo.CacheLineSize; -} - MemoryStats Win32Platform::GetMemoryStats() { MEMORYSTATUSEX statex; diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index 5d020a0fd..70b44876f 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -90,7 +90,6 @@ public: static void FreePages(void* ptr); static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); static uint64 GetCurrentProcessId(); From 7135eb3591d6cf0481f2aafde4a268a033e605d3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 11:06:10 +0100 Subject: [PATCH 201/215] Add `SystemName` and `SystemVersion` to `Platform` api --- .../Platform/Android/AndroidPlatform.cpp | 22 +++++++++++++------ .../Engine/Platform/Android/AndroidPlatform.h | 3 ++- .../Engine/Platform/Apple/ApplePlatform.cpp | 16 ++++++++++++++ Source/Engine/Platform/Apple/ApplePlatform.h | 2 ++ Source/Engine/Platform/Base/PlatformBase.h | 11 ++++++++++ Source/Engine/Platform/BatteryInfo.h | 2 +- Source/Engine/Platform/CPUInfo.h | 4 ++-- Source/Engine/Platform/GDK/GDKPlatform.cpp | 17 +++++++++++++- Source/Engine/Platform/GDK/GDKPlatform.h | 2 ++ .../Engine/Platform/Linux/LinuxPlatform.cpp | 21 ++++++++++++++++++ Source/Engine/Platform/Linux/LinuxPlatform.h | 2 ++ Source/Engine/Platform/Mac/MacPlatform.h | 5 ++--- .../Platform/Windows/WindowsPlatform.cpp | 12 +++++++++- .../Engine/Platform/Windows/WindowsPlatform.h | 2 ++ Source/Engine/Platform/iOS/iOSPlatform.h | 1 - 15 files changed, 105 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index 466e72e0c..5447fc215 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/Version.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/Math.h" @@ -681,11 +682,6 @@ String AndroidPlatform::GetDeviceBuildNumber() return DeviceBuildNumber; } -String AndroidPlatform::GetSystemVersion() -{ - return SystemVersion; -} - void AndroidPlatform::PreInit(android_app* app) { App = app; @@ -889,8 +885,8 @@ void AndroidPlatform::LogInfo() { UnixPlatform::LogInfo(); - LOG(Info, "App Package Name: {0}", AppPackageName); - LOG(Info, "System Version: {0}", SystemVersion); + LOG(Info, "App Package: {0}", AppPackageName); + LOG(Info, "Android {0}", SystemVersion); LOG(Info, "Device: {0} {1}, {2}", DeviceManufacturer, DeviceModel, DeviceBuildNumber); } @@ -945,6 +941,18 @@ void AndroidPlatform::Log(const StringView& msg) #endif +String AndroidPlatform::GetSystemName() +{ + return String::Format(TEXT("Android {}"), SystemVersion); +} + +Version AndroidPlatform::GetSystemVersion() +{ + Version version(0, 0); + Version::Parse(SystemVersion, &version); + return version; +} + int32 AndroidPlatform::GetDpi() { return AConfiguration_getScreenWidthDp(App->config); diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h index c300a0f2a..0ec21784d 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.h +++ b/Source/Engine/Platform/Android/AndroidPlatform.h @@ -21,7 +21,6 @@ public: static String GetDeviceManufacturer(); static String GetDeviceModel(); static String GetDeviceBuildNumber(); - static String GetSystemVersion(); static void PreInit(android_app* app); public: @@ -80,6 +79,8 @@ public: __builtin_prefetch(static_cast(ptr)); } static bool Is64BitPlatform(); + static String GetSystemName(); + static Version GetSystemVersion(); static CPUInfo GetCPUInfo(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 6d9b19296..9b3d2f099 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/Version.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" @@ -32,9 +33,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -129,6 +132,19 @@ bool ApplePlatform::Is64BitPlatform() return PLATFORM_64BITS; } +String ApplePlatform::GetSystemName() +{ + struct utsname systemInfo; + uname(&systemInfo); + return String(systemInfo.machine); +} + +Version ApplePlatform::GetSystemVersion() +{ + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + return Version(version.major, version.majorVersion, version.minorVersion, version.patchVersion); +} + CPUInfo ApplePlatform::GetCPUInfo() { return Cpu; diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h index 6f16c07e8..0324e18f1 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.h +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -66,6 +66,8 @@ public: __builtin_prefetch(static_cast(ptr)); } static bool Is64BitPlatform(); + static String GetSystemName(); + static Version GetSystemVersion(); static CPUInfo GetCPUInfo(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index f6f56c4b7..56da92e50 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -7,6 +7,7 @@ #include struct Guid; +struct Version; struct CPUInfo; struct MemoryStats; struct ProcessMemoryStats; @@ -372,6 +373,16 @@ public: /// True if running on 64-bit computer, otherwise false. API_PROPERTY() static bool Is64BitPlatform() = delete; + /// + /// Gets the name of the operating system. + /// + API_PROPERTY() static String GetSystemName() = delete; + + /// + /// Gets the version of the operating system version. + /// + API_PROPERTY() static Version GetSystemVersion() = delete; + /// /// Gets the CPU information. /// diff --git a/Source/Engine/Platform/BatteryInfo.h b/Source/Engine/Platform/BatteryInfo.h index c7f70ee27..ef0ebaa35 100644 --- a/Source/Engine/Platform/BatteryInfo.h +++ b/Source/Engine/Platform/BatteryInfo.h @@ -7,7 +7,7 @@ /// /// Contains information about power supply (battery). /// -API_STRUCT() struct BatteryInfo +API_STRUCT(NoDefault) struct BatteryInfo { DECLARE_SCRIPTING_TYPE_MINIMAL(BatteryInfo); diff --git a/Source/Engine/Platform/CPUInfo.h b/Source/Engine/Platform/CPUInfo.h index e5b1fcd2d..24b11a0db 100644 --- a/Source/Engine/Platform/CPUInfo.h +++ b/Source/Engine/Platform/CPUInfo.h @@ -7,9 +7,9 @@ /// /// Contains information about CPU (Central Processing Unit). /// -API_STRUCT() struct CPUInfo +API_STRUCT(NoDefault) struct CPUInfo { -DECLARE_SCRIPTING_TYPE_MINIMAL(CPUInfo); + DECLARE_SCRIPTING_TYPE_MINIMAL(CPUInfo); /// /// The number of physical processor packages. diff --git a/Source/Engine/Platform/GDK/GDKPlatform.cpp b/Source/Engine/Platform/GDK/GDKPlatform.cpp index fe58c8c98..14fa665b8 100644 --- a/Source/Engine/Platform/GDK/GDKPlatform.cpp +++ b/Source/Engine/Platform/GDK/GDKPlatform.cpp @@ -10,6 +10,7 @@ #include "Engine/Platform/Base/PlatformUtils.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/Version.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Platform/MessageBox.h" @@ -41,6 +42,7 @@ namespace HANDLE PlmSignalResume = nullptr; PAPPSTATE_REGISTRATION Plm = {}; String UserLocale, ComputerName; + XSystemAnalyticsInfo SystemAnalyticsInfo; XTaskQueueHandle TaskQueue = nullptr; XTaskQueueRegistrationToken UserChangeEventCallbackToken; XTaskQueueRegistrationToken UserDeviceAssociationChangedCallbackToken; @@ -377,6 +379,8 @@ bool GDKPlatform::Init() DWORD tmp; Char buffer[256]; + SystemAnalyticsInfo = XSystemGetAnalyticsInfo(); + // Get user locale string if (GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH)) { @@ -419,7 +423,7 @@ void GDKPlatform::LogInfo() Win32Platform::LogInfo(); // Log system info - const XSystemAnalyticsInfo analyticsInfo = XSystemGetAnalyticsInfo(); + const XSystemAnalyticsInfo& analyticsInfo = SystemAnalyticsInfo; LOG(Info, "{0}, {1}", StringAsUTF16<64>(analyticsInfo.family).Get(), StringAsUTF16<64>(analyticsInfo.form).Get()); LOG(Info, "OS Version {0}.{1}.{2}.{3}", analyticsInfo.osVersion.major, analyticsInfo.osVersion.minor, analyticsInfo.osVersion.build, analyticsInfo.osVersion.revision); } @@ -504,6 +508,17 @@ bool GDKPlatform::IsDebuggerPresent() #endif +String GDKPlatform::GetSystemName() +{ + return String(SystemAnalyticsInfo.form); +} + +Version GDKPlatform::GetSystemVersion() +{ + XVersion version = SystemAnalyticsInfo.hostingOsVersion; + return Version(version.major, version.minor, version.build, version.revision); +} + BatteryInfo GDKPlatform::GetBatteryInfo() { BatteryInfo info; diff --git a/Source/Engine/Platform/GDK/GDKPlatform.h b/Source/Engine/Platform/GDK/GDKPlatform.h index 98297cf33..eb8c9ec8e 100644 --- a/Source/Engine/Platform/GDK/GDKPlatform.h +++ b/Source/Engine/Platform/GDK/GDKPlatform.h @@ -61,6 +61,8 @@ public: static void Log(const StringView& msg); static bool IsDebuggerPresent(); #endif + static String GetSystemName(); + static Version GetSystemVersion(); static BatteryInfo GetBatteryInfo(); static int32 GetDpi(); static String GetUserLocaleName(); diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 354560c34..cfc78a0e4 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/Version.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" @@ -19,6 +20,7 @@ #include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/StringUtils.h" #include "Engine/Platform/MessageBox.h" +#include "Engine/Platform/File.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Platform/Clipboard.h" @@ -2646,6 +2648,25 @@ void LinuxPlatform::Exit() } } +String LinuxPlatform::GetSystemName() +{ + Dictionary configs = LoadConfigFile(TEXT("/etc/os-release")); + String str; + if (configs.TryGet(TEXT("NAME"), str)) + return str; + return TEXT("Linux"); +} + +Version LinuxPlatform::GetSystemVersion() +{ + Dictionary configs = LoadConfigFile(TEXT("/etc/os-release")); + String str; + Version version; + if (configs.TryGet(TEXT("VERSION_ID"), str) && !Version::Parse(str, &version)) + return version; + return Version(0, 0); +} + int32 LinuxPlatform::GetDpi() { return SystemDpi; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 864e27812..faf41febc 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -94,6 +94,8 @@ public: __builtin_prefetch(static_cast(ptr)); } static bool Is64BitPlatform(); + static String GetSystemName(); + static Version GetSystemVersion(); static CPUInfo GetCPUInfo(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); diff --git a/Source/Engine/Platform/Mac/MacPlatform.h b/Source/Engine/Platform/Mac/MacPlatform.h index c47d1b118..8e4efb887 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.h +++ b/Source/Engine/Platform/Mac/MacPlatform.h @@ -12,21 +12,20 @@ class FLAXENGINE_API MacPlatform : public ApplePlatform { public: - // [ApplePlatform] static bool Init(); static void LogInfo(); static void BeforeRun(); static void Tick(); static int32 GetDpi(); - static Guid GetUniqueDeviceId(); + static Guid GetUniqueDeviceId(); static String GetComputerName(); static Float2 GetMousePosition(); static void SetMousePosition(const Float2& pos); static Rectangle GetMonitorBounds(const Float2& screenPos); static Float2 GetDesktopSize(); static Rectangle GetVirtualDesktopBounds(); - static String GetMainDirectory(); + static String GetMainDirectory(); static Window* CreateWindow(const CreateWindowSettings& settings); static int32 CreateProcess(CreateProcessSettings& settings); }; diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 41ea7a340..20e17e800 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -11,8 +11,8 @@ #include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/BatteryInfo.h" #include "Engine/Platform/Base/PlatformUtils.h" -#include "Engine/Engine/Globals.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/Version.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Platform/MessageBox.h" @@ -793,6 +793,16 @@ void WindowsPlatform::SetHighDpiAwarenessEnabled(bool enable) ::FreeLibrary(shCoreDll); } +String WindowsPlatform::GetSystemName() +{ + return WindowsName; +} + +Version WindowsPlatform::GetSystemVersion() +{ + return Version(VersionMajor, VersionMinor, VersionBuild); +} + BatteryInfo WindowsPlatform::GetBatteryInfo() { BatteryInfo info; diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.h b/Source/Engine/Platform/Windows/WindowsPlatform.h index 046b79d58..c01b6cdaa 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.h +++ b/Source/Engine/Platform/Windows/WindowsPlatform.h @@ -67,6 +67,8 @@ public: static bool IsDebuggerPresent(); #endif static void SetHighDpiAwarenessEnabled(bool enable); + static String GetSystemName(); + static Version GetSystemVersion(); static BatteryInfo GetBatteryInfo(); static int32 GetDpi(); static String GetUserLocaleName(); diff --git a/Source/Engine/Platform/iOS/iOSPlatform.h b/Source/Engine/Platform/iOS/iOSPlatform.h index 87d08e2a6..f6dffea61 100644 --- a/Source/Engine/Platform/iOS/iOSPlatform.h +++ b/Source/Engine/Platform/iOS/iOSPlatform.h @@ -18,7 +18,6 @@ public: static void RunOnMainThread(const Function& func, bool wait = false); public: - // [ApplePlatform] static bool Init(); static void LogInfo(); From 5f57286cdb17371098d3c0034a9bfa5ca17b9497 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 11:09:32 +0100 Subject: [PATCH 202/215] Fix Windows detection to use correct system version it was build for (eg. Win10 or Win7) --- .../Platform/Windows/WindowsPlatform.cpp | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 20e17e800..1ac886afe 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -637,12 +637,32 @@ bool WindowsPlatform::Init() } } - // Check if can run Engine on current platform (requires Windows Vista SP1 or above) - if (!IsWindowsVistaSP1OrGreater() && !IsWindowsServer()) + // Check if can run Engine on current platform +#if WINVER >= 0x0A00 + if (!IsWindows10OrGreater() && !IsWindowsServer()) { - Platform::Fatal(TEXT("Flax Engine requires Windows Vista SP1 or higher.")); + Platform::Fatal(TEXT("Flax Engine requires Windows 10 or higher.")); return true; } +#elif WINVER >= 0x0603 + if (!IsWindows8Point1OrGreater() && !IsWindowsServer()) + { + Platform::Fatal(TEXT("Flax Engine requires Windows 8.1 or higher.")); + return true; + } +#elif WINVER >= 0x0602 + if (!IsWindows8OrGreater() && !IsWindowsServer()) + { + Platform::Fatal(TEXT("Flax Engine requires Windows 8 or higher.")); + return true; + } +#else + if (!IsWindows7OrGreater() && !IsWindowsServer()) + { + Platform::Fatal(TEXT("Flax Engine requires Windows 7 or higher.")); + return true; + } +#endif // Set the lowest possible timer resolution const HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); From 301491bcdec71e1f8d91ac41a9a90f5f574ff4bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 11:39:15 +0100 Subject: [PATCH 203/215] Fix Linux platform init to correctly calculate device id and add missing change --- .../Engine/Platform/Linux/LinuxPlatform.cpp | 71 +++++++++++++------ 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index cfc78a0e4..fce908756 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1331,6 +1331,33 @@ namespace Impl X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp); return FindAppWindow(display, child); } + + Dictionary LoadConfigFile(StringView path) + { + Dictionary results; + String data; + File::ReadAllText(path, data); + Array lines, parts; + data.Split('\n', lines); + for (String& line : lines) + { + line = line.TrimTrailing(); + if (line.StartsWith('#')) + continue; // Skip comments + line.Split('=', parts); + if (parts.Count() == 2) + { + String key = parts[0].TrimTrailing(); + String value = parts[1].TrimTrailing(); + if (key.StartsWith('\"')) + key = key.Substring(1, key.Length() - 2); + if (value.StartsWith('\"')) + value = value.Substring(1, value.Length() - 2); + results[key] = value; + } + } + return results; + } } class LinuxDropFilesData : public IGuiData @@ -2062,28 +2089,6 @@ bool LinuxPlatform::Init() UnixGetMacAddress(MacAddress); - // Generate unique device ID - { - DeviceId = Guid::Empty; - - // A - Computer Name and User Name - uint32 hash = GetHash(Platform::GetComputerName()); - CombineHash(hash, GetHash(Platform::GetUserName())); - DeviceId.A = hash; - - // B - MAC address - hash = MacAddress[0]; - for (uint32 i = 0; i < 6; i++) - CombineHash(hash, MacAddress[i]); - DeviceId.B = hash; - - // C - memory - DeviceId.C = (uint32)Platform::GetMemoryStats().TotalPhysicalMemory; - - // D - cpuid - DeviceId.D = (uint32)UnixCpu.ClockSpeed * UnixCpu.LogicalProcessorCount * UnixCpu.ProcessorCoreCount * UnixCpu.CacheLineSize; - } - // Get user locale string setlocale(LC_ALL, ""); const char* locale = setlocale(LC_CTYPE, NULL); @@ -2111,6 +2116,28 @@ bool LinuxPlatform::Init() Platform::MemoryClear(Cursors, sizeof(Cursors)); Platform::MemoryClear(CursorsImg, sizeof(CursorsImg)); + // Generate unique device ID + { + DeviceId = Guid::Empty; + + // A - Computer Name and User Name + uint32 hash = GetHash(Platform::GetComputerName()); + CombineHash(hash, GetHash(Platform::GetUserName())); + DeviceId.A = hash; + + // B - MAC address + hash = MacAddress[0]; + for (uint32 i = 0; i < 6; i++) + CombineHash(hash, MacAddress[i]); + DeviceId.B = hash; + + // C - memory + DeviceId.C = (uint32)Platform::GetMemoryStats().TotalPhysicalMemory; + + // D - cpuid + DeviceId.D = (uint32)UnixCpu.ClockSpeed * UnixCpu.LogicalProcessorCount * UnixCpu.ProcessorCoreCount * UnixCpu.CacheLineSize; + } + // Skip setup if running in headless mode (X11 might not be available on servers) if (CommandLine::Options.Headless.IsTrue()) return false; From 0fa88b23c6830cc5245f92e9456f0526f0913ef6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 11:39:54 +0100 Subject: [PATCH 204/215] Add `DriverVersion` to `GPUAdapter` for checking on old GPU drivers --- Source/Engine/Graphics/GPUAdapter.h | 6 + Source/Engine/Graphics/Graphics.cpp | 8 +- .../DirectX/DX11/GPUDeviceDX11.cpp | 4 +- .../DirectX/DX12/GPUDeviceDX12.cpp | 7 +- .../GraphicsDevice/DirectX/GPUAdapterDX.h | 19 +-- .../GraphicsDevice/DirectX/GPUDeviceDX.h | 9 +- .../GraphicsDevice/DirectX/RenderToolsDX.cpp | 149 +++++++++++++++++- .../GraphicsDevice/Null/GPUAdapterNull.h | 4 + .../GraphicsDevice/Vulkan/GPUAdapterVulkan.h | 21 +++ 9 files changed, 195 insertions(+), 32 deletions(-) diff --git a/Source/Engine/Graphics/GPUAdapter.h b/Source/Engine/Graphics/GPUAdapter.h index af235a085..f845d7e7f 100644 --- a/Source/Engine/Graphics/GPUAdapter.h +++ b/Source/Engine/Graphics/GPUAdapter.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Types/Version.h" #include "Engine/Scripting/ScriptingObject.h" // GPU vendors IDs @@ -56,6 +57,11 @@ public: /// API_PROPERTY() virtual String GetDescription() const = 0; + /// + /// Gets the GPU driver version. + /// + API_PROPERTY() virtual Version GetDriverVersion() const = 0; + public: // Returns true if adapter's vendor is AMD. API_PROPERTY() FORCE_INLINE bool IsAMD() const diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index b4fbefb7a..c189b6e50 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -182,12 +182,12 @@ bool GraphicsService::Init() return true; } GPUDevice::Instance = device; - LOG(Info, - "Graphics Device created! Adapter: \'{0}\', Renderer: {1}, Shader Profile: {2}, Feature Level: {3}", - device->GetAdapter()->GetDescription(), + LOG(Info, "GPU Device created: {}", device->GetAdapter()->GetDescription()); + LOG(Info, "Renderer: {}, Shader Profile: {}, Feature Level: {}, Driver: {}", ::ToString(device->GetRendererType()), ::ToString(device->GetShaderProfile()), - ::ToString(device->GetFeatureLevel()) + ::ToString(device->GetFeatureLevel()), + device->GetAdapter()->GetDriverVersion().ToString() ); // Initialize diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index ac11989d5..f6dff7b44 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -296,8 +296,6 @@ GPUDevice* GPUDeviceDX11::Create() } } } - - // Validate adapter if (!selectedAdapter.IsValid()) { LOG(Error, "Failed to choose valid DirectX adapter!"); @@ -427,7 +425,7 @@ bool GPUDeviceDX11::Init() // Create DirectX device D3D_FEATURE_LEVEL createdFeatureLevel = static_cast(0); - auto targetFeatureLevel = GetD3DFeatureLevel(); + D3D_FEATURE_LEVEL targetFeatureLevel = _adapter->MaxFeatureLevel; VALIDATE_DIRECTX_CALL(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, &targetFeatureLevel, 1, D3D11_SDK_VERSION, &_device, &createdFeatureLevel, &_imContext)); ASSERT(_device); ASSERT(_imContext); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 65e3f6d83..e646dd650 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -198,17 +198,14 @@ GPUDevice* GPUDeviceDX12::Create() } } } - - // Validate adapter if (!selectedAdapter.IsValid()) { LOG(Error, "Failed to choose valid DirectX adapter!"); return nullptr; } - - // Check if selected adapter does not support DirectX 12 - if (!selectedAdapter.IsSupportingDX12()) + if (selectedAdapter.MaxFeatureLevel < D3D_FEATURE_LEVEL_12_0) { + LOG(Error, "Failed to choose valid DirectX adapter!"); return nullptr; } #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/GPUAdapterDX.h b/Source/Engine/GraphicsDevice/DirectX/GPUAdapterDX.h index 881d40a17..5a5ef1d69 100644 --- a/Source/Engine/GraphicsDevice/DirectX/GPUAdapterDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/GPUAdapterDX.h @@ -14,25 +14,18 @@ class GPUAdapterDX : public GPUAdapter { public: - int32 Index = INVALID_INDEX; D3D_FEATURE_LEVEL MaxFeatureLevel; DXGI_ADAPTER_DESC Description; + Version DriverVersion = Version(0, 0); public: + void GetDriverVersion(); - // Returns true if adapter is supporting DirectX 12. - FORCE_INLINE bool IsSupportingDX12() const - { -#if GRAPHICS_API_DIRECTX12 - return MaxFeatureLevel >= D3D_FEATURE_LEVEL_12_0; -#else - return false; -#endif - } +private: + void SetDriverVersion(Version& ver); public: - // [GPUAdapter] bool IsValid() const override { @@ -50,6 +43,10 @@ public: { return Description.Description; } + Version GetDriverVersion() const override + { + return DriverVersion; + } }; #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h b/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h index 61b61847e..7fa9087ab 100644 --- a/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h +++ b/Source/Engine/GraphicsDevice/DirectX/GPUDeviceDX.h @@ -36,17 +36,10 @@ protected: : GPUDevice(type, profile) , _adapter(adapter) { + adapter->GetDriverVersion(); } public: - /// - /// Gets DirectX device feature level. - /// - FORCE_INLINE D3D_FEATURE_LEVEL GetD3DFeatureLevel() const - { - return _adapter->MaxFeatureLevel; - } - /// /// The video outputs. /// diff --git a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp index 39a975b74..9832e4a12 100644 --- a/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/RenderToolsDX.cpp @@ -3,11 +3,24 @@ #if GRAPHICS_API_DIRECTX11 || GRAPHICS_API_DIRECTX12 #include "RenderToolsDX.h" +#include "GPUAdapterDX.h" #include "GPUDeviceDX.h" +#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Graphics/GPUDevice.h" +#include "IncludeDirectXHeaders.h" #include +#define GPU_DRIVER_DETECTION_WIN32_REGISTRY (PLATFORM_WINDOWS) +#define GPU_DRIVER_DETECTION_WIN32_SETUPAPI (PLATFORM_WINDOWS) + +#if GPU_DRIVER_DETECTION_WIN32_SETUPAPI +#define _SETUPAPI_VER WINVER +typedef void* LPCDLGTEMPLATE; +#include +#pragma comment(lib, "SetupAPI.lib") +#endif + namespace Windows { typedef struct _devicemodeW @@ -304,7 +317,7 @@ void FormatD3DErrorString(HRESULT errorCode, StringBuilder& sb, HRESULT& removed default: sb.AppendFormat(TEXT("0x{0:x}"), static_cast(errorCode)); - break; + break; } #undef D3DERR @@ -439,6 +452,140 @@ LPCSTR RenderToolsDX::GetVertexInputSemantic(VertexElement::Types type, UINT& se return ""; } } + +void GPUAdapterDX::GetDriverVersion() +{ +#if GPU_DRIVER_DETECTION_WIN32_REGISTRY + { + // Reference: https://github.com/GameTechDev/gpudetect/blob/master/GPUDetect.cpp + + // Fetch registry data + HKEY dxKeyHandle = nullptr; + DWORD numOfAdapters = 0; + LSTATUS returnCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\DirectX"), 0, KEY_READ, &dxKeyHandle); + if (returnCode == S_OK) + { + // Find all sub keys + DWORD subKeyMaxLength = 0; + returnCode = RegQueryInfoKeyW(dxKeyHandle, 0, 0, 0, &numOfAdapters, &subKeyMaxLength, 0, 0, 0, 0, 0, 0); + if (returnCode == S_OK && subKeyMaxLength < 100) + { + subKeyMaxLength += 1; + uint64_t driverVersionRaw = 0; + TCHAR subKeyName[100]; + for (DWORD i = 0; i < numOfAdapters; i++) + { + DWORD subKeyLength = subKeyMaxLength; + returnCode = RegEnumKeyExW(dxKeyHandle, i, subKeyName, &subKeyLength, 0, 0, 0, 0); + if (returnCode == S_OK) + { + LUID adapterLUID = {}; + DWORD qwordSize = sizeof(uint64_t); + returnCode = RegGetValueW(dxKeyHandle, subKeyName, TEXT("AdapterLuid"), RRF_RT_QWORD, 0, &adapterLUID, &qwordSize); + if (returnCode == S_OK && adapterLUID.HighPart == Description.AdapterLuid.HighPart && adapterLUID.LowPart == Description.AdapterLuid.LowPart) + { + // Get driver version + returnCode = RegGetValueW(dxKeyHandle, subKeyName, TEXT("DriverVersion"), RRF_RT_QWORD, 0, &driverVersionRaw, &qwordSize); + if (returnCode == S_OK) + { + Version driverVersion( + (int32)((driverVersionRaw & 0xFFFF000000000000) >> 16 * 3), + (int32)((driverVersionRaw & 0x0000FFFF00000000) >> 16 * 2), + (int32)((driverVersionRaw & 0x00000000FFFF0000) >> 16 * 1), + (int32)((driverVersionRaw & 0x000000000000FFFF))); + SetDriverVersion(driverVersion); + } + break; + } + } + } + } + RegCloseKey(dxKeyHandle); + } + + if (DriverVersion != Version(0, 0)) + return; + } +#endif + +#if GPU_DRIVER_DETECTION_WIN32_SETUPAPI + { + // Reference: https://gist.github.com/LxLasso/eccee4d71c2e49492f2cbf01a966fa73 + + // Copied from devguid.h and devpkey.h +#define MAKE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#define MAKE_DEVPROPKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) const DEVPROPKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } + MAKE_GUID(GUID_DEVCLASS_DISPLAY, 0x4d36e968L, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18); + MAKE_DEVPROPKEY(DEVPKEY_Device_DriverDate, 0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6, 2); + MAKE_DEVPROPKEY(DEVPKEY_Device_DriverVersion, 0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6, 3); +#undef MAKE_DEVPROPKEY +#undef MAKE_GUID + + HDEVINFO deviceInfoList = SetupDiGetClassDevs(&GUID_DEVCLASS_DISPLAY, NULL, NULL, DIGCF_PRESENT); + if (deviceInfoList != INVALID_HANDLE_VALUE) + { + SP_DEVINFO_DATA deviceInfo; + ZeroMemory(&deviceInfo, sizeof(deviceInfo)); + deviceInfo.cbSize = sizeof(SP_DEVINFO_DATA); + + wchar_t searchBuffer[128]; + swprintf(searchBuffer, sizeof(searchBuffer) / 2, L"PCI\\VEN_%04X&DEV_%04X&SUBSYS_%04X", Description.VendorId, Description.DeviceId, Description.SubSysId); + size_t searchBufferLen = wcslen(searchBuffer); + + DWORD deviceIndex = 0; + DEVPROPTYPE propertyType; + wchar_t buffer[300]; + while (SetupDiEnumDeviceInfo(deviceInfoList, deviceIndex, &deviceInfo)) + { + DWORD deviceIdSize; + if (SetupDiGetDeviceInstanceId(deviceInfoList, &deviceInfo, buffer, sizeof(buffer), &deviceIdSize) && + wcsncmp(buffer, searchBuffer, searchBufferLen) == 0) + { + // Get driver version + if (SetupDiGetDeviceProperty(deviceInfoList, &deviceInfo, &DEVPKEY_Device_DriverVersion, &propertyType, (PBYTE)buffer, sizeof(buffer), NULL, 0) && + propertyType == DEVPROP_TYPE_STRING) + { + //ParseDriverVersionBuffer(buffer); + Version driverVersion; + String bufferStr(buffer); + if (!Version::Parse(bufferStr, &driverVersion)) + SetDriverVersion(driverVersion); + } + +#if 0 + // Get driver date + DEVPROPTYPE propertyType; + if (SetupDiGetDeviceProperty(deviceInfoList, &deviceInfo, &DEVPKEY_Device_DriverDate, &propertyType, (PBYTE)buffer, sizeof(FILETIME), NULL, 0) && + propertyType == DEVPROP_TYPE_FILETIME) + { + SYSTEMTIME deviceDriverSystemTime; + FileTimeToSystemTime((FILETIME*)buffer, &deviceDriverSystemTime); + DriverDate = DateTime(deviceDriverSystemTime.wYear, deviceDriverSystemTime.wMonth, deviceDriverSystemTime.wDay, deviceDriverSystemTime.wHour, deviceDriverSystemTime.wMinute, deviceDriverSystemTime.wSecond, deviceDriverSystemTime.wMilliseconds); + } +#endif + } + deviceIndex++; + } + + SetupDiDestroyDeviceInfoList(deviceInfoList); + } + + if (DriverVersion != Version(0, 0)) + return; + } +#endif +} + +void GPUAdapterDX::SetDriverVersion(Version& ver) +{ + if (IsNVIDIA() && ver.Build() > 0 && ver.Revision() > 99) + { + // Convert NVIDIA version from 32.0.15.7247 into 572.47 + ver = Version((ver.Build() % 10) * 100 + ver.Revision() / 100, ver.Revision() % 100); + } + DriverVersion = ver; +} + void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter) { #if PLATFORM_WINDOWS diff --git a/Source/Engine/GraphicsDevice/Null/GPUAdapterNull.h b/Source/Engine/GraphicsDevice/Null/GPUAdapterNull.h index 28cbac304..8f2fe49e8 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUAdapterNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUAdapterNull.h @@ -29,6 +29,10 @@ public: { return TEXT("Null"); } + Version GetDriverVersion() const override + { + return Version(0, 0); + } }; #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h index e109293a6..66b9256fd 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h @@ -70,6 +70,27 @@ public: { return Description; } + Version GetDriverVersion() const override + { + Version version(VK_VERSION_MAJOR(GpuProps.driverVersion), VK_VERSION_MINOR(GpuProps.driverVersion), VK_VERSION_PATCH(GpuProps.driverVersion)); + if (IsNVIDIA()) + { + union NvidiaDriverVersion + { + struct + { + uint32 Tertiary : 6; + uint32 Secondary : 8; + uint32 Minor : 8; + uint32 Major : 10; + }; + uint32 Packed; + } NvidiaVersion; + NvidiaVersion.Packed = GpuProps.driverVersion; + version = Version(NvidiaVersion.Major, NvidiaVersion.Minor); + } + return version; + } }; #endif From 4fada6ba40cbdf873979c66b3fdbb45e96f567f9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 12:06:15 +0100 Subject: [PATCH 205/215] Add logging CPU name on Windows --- .../Platform/Windows/WindowsPlatform.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 1ac886afe..397c743fd 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -710,6 +710,24 @@ void WindowsPlatform::LogInfo() { Win32Platform::LogInfo(); +#if PLATFORM_ARCH_X86 || PLATFORM_ARCH_X64 + // Log CPU brand + { + char brandBuffer[0x40] = {}; + int32 cpuInfo[4] = { -1 }; + __cpuid(cpuInfo, 0x80000000); + if (cpuInfo[0] >= 0x80000004) + { + for (uint32 i = 0; i < 3; i++) + { + __cpuid(cpuInfo, 0x80000002 + i); + memcpy(brandBuffer + i * sizeof(cpuInfo), cpuInfo, sizeof(cpuInfo)); + } + } + LOG(Info, "CPU: {0}", String(brandBuffer)); + } +#endif + LOG(Info, "Microsoft {0} {1}-bit ({2}.{3}.{4})", WindowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor, VersionBuild); // Check minimum amount of RAM From 5d80cb03f1c9a883bc9030ac5780e5dad25a3b29 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 12:35:54 +0100 Subject: [PATCH 206/215] Fix compilation issues --- Source/Engine/Platform/Apple/ApplePlatform.cpp | 2 +- Source/Engine/Platform/Linux/LinuxPlatform.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index 9b3d2f099..bf65042a0 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -142,7 +142,7 @@ String ApplePlatform::GetSystemName() Version ApplePlatform::GetSystemVersion() { NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; - return Version(version.major, version.majorVersion, version.minorVersion, version.patchVersion); + return Version(version.majorVersion, version.minorVersion, version.patchVersion); } CPUInfo ApplePlatform::GetCPUInfo() diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index fce908756..e38faf135 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -2677,7 +2677,7 @@ void LinuxPlatform::Exit() String LinuxPlatform::GetSystemName() { - Dictionary configs = LoadConfigFile(TEXT("/etc/os-release")); + Dictionary configs = Impl::LoadConfigFile(TEXT("/etc/os-release")); String str; if (configs.TryGet(TEXT("NAME"), str)) return str; @@ -2686,7 +2686,7 @@ String LinuxPlatform::GetSystemName() Version LinuxPlatform::GetSystemVersion() { - Dictionary configs = LoadConfigFile(TEXT("/etc/os-release")); + Dictionary configs = Impl::LoadConfigFile(TEXT("/etc/os-release")); String str; Version version; if (configs.TryGet(TEXT("VERSION_ID"), str) && !Version::Parse(str, &version)) From 84f6667105f3a4b8c5395cea3c3e89fa93415010 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 12:52:45 +0100 Subject: [PATCH 207/215] Fix missing MoltenVK error to be logged only once --- .../GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs index 663ff97a0..a22d64bca 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs +++ b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs @@ -11,6 +11,8 @@ using Flax.Build.NativeCpp; /// public sealed class VulkanSdk : Sdk { + private bool _missingMoltenVkError; + /// /// The singleton instance. /// @@ -137,7 +139,7 @@ public sealed class VulkanSdk : Sdk /// /// Adds any runtime dependency files to the build that uses Vulkan SDK. /// - /// Build options. + /// Build options. public void AddDependencyFiles(BuildOptions options) { switch (options.Platform.Target) @@ -181,7 +183,7 @@ public sealed class VulkanSdk : Sdk return; } } - Log.Error($"Missing MoltenVK files for {platformName} in VulkanSDK '{RootPath}'"); + Log.ErrorOnce($"Missing MoltenVK files for {platformName} in VulkanSDK '{RootPath}'", ref _missingMoltenVkError); break; } } From 0c0f1285852b609ba35d935b02b9f48c5a6ff9a9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Mar 2025 23:39:01 +0100 Subject: [PATCH 208/215] Fix collision proxy not updating on sequential mesh changes #2920 --- Source/Engine/Graphics/Models/MeshBase.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index 5c38dd24b..8225472b0 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -469,13 +469,10 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData); - else - _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData); - } + if (use16BitIndexBuffer) + _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData); + else + _collisionProxy.Init(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData); #endif // Initialize From baf28382a81a3a76e69e34fd93d0f67d5514f948 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Mar 2025 19:46:35 +0100 Subject: [PATCH 209/215] Disable contact shadows on low shadows quality mode and fix step count for high setting --- Content/Shaders/Shadows.flax | 4 ++-- Source/Shaders/Shadows.shader | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Content/Shaders/Shadows.flax b/Content/Shaders/Shadows.flax index ef25d1879..ddd2cd83a 100644 --- a/Content/Shaders/Shadows.flax +++ b/Content/Shaders/Shadows.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:236797180d9933b69146c2651d3778997e0da6d055ad74477b254f4cb3767783 -size 6505 +oid sha256:594bfd3a29298f746c19058b1d8d80b929e89ebe05e5fe211f8b78e4bb9d83cd +size 6571 diff --git a/Source/Shaders/Shadows.shader b/Source/Shaders/Shadows.shader index 92c2d0815..510c68c09 100644 --- a/Source/Shaders/Shadows.shader +++ b/Source/Shaders/Shadows.shader @@ -30,7 +30,7 @@ float RayCastScreenSpaceShadow(GBufferData gBufferData, GBufferSample gBuffer, f { #if SHADOWS_QUALITY == 3 const uint maxSteps = 16; -#elif SHADOWS_QUALITY == 3 +#elif SHADOWS_QUALITY == 2 const uint maxSteps = 12; #else const uint maxSteps = 8; @@ -92,7 +92,7 @@ float4 PS_PointLight(Model_VS2PS input) : SV_Target0 // Sample shadow ShadowSample shadow = SamplePointLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer); -#if CONTACT_SHADOWS +#if CONTACT_SHADOWS && SHADOWS_QUALITY > 0 // Calculate screen-space contact shadow shadow.SurfaceShadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, normalize(Light.Position - gBuffer.WorldPos), ContactShadowsLength); #endif @@ -119,7 +119,7 @@ float4 PS_DirLight(Quad_VS2PS input) : SV_Target0 // Sample shadow ShadowSample shadow = SampleDirectionalLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer, TemporalTime); -#if CONTACT_SHADOWS +#if CONTACT_SHADOWS && SHADOWS_QUALITY > 0 // Calculate screen-space contact shadow shadow.SurfaceShadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, Light.Direction, ContactShadowsLength); #endif @@ -149,7 +149,7 @@ float4 PS_SpotLight(Model_VS2PS input) : SV_Target0 // Sample shadow ShadowSample shadow = SampleSpotLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer); -#if CONTACT_SHADOWS +#if CONTACT_SHADOWS && SHADOWS_QUALITY > 0 // Calculate screen-space contact shadow shadow.SurfaceShadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, normalize(Light.Position - gBuffer.WorldPos), ContactShadowsLength); #endif From 7808c65dd3b6e50a557bef0d5ffea9736913ba74 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Mar 2025 19:47:04 +0100 Subject: [PATCH 210/215] Update engine asset --- Content/Editor/Primitives/Cone.flax | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/Editor/Primitives/Cone.flax b/Content/Editor/Primitives/Cone.flax index 881301bf1..6dc929169 100644 --- a/Content/Editor/Primitives/Cone.flax +++ b/Content/Editor/Primitives/Cone.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee9fdd8526a050bfd61cba141fb057f5e4661f4fe2d4fcf77021d49141c8a3a4 -size 27201 +oid sha256:ca7fadefea06d699af13fc086c6104579c0693e39af3298b9589e00c55293d3a +size 27251 From 4c9d51f0ef8c2b81fd0e663487f6adb324a30a6c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Mar 2025 20:18:38 +0100 Subject: [PATCH 211/215] Code style cleanup #3250 --- .../Engine/Graphics/PostProcessSettings.cpp | 1 - Source/Engine/Graphics/PostProcessSettings.h | 292 +++++++++--------- Source/Engine/Renderer/PostProcessingPass.cpp | 58 ++-- Source/Engine/Renderer/PostProcessingPass.h | 14 +- Source/Shaders/PostProcessing.shader | 91 +++--- 5 files changed, 216 insertions(+), 240 deletions(-) diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index e717559f0..5e7278638 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -48,7 +48,6 @@ void BloomSettings::BlendWith(BloomSettings& other, float weight) BLEND_FLOAT(HighMix); } - void ToneMappingSettings::BlendWith(ToneMappingSettings& other, float weight) { const bool isHalf = weight >= 0.5f; diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index a8e73f752..f3aa5c4a8 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -236,43 +236,43 @@ API_STRUCT() struct FLAXENGINE_API AmbientOcclusionSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - AmbientOcclusionSettingsOverride OverrideFlags = Override::None; + AmbientOcclusionSettingsOverride OverrideFlags = Override::None; /// /// Enable/disable ambient occlusion effect. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Enabled)") - bool Enabled = true; + bool Enabled = true; /// /// Ambient occlusion intensity. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Intensity)") - float Intensity = 0.8f; + float Intensity = 0.8f; /// /// Ambient occlusion power. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Power)") - float Power = 0.75f; + float Power = 0.75f; /// /// Ambient occlusion check range radius. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Radius)") - float Radius = 0.7f; + float Radius = 0.7f; /// /// Ambient occlusion fade out end distance from camera (in world units). /// API_FIELD(Attributes="Limit(0.0f), EditorOrder(4), PostProcessSetting((int)AmbientOcclusionSettingsOverride.FadeOutDistance)") - float FadeOutDistance = 5000.0f; + float FadeOutDistance = 5000.0f; /// /// Ambient occlusion fade distance (in world units). Defines the size of the effect fade from fully visible to fully invisible at FadeOutDistance. /// API_FIELD(Attributes="Limit(0.0f), EditorOrder(5), PostProcessSetting((int)AmbientOcclusionSettingsOverride.FadeDistance)") - float FadeDistance = 500.0f; + float FadeDistance = 500.0f; public: /// @@ -342,43 +342,43 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - GlobalIlluminationSettingsOverride OverrideFlags = Override::None; + GlobalIlluminationSettingsOverride OverrideFlags = Override::None; /// /// The Global Illumination mode to use. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Mode)") - GlobalIlluminationMode Mode = GlobalIlluminationMode::None; + GlobalIlluminationMode Mode = GlobalIlluminationMode::None; /// /// Global Illumination indirect lighting intensity scale. Can be used to boost or reduce GI effect. /// API_FIELD(Attributes="EditorOrder(10), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 1.0f; /// /// Global Illumination infinite indirect lighting bounce intensity scale. Can be used to boost or reduce GI effect for the light bouncing on the surfaces. /// API_FIELD(Attributes="EditorOrder(11), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.BounceIntensity)") - float BounceIntensity = 1.0f; + float BounceIntensity = 1.0f; /// /// Defines how quickly GI blends between the current frame and the history buffer. Lower values update GI faster, but with more jittering and noise. If the camera in your game doesn't move much, we recommend values closer to 1. /// API_FIELD(Attributes="EditorOrder(20), Limit(0, 1), PostProcessSetting((int)GlobalIlluminationSettingsOverride.TemporalResponse)") - float TemporalResponse = 0.9f; + float TemporalResponse = 0.9f; /// /// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance. /// API_FIELD(Attributes="EditorOrder(30), Limit(1000), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Distance)") - float Distance = 20000.0f; + float Distance = 20000.0f; /// /// The irradiance lighting outside the GI range used as a fallback to prevent pure-black scene outside the Global Illumination range. /// API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)GlobalIlluminationSettingsOverride.FallbackIrradiance)") - Color FallbackIrradiance = Color::Black; + Color FallbackIrradiance = Color::Black; public: /// @@ -453,49 +453,49 @@ API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - BloomSettingsOverride OverrideFlags = Override::None; + BloomSettingsOverride OverrideFlags = Override::None; /// /// If checked, bloom effect will be rendered. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)BloomSettingsOverride.Enabled)") - bool Enabled = true; + bool Enabled = true; /// /// Overall bloom effect strength. Higher values create a stronger glow effect. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.001f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 1.0f; /// /// Luminance threshold where bloom begins. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.1f), EditorOrder(2), PostProcessSetting((int)BloomSettingsOverride.Threshold)") - float Threshold = 1.0f; + float Threshold = 1.0f; /// /// Controls the threshold rolloff curve. Higher values create a softer transition. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)BloomSettingsOverride.ThresholdKnee)") - float ThresholdKnee = 0.5f; + float ThresholdKnee = 0.5f; /// /// Maximum brightness limit for bloom highlights. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.1f), EditorOrder(4), PostProcessSetting((int)BloomSettingsOverride.Clamp)") - float Clamp = 3.0f; + float Clamp = 3.0f; /// /// Base mip contribution for wider, softer bloom. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)BloomSettingsOverride.BaseMix)") - float BaseMix = 0.6f; + float BaseMix = 0.6f; /// /// High mip contribution for tighter, core bloom. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)BloomSettingsOverride.HighMix)") - float HighMix = 1.0f; + float HighMix = 1.0f; public: /// @@ -550,25 +550,25 @@ API_STRUCT() struct FLAXENGINE_API ToneMappingSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - ToneMappingSettingsOverride OverrideFlags = Override::None; + ToneMappingSettingsOverride OverrideFlags = Override::None; /// /// Adjusts the white balance in relation to the temperature of the light in the scene. When the light temperature and this one match the light will appear white. When a value is used that is higher than the light in the scene it will yield a "warm" or yellow color, and, conversely, if the value is lower, it would yield a "cool" or blue color. /// API_FIELD(Attributes="Limit(1500, 15000), EditorOrder(0), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTemperature)") - float WhiteTemperature = 6500.0f; + float WhiteTemperature = 6500.0f; /// /// Adjusts the white balance temperature tint for the scene by adjusting the cyan and magenta color ranges. Ideally, this setting should be used once you've adjusted the white balance temperature to get accurate colors. Under some light temperatures, the colors may appear to be more yellow or blue. This can be used to balance the resulting color to look more natural. /// API_FIELD(Attributes="Limit(-1, 1, 0.001f), EditorOrder(1), PostProcessSetting((int)ToneMappingSettingsOverride.WhiteTint)") - float WhiteTint = 0.0f; + float WhiteTint = 0.0f; /// /// The tone mapping mode to use for the color grading process. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)ToneMappingSettingsOverride.Mode)") - ToneMappingMode Mode = ToneMappingMode::ACES; + ToneMappingMode Mode = ToneMappingMode::ACES; public: /// @@ -728,7 +728,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - ColorGradingSettingsOverride OverrideFlags = Override::None; + ColorGradingSettingsOverride OverrideFlags = Override::None; // Global @@ -736,31 +736,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(0), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturation), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Saturation\")") - Float4 ColorSaturation = Float4::One; + Float4 ColorSaturation = Float4::One; /// /// Gets or sets the color contrast (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(1), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrast), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Contrast\")") - Float4 ColorContrast = Float4::One; + Float4 ColorContrast = Float4::One; /// /// Gets or sets the color gamma (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(2), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGamma), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Gamma\")") - Float4 ColorGamma = Float4::One; + Float4 ColorGamma = Float4::One; /// /// Gets or sets the color gain (applies globally to the whole image). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(3), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGain), Limit(0, 2, 0.01f), EditorDisplay(\"Global\", \"Gain\")") - Float4 ColorGain = Float4::One; + Float4 ColorGain = Float4::One; /// /// Gets or sets the color offset (applies globally to the whole image). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(4), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffset), Limit(-1, 1, 0.001f), EditorDisplay(\"Global\", \"Offset\")") - Float4 ColorOffset = Float4::Zero; + Float4 ColorOffset = Float4::Zero; // Shadows @@ -768,31 +768,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(5), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturationShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Saturation\")") - Float4 ColorSaturationShadows = Float4::One; + Float4 ColorSaturationShadows = Float4::One; /// /// Gets or sets the color contrast (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(6), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrastShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Contrast\")") - Float4 ColorContrastShadows = Float4::One; + Float4 ColorContrastShadows = Float4::One; /// /// Gets or sets the color gamma (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(7), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGammaShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Gamma\")") - Float4 ColorGammaShadows = Float4::One; + Float4 ColorGammaShadows = Float4::One; /// /// Gets or sets the color gain (applies to shadows only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(8), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGainShadows), Limit(0, 2, 0.01f), EditorDisplay(\"Shadows\", \"Shadows Gain\")") - Float4 ColorGainShadows = Float4::One; + Float4 ColorGainShadows = Float4::One; /// /// Gets or sets the color offset (applies to shadows only). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(9), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffsetShadows), Limit(-1, 1, 0.001f), EditorDisplay(\"Shadows\", \"Shadows Offset\")") - Float4 ColorOffsetShadows = Float4::Zero; + Float4 ColorOffsetShadows = Float4::Zero; // Midtones @@ -800,31 +800,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(10), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturationMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Saturation\")") - Float4 ColorSaturationMidtones = Float4::One; + Float4 ColorSaturationMidtones = Float4::One; /// /// Gets or sets the color contrast (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(11), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrastMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Contrast\")") - Float4 ColorContrastMidtones = Float4::One; + Float4 ColorContrastMidtones = Float4::One; /// /// Gets or sets the color gamma (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(12), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGammaMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Gamma\")") - Float4 ColorGammaMidtones = Float4::One; + Float4 ColorGammaMidtones = Float4::One; /// /// Gets or sets the color gain (applies to midtones only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(13), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGainMidtones), Limit(0, 2, 0.01f), EditorDisplay(\"Midtones\", \"Midtones Gain\")") - Float4 ColorGainMidtones = Float4::One; + Float4 ColorGainMidtones = Float4::One; /// /// Gets or sets the color offset (applies to midtones only). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(14), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffsetMidtones), Limit(-1, 1, 0.001f), EditorDisplay(\"Midtones\", \"Midtones Offset\")") - Float4 ColorOffsetMidtones = Float4::Zero; + Float4 ColorOffsetMidtones = Float4::Zero; // Highlights @@ -832,31 +832,31 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// Gets or sets the color saturation (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(15), PostProcessSetting((int)ColorGradingSettingsOverride.ColorSaturationHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Saturation\")") - Float4 ColorSaturationHighlights = Float4::One; + Float4 ColorSaturationHighlights = Float4::One; /// /// Gets or sets the color contrast (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(16), PostProcessSetting((int)ColorGradingSettingsOverride.ColorContrastHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Contrast\")") - Float4 ColorContrastHighlights = Float4::One; + Float4 ColorContrastHighlights = Float4::One; /// /// Gets or sets the color gamma (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(17), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGammaHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Gamma\")") - Float4 ColorGammaHighlights = Float4::One; + Float4 ColorGammaHighlights = Float4::One; /// /// Gets or sets the color gain (applies to highlights only). Default is 1. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"1,1,1,1\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(18), PostProcessSetting((int)ColorGradingSettingsOverride.ColorGainHighlights), Limit(0, 2, 0.01f), EditorDisplay(\"Highlights\", \"Highlights Gain\")") - Float4 ColorGainHighlights = Float4::One; + Float4 ColorGainHighlights = Float4::One; /// /// Gets or sets the color offset (applies to highlights only). Default is 0. /// API_FIELD(Attributes="DefaultValue(typeof(Float4), \"0,0,0,0\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ColorTrackball\"), EditorOrder(19), PostProcessSetting((int)ColorGradingSettingsOverride.ColorOffsetHighlights), Limit(-1, 1, 0.001f), EditorDisplay(\"Highlights\", \"Highlights Offset\")") - Float4 ColorOffsetHighlights = Float4::Zero; + Float4 ColorOffsetHighlights = Float4::Zero; // @@ -864,13 +864,13 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The shadows maximum value. Default is 0.09. /// API_FIELD(Attributes="Limit(-1, 1, 0.01f), EditorOrder(20), PostProcessSetting((int)ColorGradingSettingsOverride.ShadowsMax)") - float ShadowsMax = 0.09f; + float ShadowsMax = 0.09f; /// /// The highlights minimum value. Default is 0.5. /// API_FIELD(Attributes="Limit(-1, 1, 0.01f), EditorOrder(21), PostProcessSetting((int)ColorGradingSettingsOverride.HighlightsMin)") - float HighlightsMin = 0.5f; + float HighlightsMin = 0.5f; // @@ -878,13 +878,13 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The Lookup Table (LUT) used to perform color correction. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)") - SoftAssetReference LutTexture; + SoftAssetReference LutTexture; /// /// The LUT blending weight (normalized to range 0-1). Default is 1.0. /// API_FIELD(Attributes="Limit(0, 1, 0.01f), EditorOrder(23), PostProcessSetting((int)ColorGradingSettingsOverride.LutWeight)") - float LutWeight = 1.0f; + float LutWeight = 1.0f; public: /// @@ -969,61 +969,61 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - EyeAdaptationSettingsOverride OverrideFlags = Override::None; + EyeAdaptationSettingsOverride OverrideFlags = Override::None; /// /// The effect rendering mode used for the exposure processing. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)EyeAdaptationSettingsOverride.Mode)") - EyeAdaptationMode Mode = EyeAdaptationMode::AutomaticHistogram; + EyeAdaptationMode Mode = EyeAdaptationMode::AutomaticHistogram; /// /// The speed at which the exposure changes when the scene brightness moves from a dark area to a bright area (brightness goes up). /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedUp)") - float SpeedUp = 3.0f; + float SpeedUp = 3.0f; /// /// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down). /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)") - float SpeedDown = 10.0f; + float SpeedDown = 10.0f; /// /// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.). /// API_FIELD(Attributes="Limit(-100, 100, 0.01f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.PreExposure)") - float PreExposure = 0.0f; + float PreExposure = 0.0f; /// /// The post-exposure value applied to the scene color after performing post-processing (such as bloom, lens flares, etc.) but before color grading and tone mapping. /// API_FIELD(Attributes="Limit(-100, 100, 0.01f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.PostExposure)") - float PostExposure = 0.0f; + float PostExposure = 0.0f; /// /// The minimum brightness for the auto exposure which limits the lower brightness the eye can adapt within. /// API_FIELD(Attributes="Limit(0, 20.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)EyeAdaptationSettingsOverride.MinBrightness), EditorDisplay(null, \"Minimum Brightness\")") - float MinBrightness = 0.03f; + float MinBrightness = 0.03f; /// /// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")") - float MaxBrightness = 15.0f; + float MaxBrightness = 15.0f; /// /// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode. /// API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramLowPercent)") - float HistogramLowPercent = 70.0f; + float HistogramLowPercent = 70.0f; /// /// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode. /// API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)") - float HistogramHighPercent = 90.0f; + float HistogramHighPercent = 90.0f; public: /// @@ -1103,55 +1103,55 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - CameraArtifactsSettingsOverride OverrideFlags = Override::None; + CameraArtifactsSettingsOverride OverrideFlags = Override::None; /// /// Strength of the vignette effect. Value 0 hides it. /// API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)") - float VignetteIntensity = 0.4f; + float VignetteIntensity = 0.4f; /// /// Color of the vignette. /// API_FIELD(Attributes="EditorOrder(1), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteColor)") - Float3 VignetteColor = Float3(0, 0, 0.001f); + Float3 VignetteColor = Float3(0, 0, 0.001f); /// /// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. /// API_FIELD(Attributes="Limit(0.0001f, 2.0f, 0.001f), EditorOrder(2), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteShapeFactor)") - float VignetteShapeFactor = 0.125f; + float VignetteShapeFactor = 0.125f; /// /// Intensity of the grain filter. A value of 0 hides it. /// API_FIELD(Attributes="Limit(0.0f, 2.0f, 0.005f), EditorOrder(3), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainAmount)") - float GrainAmount = 0.006f; + float GrainAmount = 0.006f; /// /// Size of the grain particles. /// API_FIELD(Attributes="Limit(1.0f, 3.0f, 0.01f), EditorOrder(4), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainParticleSize)") - float GrainParticleSize = 1.6f; + float GrainParticleSize = 1.6f; /// /// Speed of the grain particle animation. /// API_FIELD(Attributes="Limit(0.0f, 10.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainSpeed)") - float GrainSpeed = 1.0f; + float GrainSpeed = 1.0f; /// /// Controls the chromatic aberration effect strength. A value of 0 hides it. /// API_FIELD(Attributes="Limit(0.0f, 1.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)CameraArtifactsSettingsOverride.ChromaticDistortion)") - float ChromaticDistortion = 0.0f; + float ChromaticDistortion = 0.0f; /// /// Screen tint color (the alpha channel defines the blending factor). /// API_FIELD(Attributes="DefaultValue(typeof(Color), \"0,0,0,0\"), EditorOrder(7), PostProcessSetting((int)CameraArtifactsSettingsOverride.ScreenFadeColor)") - Color ScreenFadeColor = Color::Transparent; + Color ScreenFadeColor = Color::Transparent; public: /// @@ -1251,79 +1251,79 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - LensFlaresSettingsOverride OverrideFlags = Override::None; + LensFlaresSettingsOverride OverrideFlags = Override::None; /// /// Strength of the effect. A value of 0 disables it. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)") - float Intensity = 0.5f; + float Intensity = 0.5f; /// /// Amount of lens flares ghosts. /// API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)") - int32 Ghosts = 4; + int32 Ghosts = 4; /// /// Lens flares halo width. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)") - float HaloWidth = 0.04f; + float HaloWidth = 0.04f; /// /// Lens flares halo intensity. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)") - float HaloIntensity = 0.5f; + float HaloIntensity = 0.5f; /// /// Ghost samples dispersal parameter. /// API_FIELD(Attributes="EditorOrder(4), PostProcessSetting((int)LensFlaresSettingsOverride.GhostDispersal)") - float GhostDispersal = 0.3f; + float GhostDispersal = 0.3f; /// /// Lens flares color distortion parameter. /// API_FIELD(Attributes="EditorOrder(5), PostProcessSetting((int)LensFlaresSettingsOverride.Distortion)") - float Distortion = 1.5f; + float Distortion = 1.5f; /// /// Input image brightness threshold. Added to input pixels. /// API_FIELD(Attributes="EditorOrder(6), PostProcessSetting((int)LensFlaresSettingsOverride.ThresholdBias)") - float ThresholdBias = -0.5f; + float ThresholdBias = -0.5f; /// /// Input image brightness threshold scale. Used to multiply input pixels. /// API_FIELD(Attributes="EditorOrder(7), PostProcessSetting((int)LensFlaresSettingsOverride.ThresholdScale)") - float ThresholdScale = 0.22f; + float ThresholdScale = 0.22f; /// /// Fullscreen lens dirt texture. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)") - SoftAssetReference LensDirt; + SoftAssetReference LensDirt; /// /// Fullscreen lens dirt intensity parameter. Allows tuning dirt visibility. /// API_FIELD(Attributes="Limit(0, 100, 0.01f), EditorOrder(9), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirtIntensity)") - float LensDirtIntensity = 1.0f; + float LensDirtIntensity = 1.0f; /// /// Custom lens color texture (1D) used for lens color spectrum. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)") - SoftAssetReference LensColor; + SoftAssetReference LensColor; /// /// Custom lens star texture sampled by lens flares. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)") - SoftAssetReference LensStar; + SoftAssetReference LensStar; public: /// @@ -1443,103 +1443,103 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - DepthOfFieldSettingsOverride OverrideFlags = Override::None; + DepthOfFieldSettingsOverride OverrideFlags = Override::None; /// /// If checked, the depth of field effect will be visible. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)DepthOfFieldSettingsOverride.Enabled)") - bool Enabled = false; + bool Enabled = false; /// /// The blur intensity in the out-of-focus areas. Allows reducing the blur amount by scaling down the Gaussian Blur radius. Normalized to range 0-1. /// API_FIELD(Attributes="Limit(0, 1, 0.01f), EditorOrder(1), PostProcessSetting((int)DepthOfFieldSettingsOverride.BlurStrength)") - float BlurStrength = 1.0f; + float BlurStrength = 1.0f; /// /// The distance in World Units from the camera that acts as the center of the region where the scene is perfectly in focus and no blurring occurs. /// API_FIELD(Attributes="Limit(0), EditorOrder(2), PostProcessSetting((int)DepthOfFieldSettingsOverride.FocalDistance)") - float FocalDistance = 1700.0f; + float FocalDistance = 1700.0f; /// /// The distance in World Units beyond the focal distance where the scene is perfectly in focus and no blurring occurs. /// API_FIELD(Attributes="Limit(0), EditorOrder(3), PostProcessSetting((int)DepthOfFieldSettingsOverride.FocalRegion)") - float FocalRegion = 3000.0f; + float FocalRegion = 3000.0f; /// /// The distance in World Units from the focal region on the side nearer to the camera over which the scene transitions from focused to blurred. /// API_FIELD(Attributes="Limit(0), EditorOrder(4), PostProcessSetting((int)DepthOfFieldSettingsOverride.NearTransitionRange)") - float NearTransitionRange = 300.0f; + float NearTransitionRange = 300.0f; /// /// The distance in World Units from the focal region on the side farther from the camera over which the scene transitions from focused to blurred. /// API_FIELD(Attributes="Limit(0), EditorOrder(5), PostProcessSetting((int)DepthOfFieldSettingsOverride.FarTransitionRange)") - float FarTransitionRange = 500.0f; + float FarTransitionRange = 500.0f; /// /// The distance in World Units which describes border after that there is no blur (useful to disable DoF on sky). Use 0 to disable that feature. /// API_FIELD(Attributes="Limit(0, float.MaxValue, 2), EditorOrder(6), PostProcessSetting((int)DepthOfFieldSettingsOverride.DepthLimit)") - float DepthLimit = 0.0f; + float DepthLimit = 0.0f; /// /// If checked, bokeh shapes will be rendered. /// API_FIELD(Attributes="EditorOrder(7), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehEnabled)") - bool BokehEnabled = true; + bool BokehEnabled = true; /// /// Controls size of the bokeh shapes. /// API_FIELD(Attributes="Limit(0, 200.0f, 0.1f), EditorOrder(8), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehSize)") - float BokehSize = 25.0f; + float BokehSize = 25.0f; /// /// Controls brightness of the bokeh shapes. Can be used to fade them or make more intense. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(9), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBrightness)") - float BokehBrightness = 1.0f; + float BokehBrightness = 1.0f; /// /// Defines the type of the bokeh shapes. /// API_FIELD(Attributes="EditorOrder(10), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShape)") - BokehShapeType BokehShape = BokehShapeType::Octagon; + BokehShapeType BokehShape = BokehShapeType::Octagon; /// /// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px). /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)") - SoftAssetReference BokehShapeCustom; + SoftAssetReference BokehShapeCustom; /// /// The minimum pixel brightness to create the bokeh. Pixels with lower brightness will be skipped. /// API_FIELD(Attributes="Limit(0, 10000.0f, 0.01f), EditorOrder(12), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBrightnessThreshold)") - float BokehBrightnessThreshold = 3.0f; + float BokehBrightnessThreshold = 3.0f; /// /// Depth of Field bokeh shape blur threshold. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.001f), EditorOrder(13), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBlurThreshold)") - float BokehBlurThreshold = 0.05f; + float BokehBlurThreshold = 0.05f; /// /// Controls bokeh shape brightness falloff. Higher values reduce bokeh visibility. /// API_FIELD(Attributes="Limit(0, 2.0f, 0.001f), EditorOrder(14), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehFalloff)") - float BokehFalloff = 0.5f; + float BokehFalloff = 0.5f; /// /// Controls bokeh shape generation for depth discontinuities. /// API_FIELD(Attributes="Limit(0, 5.0f, 0.001f), EditorOrder(15), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehDepthCutoff)") - float BokehDepthCutoff = 1.5f; + float BokehDepthCutoff = 1.5f; public: /// @@ -1599,31 +1599,31 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - MotionBlurSettingsOverride OverrideFlags = Override::None; + MotionBlurSettingsOverride OverrideFlags = Override::None; /// /// If checked, the motion blur effect will be rendered. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)MotionBlurSettingsOverride.Enabled)") - bool Enabled = true; + bool Enabled = true; /// /// The blur effect strength. A value of 0 disables it, while higher values increase the effect. /// API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)") - float Scale = 0.5f; + float Scale = 0.5f; /// /// The amount of sample points used during motion blur rendering. It affects blur quality and performance. /// API_FIELD(Attributes="Limit(4, 32, 0.1f), EditorOrder(2), PostProcessSetting((int)MotionBlurSettingsOverride.SampleCount)") - int32 SampleCount = 10; + int32 SampleCount = 10; /// /// The motion vectors texture resolution. Motion blur uses a per-pixel motion vector buffer that contains an objects movement information. Use a lower resolution to improve performance. /// API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)MotionBlurSettingsOverride.MotionVectorsResolution)") - ResolutionMode MotionVectorsResolution = ResolutionMode::Half; + ResolutionMode MotionVectorsResolution = ResolutionMode::Half; public: /// @@ -1743,103 +1743,103 @@ API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializabl /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - ScreenSpaceReflectionsSettingsOverride OverrideFlags = Override::None; + ScreenSpaceReflectionsSettingsOverride OverrideFlags = Override::None; /// /// The effect intensity (normalized to range [0;1]). Use 0 to disable it. /// API_FIELD(Attributes="Limit(0, 5.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 1.0f; /// /// The reflections tracing mode. /// API_FIELD(Attributes="EditorOrder(1), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TraceMode)") - ReflectionsTraceMode TraceMode = ReflectionsTraceMode::ScreenTracing; + ReflectionsTraceMode TraceMode = ReflectionsTraceMode::ScreenTracing; /// /// The depth buffer downscale option to optimize raycast performance. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.DepthResolution)") - ResolutionMode DepthResolution = ResolutionMode::Half; + ResolutionMode DepthResolution = ResolutionMode::Half; /// /// The raycast resolution. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RayTracePassResolution)") - ResolutionMode RayTracePassResolution = ResolutionMode::Half; + ResolutionMode RayTracePassResolution = ResolutionMode::Half; /// /// The reflection spread parameter. This value controls source roughness effect on reflections blur. Smaller values produce wider reflections spread but also introduce more noise. Higher values provide more mirror-like reflections. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(10), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.BRDFBias), EditorDisplay(null, \"BRDF Bias\")") - float BRDFBias = 0.82f; + float BRDFBias = 0.82f; /// /// The maximum amount of roughness a material must have to reflect the scene. For example, if this value is set to 0.4, only materials with a roughness value of 0.4 or below reflect the scene. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.01f), EditorOrder(15), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.RoughnessThreshold)") - float RoughnessThreshold = 0.45f; + float RoughnessThreshold = 0.45f; /// /// The offset of the raycast origin. Lower values produce more correct reflection placement, but produce more artifacts. We recommend values of 0.3 or lower. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(20), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.WorldAntiSelfOcclusionBias)") - float WorldAntiSelfOcclusionBias = 0.1f; + float WorldAntiSelfOcclusionBias = 0.1f; /// /// The raycast resolution. Full gives better quality, but half improves performance. /// API_FIELD(Attributes="EditorOrder(25), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolvePassResolution)") - ResolutionMode ResolvePassResolution = ResolutionMode::Full; + ResolutionMode ResolvePassResolution = ResolutionMode::Full; /// /// The number of rays used to resolve the reflection color. Higher values provide better quality but reduce effect performance. Use value of 1 for the best performance at cost of quality. /// API_FIELD(Attributes="Limit(1, 8), EditorOrder(26), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.ResolveSamples)") - int32 ResolveSamples = 4; + int32 ResolveSamples = 4; /// /// The point at which the far edges of the reflection begin to fade. Has no effect on performance. /// API_FIELD(Attributes="Limit(0, 1.0f, 0.02f), EditorOrder(30), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.EdgeFadeFactor)") - float EdgeFadeFactor = 0.1f; + float EdgeFadeFactor = 0.1f; /// /// The effect fade out end distance from camera (in world units). /// API_FIELD(Attributes="Limit(0), EditorOrder(31), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.FadeOutDistance)") - float FadeOutDistance = 5000.0f; + float FadeOutDistance = 5000.0f; /// /// The effect fade distance (in world units). Defines the size of the effect fade from fully visible to fully invisible at FadeOutDistance. /// API_FIELD(Attributes="Limit(0), EditorOrder(32), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.FadeDistance)") - float FadeDistance = 500.0f; + float FadeDistance = 500.0f; /// /// "The input color buffer downscale mode that uses blurred mipmaps when resolving the reflection color. Produces more realistic results by blurring distant parts of reflections in rough (low-gloss) materials. It also improves performance on most platforms but uses more memory. /// API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.UseColorBufferMips), EditorDisplay(null, \"Use Color Buffer Mips\")") - bool UseColorBufferMips = true; + bool UseColorBufferMips = true; /// /// If checked, enables the temporal pass. Reduces noise, but produces an animated "jittering" effect that's sometimes noticeable. If disabled, the properties below have no effect. /// API_FIELD(Attributes="EditorOrder(50), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalEffect), EditorDisplay(null, \"Enable Temporal Effect\")") - bool TemporalEffect = true; + bool TemporalEffect = true; /// /// The intensity of the temporal effect. Lower values produce reflections faster, but more noise. /// API_FIELD(Attributes="Limit(0, 20.0f, 0.5f), EditorOrder(55), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalScale)") - float TemporalScale = 8.0f; + float TemporalScale = 8.0f; /// /// Defines how quickly reflections blend between the reflection in the current frame and the history buffer. Lower values produce reflections faster, but with more jittering. If the camera in your game doesn't move much, we recommend values closer to 1. /// API_FIELD(Attributes="Limit(0.05f, 1.0f, 0.01f), EditorOrder(60), PostProcessSetting((int)ScreenSpaceReflectionsSettingsOverride.TemporalResponse)") - float TemporalResponse = 0.8f; + float TemporalResponse = 0.8f; public: /// @@ -1924,61 +1924,61 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable /// The flags for overriden properties. /// API_FIELD(Attributes="HideInEditor") - AntiAliasingSettingsOverride OverrideFlags = Override::None; + AntiAliasingSettingsOverride OverrideFlags = Override::None; /// /// The anti-aliasing effect mode. /// API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)AntiAliasingSettingsOverride.Mode)") - AntialiasingMode Mode = AntialiasingMode::FastApproximateAntialiasing; + AntialiasingMode Mode = AntialiasingMode::FastApproximateAntialiasing; /// /// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output. /// API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_JitterSpread = 1.0f; + float TAA_JitterSpread = 1.0f; /// /// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts. /// API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_Sharpness = 0.1f; + float TAA_Sharpness = 0.1f; /// /// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion. /// API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_StationaryBlending = 0.95f; + float TAA_StationaryBlending = 0.95f; /// /// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion. /// API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))") - float TAA_MotionBlending = 0.85f; + float TAA_MotionBlending = 0.85f; /// /// The sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// API_FIELD(Attributes="Limit(0, 10f, 0.001f), EditorOrder(10), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_SharpeningAmount), EditorDisplay(null, \"CAS Sharpening Amount\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_SharpeningAmount = 0.0f; + float CAS_SharpeningAmount = 0.0f; /// /// The edge sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// API_FIELD(Attributes="Limit(0, 10f, 0.001f), EditorOrder(11), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_EdgeSharpening), EditorDisplay(null, \"CAS Edge Sharpening\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_EdgeSharpening = 0.5f; + float CAS_EdgeSharpening = 0.5f; /// /// The minimum edge threshold for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// API_FIELD(Attributes="Limit(0, 10f, 0.001f), EditorOrder(12), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_MinEdgeThreshold), EditorDisplay(null, \"CAS Min Edge Threshold\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_MinEdgeThreshold = 0.03f; + float CAS_MinEdgeThreshold = 0.03f; /// /// The over-blur limit for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. /// API_FIELD(Attributes="Limit(0, 100f, 0.001f), EditorOrder(13), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_OverBlurLimit), EditorDisplay(null, \"CAS Over-blur Limit\"), VisibleIf(nameof(ShowTAASettings), true)") - float CAS_OverBlurLimit = 1.0f; + float CAS_OverBlurLimit = 1.0f; public: /// @@ -2001,7 +2001,7 @@ API_STRUCT() struct FLAXENGINE_API PostFxMaterialsSettings : ISerializable /// The post-process materials collection for rendering (fixed capacity). /// API_FIELD(Attributes="EditorDisplay(null, EditorDisplayAttribute.InlineStyle), Collection(MaxCount=8)") - Array, FixedAllocation> Materials; + Array, FixedAllocation> Materials; public: /// @@ -2023,79 +2023,79 @@ API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable /// The ambient occlusion effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Ambient Occlusion\"), EditorOrder(100), JsonProperty(\"AO\")") - AmbientOcclusionSettings AmbientOcclusion; + AmbientOcclusionSettings AmbientOcclusion; /// /// The global illumination effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Global Illumination\"), EditorOrder(150), JsonProperty(\"GI\")") - GlobalIlluminationSettings GlobalIllumination; + GlobalIlluminationSettings GlobalIllumination; /// /// The bloom effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Bloom\"), EditorOrder(200)") - BloomSettings Bloom; + BloomSettings Bloom; /// /// The tone mapping effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Tone Mapping\"), EditorOrder(300)") - ToneMappingSettings ToneMapping; + ToneMappingSettings ToneMapping; /// /// The color grading effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Color Grading\"), EditorOrder(400)") - ColorGradingSettings ColorGrading; + ColorGradingSettings ColorGrading; /// /// The eye adaptation effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Eye Adaptation\"), EditorOrder(500)") - EyeAdaptationSettings EyeAdaptation; + EyeAdaptationSettings EyeAdaptation; /// /// The camera artifacts effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Camera Artifacts\"), EditorOrder(600)") - CameraArtifactsSettings CameraArtifacts; + CameraArtifactsSettings CameraArtifacts; /// /// The lens flares effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Lens Flares\"), EditorOrder(700)") - LensFlaresSettings LensFlares; + LensFlaresSettings LensFlares; /// /// The depth of field effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Depth Of Field\"), EditorOrder(800)") - DepthOfFieldSettings DepthOfField; + DepthOfFieldSettings DepthOfField; /// /// The motion blur effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Motion Blur\"), EditorOrder(900)") - MotionBlurSettings MotionBlur; + MotionBlurSettings MotionBlur; /// /// The screen space reflections effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Screen Space Reflections\"), EditorOrder(1000), JsonProperty(\"SSR\")") - ScreenSpaceReflectionsSettings ScreenSpaceReflections; + ScreenSpaceReflectionsSettings ScreenSpaceReflections; /// /// The antialiasing effect settings. /// API_FIELD(Attributes="EditorDisplay(\"Anti Aliasing\"), EditorOrder(1100), JsonProperty(\"AA\")") - AntiAliasingSettings AntiAliasing; + AntiAliasingSettings AntiAliasing; /// /// The PostFx materials rendering settings. /// API_FIELD(Attributes="EditorDisplay(\"PostFx Materials\"), NoAnimate, EditorOrder(1200)") - PostFxMaterialsSettings PostFxMaterials; + PostFxMaterialsSettings PostFxMaterials; public: /// diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index f4d86ec21..93cd56536 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -83,7 +83,6 @@ bool PostProcessingPass::setupResources() if (_psBloomDownsample->Init(psDesc)) return true; } - if (!_psBloomDualFilterUpsample->IsValid()) { psDesc.PS = shader->GetPS("PS_BloomDualFilterUpsample"); @@ -189,7 +188,7 @@ void PostProcessingPass::Dispose() _defaultLensStar = nullptr; } -float CalculateBloomMipCount(int32 width, int32 height) +int32 CalculateBloomMipCount(int32 width, int32 height) { // Calculate the smallest dimension int32 minDimension = Math::Min(width, height); @@ -197,14 +196,12 @@ float CalculateBloomMipCount(int32 width, int32 height) // Calculate how many times we can half the dimension until we hit a minimum size // (e.g., 16x16 pixels as the smallest mip) const int32 MIN_MIP_SIZE = 16; - float mipCount = 1.0f; - - while (minDimension > MIN_MIP_SIZE) { + int32 mipCount = 1; + while (minDimension > MIN_MIP_SIZE) + { minDimension /= 2; - mipCount += 1.0f; + mipCount++; } - - return mipCount; } @@ -282,7 +279,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, data.BloomThresholdKnee = settings.Bloom.ThresholdKnee; data.BloomBaseMix = settings.Bloom.BaseMix; data.BloomHighMix = settings.Bloom.HighMix; - data.BloomMipCount = bloomMipCount; + data.BloomMipCount = (float)bloomMipCount; data.BloomLayer = 0.0f; } else @@ -333,7 +330,6 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, //////////////////////////////////////////////////////////////////////////////////// // Bloom - auto tempDesc = GPUTextureDescription::New2D(w2, h2, bloomMipCount, output->Format(), GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews); auto bloomBuffer1 = RenderTargetPool::Get(tempDesc); RENDER_TARGET_POOL_SET_NAME(bloomBuffer1, "PostProcessing.Bloom"); @@ -346,10 +342,8 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, context->Clear(bloomBuffer2->View(0, mip), Color::Transparent); } - // Check if use bloom if (useBloom) { - context->SetRenderTarget(bloomBuffer1->View(0, 0)); context->SetViewportAndScissors((float)w2, (float)h2); context->BindSR(0, input->View()); @@ -360,47 +354,44 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, // Progressive downsamples for (int32 mip = 1; mip < bloomMipCount; mip++) { - const float mipWidth = w2 >> mip; - const float mipHeight = h2 >> mip; + const int32 mipWidth = w2 >> mip; + const int32 mipHeight = h2 >> mip; context->SetRenderTarget(bloomBuffer1->View(0, mip)); - context->SetViewportAndScissors(mipWidth, mipHeight); + context->SetViewportAndScissors((float)mipWidth, (float)mipHeight); context->BindSR(0, bloomBuffer1->View(0, mip - 1)); context->SetState(_psBloomDownsample); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); - } // Progressive upsamples for (int32 mip = bloomMipCount - 2; mip >= 0; mip--) { - auto upscalebuffer = bloomBuffer2; + auto upscaleBuffer = bloomBuffer2; if (mip == bloomMipCount - 2) { - // if it's the first, copy the chain over. - upscalebuffer = bloomBuffer1; - + // If it's the first, copy the chain over + upscaleBuffer = bloomBuffer1; } + const int32 mipWidth = w2 >> mip; + const int32 mipHeight = h2 >> mip; - const float mipWidth = w2 >> mip; - const float mipHeight = h2 >> mip; - - data.BloomLayer = static_cast(mip); // Set the current mip as bloom layer - context->UpdateCB(cb0, &data); // Update the constant buffer - + data.BloomLayer = static_cast(mip); + context->UpdateCB(cb0, &data); context->SetRenderTarget(bloomBuffer2->View(0, mip)); - context->SetViewportAndScissors(mipWidth, mipHeight); - context->BindSR(0, upscalebuffer->View(0, mip + 1)); + context->SetViewportAndScissors((float)mipWidth, (float)mipHeight); + context->BindSR(0, upscaleBuffer->View(0, mip + 1)); context->BindSR(1, bloomBuffer1->View(0, mip + 1)); context->SetState(_psBloomDualFilterUpsample); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); } - // Final composite - context->BindSR(1, bloomBuffer1); - context->BindSR(2, bloomBuffer2); + // Set bloom output + context->UnBindSR(0); + context->UnBindSR(1); + context->BindSR(2, bloomBuffer2->View(0, 0)); } else { @@ -408,8 +399,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, } //////////////////////////////////////////////////////////////////////////////////// - //Lens Flares - + // Lens Flares // Check if use lens flares if (useLensFlares) @@ -418,8 +408,6 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, context->BindSR(5, getCustomOrDefault(settings.LensFlares.LensStar, _defaultLensStar, TEXT("Engine/Textures/DefaultLensStarburst"))); context->BindSR(6, getCustomOrDefault(settings.LensFlares.LensColor, _defaultLensColor, TEXT("Engine/Textures/DefaultLensColor"))); - // use bloomBuffer1's mip 1 as input for lens flares - // Render lens flares context->SetRenderTarget(bloomBuffer2->View(0, 1)); context->SetViewportAndScissors((float)w4, (float)h4); diff --git a/Source/Engine/Renderer/PostProcessingPass.h b/Source/Engine/Renderer/PostProcessingPass.h index b7d73a5fd..d27fbedfa 100644 --- a/Source/Engine/Renderer/PostProcessingPass.h +++ b/Source/Engine/Renderer/PostProcessingPass.h @@ -16,16 +16,16 @@ class PostProcessingPass : public RendererPass private: GPU_CB_STRUCT(Data{ - float BloomIntensity; // Overall bloom strength multiplier - float BloomClamp; // Maximum brightness limit for bloom - float BloomThreshold; // Luminance threshold where bloom begins - float BloomThresholdKnee; // Controls the threshold rolloff curve - float BloomBaseMix; // Base mip contribution - float BloomHighMix; // High mip contribution + float BloomIntensity; // Overall bloom strength multiplier + float BloomClamp; // Maximum brightness limit for bloom + float BloomThreshold; // Luminance threshold where bloom begins + float BloomThresholdKnee; // Controls the threshold rolloff curve + + float BloomBaseMix; // Base mip contribution + float BloomHighMix; // High mip contribution float BloomMipCount; float BloomLayer; - Float3 VignetteColor; float VignetteShapeFactor; diff --git a/Source/Shaders/PostProcessing.shader b/Source/Shaders/PostProcessing.shader index 70f5c6a0c..067b0efd3 100644 --- a/Source/Shaders/PostProcessing.shader +++ b/Source/Shaders/PostProcessing.shader @@ -36,7 +36,6 @@ META_CB_BEGIN(0, Data) -// Bloom parameters float BloomIntensity; float BloomClamp; float BloomThreshold; @@ -47,7 +46,6 @@ float BloomHighMix; float BloomMipCount; float BloomLayer; - float3 VignetteColor; float VignetteShapeFactor; @@ -260,7 +258,6 @@ float2 coordRot(in float2 tc, in float angle) } // Uses a lower exposure to produce a value suitable for a bloom pass - META_PS(true, FEATURE_LEVEL_ES2) float4 PS_BloomBrightPass(Quad_VS2PS input) : SV_Target { @@ -278,10 +275,11 @@ float4 PS_BloomBrightPass(Quad_VS2PS input) : SV_Target // Apply Karis average to prevent bright pixels from dominating float centerLuma = max(dot(center, float3(0.2126, 0.7152, 0.0722)), 0.0001); center = center / (1.0 + centerLuma); - + float centerWeight = 4.0; color += center * centerWeight; totalWeight += centerWeight; + // Inner ring - fixed offset at 1.0 texel distance UNROLL for (int i = 0; i < 4; i++) @@ -293,11 +291,12 @@ float4 PS_BloomBrightPass(Quad_VS2PS input) : SV_Target // Apply Karis average float sampleLuma = max(dot(sample, float3(0.2126, 0.7152, 0.0722)), 0.0001); sample = sample / (1.0 + sampleLuma); - + float weight = 2.0; color += sample * weight; totalWeight += weight; } + // Outer ring - fixed offset at 1.4142 texel distance (diagonal) UNROLL for (int j = 0; j < 8; j++) @@ -305,11 +304,11 @@ float4 PS_BloomBrightPass(Quad_VS2PS input) : SV_Target float angle = j * (PI / 4.0); float2 offset = float2(cos(angle), sin(angle)) * texelSize * 1.4142; float3 sample = Input0.Sample(SamplerLinearClamp, input.TexCoord + offset).rgb; - + // Apply Karis average float sampleLuma = max(dot(sample, float3(0.2126, 0.7152, 0.0722)), 0.0001); sample = sample / (1.0 + sampleLuma); - + float weight = 1.0; color += sample * weight; totalWeight += weight; @@ -320,24 +319,28 @@ float4 PS_BloomBrightPass(Quad_VS2PS input) : SV_Target float finalLuma = max(dot(color, float3(0.2126, 0.7152, 0.0722)), 0.0001); color = color * (1.0 + finalLuma); - // Apply threshold with quadratic rolloff for smoother transition + // Apply threshold with quadratic rolloff for smoother transition float luminance = dot(color, float3(0.2126, 0.7152, 0.0722)); float threshold = max(BloomThreshold, 0.2); float knee = threshold * BloomThresholdKnee; - float soft_max = threshold + knee; + float softMax = threshold + knee; float contribution = 0; - if (luminance > threshold) { - if (luminance < soft_max) { + if (luminance > threshold) + { + if (luminance < softMax) + { // Quadratic softening between threshold and (threshold + knee) float x = (luminance - threshold) / knee; contribution = x * x * 0.5; - } else { - // Full contribution above soft_max + } + else + { + // Full contribution above softMax contribution = luminance - threshold; } } - + float testc = BloomClamp; float3 clamped = (color * contribution); clamped.r = min(clamped.r, testc); @@ -360,7 +363,8 @@ float4 PS_BloomDownsample(Quad_VS2PS input) : SV_Target float totalWeight = 0; // Sample offsets (fixed) - const float2 offsets[9] = { + const float2 offsets[9] = + { float2( 0, 0), // Center float2(-1, -1), // Corners float2( 1, -1), @@ -373,7 +377,8 @@ float4 PS_BloomDownsample(Quad_VS2PS input) : SV_Target }; // Sample weights (fixed) - const float weights[9] = { + const float weights[9] = + { 4.0, // Center 1.0, // Corners 1.0, @@ -401,11 +406,10 @@ META_PS(true, FEATURE_LEVEL_ES2) float4 PS_BloomDualFilterUpsample(Quad_VS2PS input) : SV_Target { float anisotropy = 1.0; - uint width, height; Input0.GetDimensions(width, height); float2 texelSize = 1.0 / float2(width, height); - + // Maintain fixed scale through mip chain float baseOffset = 1.0; float offsetScale = (1.0) * baseOffset; @@ -436,7 +440,8 @@ float4 PS_BloomDualFilterUpsample(Quad_VS2PS input) : SV_Target } // Corners - fixed distance samples - float2 cornerOffsets[4] = { + float2 cornerOffsets[4] = + { float2(offsetScale * anisotropy, offsetScale), float2(-offsetScale * anisotropy, offsetScale), float2(offsetScale * anisotropy, -offsetScale), @@ -451,12 +456,12 @@ float4 PS_BloomDualFilterUpsample(Quad_VS2PS input) : SV_Target color += sample.rgb * weight; totalWeight += weight; } - + color /= totalWeight; - + uint width1, height1; Input1.GetDimensions(width1, height1); - + // Calculate mip fade factor (0 = smallest mip, 1 = largest mip) float mipFade = BloomLayer / (BloomMipCount - 1); @@ -476,24 +481,20 @@ float4 PS_BloomDualFilterUpsample(Quad_VS2PS input) : SV_Target float3 previousMip = Input1.Sample(SamplerLinearClamp, input.TexCoord).rgb; color += previousMip; } - + return float4(color, 1.0); } - - // Horizontal gaussian blur META_PS(true, FEATURE_LEVEL_ES2) float4 PS_GaussainBlurH(Quad_VS2PS input) : SV_Target { float4 color = 0; - UNROLL for (int i = 0; i < GB_KERNEL_SIZE; i++) { color += Input0.Sample(SamplerLinearClamp, input.TexCoord + float2(GaussianBlurCache[i].y, 0.0)) * GaussianBlurCache[i].x; } - return color; } @@ -502,18 +503,14 @@ META_PS(true, FEATURE_LEVEL_ES2) float4 PS_GaussainBlurV(Quad_VS2PS input) : SV_Target { float4 color = 0; - UNROLL for (int i = 0; i < GB_KERNEL_SIZE; i++) { color += Input0.Sample(SamplerLinearClamp, input.TexCoord + float2(0.0, GaussianBlurCache[i].y)) * GaussianBlurCache[i].x; } - return color; } - - // Generate 'ghosts' for lens flare META_PS(true, FEATURE_LEVEL_ES2) float4 PS_Ghosts(Quad_VS2PS input) : SV_Target @@ -553,8 +550,7 @@ float4 PS_Ghosts(Quad_VS2PS input) : SV_Target Input3.Sample(SamplerLinearClamp, offset + ghostVecnNorm * distortion.r).r, Input3.Sample(SamplerLinearClamp, offset + ghostVecnNorm * distortion.g).g, Input3.Sample(SamplerLinearClamp, offset + ghostVecnNorm * distortion.b).b); - color = clamp((color * 1.0f) + LensBias, 0, 10) * (LensScale * weight); - + color = clamp(color + LensBias, 0, 10) * (LensScale * weight); // Accumulate color result += color; @@ -610,8 +606,6 @@ float nrand(float2 n) return frac(sin(dot(n.xy, float2(12.9898, 78.233)))* 43758.5453); } - - // Applies exposure, color grading and tone mapping to the input. // Combines it with the results of the bloom pass and other postFx. META_PS(true, FEATURE_LEVEL_ES2) @@ -658,8 +652,7 @@ float4 PS_Composite(Quad_VS2PS input) : SV_Target color = Input0.Sample(SamplerLinearClamp, uv); } - - // Lens Flaresf + // Lens Flares BRANCH if (LensFlareIntensity > 0) { @@ -677,21 +670,17 @@ float4 PS_Composite(Quad_VS2PS input) : SV_Target lensLight += lensFlares * 1.5f; color.rgb += lensFlares; } - -// In PS_Composite: -BRANCH -if (BloomIntensity > 0) -{ - // Sample the final bloom result - float3 bloom = Input2.Sample(SamplerLinearClamp, input.TexCoord).rgb; - bloom = bloom * BloomIntensity; - - - - lensLight += max(0, bloom * 3.0f + (- 1.0f * 3.0f)); - color.rgb += bloom; -} + // Bloom + BRANCH + if (BloomIntensity > 0) + { + // Sample the final bloom result + float3 bloom = Input2.Sample(SamplerLinearClamp, input.TexCoord).rgb; + bloom = bloom * BloomIntensity; + lensLight += max(0, bloom * 3.0f + (-1.0f * 3.0f)); + color.rgb += bloom; + } // Lens Dirt float3 lensDirt = LensDirt.SampleLevel(SamplerLinearClamp, uv, 0).rgb; From 5e8f9e357aa9607d6edcfe4e0c61e1e68768d710 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Mar 2025 20:25:03 +0100 Subject: [PATCH 212/215] Reorganize code --- Content/Shaders/PostProcessing.flax | 4 +- Source/Engine/Renderer/PostProcessingPass.cpp | 111 +++++++++++++----- Source/Engine/Renderer/PostProcessingPass.h | 100 ++-------------- 3 files changed, 90 insertions(+), 125 deletions(-) diff --git a/Content/Shaders/PostProcessing.flax b/Content/Shaders/PostProcessing.flax index 8747c4eb4..9efa7fc98 100644 --- a/Content/Shaders/PostProcessing.flax +++ b/Content/Shaders/PostProcessing.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05a68ff4987dbd550865098e85e5aad29e12589d541b07826784a9cb2eefbb78 -size 16522 +oid sha256:4b7c6f13b504eaa8f8ef6ba9e74c3210effd12d93aa519a63c19b365058f184d +size 22577 diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 93cd56536..3122e847d 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -9,19 +9,61 @@ #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Engine/Time.h" -PostProcessingPass::PostProcessingPass() - : _shader(nullptr) - , _psBloomBrightPass(nullptr) - , _psBloomDownsample(nullptr) - , _psBloomDualFilterUpsample(nullptr) - , _psBlurH(nullptr) - , _psBlurV(nullptr) - , _psGenGhosts(nullptr) - , _defaultLensColor(nullptr) - , _defaultLensStar(nullptr) - , _defaultLensDirt(nullptr) -{ -} +#define GB_RADIUS 6 +#define GB_KERNEL_SIZE (GB_RADIUS * 2 + 1) + +GPU_CB_STRUCT(Data{ + float BloomIntensity; // Overall bloom strength multiplier + float BloomClamp; // Maximum brightness limit for bloom + float BloomThreshold; // Luminance threshold where bloom begins + float BloomThresholdKnee; // Controls the threshold rolloff curve + + float BloomBaseMix; // Base mip contribution + float BloomHighMix; // High mip contribution + float BloomMipCount; + float BloomLayer; + + Float3 VignetteColor; + float VignetteShapeFactor; + + Float2 InputSize; + float InputAspect; + float GrainAmount; + + float GrainTime; + float GrainParticleSize; + int32 Ghosts; + float HaloWidth; + + float HaloIntensity; + float Distortion; + float GhostDispersal; + float LensFlareIntensity; + + Float2 LensInputDistortion; + float LensScale; + float LensBias; + + Float2 InvInputSize; + float ChromaticDistortion; + float Time; + + float Dummy1; + float PostExposure; + float VignetteIntensity; + float LensDirtIntensity; + + Color ScreenFadeColor; + + Matrix LensFlareStarMat; + }); + +GPU_CB_STRUCT(GaussianBlurData{ + Float2 Size; + float Dummy3; + float Dummy4; + Float4 GaussianBlurCache[GB_KERNEL_SIZE]; // x-weight, y-offset + }); String PostProcessingPass::ToString() const { @@ -116,7 +158,7 @@ bool PostProcessingPass::setupResources() return false; } -GPUTexture* PostProcessingPass::getCustomOrDefault(Texture* customTexture, AssetReference& defaultTexture, const Char* defaultName) +GPUTexture* GetCustomOrDefault(Texture* customTexture, AssetReference& defaultTexture, const Char* defaultName) { // Check if use custom texture if (customTexture) @@ -133,7 +175,15 @@ GPUTexture* PostProcessingPass::getCustomOrDefault(Texture* customTexture, Asset return defaultTexture ? defaultTexture->GetTexture() : nullptr; } -void PostProcessingPass::GB_ComputeKernel(float sigma, float width, float height) +/// +/// Calculates the Gaussian blur filter kernel. This implementation is +/// ported from the original Java code appearing in chapter 16 of +/// "Filthy Rich Clients: Developing Animated and Graphical Effects for Desktop Java". +/// +/// Gaussian Blur sigma parameter +/// Texture to blur width in pixels +/// Texture to blur height in pixels +void GB_ComputeKernel(float sigma, float width, float height, Float4 gaussianBlurCacheH[GB_KERNEL_SIZE], Float4 gaussianBlurCacheV[GB_KERNEL_SIZE]) { float total = 0.0f; float twoSigmaSquare = 2.0f * sigma * sigma; @@ -154,19 +204,16 @@ void PostProcessingPass::GB_ComputeKernel(float sigma, float width, float height // Calculate total weights sum total += weight; - GaussianBlurCacheH[index] = Float4(weight, i * xOffset, 0, 0); - GaussianBlurCacheV[index] = Float4(weight, i * yOffset, 0, 0); + gaussianBlurCacheH[index] = Float4(weight, i * xOffset, 0, 0); + gaussianBlurCacheV[index] = Float4(weight, i * yOffset, 0, 0); } // Normalize weights for (int32 i = 0; i < GB_KERNEL_SIZE; i++) { - GaussianBlurCacheH[i].X /= total; - GaussianBlurCacheV[i].X /= total; + gaussianBlurCacheH[i].X /= total; + gaussianBlurCacheV[i].X /= total; } - - // Assign size - _gbData.Size = Float2(width, height); } void PostProcessingPass::Dispose() @@ -405,8 +452,8 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, if (useLensFlares) { // Prepare lens flares helper textures - context->BindSR(5, getCustomOrDefault(settings.LensFlares.LensStar, _defaultLensStar, TEXT("Engine/Textures/DefaultLensStarburst"))); - context->BindSR(6, getCustomOrDefault(settings.LensFlares.LensColor, _defaultLensColor, TEXT("Engine/Textures/DefaultLensColor"))); + context->BindSR(5, GetCustomOrDefault(settings.LensFlares.LensStar, _defaultLensStar, TEXT("Engine/Textures/DefaultLensStarburst"))); + context->BindSR(6, GetCustomOrDefault(settings.LensFlares.LensColor, _defaultLensColor, TEXT("Engine/Textures/DefaultLensColor"))); // Render lens flares context->SetRenderTarget(bloomBuffer2->View(0, 1)); @@ -418,11 +465,15 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, context->UnBindSR(3); // Gaussian blur kernel - GB_ComputeKernel(2.0f, static_cast(w4), static_cast(h4)); + GaussianBlurData gbData; + Float4 GaussianBlurCacheH[GB_KERNEL_SIZE]; + Float4 GaussianBlurCacheV[GB_KERNEL_SIZE]; + gbData.Size = Float2(static_cast(w4), static_cast(h4)); + GB_ComputeKernel(2.0f, gbData.Size.X, gbData.Size.Y, GaussianBlurCacheH, GaussianBlurCacheV); // Gaussian blur H - Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheH, sizeof(GaussianBlurCacheH)); - context->UpdateCB(cb1, &_gbData); + Platform::MemoryCopy(gbData.GaussianBlurCache, GaussianBlurCacheH, sizeof(GaussianBlurCacheH)); + context->UpdateCB(cb1, &gbData); context->BindCB(1, cb1); context->SetRenderTarget(bloomBuffer1->View(0, 1)); context->BindSR(0, bloomBuffer2->View(0, 1)); @@ -431,8 +482,8 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, context->ResetRenderTarget(); // Gaussian blur V - Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheV, sizeof(GaussianBlurCacheV)); - context->UpdateCB(cb1, &_gbData); + Platform::MemoryCopy(gbData.GaussianBlurCache, GaussianBlurCacheV, sizeof(GaussianBlurCacheV)); + context->UpdateCB(cb1, &gbData); context->BindCB(1, cb1); context->SetRenderTarget(bloomBuffer2->View(0, 1)); context->BindSR(0, bloomBuffer1->View(0, 1)); @@ -481,7 +532,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, // - 5 - LensStar - lens star texture // - 7 - ColorGradingLUT context->BindSR(0, input->View()); - context->BindSR(4, getCustomOrDefault(settings.LensFlares.LensDirt, _defaultLensDirt, TEXT("Engine/Textures/DefaultLensDirt"))); + context->BindSR(4, GetCustomOrDefault(settings.LensFlares.LensDirt, _defaultLensDirt, TEXT("Engine/Textures/DefaultLensDirt"))); context->BindSR(7, colorGradingLutView); // Composite final frame during single pass (done in full resolution) diff --git a/Source/Engine/Renderer/PostProcessingPass.h b/Source/Engine/Renderer/PostProcessingPass.h index d27fbedfa..ed45be1ea 100644 --- a/Source/Engine/Renderer/PostProcessingPass.h +++ b/Source/Engine/Renderer/PostProcessingPass.h @@ -5,96 +5,25 @@ #include "RendererPass.h" #include "Engine/Graphics/GPUPipelineStatePermutations.h" -#define GB_RADIUS 6 -#define GB_KERNEL_SIZE (GB_RADIUS * 2 + 1) - /// -/// Post processing rendering service +/// Post-processing rendering service. /// class PostProcessingPass : public RendererPass { private: - - GPU_CB_STRUCT(Data{ - float BloomIntensity; // Overall bloom strength multiplier - float BloomClamp; // Maximum brightness limit for bloom - float BloomThreshold; // Luminance threshold where bloom begins - float BloomThresholdKnee; // Controls the threshold rolloff curve - - float BloomBaseMix; // Base mip contribution - float BloomHighMix; // High mip contribution - float BloomMipCount; - float BloomLayer; - - Float3 VignetteColor; - float VignetteShapeFactor; - - Float2 InputSize; - float InputAspect; - float GrainAmount; - - float GrainTime; - float GrainParticleSize; - int32 Ghosts; - float HaloWidth; - - float HaloIntensity; - float Distortion; - float GhostDispersal; - float LensFlareIntensity; - - Float2 LensInputDistortion; - float LensScale; - float LensBias; - - Float2 InvInputSize; - float ChromaticDistortion; - float Time; - - float Dummy1; - float PostExposure; - float VignetteIntensity; - float LensDirtIntensity; - - Color ScreenFadeColor; - - Matrix LensFlareStarMat; - }); - - GPU_CB_STRUCT(GaussianBlurData{ - Float2 Size; - float Dummy3; - float Dummy4; - Float4 GaussianBlurCache[GB_KERNEL_SIZE]; // x-weight, y-offset - }); - - // Post Processing AssetReference _shader; - GPUPipelineState* _psBloomBrightPass; - GPUPipelineState* _psBloomDownsample; - GPUPipelineState* _psBloomDualFilterUpsample; - GPUPipelineState* _psBlurH; - GPUPipelineState* _psBlurV; - GPUPipelineState* _psGenGhosts; + GPUPipelineState* _psBloomBrightPass = nullptr; + GPUPipelineState* _psBloomDownsample = nullptr; + GPUPipelineState* _psBloomDualFilterUpsample = nullptr; + GPUPipelineState* _psBlurH = nullptr; + GPUPipelineState* _psBlurV = nullptr; + GPUPipelineState* _psGenGhosts = nullptr; GPUPipelineStatePermutationsPs<3> _psComposite; - - GaussianBlurData _gbData; - Float4 GaussianBlurCacheH[GB_KERNEL_SIZE]; - Float4 GaussianBlurCacheV[GB_KERNEL_SIZE]; - AssetReference _defaultLensColor; AssetReference _defaultLensStar; AssetReference _defaultLensDirt; public: - - /// - /// Init - /// - PostProcessingPass(); - -public: - /// /// Perform postFx rendering for the input task /// @@ -105,19 +34,6 @@ public: void Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT); private: - - GPUTexture* getCustomOrDefault(Texture* customTexture, AssetReference& defaultTexture, const Char* defaultName); - - /// - /// Calculates the Gaussian blur filter kernel. This implementation is - /// ported from the original Java code appearing in chapter 16 of - /// "Filthy Rich Clients: Developing Animated and Graphical Effects for Desktop Java". - /// - /// Gaussian Blur sigma parameter - /// Texture to blur width in pixels - /// Texture to blur height in pixels - void GB_ComputeKernel(float sigma, float width, float height); - #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { @@ -133,14 +49,12 @@ private: #endif public: - // [RendererPass] String ToString() const override; bool Init() override; void Dispose() override; protected: - // [RendererPass] bool setupResources() override; }; From fe95ac96af4001b6cf6d94b3a0c1c7e3e847a5f6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Mar 2025 21:09:48 +0100 Subject: [PATCH 213/215] Rename param --- Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index decfb48de..44c6e9b42 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -50,13 +50,13 @@ namespace Flax.Build.Platforms /// Runs codesign tool on macOS to sign the code with a given identity from local keychain. /// /// Path to file to codesign. - /// App code signing idenity name (from local Mac keychain). Use 'security find-identity -v -p codesigning' to list possible options. - public static void CodeSign(string file, string signIdenity) + /// App code signing identity name (from local Mac keychain). Use 'security find-identity -v -p codesigning' to list possible options. + public static void CodeSign(string file, string signIdentity) { var isDirectory = Directory.Exists(file); if (!isDirectory && !File.Exists(file)) throw new FileNotFoundException("Missing file to sign.", file); - string cmdLine = string.Format("--force --timestamp -s \"{0}\" \"{1}\"", signIdenity, file); + string cmdLine = string.Format("--force --timestamp -s \"{0}\" \"{1}\"", signIdentity, file); if (isDirectory) { // Automatically sign contents From 1e2e613a787146028df080005f0c6371e6e49e33 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Mar 2025 23:49:12 +0100 Subject: [PATCH 214/215] Fix rich textbox carret placement regressions #3118 #2964 --- Source/Engine/Render2D/Font.cpp | 6 +-- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 49 ++++++++++++------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 41924e50b..58d87bae1 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -388,11 +388,11 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te if (dst < smallestDst) { // Pointer is behind the last character in the line - smallestIndex = line.LastCharIndex; + smallestIndex = line.LastCharIndex + 1; // Fix for last line - if (lineIndex == lines.Count() - 1) - smallestIndex++; + //if (lineIndex == lines.Count() - 1) + // smallestIndex++; } return smallestIndex; diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 4a49f5bad..15576cbcc 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -65,7 +65,7 @@ namespace FlaxEngine.GUI /// /// The character index. /// The result text block descriptor. - /// If true, the when the index is between two text blocks, it will return the next block. + /// If true, when the index is between two text blocks, it will return the next block. /// True if a text block is found, otherwise false. public bool GetNearestTextBlock(int index, out TextBlock result, bool snapToNext = false) { @@ -85,7 +85,8 @@ namespace FlaxEngine.GUI if (i < blockCount - 1) { ref TextBlock nextBlock = ref textBlocksSpan[i + 1]; - if (index >= currentBlock.Range.EndIndex && index < nextBlock.Range.StartIndex) + var containsY = nextBlock.Bounds.Y >= currentBlock.Bounds.Top && nextBlock.Bounds.Y < currentBlock.Bounds.Bottom; + if (index >= currentBlock.Range.EndIndex && index < nextBlock.Range.StartIndex && containsY) { result = snapToNext ? nextBlock : currentBlock; return true; @@ -94,9 +95,14 @@ namespace FlaxEngine.GUI } // Handle case when index is outside all text ranges - if (index >= 0 && blockCount > 0) + if (index >= 0 && blockCount > 0 && index <= textBlocksSpan[0].Range.StartIndex) { - result = (index <= textBlocksSpan[0].Range.StartIndex) ? textBlocksSpan[0] : textBlocksSpan[blockCount - 1]; + result = textBlocksSpan[0]; + return true; + } + if (index >= 0 && blockCount > 0 && index >= textBlocksSpan[blockCount - 1].Range.StartIndex) + { + result = textBlocksSpan[blockCount - 1]; return true; } @@ -233,8 +239,8 @@ namespace FlaxEngine.GUI { ref TextBlock textBlock = ref textBlocks[i]; - var containsX = location.X >= textBlock.Bounds.Location.X && location.X < textBlock.Bounds.Location.X + textBlock.Bounds.Size.X; - var containsY = location.Y >= textBlock.Bounds.Location.Y && location.Y < textBlock.Bounds.Location.Y + textBlock.Bounds.Size.Y; + var containsX = location.X >= textBlock.Bounds.Left && location.X < textBlock.Bounds.Right; + var containsY = location.Y >= textBlock.Bounds.Top && location.Y < textBlock.Bounds.Bottom; if (containsY && (containsX || (i + 1 < count && textBlocks[i + 1].Bounds.Location.Y > textBlock.Bounds.Location.Y + 1.0f))) { @@ -260,6 +266,7 @@ namespace FlaxEngine.GUI var left = spaceLoc == -1 ? 0 : spaceLoc + 1; spaceLoc = _text.IndexOfAny(Separators, Math.Min(hitPos + 1, _text.Length)); var right = spaceLoc == -1 ? textLength : spaceLoc; + Deselect(); SetSelection(left, right); } @@ -272,24 +279,30 @@ namespace FlaxEngine.GUI int snappedStart = start; int snappedEnd = end; + // Snapping selection between text blocks if (start != -1 && end != -1) { bool movingBack = (_selectionStart != -1 && _selectionEnd != -1) && (end < _selectionEnd || start < _selectionStart); - GetNearestTextBlock(start, out TextBlock startTextBlock, !movingBack); - GetNearestTextBlock(end, out TextBlock endTextBlock, !movingBack); + if (GetNearestTextBlock(start, out TextBlock startTextBlock, !movingBack)) + { + snappedStart = startTextBlock.Range.Contains(start) ? start : (movingBack ? startTextBlock.Range.EndIndex - 1 : startTextBlock.Range.StartIndex); + snappedStart = movingBack ? Math.Min(start, snappedStart) : Math.Max(start, snappedStart); - snappedStart = startTextBlock.Range.Contains(start) ? start : (movingBack ? startTextBlock.Range.EndIndex - 1 : startTextBlock.Range.StartIndex); - snappedEnd = endTextBlock.Range.Contains(end) ? end : (movingBack ? endTextBlock.Range.EndIndex - 1 : endTextBlock.Range.StartIndex); + // Don't snap if selection is right in the end of the text + if (start == _text.Length) + snappedStart = _text.Length; + } - snappedStart = movingBack ? Math.Min(start, snappedStart) : Math.Max(start, snappedStart); - snappedEnd = movingBack ? Math.Min(end, snappedEnd) : Math.Max(end, snappedEnd); - - // Don't snap if selection is right in the end of the text - if (start == _text.Length) - snappedStart = _text.Length; - if (end == _text.Length) - snappedEnd = _text.Length; + if (GetNearestTextBlock(end, out TextBlock endTextBlock, !movingBack)) + { + snappedEnd = endTextBlock.Range.Contains(end) ? end : (movingBack ? endTextBlock.Range.EndIndex - 1 : endTextBlock.Range.StartIndex); + snappedEnd = movingBack ? Math.Min(end, snappedEnd) : Math.Max(end, snappedEnd); + + // Don't snap if selection is right in the end of the text + if (end == _text.Length) + snappedEnd = _text.Length; + } } base.SetSelection(snappedStart, snappedEnd, withScroll); From 3643ec7a58f63b7b3cb6f34bd1d81e3332b2f269 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 9 Mar 2025 09:09:45 +0100 Subject: [PATCH 215/215] Fix crash when loading old triplanar node --- .../Tools/MaterialGenerator/MaterialGenerator.Textures.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index e155a576a..5e4484605 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -706,7 +706,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) const auto texture = eatBox(textureBox->GetParent(), textureBox->FirstConnection()); const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat(); const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat(); - const auto offset = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat2(); + const auto offset = tryGetValue(node->GetBox(3), node->Values.Count() > 2 ? node->Values[2] : Float2::Zero).AsFloat2(); const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false; const Char* samplerName;