From 4a79df860d76f9c55e13ed7a22fd6ac7233268a7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Dec 2020 15:48:42 +0100 Subject: [PATCH 001/222] Refactor NavMesh into NavMeshRuntime and make it internal --- Source/Engine/Navigation/NavLink.cpp | 1 - Source/Engine/Navigation/NavMeshBuilder.cpp | 36 ++++++++--------- .../{NavMesh.cpp => NavMeshRuntime.cpp} | 40 +++++++++---------- .../{NavMesh.h => NavMeshRuntime.h} | 15 ++++--- Source/Engine/Navigation/Navigation.cpp | 22 +++++----- Source/Engine/Navigation/Navigation.h | 9 ----- Source/Engine/Navigation/NavigationScene.cpp | 10 ++--- 7 files changed, 65 insertions(+), 68 deletions(-) rename Source/Engine/Navigation/{NavMesh.cpp => NavMeshRuntime.cpp} (86%) rename Source/Engine/Navigation/{NavMesh.h => NavMeshRuntime.h} (91%) diff --git a/Source/Engine/Navigation/NavLink.cpp b/Source/Engine/Navigation/NavLink.cpp index 2fb6bc368..d23018646 100644 --- a/Source/Engine/Navigation/NavLink.cpp +++ b/Source/Engine/Navigation/NavLink.cpp @@ -14,7 +14,6 @@ NavLink::NavLink(const SpawnParams& params) void NavLink::UpdateBounds() { - // Cache bounds const auto start = _transform.LocalToWorld(Start); const auto end = _transform.LocalToWorld(End); BoundingBox::FromPoints(start, end, _box); diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 1765d78c7..c0ce014ee 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -18,9 +18,9 @@ #include "NavigationScene.h" #include "NavigationSettings.h" #include "NavMeshBoundsVolume.h" -#include "NavMesh.h" #include "NavLink.h" #include "Navigation.h" +#include "NavMeshRuntime.h" #include #include #include @@ -184,9 +184,9 @@ void RasterizeGeometry(const BoundingBox& tileBounds, rcContext* context, rcConf bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds) { // Build initial tile bounds (with infinite extent) - tileBounds.Minimum.X = x * tileSize; + tileBounds.Minimum.X = (float)x * tileSize; tileBounds.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Minimum.Z = y * tileSize; + tileBounds.Minimum.Z = (float)y * tileSize; tileBounds.Maximum.X = tileBounds.Minimum.X + tileSize; tileBounds.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; tileBounds.Maximum.Z = tileBounds.Minimum.Z + tileSize; @@ -224,7 +224,7 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi return foundAnyVolume; } -void RemoveTile(NavMesh* navMesh, NavigationScene* scene, int32 x, int32 y, int32 layer) +void RemoveTile(NavMeshRuntime* navMesh, NavigationScene* scene, int32 x, int32 y, int32 layer) { ScopeLock lock(navMesh->Locker); @@ -250,7 +250,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou int32 layer = 0; // Expand tile bounds by a certain margin - const float tileBorderSize = (1 + config.borderSize) * config.cs; + const float tileBorderSize = (1.0f + (float)config.borderSize) * config.cs; tileBounds.Minimum -= tileBorderSize; tileBounds.Maximum += tileBorderSize; @@ -347,7 +347,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou rcFreeCompactHeightfield(compactHeightfield); rcFreeContourSet(contourSet); - for (int i = 0; i < polyMesh->npolys; ++i) + for (int i = 0; i < polyMesh->npolys; i++) { polyMesh->flags[i] = polyMesh->areas[i] == RC_WALKABLE_AREA ? 1 : 0; } @@ -355,7 +355,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou if (polyMesh->nverts == 0) { // Empty tile - RemoveTile(Navigation::GetNavMesh(), scene, x, y, layer); + RemoveTile(NavMeshRuntime::Get(), scene, x, y, layer); return false; } @@ -373,9 +373,9 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou params.detailVertsCount = detailMesh->nverts; params.detailTris = detailMesh->tris; params.detailTriCount = detailMesh->ntris; - params.walkableHeight = config.walkableHeight * config.ch; - params.walkableRadius = config.walkableRadius * config.cs; - params.walkableClimb = config.walkableClimb * config.ch; + params.walkableHeight = (float)config.walkableHeight * config.ch; + params.walkableRadius = (float)config.walkableRadius * config.cs; + params.walkableClimb = (float)config.walkableClimb * config.ch; params.tileX = x; params.tileY = y; params.tileLayer = layer; @@ -438,7 +438,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou { PROFILE_CPU_NAMED("Navigation.CreateTile"); - ScopeLock lock(Navigation::GetNavMesh()->Locker); + ScopeLock lock(NavMeshRuntime::Get()->Locker); // Add tile data scene->IsDataDirty = true; @@ -451,7 +451,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou tile.Data.Copy(navData, navDataSize); // Add tile to navmesh - Navigation::GetNavMesh()->AddTile(scene, tile); + NavMeshRuntime::Get()->AddTile(scene, tile); } dtFree(navData); @@ -635,7 +635,7 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, void BuildWholeScene(NavigationScene* scene) { const float tileSize = GetTileSize(); - const auto navMesh = Navigation::GetNavMesh(); + const auto navMesh = NavMeshRuntime::Get(); // Compute total navigation area bounds const BoundingBox worldBounds = scene->GetNavigationBounds(); @@ -674,9 +674,9 @@ void BuildWholeScene(NavigationScene* scene) { PROFILE_CPU_NAMED("StartBuildingTiles"); - for (int32 y = tilesMin.Z; y < tilesMax.Z; y ++) + for (int32 y = tilesMin.Z; y < tilesMax.Z; y++) { - for (int32 x = tilesMin.X; x < tilesMax.X; x ++) + for (int32 x = tilesMin.X; x < tilesMax.X; x++) { BoundingBox tileBounds; if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) @@ -695,7 +695,7 @@ void BuildWholeScene(NavigationScene* scene) void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) { const float tileSize = GetTileSize(); - const auto navMesh = Navigation::GetNavMesh(); + const auto navMesh = NavMeshRuntime::Get(); // Align dirty bounds to tile size BoundingBox dirtyBoundsAligned; @@ -739,9 +739,9 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) { PROFILE_CPU_NAMED("StartBuildingTiles"); - for (int32 y = tilesMin.Z; y < tilesMax.Z; y ++) + for (int32 y = tilesMin.Z; y < tilesMax.Z; y++) { - for (int32 x = tilesMin.X; x < tilesMax.X; x ++) + for (int32 x = tilesMin.X; x < tilesMax.X; x++) { BoundingBox tileBounds; if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp similarity index 86% rename from Source/Engine/Navigation/NavMesh.cpp rename to Source/Engine/Navigation/NavMeshRuntime.cpp index 347480af1..7a1c4d3c4 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -1,8 +1,8 @@ // Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. -#include "NavMesh.h" -#include "Engine/Core/Log.h" +#include "NavMeshRuntime.h" #include "NavigationScene.h" +#include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" #include @@ -12,25 +12,25 @@ #define USE_DATA_LINK 0 #define USE_NAV_MESH_ALLOC 1 -NavMesh::NavMesh() +NavMeshRuntime::NavMeshRuntime() { _navMesh = nullptr; _navMeshQuery = dtAllocNavMeshQuery(); _tileSize = 0; } -NavMesh::~NavMesh() +NavMeshRuntime::~NavMeshRuntime() { dtFreeNavMesh(_navMesh); dtFreeNavMeshQuery(_navMeshQuery); } -int32 NavMesh::GetTilesCapacity() const +int32 NavMeshRuntime::GetTilesCapacity() const { return _navMesh ? _navMesh->getMaxTiles() : 0; } -void NavMesh::SetTileSize(float tileSize) +void NavMeshRuntime::SetTileSize(float tileSize) { ScopeLock lock(Locker); @@ -49,7 +49,7 @@ void NavMesh::SetTileSize(float tileSize) _tileSize = tileSize; } -void NavMesh::EnsureCapacity(int32 tilesToAddCount) +void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) { ScopeLock lock(Locker); @@ -58,7 +58,7 @@ void NavMesh::EnsureCapacity(int32 tilesToAddCount) if (newTilesCount <= capacity) return; - PROFILE_CPU_NAMED("NavMesh.EnsureCapacity"); + PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity"); // Navmesh tiles capacity growing rule int32 newCapacity = 0; @@ -132,7 +132,7 @@ void NavMesh::EnsureCapacity(int32 tilesToAddCount) } } -void NavMesh::AddTiles(NavigationScene* scene) +void NavMeshRuntime::AddTiles(NavigationScene* scene) { // Skip if no data ASSERT(scene); @@ -140,7 +140,7 @@ void NavMesh::AddTiles(NavigationScene* scene) return; auto& data = scene->Data; - PROFILE_CPU_NAMED("NavMesh.AddTiles"); + PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles"); ScopeLock lock(Locker); @@ -168,12 +168,12 @@ void NavMesh::AddTiles(NavigationScene* scene) } } -void NavMesh::AddTile(NavigationScene* scene, NavMeshTileData& tileData) +void NavMeshRuntime::AddTile(NavigationScene* scene, NavMeshTileData& tileData) { ASSERT(scene); auto& data = scene->Data; - PROFILE_CPU_NAMED("NavMesh.AddTile"); + PROFILE_CPU_NAMED("NavMeshRuntime.AddTile"); ScopeLock lock(Locker); @@ -198,17 +198,17 @@ void NavMesh::AddTile(NavigationScene* scene, NavMeshTileData& tileData) AddTileInternal(scene, tileData); } -bool IsTileFromScene(const NavMesh* navMesh, const NavMeshTile& tile, void* customData) +bool IsTileFromScene(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData) { return tile.Scene == (NavigationScene*)customData; } -void NavMesh::RemoveTiles(NavigationScene* scene) +void NavMeshRuntime::RemoveTiles(NavigationScene* scene) { RemoveTiles(IsTileFromScene, scene); } -void NavMesh::RemoveTile(int32 x, int32 y, int32 layer) +void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer) { ScopeLock lock(Locker); @@ -216,7 +216,7 @@ void NavMesh::RemoveTile(int32 x, int32 y, int32 layer) if (!_navMesh) return; - PROFILE_CPU_NAMED("NavMesh.RemoveTile"); + PROFILE_CPU_NAMED("NavMeshRuntime.RemoveTile"); const auto tileRef = _navMesh->getTileRefAt(x, y, layer); if (tileRef == 0) @@ -240,7 +240,7 @@ void NavMesh::RemoveTile(int32 x, int32 y, int32 layer) } } -void NavMesh::RemoveTiles(bool (* prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData) +void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData) { ScopeLock lock(Locker); @@ -249,7 +249,7 @@ void NavMesh::RemoveTiles(bool (* prediction)(const NavMesh* navMesh, const NavM if (!_navMesh) return; - PROFILE_CPU_NAMED("NavMesh.RemoveTiles"); + PROFILE_CPU_NAMED("NavMeshRuntime.RemoveTiles"); for (int32 i = 0; i < _tiles.Count(); i++) { @@ -274,7 +274,7 @@ void NavMesh::RemoveTiles(bool (* prediction)(const NavMesh* navMesh, const NavM } } -void NavMesh::Dispose() +void NavMeshRuntime::Dispose() { if (_navMesh) { @@ -284,7 +284,7 @@ void NavMesh::Dispose() _tiles.Resize(0); } -void NavMesh::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData) +void NavMeshRuntime::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData) { // Check if that tile has been added to navmesh NavMeshTile* tile = nullptr; diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMeshRuntime.h similarity index 91% rename from Source/Engine/Navigation/NavMesh.h rename to Source/Engine/Navigation/NavMeshRuntime.h index f4364331e..0e073cd81 100644 --- a/Source/Engine/Navigation/NavMesh.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -21,8 +21,12 @@ public: BytesContainer Data; }; -class FLAXENGINE_API NavMesh +class NavMeshRuntime { +public: + + static NavMeshRuntime* Get(); + private: dtNavMesh* _navMesh; @@ -32,14 +36,13 @@ private: public: - NavMesh(); - - ~NavMesh(); + NavMeshRuntime(); + ~NavMeshRuntime(); public: /// - /// The NavMesh object locker. + /// The object locker. /// CriticalSection Locker; @@ -110,7 +113,7 @@ public: /// /// The prediction callback, returns true for tiles to remove and false for tiles to preserve. /// The user data passed to the callback method. - void RemoveTiles(bool (*prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData); + void RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData); /// /// Releases the navmesh. diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 5f6ed573f..c7d727557 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -1,11 +1,12 @@ // Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #include "Navigation.h" +#include "NavMeshRuntime.h" +#include "NavMeshBuilder.h" #include "Engine/Threading/Threading.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Engine/EngineService.h" -#include "NavMeshBuilder.h" -#include "NavMesh.h" +#include "Engine/Profiler/ProfilerCPU.h" #include #include #include @@ -13,7 +14,15 @@ #define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f #define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f -NavMesh* _navMesh = nullptr; +namespace +{ + NavMeshRuntime* _navMesh; +} + +NavMeshRuntime* NavMeshRuntime::Get() +{ + return ::_navMesh; +} class NavigationService : public EngineService { @@ -36,11 +45,6 @@ public: NavigationService NavigationServiceInstance; -NavMesh* Navigation::GetNavMesh() -{ - return _navMesh; -} - void* dtAllocDefault(size_t size, dtAllocHint) { return Allocator::Allocate(size); @@ -58,7 +62,7 @@ bool NavigationService::Init() rcAllocSetCustom(rcAllocDefault, Allocator::Free); // Create global nav mesh - _navMesh = New(); + _navMesh = New(); return false; } diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index d1fad8083..29d21cffb 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -6,7 +6,6 @@ #include "Engine/Core/Math/Vector3.h" class Scene; -class NavMesh; #define NAV_MESH_PATH_MAX_SIZE 200 @@ -39,14 +38,6 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); API_CLASS(Static) class FLAXENGINE_API Navigation { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Navigation); -public: - - /// - /// Gets the navigation mesh (read-only). Use the other API to request data to use safe access to the navigation system. - /// - /// The navigation mesh. - static NavMesh* GetNavMesh(); - public: /// diff --git a/Source/Engine/Navigation/NavigationScene.cpp b/Source/Engine/Navigation/NavigationScene.cpp index 03c87f852..397de0d31 100644 --- a/Source/Engine/Navigation/NavigationScene.cpp +++ b/Source/Engine/Navigation/NavigationScene.cpp @@ -1,8 +1,8 @@ // Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #include "NavigationScene.h" -#include "NavMesh.h" #include "Navigation.h" +#include "NavMeshRuntime.h" #include "NavMeshBoundsVolume.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Level/Scene/Scene.h" @@ -101,14 +101,14 @@ void NavigationScene::SaveNavMesh() void NavigationScene::OnEnable() { - auto navMesh = Navigation::GetNavMesh(); - if (navMesh) - navMesh->AddTiles(this); + auto navMesh = NavMeshRuntime::Get(); + CHECK(navMesh); + navMesh->AddTiles(this); } void NavigationScene::OnDisable() { - auto navMesh = Navigation::GetNavMesh(); + auto navMesh = NavMeshRuntime::Get(); if (navMesh) navMesh->RemoveTiles(this); } From 4c0c7cc3940e33d71a0beea5fbd1088b23166d6e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Dec 2020 17:15:29 +0100 Subject: [PATCH 002/222] Add NavAgentProperties and NavigationTypes.h --- Source/Engine/Navigation/Navigation.h | 26 +--------- Source/Engine/Navigation/NavigationTypes.h | 57 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 Source/Engine/Navigation/NavigationTypes.h diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index 29d21cffb..140f32f77 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -2,36 +2,12 @@ #pragma once -#include "Engine/Scripting/ScriptingType.h" -#include "Engine/Core/Math/Vector3.h" +#include "NavigationTypes.h" class Scene; #define NAV_MESH_PATH_MAX_SIZE 200 -/// -/// The result information for navigation mesh queries. -/// -API_STRUCT() struct NavMeshHit -{ -DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); - - /// - /// The hit point position. - /// - API_FIELD() Vector3 Position; - - /// - /// The distance to hit point (from the query origin). - /// - API_FIELD() float Distance; - - /// - /// The hit point normal vector. - /// - API_FIELD() Vector3 Normal; -}; - /// /// The navigation service used for path finding and agents navigation system. /// diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h new file mode 100644 index 000000000..e06794a90 --- /dev/null +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingType.h" +#include "Engine/Core/Math/Vector3.h" + +/// +/// The navigation system agent properties container for navmesh building and querying. +/// +API_STRUCT() struct NavAgentProperties +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); + + /// + /// The radius of the agent used for navigation. Agents can't pass through gaps of less than twice the radius. + /// + API_FIELD() float Radius = 34.0f; + + /// + /// The height of the agent used for navigation. Agents can't enter areas with ceilings lower than this value. + /// + API_FIELD() float Height = 144.0f; + + /// + /// The step height used for navigation. Defines the maximum ledge height that is considered to still be traversable by the agent. + /// + API_FIELD() float StepHeight = 35.0f; + + /// + /// The maximum slope (in degrees) that is considered walkable for navigation. Agents can't go up or down slopes higher than this value. + /// + API_FIELD() float MaxSlopeAngle = 60.0f; +}; + +/// +/// The result information for navigation mesh queries. +/// +API_STRUCT() struct NavMeshHit +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); + + /// + /// The hit point position. + /// + API_FIELD() Vector3 Position; + + /// + /// The distance to hit point (from the query origin). + /// + API_FIELD() float Distance; + + /// + /// The hit point normal vector. + /// + API_FIELD() Vector3 Normal; +}; From a36a54adcdf1d2f8fe520c7656011ea4b6cd0927 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 15 Dec 2020 17:43:46 +0100 Subject: [PATCH 003/222] Tweaks --- Source/Engine/Navigation/NavMeshRuntime.h | 1 - Source/Engine/Navigation/NavigationTypes.h | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h index 0e073cd81..9c5df539c 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -49,7 +49,6 @@ public: /// /// Gets the size of the tile (in world-units). Returns zero if not initialized yet. /// - /// The tile size. FORCE_INLINE float GetTileSize() const { return _tileSize; diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index e06794a90..e786567dd 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -4,11 +4,12 @@ #include "Engine/Scripting/ScriptingType.h" #include "Engine/Core/Math/Vector3.h" +#include "FlaxEngine.Gen.h" /// /// The navigation system agent properties container for navmesh building and querying. /// -API_STRUCT() struct NavAgentProperties +API_STRUCT() struct FLAXENGINE_API NavAgentProperties { DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); @@ -36,7 +37,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); /// /// The result information for navigation mesh queries. /// -API_STRUCT() struct NavMeshHit +API_STRUCT() struct FLAXENGINE_API NavMeshHit { DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); From 93c35a19f512b55792da89d61e8cbbcb306429ee Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 16 Dec 2020 10:24:18 +0100 Subject: [PATCH 004/222] Add NavMeshProperties --- Source/Engine/Navigation/NavigationTypes.h | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index e786567dd..a5672f342 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -3,8 +3,10 @@ #pragma once #include "Engine/Scripting/ScriptingType.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Vector3.h" -#include "FlaxEngine.Gen.h" +#include "Engine/Core/Math/Quaternion.h" /// /// The navigation system agent properties container for navmesh building and querying. @@ -34,6 +36,34 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); API_FIELD() float MaxSlopeAngle = 60.0f; }; +/// +/// The navigation mesh properties container for navmesh building. +/// +API_STRUCT() struct FLAXENGINE_API NavMeshProperties +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties); + + /// + /// The navmesh type name (for debugging). + /// + API_FIELD() String Name; + + /// + /// The navmesh type color (for debugging). + /// + API_FIELD() Color Color; + + /// + /// The navmesh rotation applied to navigation surface. Used during building to the rotate scene geometry and to revert back result during path finding queries. Can be used to generate navmesh on walls. + /// + API_FIELD() Quaternion Rotation; + + /// + /// The properties of the agent used to generate walkable navigation surface. + /// + API_FIELD() NavAgentProperties Agent; +}; + /// /// The result information for navigation mesh queries. /// From 0e6ead938edd5bfc2f491d897066dff631401a7e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Dec 2020 23:09:09 +0100 Subject: [PATCH 005/222] Add support for API class not using ScriptingObject as a base --- .../Bindings/BindingsGenerator.CSharp.cs | 29 +++++++--- .../Bindings/BindingsGenerator.Cpp.cs | 56 +++++++++++-------- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index d51d7b754..b53c147c2 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -431,6 +431,7 @@ namespace Flax.Build.Bindings private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo) { + var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; contents.AppendLine(); // Namespace begin @@ -446,7 +447,7 @@ namespace Flax.Build.Bindings GenerateCSharpComment(contents, indent, classInfo.Comment); // Class begin - GenerateCSharpAttributes(buildData, contents, indent, classInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged); contents.Append(indent); if (classInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -482,6 +483,9 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { + if (!useUnmanaged) + throw new NotImplementedException("TODO: support events inside non-static and non-scripting API class types."); + contents.AppendLine(); foreach (var comment in eventInfo.Comment) @@ -494,7 +498,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, eventInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, eventInfo, useUnmanaged); contents.Append(indent); if (eventInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -593,7 +597,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, useUnmanaged); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -604,8 +608,13 @@ namespace Flax.Build.Bindings if (fieldInfo.IsStatic) contents.Append("static "); var returnValueType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, classInfo); - contents.Append(returnValueType).Append(' ').AppendLine(fieldInfo.Name); - contents.AppendLine(indent + "{"); + contents.Append(returnValueType).Append(' ').Append(fieldInfo.Name); + if (!useUnmanaged) + { + contents.AppendLine(";"); + continue; + } + contents.AppendLine().AppendLine(indent + "{"); indent += " "; contents.Append(indent).Append("get { "); @@ -615,7 +624,7 @@ namespace Flax.Build.Bindings if (!fieldInfo.IsReadOnly) { contents.Append(indent).Append("set { "); - GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, true); + GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, useUnmanaged); contents.Append(" }").AppendLine(); } @@ -630,6 +639,9 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { + if (!useUnmanaged) + throw new NotImplementedException("TODO: support properties inside non-static and non-scripting API class types."); + contents.AppendLine(); foreach (var comment in propertyInfo.Comment) @@ -646,7 +658,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, propertyInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, propertyInfo, useUnmanaged); contents.Append(indent); if (propertyInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -692,6 +704,9 @@ namespace Flax.Build.Bindings // Functions foreach (var functionInfo in classInfo.Functions) { + if (!useUnmanaged) + throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); + if (!functionInfo.NoProxy) { contents.AppendLine(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 393cb9ea5..f9a695b77 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1044,6 +1044,7 @@ namespace Flax.Build.Bindings var classTypeNameInternal = classInfo.NativeName; if (classInfo.Parent != null && !(classInfo.Parent is FileInfo)) classTypeNameInternal = classInfo.Parent.FullNameNative + '_' + classTypeNameInternal; + var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); @@ -1056,6 +1057,8 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { + if (!useUnmanaged) + continue; CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); // C# event invoking wrapper (calls C# event from C++ delegate) @@ -1126,6 +1129,8 @@ namespace Flax.Build.Bindings // Fields foreach (var fieldInfo in classInfo.Fields) { + if (!useUnmanaged) + continue; if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Getter, "{0}"); if (fieldInfo.Setter != null) @@ -1135,6 +1140,8 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { + if (!useUnmanaged) + continue; if (propertyInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, propertyInfo.Getter); if (propertyInfo.Setter != null) @@ -1144,6 +1151,8 @@ namespace Flax.Build.Bindings // Functions foreach (var functionInfo in classInfo.Functions) { + if (!useUnmanaged) + throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); GenerateCppWrapperFunction(buildData, contents, classInfo, functionInfo); } @@ -1253,27 +1262,30 @@ namespace Flax.Build.Bindings // Runtime initialization (internal methods binding) contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); - foreach (var eventInfo in classInfo.Events) + if (useUnmanaged) { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); - } - foreach (var fieldInfo in classInfo.Fields) - { - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); - } - foreach (var propertyInfo in classInfo.Properties) - { - if (propertyInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); - if (propertyInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); - } - foreach (var functionInfo in classInfo.Functions) - { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); + foreach (var eventInfo in classInfo.Events) + { + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); + } + foreach (var fieldInfo in classInfo.Fields) + { + if (fieldInfo.Getter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); + if (fieldInfo.Setter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); + } + foreach (var propertyInfo in classInfo.Properties) + { + if (propertyInfo.Getter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); + if (propertyInfo.Setter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); + } + foreach (var functionInfo in classInfo.Functions) + { + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); + } } GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents); @@ -1288,11 +1300,11 @@ namespace Flax.Build.Bindings contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), "); contents.Append($"sizeof({classTypeNameNative}), "); contents.Append($"&{classTypeNameInternal}Internal::InitRuntime, "); - if (!classInfo.IsStatic && !classInfo.NoSpawn) + if (!classInfo.IsStatic && !classInfo.NoSpawn && useUnmanaged) contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); else contents.Append("&ScriptingType::DefaultSpawn, "); - if (classInfo.BaseType != null) + if (classInfo.BaseType != null && useUnmanaged) contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); else contents.Append("nullptr, "); From 3a314d97eb456624329420f58773938ef11d902e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 13:29:52 +0100 Subject: [PATCH 006/222] Add ScriptingTypeHandle debugger view to flax.natvis --- Source/flax.natvis | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/flax.natvis b/Source/flax.natvis index fb3ef1228..da4ead650 100644 --- a/Source/flax.natvis +++ b/Source/flax.natvis @@ -206,4 +206,13 @@ + + + Null + Type={Module->Types._allocation._data[TypeIndex].Fullname} + + Module->Types._allocation._data[TypeIndex] + + + From 585c752d7296956e5c10ba611998f759c651edbd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 14:03:15 +0100 Subject: [PATCH 007/222] Add more objects types checks and casting utilities to ScriptingObject --- Source/Engine/Scripting/ScriptingObject.cpp | 14 +++++++++++++ Source/Engine/Scripting/ScriptingObject.h | 22 +++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index a065d6a0a..d7e8c3729 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -72,6 +72,12 @@ ScriptingObject* ScriptingObject::ToNative(MonoObject* obj) return ptr; } +bool ScriptingObject::Is(const ScriptingTypeHandle& type) const +{ + CHECK_RETURN(type, false); + return _type == type || CanCast(GetClass(), type.GetType().ManagedClass); +} + void ScriptingObject::ChangeID(const Guid& newId) { ASSERT(newId.IsValid() && newId != _id); @@ -206,6 +212,14 @@ void ScriptingObject::UnregisterObject() Scripting::UnregisterObject(this); } +bool ScriptingObject::CanCast(const ScriptingTypeHandle& from, const ScriptingTypeHandle& to) +{ + if (!from && !to) + return true; + CHECK_RETURN(from && to, false); + return CanCast(from.GetType().ManagedClass, to.GetType().ManagedClass); +} + bool ScriptingObject::CanCast(MClass* from, MClass* to) { if (!from && !to) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 0f8b26375..540e0d60b 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -131,12 +131,30 @@ public: return obj ? obj->GetOrCreateManagedInstance() : nullptr; } + /// + /// Checks if can cast one scripting object type into another type. + /// + /// The object type for the cast. + /// The destination type to the cast. + /// True if can, otherwise false. + static bool CanCast(const ScriptingTypeHandle& from, const ScriptingTypeHandle& to); + + /// + /// Checks if can cast one scripting object type into another type. + /// + /// The object class for the cast. + /// The destination class to the cast. + /// True if can, otherwise false. + static bool CanCast(MClass* from, MClass* to); + template static T* Cast(ScriptingObject* obj) { return obj && CanCast(obj->GetClass(), T::GetStaticClass()) ? (T*)obj : nullptr; } + bool Is(const ScriptingTypeHandle& type) const; + bool Is(MClass* type) const { return CanCast(GetClass(), type); @@ -185,10 +203,6 @@ public: /// void UnregisterObject(); -private: - - static bool CanCast(MClass* from, MClass* to); - protected: /// From aa7711dc315bbe07366193e1ba914355e7a457c7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 14:03:53 +0100 Subject: [PATCH 008/222] Fix loaded asset verification error for json assets that have the scripting type --- Source/Engine/Content/Content.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 78d0ff1d6..8d43a7318 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -872,7 +872,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) if (result) { // Validate type - if (IsAssetTypeIdInvalid(type, result->GetTypeHandle())) + if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) { LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString()); return nullptr; @@ -945,15 +945,6 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& return nullptr; } - // Validate type - const StringAsANSI<> typeNameStd(assetInfo.TypeName.Get(), assetInfo.TypeName.Length()); - const auto assetType = Scripting::FindScriptingType(StringAnsiView(typeNameStd.Get(), assetInfo.TypeName.Length())); - if (IsAssetTypeIdInvalid(type, assetType)) - { - LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString()); - return nullptr; - } - #endif // Find asset factory based in its type @@ -972,6 +963,18 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& return nullptr; } +#if ASSETS_LOADING_EXTRA_VERIFICATION + + // Validate type + if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) + { + LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString()); + result->DeleteObject(); + return nullptr; + } + +#endif + // Register asset { AssetsLocker.Lock(); From cea1a6774910e82d9ae4f26859367d6f351bfba5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 14:53:27 +0100 Subject: [PATCH 009/222] Refactor game settings to support using API bindings --- .gitmodules | 6 ++++++ Source/Engine/Audio/AudioSettings.h | 6 +++--- Source/Engine/Core/Config/BuildSettings.h | 5 ++--- Source/Engine/Core/Config/GameSettings.cpp | 2 +- Source/Engine/Core/Config/GraphicsSettings.h | 5 ++--- .../Engine/Core/Config/LayersTagsSettings.h | 5 ++--- Source/Engine/Core/Config/Settings.h | 20 +++++++++---------- Source/Engine/Core/Config/TimeSettings.h | 5 ++--- Source/Engine/Input/InputSettings.h | 6 +++--- Source/Engine/Navigation/NavigationSettings.h | 2 +- Source/Engine/Physics/PhysicsSettings.h | 6 +++--- .../Android/AndroidPlatformSettings.h | 6 +++--- .../Platform/Linux/LinuxPlatformSettings.h | 6 +++--- .../Engine/Platform/UWP/UWPPlatformSettings.h | 6 +++--- .../Windows/WindowsPlatformSettings.h | 6 +++--- Source/Platforms/PS4 | 1 + Source/Platforms/XboxScarlett | 1 + 17 files changed, 49 insertions(+), 45 deletions(-) create mode 160000 Source/Platforms/PS4 create mode 160000 Source/Platforms/XboxScarlett diff --git a/.gitmodules b/.gitmodules index e69de29bb..36d11c4bd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "Source/Platforms/PS4"] + path = Source/Platforms/PS4 + url = https://gitlab.flaxengine.com/flax/flaxengine-ps4.git +[submodule "Source/Platforms/XboxScarlett"] + path = Source/Platforms/XboxScarlett + url = https://gitlab.flaxengine.com/flax/flaxengine-xboxscarlett.git diff --git a/Source/Engine/Audio/AudioSettings.h b/Source/Engine/Audio/AudioSettings.h index 388e901b5..c8850adda 100644 --- a/Source/Engine/Audio/AudioSettings.h +++ b/Source/Engine/Audio/AudioSettings.h @@ -8,8 +8,8 @@ /// /// Audio settings container. /// -/// -class AudioSettings : public Settings +/// +class AudioSettings : public SettingsBase { public: @@ -30,7 +30,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() final override { DisableAudio = false; diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index 26bf13d61..253ab5c7a 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -10,8 +10,7 @@ /// /// The game building rendering settings. /// -/// -class BuildSettings : public Settings +class BuildSettings : public SettingsBase { public: @@ -62,7 +61,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() final override { MaxAssetsPerPackage = 1024; diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index b87c380aa..ea4fef45f 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -28,7 +28,7 @@ bool GameSettings::NoSplashScreen = false; Guid GameSettings::SplashScreen; Dictionary GameSettings::CustomSettings; -Array SettingsBase::Containers(32); +Array Settings::Containers(32); #if USE_EDITOR extern void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data); diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 2efd08eac..79f0b23c5 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -9,8 +9,7 @@ /// /// Graphics rendering settings. /// -/// -class GraphicsSettings : public Settings +class GraphicsSettings : public SettingsBase { public: @@ -56,7 +55,7 @@ public: public: - // [Settings] + // [SettingsBase] void Apply() override; void RestoreDefault() final override diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index 161302d49..db7292676 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -7,8 +7,7 @@ /// /// Layers and objects tags settings. /// -/// -class LayersAndTagsSettings : public Settings +class LayersAndTagsSettings : public SettingsBase { public: @@ -54,7 +53,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() override { Tags.Clear(); diff --git a/Source/Engine/Core/Config/Settings.h b/Source/Engine/Core/Config/Settings.h index 6400a43a7..4d12788ba 100644 --- a/Source/Engine/Core/Config/Settings.h +++ b/Source/Engine/Core/Config/Settings.h @@ -7,16 +7,16 @@ #include "Engine/Serialization/ISerializable.h" /// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to c#. +/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game. /// -class SettingsBase +class FLAXENGINE_API Settings { public: /// /// The settings containers. /// - static Array Containers; + static Array Containers; /// /// Restores the default settings for all the registered containers. @@ -30,19 +30,19 @@ public: private: // Disable copy/move - SettingsBase(const SettingsBase&) = delete; - SettingsBase& operator=(const SettingsBase&) = delete; + Settings(const Settings&) = delete; + Settings& operator=(const Settings&) = delete; protected: - SettingsBase() + Settings() { Containers.Add(this); } public: - virtual ~SettingsBase() = default; + virtual ~Settings() = default; public: @@ -69,14 +69,14 @@ public: }; /// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to c#. +/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game. /// template -class Settings : public SettingsBase, public Singleton +class SettingsBase : public Settings, public Singleton { protected: - Settings() + SettingsBase() { } }; diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 385cbad7e..c020bea09 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -8,8 +8,7 @@ /// /// Time and game simulation settings container. /// -/// -class TimeSettings : public Settings +class TimeSettings : public SettingsBase { public: @@ -40,7 +39,7 @@ public: public: - // [Settings] + // [SettingsBase] void Apply() override; void RestoreDefault() override diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h index 7fc7d64a0..20b87943d 100644 --- a/Source/Engine/Input/InputSettings.h +++ b/Source/Engine/Input/InputSettings.h @@ -10,8 +10,8 @@ /// /// Input settings container. /// -/// -class InputSettings : public Settings +/// +class InputSettings : public SettingsBase { public: @@ -27,7 +27,7 @@ public: public: - // [Settings] + // [SettingsBase] void Apply() override { Input::ActionMappings = ActionMappings; diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index 86abac470..8ae98266d 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -82,7 +82,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() final override { CellHeight = 10.0f; diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index 58f89feab..2ffd2307b 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -25,8 +25,8 @@ /// /// Physics simulation settings container. /// -/// -class PhysicsSettings : public Settings +/// +class PhysicsSettings : public SettingsBase { public: @@ -115,7 +115,7 @@ public: public: - // [Settings] + // [SettingsBase] void Apply() override; void RestoreDefault() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index 3a1c1f736..061368486 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -9,8 +9,8 @@ /// /// Android platform settings. /// -/// -class AndroidPlatformSettings : public Settings +/// +class AndroidPlatformSettings : public SettingsBase { public: @@ -36,7 +36,7 @@ public: RestoreDefault(); } - // [Settings] + // [SettingsBase] void RestoreDefault() final override { PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); diff --git a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h index eb8230da7..6086ae9f5 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h +++ b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h @@ -9,8 +9,8 @@ /// /// Linux platform settings. /// -/// -class LinuxPlatformSettings : public Settings +/// +class LinuxPlatformSettings : public SettingsBase { public: @@ -56,7 +56,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() final override { WindowMode = GameWindowMode::Windowed; diff --git a/Source/Engine/Platform/UWP/UWPPlatformSettings.h b/Source/Engine/Platform/UWP/UWPPlatformSettings.h index 0d5baa958..adc1469a4 100644 --- a/Source/Engine/Platform/UWP/UWPPlatformSettings.h +++ b/Source/Engine/Platform/UWP/UWPPlatformSettings.h @@ -9,8 +9,8 @@ /// /// Universal Windows Platform settings. /// -/// -class UWPPlatformSettings : public Settings +/// +class UWPPlatformSettings : public SettingsBase { public: @@ -95,7 +95,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() final override { PreferredLaunchWindowingMode = WindowMode::FullScreen; diff --git a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h index 2c1ca3ce4..3e163066e 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h +++ b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h @@ -9,8 +9,8 @@ /// /// Windows platform settings. /// -/// -class WindowsPlatformSettings : public Settings +/// +class WindowsPlatformSettings : public SettingsBase { public: @@ -71,7 +71,7 @@ public: public: - // [Settings] + // [SettingsBase] void RestoreDefault() final override { WindowMode = GameWindowMode::Windowed; diff --git a/Source/Platforms/PS4 b/Source/Platforms/PS4 new file mode 160000 index 000000000..b9e29ede6 --- /dev/null +++ b/Source/Platforms/PS4 @@ -0,0 +1 @@ +Subproject commit b9e29ede69d31f93cbdd012d6e6c4ad8120dc7c0 diff --git a/Source/Platforms/XboxScarlett b/Source/Platforms/XboxScarlett new file mode 160000 index 000000000..7691bbaf2 --- /dev/null +++ b/Source/Platforms/XboxScarlett @@ -0,0 +1 @@ +Subproject commit 7691bbaf2a7e47459d9fb18fd1a8833cdbd7517a From 4d2d801bf4330c8f779663737e3a7e74e8da500d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 14:53:33 +0100 Subject: [PATCH 010/222] Fix typos --- Source/Engine/Physics/Colliders/CharacterController.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 2abd1e62a..25db38937 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -143,7 +143,7 @@ public: API_PROPERTY() void SetStepOffset(float value); /// - /// Gets the minimum move distance of the character controller. The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. + /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")") FORCE_INLINE float GetMinMoveDistance() const @@ -152,7 +152,7 @@ public: } /// - /// Sets the minimum move distance of the character controller.The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. + /// Sets the minimum move distance of the character controller.The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// API_PROPERTY() void SetMinMoveDistance(float value); From 41b938b3cd26cf55c0a9e66714765b8ea554a5ab Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 14:53:54 +0100 Subject: [PATCH 011/222] Refactor NavigationSettings to use API bindings --- .../Content/Settings/NavigationSettings.cs | 106 ------------------ Source/Engine/Level/Level.Build.cs | 1 - Source/Engine/Navigation/Navigation.Build.cs | 1 + Source/Engine/Navigation/NavigationSettings.h | 24 +++- 4 files changed, 19 insertions(+), 113 deletions(-) delete mode 100644 Source/Editor/Content/Settings/NavigationSettings.cs diff --git a/Source/Editor/Content/Settings/NavigationSettings.cs b/Source/Editor/Content/Settings/NavigationSettings.cs deleted file mode 100644 index f4e0e3a02..000000000 --- a/Source/Editor/Content/Settings/NavigationSettings.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The navigation system settings container. - /// - public sealed class NavigationSettings : SettingsBase - { - /// - /// The height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the vertical axis but longer build times. - /// - [DefaultValue(10.0f), Limit(1, 400)] - [EditorOrder(10), EditorDisplay("Nav Mesh Options"), Tooltip("The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.")] - public float CellHeight = 10.0f; - - /// - /// The width/height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the horizontal axes but longer build times. - /// - [DefaultValue(30.0f), Limit(1, 400)] - [EditorOrder(20), EditorDisplay("Nav Mesh Options"), Tooltip("The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.")] - public float CellSize = 30.0f; - - /// - /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. - /// - [DefaultValue(64), Limit(8, 4096)] - [EditorOrder(30), EditorDisplay("Nav Mesh Options"), Tooltip("Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize.")] - public int TileSize = 64; - - /// - /// The minimum number of cells allowed to form isolated island areas. - /// - [DefaultValue(0), Limit(0, 100)] - [EditorOrder(40), EditorDisplay("Nav Mesh Options"), Tooltip("The minimum number of cells allowed to form isolated island areas.")] - public int MinRegionArea = 0; - - /// - /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. - /// - [DefaultValue(20), Limit(0, 100)] - [EditorOrder(50), EditorDisplay("Nav Mesh Options"), Tooltip("Any regions with a span count smaller than this value will, if possible, be merged with larger regions.")] - public int MergeRegionArea = 20; - - /// - /// The maximum allowed length for contour edges along the border of the mesh. - /// - [DefaultValue(1200.0f), Limit(100)] - [EditorOrder(60), EditorDisplay("Nav Mesh Options", "Max Edge Length"), Tooltip("The maximum allowed length for contour edges along the border of the mesh.")] - public float MaxEdgeLen = 1200.0f; - - /// - /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. - /// - [DefaultValue(1.3f), Limit(0.1f, 4)] - [EditorOrder(70), EditorDisplay("Nav Mesh Options"), Tooltip("The maximum distance a simplified contour's border edges should deviate from the original raw contour.")] - public float MaxEdgeError = 1.3f; - - /// - /// The sampling distance to use when generating the detail mesh. For height detail only. - /// - [DefaultValue(600.0f), Limit(1)] - [EditorOrder(80), EditorDisplay("Nav Mesh Options", "Detail Sampling Distance"), Tooltip("The sampling distance to use when generating the detail mesh.")] - public float DetailSamplingDist = 600.0f; - - /// - /// The maximum distance the detail mesh surface should deviate from heightfield data. For height detail only. - /// - [DefaultValue(1.0f), Limit(0, 3)] - [EditorOrder(90), EditorDisplay("Nav Mesh Options"), Tooltip("The maximum distance the detail mesh surface should deviate from heightfield data.")] - public float MaxDetailSamplingError = 1.0f; - - /// - /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. - /// - [DefaultValue(34.0f), Limit(0)] - [EditorOrder(1000), EditorDisplay("Agent Options"), Tooltip("The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius.")] - public float WalkableRadius = 34.0f; - - /// - /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. - /// - [DefaultValue(144.0f), Limit(0)] - [EditorOrder(1010), EditorDisplay("Agent Options"), Tooltip("The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value.")] - public float WalkableHeight = 144.0f; - - /// - /// The maximum ledge height that is considered to still be traversable. - /// - [DefaultValue(35.0f), Limit(0)] - [EditorOrder(1020), EditorDisplay("Agent Options"), Tooltip("The maximum ledge height that is considered to still be traversable.")] - public float WalkableMaxClimb = 35.0f; - - /// - /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. - /// - [DefaultValue(60.0f), Limit(0, 89.0f)] - [EditorOrder(1030), EditorDisplay("Agent Options"), Tooltip("The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value.")] - public float WalkableMaxSlopeAngle = 60.0f; - } -} diff --git a/Source/Engine/Level/Level.Build.cs b/Source/Engine/Level/Level.Build.cs index e65967025..a2b680aee 100644 --- a/Source/Engine/Level/Level.Build.cs +++ b/Source/Engine/Level/Level.Build.cs @@ -21,7 +21,6 @@ public class Level : EngineModule options.PublicDependencies.Add("Scripting"); options.PublicDependencies.Add("Serialization"); - options.PublicDependencies.Add("Navigation"); if (options.Target.IsEditor) { diff --git a/Source/Engine/Navigation/Navigation.Build.cs b/Source/Engine/Navigation/Navigation.Build.cs index 96d086e44..00edf3dde 100644 --- a/Source/Engine/Navigation/Navigation.Build.cs +++ b/Source/Engine/Navigation/Navigation.Build.cs @@ -15,6 +15,7 @@ public class Navigation : EngineModule options.PublicDefinitions.Add("COMPILE_WITH_NAV_MESH_BUILDER"); + options.PrivateDependencies.Add("Level"); options.PrivateDependencies.Add("recastnavigation"); if (options.Target.IsEditor) diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index 8ae98266d..4e2fde1a2 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -2,82 +2,94 @@ #pragma once +#include "NavigationTypes.h" #include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/Serialization.h" /// /// The navigation system settings container. /// -/// -class NavigationSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); public: /// - /// The height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the vertical axis but longer build times. + /// The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times. /// + API_FIELD(Attributes="DefaultValue(10.0f), Limit(1, 400), EditorOrder(10), EditorDisplay(\"Nav Mesh Options\")") float CellHeight = 10.0f; /// - /// The width/height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the horizontal axes but longer build times. + /// The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the horizontal axes but longer build times. /// + API_FIELD(Attributes="DefaultValue(30.0f), Limit(1, 400), EditorOrder(20), EditorDisplay(\"Nav Mesh Options\")") float CellSize = 30.0f; /// /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. /// + API_FIELD(Attributes="DefaultValue(64), Limit(8, 4096), EditorOrder(30), EditorDisplay(\"Nav Mesh Options\")") int32 TileSize = 64; /// /// The minimum number of cells allowed to form isolated island areas. /// + API_FIELD(Attributes="DefaultValue(0), Limit(0, 100), EditorOrder(40), EditorDisplay(\"Nav Mesh Options\")") int32 MinRegionArea = 0; /// /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. /// + API_FIELD(Attributes="DefaultValue(20), Limit(0, 100), EditorOrder(50), EditorDisplay(\"Nav Mesh Options\")") int32 MergeRegionArea = 20; /// /// The maximum allowed length for contour edges along the border of the mesh. /// + API_FIELD(Attributes="DefaultValue(1200.0f), Limit(100), EditorOrder(60), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")") float MaxEdgeLen = 1200.0f; /// /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. /// + API_FIELD(Attributes="DefaultValue(1.3f), Limit(0.1f, 4), EditorOrder(70), EditorDisplay(\"Nav Mesh Options\")") float MaxEdgeError = 1.3f; /// /// The sampling distance to use when generating the detail mesh. /// + API_FIELD(Attributes="DefaultValue(600.0f), Limit(1), EditorOrder(80), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")") float DetailSamplingDist = 600.0f; /// /// The maximum distance the detail mesh surface should deviate from heightfield data. /// + API_FIELD(Attributes="DefaultValue(1.0f), Limit(0, 3), EditorOrder(90), EditorDisplay(\"Nav Mesh Options\")") float MaxDetailSamplingError = 1.0f; /// /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. /// + API_FIELD(Attributes="DefaultValue(34.0f), Limit(0), EditorOrder(1000), EditorDisplay(\"Agent Options\")") float WalkableRadius = 34.0f; /// /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. /// + API_FIELD(Attributes="DefaultValue(144.0f), Limit(0), EditorOrder(1010), EditorDisplay(\"Agent Options\")") float WalkableHeight = 144.0f; /// /// The maximum ledge height that is considered to still be traversable. /// + API_FIELD(Attributes="DefaultValue(35.0f), Limit(0), EditorOrder(1020), EditorDisplay(\"Agent Options\")") float WalkableMaxClimb = 35.0f; /// /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. /// + API_FIELD(Attributes="DefaultValue(60.0f), Limit(0, 89.0f), EditorOrder(1030), EditorDisplay(\"Agent Options\")") float WalkableMaxSlopeAngle = 60.0f; public: From 7798a7f6d5699be320ff2c6fe067b8c9bf8eaa73 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Dec 2020 15:39:14 +0100 Subject: [PATCH 012/222] Refactor PhysicalMaterial to use API bindings --- Source/Engine/Content/JsonAsset.cpp | 4 +- Source/Engine/Engine/PhysicalMaterial.cs | 46 ------------------- Source/Engine/Physics/PhysicalMaterial.cpp | 16 ------- Source/Engine/Physics/PhysicalMaterial.h | 37 ++++++--------- .../Bindings/BindingsGenerator.CSharp.cs | 4 +- .../Bindings/BindingsGenerator.Cpp.cs | 2 + Source/Tools/Flax.Build/Bindings/ClassInfo.cs | 4 ++ 7 files changed, 24 insertions(+), 89 deletions(-) delete mode 100644 Source/Engine/Engine/PhysicalMaterial.cs diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 4e1f90d70..3779181b7 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -190,13 +190,11 @@ ISerializable* Create() return New(); } +// Key: managed class typename, Value: unmanaged instance spawner function Dictionary UnmanagedTypes(32); void InitUnmanagedJsonTypes() { - ASSERT(UnmanagedTypes.IsEmpty()); - - // Key: managed class typename, Value: unmanaged instance spawner function UnmanagedTypes[TEXT("FlaxEngine.PhysicalMaterial")] = &Create; } diff --git a/Source/Engine/Engine/PhysicalMaterial.cs b/Source/Engine/Engine/PhysicalMaterial.cs deleted file mode 100644 index 1ad1c732c..000000000 --- a/Source/Engine/Engine/PhysicalMaterial.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. - /// - public sealed class PhysicalMaterial - { - /// - /// The friction value of surface, controls how easily things can slide on this surface. - /// - [EditorOrder(0), Limit(0), EditorDisplay("Physical Material"), Tooltip("The friction value of surface, controls how easily things can slide on this surface.")] - public float Friction = 0.7f; - - /// - /// The friction combine mode, controls how friction is computed for multiple materials. - /// - [EditorOrder(1), EditorDisplay("Physical Material"), Tooltip("The friction combine mode, controls how friction is computed for multiple materials.")] - public PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode.Average; - - /// - /// If set we will use the FrictionCombineMode of this material, instead of the FrictionCombineMode found in the Physics settings. - /// - [HideInEditor] - public bool OverrideFrictionCombineMode = false; - - /// - /// The restitution or 'bounciness' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming). - /// - [EditorOrder(3), Range(0, 1), EditorDisplay("Physical Material"), Tooltip("The restitution or \'bounciness\' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming).")] - public float Restitution = 0.3f; - - /// - /// The restitution combine mode, controls how restitution is computed for multiple materials. - /// - [EditorOrder(4), EditorDisplay("Physical Material"), Tooltip("The restitution combine mode, controls how restitution is computed for multiple materials.")] - public PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode.Average; - - /// - /// If set we will use the RestitutionCombineMode of this material, instead of the RestitutionCombineMode found in the Physics settings. - /// - [HideInEditor] - public bool OverrideRestitutionCombineMode = false; - } -} diff --git a/Source/Engine/Physics/PhysicalMaterial.cpp b/Source/Engine/Physics/PhysicalMaterial.cpp index 61ba840be..115e85a15 100644 --- a/Source/Engine/Physics/PhysicalMaterial.cpp +++ b/Source/Engine/Physics/PhysicalMaterial.cpp @@ -2,7 +2,6 @@ #include "PhysicalMaterial.h" #include "PhysicsSettings.h" -#include "Engine/Serialization/JsonTools.h" #include "Physics.h" #include #include @@ -51,18 +50,3 @@ void PhysicalMaterial::UpdatePhysXMaterial() _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); } } - -void PhysicalMaterial::Serialize(SerializeStream& stream, const void* otherObj) -{ - MISSING_CODE("PhysicalMaterial::Serialize is not implemented"); -} - -void PhysicalMaterial::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - Friction = JsonTools::GetFloat(stream, "Friction", PhysicalMaterial_Friction); - FrictionCombineMode = JsonTools::GetEnum(stream, "FrictionCombineMode", PhysicalMaterial_FrictionCombineMode); - OverrideFrictionCombineMode = JsonTools::GetBool(stream, "OverrideFrictionCombineMode", PhysicalMaterial_OverrideFrictionCombineMode); - Restitution = JsonTools::GetFloat(stream, "Restitution", PhysicalMaterial_Restitution); - RestitutionCombineMode = JsonTools::GetEnum(stream, "RestitutionCombineMode", PhysicalMaterial_RestitutionCombineMode); - OverrideRestitutionCombineMode = JsonTools::GetBool(stream, "OverrideRestitutionCombineMode", PhysicalMaterial_OverrideRestitutionCombineMode); -} diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index b2dc3f8e6..8bc0eebf7 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -5,20 +5,13 @@ #include "Types.h" #include "Engine/Serialization/ISerializable.h" -// Default values for the physical material - -#define PhysicalMaterial_Friction 0.7f -#define PhysicalMaterial_FrictionCombineMode PhysicsCombineMode::Average -#define PhysicalMaterial_OverrideFrictionCombineMode false -#define PhysicalMaterial_Restitution 0.3f -#define PhysicalMaterial_RestitutionCombineMode PhysicsCombineMode::Average -#define PhysicalMaterial_OverrideRestitutionCombineMode false - /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// -class FLAXENGINE_API PhysicalMaterial : public ISerializable +API_CLASS() class FLAXENGINE_API PhysicalMaterial final : public ISerializable { +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); private: PxMaterial* _material; @@ -40,32 +33,38 @@ public: /// /// The friction value of surface, controls how easily things can slide on this surface. /// - float Friction = PhysicalMaterial_Friction; + API_FIELD(Attributes="EditorOrder(0), Limit(0), EditorDisplay(\"Physical Material\")") + float Friction = 0.7f; /// /// The friction combine mode, controls how friction is computed for multiple materials. /// - PhysicsCombineMode FrictionCombineMode = PhysicalMaterial_FrictionCombineMode; + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Physical Material\")") + PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode::Average; /// /// If set we will use the FrictionCombineMode of this material, instead of the FrictionCombineMode found in the Physics settings. /// - bool OverrideFrictionCombineMode = PhysicalMaterial_OverrideFrictionCombineMode; + API_FIELD(Attributes="HideInEditor") + bool OverrideFrictionCombineMode = false; /// /// The restitution or 'bounciness' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming). /// - float Restitution = PhysicalMaterial_Restitution; + API_FIELD(Attributes="EditorOrder(3), Range(0, 1), EditorDisplay(\"Physical Material\")") + float Restitution = 0.3f; /// /// The restitution combine mode, controls how restitution is computed for multiple materials. /// - PhysicsCombineMode RestitutionCombineMode = PhysicalMaterial_RestitutionCombineMode; + API_FIELD(Attributes="EditorOrder(4), EditorDisplay(\"Physical Material\")") + PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode::Average; /// /// If set we will use the RestitutionCombineMode of this material, instead of the RestitutionCombineMode found in the Physics settings. /// - bool OverrideRestitutionCombineMode = PhysicalMaterial_OverrideRestitutionCombineMode; + API_FIELD(Attributes="HideInEditor") + bool OverrideRestitutionCombineMode = false; public: @@ -79,10 +78,4 @@ public: /// Updates the PhysX material (after any property change). /// void UpdatePhysXMaterial(); - -public: - - // [ISerializable] - void Serialize(SerializeStream& stream, const void* otherObj) override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index b53c147c2..966737e95 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -393,7 +393,7 @@ namespace Flax.Build.Bindings { // Write attributes contents.Append(indent).Append('[').Append(attributes).Append(']').AppendLine(); - writeTooltip = !attributes.Contains("Tooltip("); + writeTooltip = !attributes.Contains("Tooltip(") && !attributes.Contains("HideInEditor"); } if (useUnmanaged) @@ -462,7 +462,7 @@ namespace Flax.Build.Bindings else if (classInfo.IsAbstract) contents.Append("abstract "); contents.Append("unsafe partial class ").Append(classInfo.Name); - if (classInfo.BaseType != null && classInfo.BaseTypeInheritance != AccessLevel.Private) + if (classInfo.BaseType != null && !classInfo.IsBaseTypeHidden) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, classInfo.BaseType, classInfo)); contents.AppendLine(); contents.Append(indent + "{"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index f9a695b77..e64240b9e 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -949,6 +949,8 @@ namespace Flax.Build.Bindings var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; if (baseType != null && baseType.Type == "ISerializable") baseType = null; + else if (classInfo != null && classInfo.IsBaseTypeHidden) + baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); CppIncludeFiles.Add("Engine/Serialization/Serialization.h"); diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index 1a62132b2..3ac5b37fc 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -25,6 +25,7 @@ namespace Flax.Build.Bindings public AccessLevel Access; public TypeInfo BaseType; public AccessLevel BaseTypeInheritance; + public bool IsBaseTypeHidden; public bool IsStatic; public bool IsSealed; public bool IsAbstract; @@ -50,6 +51,9 @@ namespace Flax.Build.Bindings { base.Init(buildData); + // Internal base types are usually hidden from bindings (used in core-only internally) + IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType.Type == "ISerializable"; + // Cache if it it Scripting Object type if (InBuildScriptingObjectTypes.Contains(Name)) _isScriptingObject = true; From 5e77925492addb7c226d9db2758288b3918c429d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Dec 2020 11:18:00 +0100 Subject: [PATCH 013/222] Fix crash when using Physical Material with missing instance --- Source/Engine/Physics/Colliders/Collider.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index b00ceb925..9ddc4f1bc 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -207,7 +207,7 @@ void Collider::Attach(RigidBody* rigidBody) // Attach rigidBody->GetPhysXRigidActor()->attachShape(*_shape); - _shape->setLocalPose(PxTransform(C2P((_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale()), C2P(_localTransform.Orientation))); + _shape->setLocalPose(PxTransform(C2P((_localTransform.Translation + _localTransform.Orientation * _center) * rigidBody->GetScale()), C2P(_localTransform.Orientation))); if (rigidBody->IsDuringPlay()) rigidBody->UpdateBounds(); } @@ -246,7 +246,7 @@ void Collider::CreateShapeBase(const PxGeometry& geometry) const bool isTrigger = _isTrigger && CanBeTrigger(); const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy()); PxMaterial* material = Physics::GetDefaultMaterial(); - if (Material && !Material->WaitForLoaded()) + if (Material && !Material->WaitForLoaded() && Material->Instance) { material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial(); } @@ -291,12 +291,9 @@ void Collider::OnMaterialChanged() if (_shape) { PxMaterial* material = Physics::GetDefaultMaterial(); - if (Material) + if (Material && !Material->WaitForLoaded() && Material->Instance) { - if (!Material->WaitForLoaded()) - { - material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial(); - } + material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial(); } _shape->setMaterials(&material, 1); } From 4665e8fbdb4806fe9425667b9145ff71fedfecf9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Dec 2020 12:55:57 +0100 Subject: [PATCH 014/222] Add support for basic classes to Scripting Type (without scripting object as a base) --- Source/Engine/Content/Assets/VisualScript.cpp | 46 ++--- Source/Engine/Level/SceneObjectsFactory.cpp | 6 +- Source/Engine/Scripting/BinaryModule.cpp | 172 ++++++++++-------- Source/Engine/Scripting/Script.cpp | 6 +- Source/Engine/Scripting/ScriptingObject.cpp | 6 +- Source/Engine/Scripting/ScriptingType.h | 14 +- .../Bindings/BindingsGenerator.Cpp.cs | 6 +- .../Build/Plugins/VisualScriptingPlugin.cs | 4 +- 8 files changed, 150 insertions(+), 110 deletions(-) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index bb27a233e..e59f4ceb1 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1290,11 +1290,11 @@ Asset::LoadResult VisualScript::load() // Hack vtable similarly to VisualScriptObjectSpawn ScriptingType& visualScriptType = (ScriptingType&)object->GetType(); - if (visualScriptType.Class.ScriptVTable) + if (visualScriptType.Script.ScriptVTable) { // Override object vtable with hacked one that has Visual Script functions calls - ASSERT(visualScriptType.Class.VTable); - *(void**)object = visualScriptType.Class.VTable; + ASSERT(visualScriptType.Script.VTable); + *(void**)object = visualScriptType.Script.VTable; } } const int32 oldCount = _oldParamsLayout.Count(); @@ -1361,10 +1361,10 @@ void VisualScript::unload(bool isReloading) if (_scriptingTypeHandle) { auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex]; - if (type.Class.DefaultInstance) + if (type.Script.DefaultInstance) { - Delete(type.Class.DefaultInstance); - type.Class.DefaultInstance = nullptr; + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; } VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(_scriptingTypeHandle.TypeIndex); VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr; @@ -1389,7 +1389,7 @@ void VisualScript::CacheScriptingType() { // Find first native base C++ class of this Visual Script class ScriptingTypeHandle nativeType = baseType; - while (nativeType && nativeType.GetType().Class.ScriptVTable) + while (nativeType && nativeType.GetType().Script.ScriptVTable) { nativeType = nativeType.GetType().GetBaseType(); } @@ -1437,14 +1437,14 @@ void VisualScript::CacheScriptingType() for (ScriptingTypeHandle e = nativeType; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptVTable) + if (eType.Script.SetupScriptVTable) { ASSERT(eType.ManagedClass); - eType.Class.SetupScriptVTable(eType.ManagedClass, type.Class.ScriptVTable, type.Class.ScriptVTableBase); + eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase); } e = eType.GetBaseType(); } - MMethod** scriptVTable = (MMethod**)type.Class.ScriptVTable; + MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable; while (scriptVTable && *scriptVTable) { const MMethod* referenceMethod = *scriptVTable; @@ -1498,12 +1498,12 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri ScriptingType& visualScriptType = (ScriptingType&)params.Type.GetType(); ScriptingTypeHandle baseTypeHandle = visualScriptType.GetBaseType(); const ScriptingType* baseTypePtr = &baseTypeHandle.GetType(); - while (baseTypePtr->Class.Spawn == &VisualScriptObjectSpawn) + while (baseTypePtr->Script.Spawn == &VisualScriptObjectSpawn) { baseTypeHandle = baseTypePtr->GetBaseType(); baseTypePtr = &baseTypeHandle.GetType(); } - ScriptingObject* object = baseTypePtr->Class.Spawn(params); + ScriptingObject* object = baseTypePtr->Script.Spawn(params); if (!object) { return nullptr; @@ -1514,9 +1514,9 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri // We create a custom vtable for the Visual Script objects that use a native class object with virtual functions overrides. // To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to execute Visual Script graph from them. // Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works. - if (visualScriptType.Class.ScriptVTable) + if (visualScriptType.Script.ScriptVTable) { - if (!visualScriptType.Class.VTable) + if (!visualScriptType.Script.VTable) { // Duplicate vtable void** vtable = *(void***)object; @@ -1525,21 +1525,21 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri while (vtable[entriesCount] && entriesCount < 200) entriesCount++; const int32 size = entriesCount * sizeof(void*); - visualScriptType.Class.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); - Platform::MemoryCopy((byte*)visualScriptType.Class.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); + visualScriptType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); + Platform::MemoryCopy((byte*)visualScriptType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); // Override vtable entries by the class for (ScriptingTypeHandle e = baseTypeHandle; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptObjectVTable) - eType.Class.SetupScriptObjectVTable(visualScriptType.Class.ScriptVTable, visualScriptType.Class.ScriptVTableBase, visualScriptType.Class.VTable, entriesCount, 1); + if (eType.Script.SetupScriptObjectVTable) + eType.Script.SetupScriptObjectVTable(visualScriptType.Script.ScriptVTable, visualScriptType.Script.ScriptVTableBase, visualScriptType.Script.VTable, entriesCount, 1); e = eType.GetBaseType(); } } // Override object vtable with hacked one that has Visual Script functions calls - *(void**)object = visualScriptType.Class.VTable; + *(void**)object = visualScriptType.Script.VTable; } // Mark as custom scripting type @@ -1573,10 +1573,10 @@ void VisualScriptingBinaryModule::OnScriptsReloading() if (script->_scriptingTypeHandle) { auto& type = VisualScriptingModule.Types[script->_scriptingTypeHandle.TypeIndex]; - if (type.Class.DefaultInstance) + if (type.Script.DefaultInstance) { - Delete(type.Class.DefaultInstance); - type.Class.DefaultInstance = nullptr; + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; } VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(script->_scriptingTypeHandle.TypeIndex); script->_scriptingTypeHandleCached = script->_scriptingTypeHandle; @@ -1862,7 +1862,7 @@ ScriptingTypeHandle VisualScript::GetScriptingType() ScriptingObject* VisualScript::CreateInstance() { const auto scriptingTypeHandle = GetScriptingType(); - return scriptingTypeHandle ? scriptingTypeHandle.GetType().Class.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; + return scriptingTypeHandle ? scriptingTypeHandle.GetType().Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; } Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 5d124d0a7..a52d63586 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -79,7 +79,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream if (type) { const ScriptingObjectSpawnParams params(id, type); - obj = (SceneObject*)type.GetType().Class.Spawn(params); + obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); @@ -109,7 +109,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream if (type) { const ScriptingObjectSpawnParams params(id, type); - obj = (SceneObject*)type.GetType().Class.Spawn(params); + obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); @@ -384,7 +384,7 @@ Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) if (type) { const ScriptingObjectSpawnParams params(id, type); - const auto result = dynamic_cast(type.GetType().Class.Spawn(params)); + const auto result = dynamic_cast(type.GetType().Script.Spawn(params)); if (result == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 6d41bbee4..4c287f2cb 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -60,16 +60,16 @@ ScriptingType::ScriptingType() , Module(nullptr) , InitRuntime(nullptr) , Fullname(nullptr, 0) - , Type(ScriptingTypes::Class) + , Type(ScriptingTypes::Script) , BaseTypePtr(nullptr) { - Class.Spawn = nullptr; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = nullptr; - Class.SetupScriptObjectVTable = nullptr; - Class.DefaultInstance = nullptr; + Script.Spawn = nullptr; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = nullptr; + Script.SetupScriptObjectVTable = nullptr; + Script.DefaultInstance = nullptr; } ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) @@ -77,21 +77,39 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) - , Type(ScriptingTypes::Class) + , Type(ScriptingTypes::Script) , BaseTypeHandle(baseType) , BaseTypePtr(nullptr) , Size(size) { - Class.Spawn = spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = setupScriptVTable; - Class.SetupScriptObjectVTable = setupScriptObjectVTable; - Class.DefaultInstance = nullptr; + Script.Spawn = spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = setupScriptVTable; + Script.SetupScriptObjectVTable = setupScriptObjectVTable; + Script.DefaultInstance = nullptr; } ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Script) + , BaseTypePtr(baseType) + , Size(size) +{ + Script.Spawn = spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = setupScriptVTable; + Script.SetupScriptObjectVTable = setupScriptObjectVTable; + Script.DefaultInstance = nullptr; +} + +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) @@ -100,13 +118,8 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , BaseTypePtr(baseType) , Size(size) { - Class.Spawn = spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = setupScriptVTable; - Class.SetupScriptObjectVTable = setupScriptObjectVTable; - Class.DefaultInstance = nullptr; + Class.Ctor = ctor; + Class.Dtor = dtor; } ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType) @@ -139,14 +152,14 @@ ScriptingType::ScriptingType(const ScriptingType& other) { switch (other.Type) { - case ScriptingTypes::Class: - Class.Spawn = other.Class.Spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = other.Class.SetupScriptVTable; - Class.SetupScriptObjectVTable = other.Class.SetupScriptObjectVTable; - Class.DefaultInstance = nullptr; + case ScriptingTypes::Script: + Script.Spawn = other.Script.Spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = other.Script.SetupScriptVTable; + Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable; + Script.DefaultInstance = nullptr; break; case ScriptingTypes::Structure: Struct.Ctor = other.Struct.Ctor; @@ -175,18 +188,18 @@ ScriptingType::ScriptingType(ScriptingType&& other) { switch (other.Type) { - case ScriptingTypes::Class: - Class.Spawn = other.Class.Spawn; - Class.VTable = other.Class.VTable; - other.Class.VTable = nullptr; - Class.ScriptVTable = other.Class.ScriptVTable; - other.Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = other.Class.ScriptVTableBase; - other.Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = other.Class.SetupScriptVTable; - Class.SetupScriptObjectVTable = other.Class.SetupScriptObjectVTable; - Class.DefaultInstance = other.Class.DefaultInstance; - other.Class.DefaultInstance = nullptr; + case ScriptingTypes::Script: + Script.Spawn = other.Script.Spawn; + Script.VTable = other.Script.VTable; + other.Script.VTable = nullptr; + Script.ScriptVTable = other.Script.ScriptVTable; + other.Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = other.Script.ScriptVTableBase; + other.Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = other.Script.SetupScriptVTable; + Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable; + Script.DefaultInstance = other.Script.DefaultInstance; + other.Script.DefaultInstance = nullptr; break; case ScriptingTypes::Structure: Struct.Ctor = other.Struct.Ctor; @@ -207,13 +220,13 @@ ScriptingType::~ScriptingType() { switch (Type) { - case ScriptingTypes::Class: - if (Class.DefaultInstance) - Delete(Class.DefaultInstance); - if (Class.VTable) - Platform::Free((byte*)Class.VTable - GetVTablePrefix()); - Platform::Free(Class.ScriptVTable); - Platform::Free(Class.ScriptVTableBase); + case ScriptingTypes::Script: + if (Script.DefaultInstance) + Delete(Script.DefaultInstance); + if (Script.VTable) + Platform::Free((byte*)Script.VTable - GetVTablePrefix()); + Platform::Free(Script.ScriptVTable); + Platform::Free(Script.ScriptVTableBase); break; case ScriptingTypes::Structure: break; @@ -235,17 +248,17 @@ ScriptingTypeHandle ScriptingType::GetHandle() const ScriptingObject* ScriptingType::GetDefaultInstance() const { - ASSERT(Type == ScriptingTypes::Class); - if (!Class.DefaultInstance) + ASSERT(Type == ScriptingTypes::Script); + if (!Script.DefaultInstance) { const ScriptingObjectSpawnParams params(Guid::New(), GetHandle()); - Class.DefaultInstance = Class.Spawn(params); - if (!Class.DefaultInstance) + Script.DefaultInstance = Script.Spawn(params); + if (!Script.DefaultInstance) { LOG(Error, "Failed to create default instance of type {0}", ToString()); } } - return Class.DefaultInstance; + return Script.DefaultInstance; } String ScriptingType::ToString() const @@ -268,6 +281,21 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType) : ScriptingTypeHandle(module, module->Types.Count()) { @@ -368,7 +396,7 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // Create native object ScriptingTypeHandle managedTypeHandle = params.Type; const ScriptingType* managedTypePtr = &managedTypeHandle.GetType(); - while (managedTypePtr->Class.Spawn != &ManagedObjectSpawn) + while (managedTypePtr->Script.Spawn != &ManagedObjectSpawn) { managedTypeHandle = managedTypePtr->GetBaseType(); managedTypePtr = &managedTypeHandle.GetType(); @@ -376,12 +404,12 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp ScriptingType& managedType = (ScriptingType&)*managedTypePtr; ScriptingTypeHandle nativeTypeHandle = managedType.GetBaseType(); const ScriptingType* nativeTypePtr = &nativeTypeHandle.GetType(); - while (nativeTypePtr->Class.Spawn == &ManagedObjectSpawn) + while (nativeTypePtr->Script.Spawn == &ManagedObjectSpawn) { nativeTypeHandle = nativeTypePtr->GetBaseType(); nativeTypePtr = &nativeTypeHandle.GetType(); } - ScriptingObject* object = nativeTypePtr->Class.Spawn(params); + ScriptingObject* object = nativeTypePtr->Script.Spawn(params); if (!object) { return nullptr; @@ -392,9 +420,9 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // We create a custom vtable for the C# objects that use a native class object with virtual functions overrides. // To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to call C# code from them. // Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works. - if (managedType.Class.ScriptVTable) + if (managedType.Script.ScriptVTable) { - if (!managedType.Class.VTable) + if (!managedType.Script.VTable) { // Duplicate vtable void** vtable = *(void***)object; @@ -403,23 +431,23 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp while (vtable[entriesCount] && entriesCount < 200) entriesCount++; const int32 size = entriesCount * sizeof(void*); - managedType.Class.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); - Platform::MemoryCopy((byte*)managedType.Class.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); + managedType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); + Platform::MemoryCopy((byte*)managedType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); // Override vtable entries by the class for (ScriptingTypeHandle e = nativeTypeHandle; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptObjectVTable) + if (eType.Script.SetupScriptObjectVTable) { - eType.Class.SetupScriptObjectVTable(managedType.Class.ScriptVTable, managedType.Class.ScriptVTableBase, managedType.Class.VTable, entriesCount, 0); + eType.Script.SetupScriptObjectVTable(managedType.Script.ScriptVTable, managedType.Script.ScriptVTableBase, managedType.Script.VTable, entriesCount, 0); } e = eType.GetBaseType(); } } // Override object vtable with hacked one that has C# functions calls - *(void**)object = managedType.Class.VTable; + *(void**)object = managedType.Script.VTable; } // Mark as managed type @@ -557,7 +585,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) if (baseClassModule->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), typeIndex)) { nativeType = ScriptingTypeHandle(baseClassModule, typeIndex); - if (nativeType.GetType().Class.Spawn != &ManagedObjectSpawn) + if (nativeType.GetType().Script.Spawn != &ManagedObjectSpawn) break; } baseClass = baseClass->GetBaseClass(); @@ -592,14 +620,14 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) for (ScriptingTypeHandle e = nativeType; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptVTable) + if (eType.Script.SetupScriptVTable) { ASSERT(eType.ManagedClass); - eType.Class.SetupScriptVTable(eType.ManagedClass, type.Class.ScriptVTable, type.Class.ScriptVTableBase); + eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase); } e = eType.GetBaseType(); } - MMethod** scriptVTable = (MMethod**)type.Class.ScriptVTable; + MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable; while (scriptVTable && *scriptVTable) { const MMethod* referenceMethod = *scriptVTable; @@ -657,10 +685,10 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly) for (ScriptingType& type : Types) { type.ManagedClass = nullptr; - if (type.Type == ScriptingTypes::Class && type.Class.ScriptVTable) + if (type.Type == ScriptingTypes::Script && type.Script.ScriptVTable) { - Platform::Free(type.Class.ScriptVTable); - type.Class.ScriptVTable = nullptr; + Platform::Free(type.Script.ScriptVTable); + type.Script.ScriptVTable = nullptr; } } ClassToTypeIndex.Clear(); diff --git a/Source/Engine/Scripting/Script.cpp b/Source/Engine/Scripting/Script.cpp index 6b98ba7d9..7b1de3234 100644 --- a/Source/Engine/Scripting/Script.cpp +++ b/Source/Engine/Scripting/Script.cpp @@ -186,9 +186,9 @@ void Script::SetupType() while (typeHandle != Script::TypeInitializer) { auto& type = typeHandle.GetType(); - _tickUpdate |= type.Class.ScriptVTable[8] != nullptr; - _tickLateUpdate |= type.Class.ScriptVTable[9] != nullptr; - _tickFixedUpdate |= type.Class.ScriptVTable[10] != nullptr; + _tickUpdate |= type.Script.ScriptVTable[8] != nullptr; + _tickLateUpdate |= type.Script.ScriptVTable[9] != nullptr; + _tickFixedUpdate |= type.Script.ScriptVTable[10] != nullptr; typeHandle = type.GetBaseType(); } } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index d7e8c3729..cc3972bd0 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -371,7 +371,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); - ScriptingObject* obj = scriptingType.Class.Spawn(params); + ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); @@ -414,7 +414,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), type); - ScriptingObject* obj = type.GetType().Class.Spawn(params); + ScriptingObject* obj = type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}\'.", String(typeName)); @@ -468,7 +468,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); - ScriptingObject* obj = scriptingType.Class.Spawn(params); + ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index 815bb99d1..acde5612f 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -93,9 +93,10 @@ inline uint32 GetHash(const ScriptingTypeHandle& key) /// enum class ScriptingTypes { - Class = 0, + Script = 0, Structure = 1, Enum = 2, + Class = 3, }; /// @@ -193,6 +194,15 @@ struct FLAXENGINE_API ScriptingType /// The default instance of the scripting type. Used by serialization system for comparision to save only modified properties of the object. /// mutable ScriptingObject* DefaultInstance; + } Script; + + struct + { + // Class constructor method pointer + Ctor Ctor; + + // Class destructor method pointer + Dtor Dtor; } Class; struct @@ -223,6 +233,7 @@ struct FLAXENGINE_API ScriptingType ScriptingType(); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); @@ -268,6 +279,7 @@ struct FLAXENGINE_API ScriptingType struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle { ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr); }; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e64240b9e..026ffdbbb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -818,7 +818,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();"); contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();"); - contents.AppendLine(" while (managedTypePtr->Class.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); + contents.AppendLine(" while (managedTypePtr->Script.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); contents.AppendLine(" {"); contents.AppendLine(" managedTypeHandle = managedTypePtr->GetBaseType();"); contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();"); @@ -827,7 +827,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" if (IsDuringWrapperCall)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling native base method"); - contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Class.ScriptVTableBase;"); + contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Script.ScriptVTableBase;"); contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])("); separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) @@ -840,7 +840,7 @@ namespace Flax.Build.Bindings } contents.AppendLine(");"); contents.AppendLine(" }"); - contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Class.ScriptVTable;"); + contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];"); contents.AppendLine(" MonoObject* exception = nullptr;"); diff --git a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs index 4d485011b..803f40142 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs @@ -48,7 +48,7 @@ namespace Flax.Build.Plugins contents.AppendLine(" if (IsDuringWrapperCall)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling base method"); - contents.AppendLine(" const auto scriptVTableBase = object->GetType().Class.ScriptVTableBase;"); + contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;"); contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])("); separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) @@ -61,7 +61,7 @@ namespace Flax.Build.Plugins } contents.AppendLine(");"); contents.AppendLine(" }"); - contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Class.ScriptVTable;"); + contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); if (functionInfo.Parameters.Count != 0) From 16cd75d1792e5b148fd7841312906ae9cecb515c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 22 Dec 2020 12:57:00 +0100 Subject: [PATCH 015/222] Fix code style --- Source/Engine/Core/Config/GameSettings.cpp | 10 ++---- .../GraphicsDevice/Vulkan/CmdBufferVulkan.cpp | 4 +-- Source/Engine/GraphicsDevice/Vulkan/Config.h | 11 ------ .../Vulkan/GPUAdapterVulkan.cpp | 13 ------- .../GraphicsDevice/Vulkan/GPUAdapterVulkan.h | 12 ------- .../Vulkan/GPUDeviceVulkan.Layers.cpp | 7 ---- .../Vulkan/GPUSwapChainVulkan.cpp | 24 ------------- .../Vulkan/GPUSwapChainVulkan.h | 9 ----- .../Vulkan/Linux/LinuxVulkanPlatform.h | 1 - .../GraphicsDevice/Vulkan/RenderToolsVulkan.h | 34 +++++++------------ 10 files changed, 17 insertions(+), 108 deletions(-) diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index ea4fef45f..fc629e001 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -78,13 +78,10 @@ bool GameSettings::Load() if (id.IsValid()) \ { \ AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset) \ + if (subAsset && !subAsset->WaitForLoaded()) \ { \ - if (!subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ + settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ + settingsType::Instance()->Apply(); \ } \ else \ { LOG(Warning, "Cannot load " nodeName " settings"); } \ @@ -143,7 +140,6 @@ bool GameSettings::Load() LOAD_SETTINGS("Navigation", NavigationSettings); // Load platform settings - // TODO: refactor platform settings impl to be more modular (better multi-platform handling) #if PLATFORM_WINDOWS LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp index a2dbb7733..a143f7dd8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp @@ -24,14 +24,12 @@ void CmdBufferVulkan::Begin() RenderToolsVulkan::ZeroStruct(beginInfo, VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO); beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VALIDATE_VULKAN_RESULT(vkBeginCommandBuffer(_commandBufferHandle, &beginInfo)); - -#if VULKAN_USE_DESCRIPTOR_POOL_MANAGER + // Acquire a descriptor pool set on if (_descriptorPoolSetContainer == nullptr) { AcquirePoolSet(); } -#endif _state = State::IsInsideBegin; diff --git a/Source/Engine/GraphicsDevice/Vulkan/Config.h b/Source/Engine/GraphicsDevice/Vulkan/Config.h index 28ba0bc7b..a3bb1662c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Config.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Config.h @@ -23,29 +23,18 @@ /// #define VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT 10 -// Enables the VK_LAYER_LUNARG_api_dump layer and the report VK_DEBUG_REPORT_INFORMATION_BIT_EXT flag #define VULKAN_ENABLE_API_DUMP 0 - #define VULKAN_RESET_QUERY_POOLS 0 - #define VULKAN_HASH_POOLS_WITH_TYPES_USAGE_ID 1 #ifndef VULKAN_USE_DEBUG_LAYER #define VULKAN_USE_DEBUG_LAYER GPU_ENABLE_DIAGNOSTICS #endif -#ifndef VULKAN_USE_DESCRIPTOR_POOL_MANAGER -#define VULKAN_USE_DESCRIPTOR_POOL_MANAGER 1 -#endif - #ifndef VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 #define VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 0 #endif -#ifndef VULKAN_ENABLE_DESKTOP_HMD_SUPPORT -#define VULKAN_ENABLE_DESKTOP_HMD_SUPPORT 0 -#endif - #ifdef VK_KHR_maintenance1 #define VULKAN_SUPPORTS_MAINTENANCE_LAYER1 1 #else diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.cpp index 982878ce0..4d451fa52 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.cpp @@ -4,24 +4,11 @@ #include "GPUAdapterVulkan.h" #include "GPUDeviceVulkan.h" -#include "RenderToolsVulkan.h" GPUAdapterVulkan::GPUAdapterVulkan(VkPhysicalDevice gpu) : Gpu(gpu) { - // Query device information vkGetPhysicalDeviceProperties(gpu, &GpuProps); -#if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT - if (GPUDeviceVulkan::OptionalDeviceExtensions.HasKHRGetPhysicalDeviceProperties2) - { - VkPhysicalDeviceProperties2KHR GpuProps2; - RenderToolsVulkan::ZeroStruct(GpuProps2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR); - GpuProps2.pNext = &GpuProps2; - RenderToolsVulkan::ZeroStruct(GpuIdProps, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR); - vkGetPhysicalDeviceProperties2KHR(Gpu, &GpuProps2); - } -#endif - Description = GpuProps.deviceName; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h index 559eea7ce..17a9212e7 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h @@ -33,9 +33,6 @@ public: { Gpu = other.Gpu; GpuProps = other.GpuProps; -#if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT - GpuIdProps = other.GpuIdProps; -#endif Description = other.Description; return *this; } @@ -58,15 +55,6 @@ public: /// VkPhysicalDeviceProperties GpuProps; -#if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT - - /// - /// The GPU device extended properties. - /// - VkPhysicalDeviceIDPropertiesKHR GpuIdProps; - -#endif - /// /// The GPU description. /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp index 65451ca81..88c30d660 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp @@ -34,10 +34,6 @@ static const char* GValidationLayers[] = static const char* GInstanceExtensions[] = { -#if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT - VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, - VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, -#endif #if VULKAN_SUPPORTS_VALIDATION_CACHE VK_EXT_VALIDATION_CACHE_EXTENSION_NAME, #endif @@ -50,9 +46,6 @@ static const char* GDeviceExtensions[] = #if VULKAN_SUPPORTS_MAINTENANCE_LAYER1 VK_KHR_MAINTENANCE1_EXTENSION_NAME, #endif -#if VULKAN_SUPPORTS_MAINTENANCE_LAYER2 - VK_KHR_MAINTENANCE2_EXTENSION_NAME, -#endif #if VULKAN_SUPPORTS_VALIDATION_CACHE VK_EXT_VALIDATION_CACHE_EXTENSION_NAME, #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index ca8af3873..6354e9956 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -17,9 +17,6 @@ void BackBufferVulkan::Setup(GPUSwapChainVulkan* window, VkImage backbuffer, Pix Device = window->GetDevice(); Handle.Init(window->GetDevice(), this, backbuffer, 1, format, MSAALevel::None, extent, VK_IMAGE_VIEW_TYPE_2D); -#if VULKAN_USE_IMAGE_ACQUIRE_FENCES - ImageAcquiredFence = Device->FenceManager.AllocateFence(true); -#endif RenderingDoneSemaphore = New(Device); ImageAcquiredSemaphore = New(Device); } @@ -27,9 +24,6 @@ void BackBufferVulkan::Setup(GPUSwapChainVulkan* window, VkImage backbuffer, Pix void BackBufferVulkan::Release() { Handle.Release(); -#if VULKAN_USE_IMAGE_ACQUIRE_FENCES - Device->FenceManager.ReleaseFence(ImageAcquiredFence); -#endif Delete(RenderingDoneSemaphore); Delete(ImageAcquiredSemaphore); } @@ -509,19 +503,9 @@ int32 GPUSwapChainVulkan::AcquireImageIndex(SemaphoreVulkan** outSemaphore) { ASSERT(_swapChain && _backBuffers.HasItems()); - // Get the index of the next swap chain image to render to. - // Wait with an "infinite" timeout, the function will block until an image is ready. - // The semaphore will get signaled when the image is ready (upon function return). - uint32 imageIndex = 0; const int32 prevSemaphoreIndex = _semaphoreIndex; _semaphoreIndex = (_semaphoreIndex + 1) % _backBuffers.Count(); - -#if VULKAN_USE_IMAGE_ACQUIRE_FENCES - const auto fence = _backBuffers[_semaphoreIndex].ImageAcquiredFence; - _device->FenceManager.ResetFence(fence); -#endif - const auto semaphore = _backBuffers[_semaphoreIndex].ImageAcquiredSemaphore; const VkResult result = vkAcquireNextImageKHR( @@ -529,11 +513,7 @@ int32 GPUSwapChainVulkan::AcquireImageIndex(SemaphoreVulkan** outSemaphore) _swapChain, UINT64_MAX, semaphore->GetHandle(), -#if VULKAN_USE_IMAGE_ACQUIRE_FENCES - fence->GetHandle(), -#else VK_NULL_HANDLE, -#endif &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) @@ -562,10 +542,6 @@ int32 GPUSwapChainVulkan::AcquireImageIndex(SemaphoreVulkan** outSemaphore) } _currentImageIndex = (int32)imageIndex; -#if VULKAN_USE_IMAGE_ACQUIRE_FENCES - ASSERT(_device->FenceManager.WaitForFence(fence, UINT64_MAX)); -#endif - return _currentImageIndex; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h index 93ff126d9..36204581c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h @@ -30,15 +30,6 @@ public: /// SemaphoreVulkan* RenderingDoneSemaphore; -#if VULKAN_USE_IMAGE_ACQUIRE_FENCES - - /// - /// The image acquired fence handle. - /// - FenceVulkan* ImageAcquiredFence; - -#endif - /// /// The render target surface handle. /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h index 66e5a608d..070d3a9c9 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h @@ -8,7 +8,6 @@ #define VULKAN_USE_DEBUG_LAYER GPU_ENABLE_DIAGNOSTICS #define VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 1 -#define VULKAN_ENABLE_DESKTOP_HMD_SUPPORT 0 /// /// The implementation for the Vulkan API support for Linux platform. diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index 02dd2c68d..c0c45b636 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -78,40 +78,32 @@ public: case 0: stageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; break; - case VK_ACCESS_TRANSFER_WRITE_BIT: stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; break; - case VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT: stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; break; - case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT: stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; break; - + case VK_ACCESS_TRANSFER_READ_BIT: + stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_ACCESS_SHADER_READ_BIT: + case VK_ACCESS_SHADER_WRITE_BIT: + stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; #if VULKAN_SUPPORTS_MAINTENANCE_LAYER2 case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT: case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT: stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; break; #endif - - case VK_ACCESS_TRANSFER_READ_BIT: - stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - - case VK_ACCESS_SHADER_READ_BIT: - case VK_ACCESS_SHADER_WRITE_BIT: - stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - break; - default: CRASH; break; } - return stageFlags; } @@ -136,12 +128,6 @@ public: accessFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; break; -#if VULKAN_SUPPORTS_MAINTENANCE_LAYER2 - case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR: - accessFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - break; -#endif case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: accessFlags = VK_ACCESS_TRANSFER_READ_BIT; stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT; @@ -158,6 +144,12 @@ public: accessFlags = VK_ACCESS_SHADER_READ_BIT; stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; +#if VULKAN_SUPPORTS_MAINTENANCE_LAYER2 + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR: + accessFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + break; +#endif case VK_IMAGE_LAYOUT_GENERAL: accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; From ce6c360d29ee44494193e3e03e93bd94852b02c8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 23 Dec 2020 12:55:53 +0100 Subject: [PATCH 016/222] Add proper typeInfo generation for non-scripting API classes --- .../Bindings/BindingsGenerator.Cpp.cs | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 026ffdbbb..d7711a13a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1046,7 +1046,7 @@ namespace Flax.Build.Bindings var classTypeNameInternal = classInfo.NativeName; if (classInfo.Parent != null && !(classInfo.Parent is FileInfo)) classTypeNameInternal = classInfo.Parent.FullNameNative + '_' + classTypeNameInternal; - var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; + var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject; if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); @@ -1059,7 +1059,7 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { - if (!useUnmanaged) + if (!useScripting) continue; CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); @@ -1131,7 +1131,7 @@ namespace Flax.Build.Bindings // Fields foreach (var fieldInfo in classInfo.Fields) { - if (!useUnmanaged) + if (!useScripting) continue; if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Getter, "{0}"); @@ -1142,7 +1142,7 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { - if (!useUnmanaged) + if (!useScripting) continue; if (propertyInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, propertyInfo.Getter); @@ -1153,7 +1153,7 @@ namespace Flax.Build.Bindings // Functions foreach (var functionInfo in classInfo.Functions) { - if (!useUnmanaged) + if (!useScripting) throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); GenerateCppWrapperFunction(buildData, contents, classInfo, functionInfo); } @@ -1264,7 +1264,7 @@ namespace Flax.Build.Bindings // Runtime initialization (internal methods binding) contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); - if (useUnmanaged) + if (useScripting) { foreach (var eventInfo in classInfo.Events) { @@ -1291,10 +1291,24 @@ namespace Flax.Build.Bindings } GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents); - contents.AppendLine(" }"); - contents.Append('}'); - contents.Append(';'); - contents.AppendLine(); + contents.AppendLine(" }").AppendLine(); + + if (!useScripting) + { + // Constructor + contents.AppendLine(" static void Ctor(void* ptr)"); + contents.AppendLine(" {"); + contents.AppendLine($" new(ptr){classTypeNameNative}();"); + contents.AppendLine(" }").AppendLine(); + + // Destructor + contents.AppendLine(" static void Dtor(void* ptr)"); + contents.AppendLine(" {"); + contents.AppendLine($" (({classTypeNameNative}*)ptr)->~{classInfo.NativeName}();"); + contents.AppendLine(" }").AppendLine(); + } + + contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); // Type initializer @@ -1302,15 +1316,22 @@ namespace Flax.Build.Bindings contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), "); contents.Append($"sizeof({classTypeNameNative}), "); contents.Append($"&{classTypeNameInternal}Internal::InitRuntime, "); - if (!classInfo.IsStatic && !classInfo.NoSpawn && useUnmanaged) - contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); + if (useScripting) + { + if (classInfo.IsStatic || classInfo.NoSpawn) + contents.Append("&ScriptingType::DefaultSpawn, "); + else + contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); + if (classInfo.BaseType != null && useScripting) + contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); + else + contents.Append("nullptr, "); + contents.Append(setupScriptVTable); + } else - contents.Append("&ScriptingType::DefaultSpawn, "); - if (classInfo.BaseType != null && useUnmanaged) - contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); - else - contents.Append("nullptr, "); - contents.Append(setupScriptVTable); + { + contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor"); + } contents.Append(");"); contents.AppendLine(); From e242dbf89fb48135a0a991d428da17a58cadcce0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 23 Dec 2020 12:58:31 +0100 Subject: [PATCH 017/222] Add missing code for ScriptingType copy/move --- Source/Engine/Scripting/BinaryModule.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 4c287f2cb..862b95347 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -170,6 +170,10 @@ ScriptingType::ScriptingType(const ScriptingType& other) Struct.GetField = other.Struct.GetField; Struct.SetField = other.Struct.SetField; break; + case ScriptingTypes::Class: + Class.Ctor = other.Class.Ctor; + Class.Dtor = other.Class.Dtor; + break; case ScriptingTypes::Enum: break; default: ; @@ -210,6 +214,10 @@ ScriptingType::ScriptingType(ScriptingType&& other) Struct.GetField = other.Struct.GetField; Struct.SetField = other.Struct.SetField; break; + case ScriptingTypes::Class: + Class.Ctor = other.Class.Ctor; + Class.Dtor = other.Class.Dtor; + break; case ScriptingTypes::Enum: break; default: ; From 8dc5b11f5172dafc4cfb09fec769dd7e240ad0b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Jan 2021 14:18:59 +0100 Subject: [PATCH 018/222] Add `API_INTERFACE` to scripting API bindings for implementing interfaces --- Source/Engine/Core/Config.h | 2 + Source/Engine/Core/Types/BaseTypes.h | 6 +- Source/Engine/Scripting/BinaryModule.cpp | 87 +++++- Source/Engine/Scripting/ScriptingType.h | 54 ++-- .../Tools/Flax.Build/Bindings/ApiTypeInfo.cs | 1 + .../Bindings/BindingsGenerator.Api.cs | 2 + .../Bindings/BindingsGenerator.Cpp.cs | 67 ++++- .../Bindings/BindingsGenerator.Parsing.cs | 262 +++++++++++------- .../Flax.Build/Bindings/BindingsGenerator.cs | 10 + Source/Tools/Flax.Build/Bindings/ClassInfo.cs | 9 +- .../Flax.Build/Bindings/ClassStructInfo.cs | 40 +++ .../Flax.Build/Bindings/InterfaceInfo.cs | 24 ++ .../Flax.Build/Bindings/StructureInfo.cs | 6 +- Source/Tools/Flax.Build/Flax.Build.csproj | 2 + 14 files changed, 421 insertions(+), 151 deletions(-) create mode 100644 Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs create mode 100644 Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs diff --git a/Source/Engine/Core/Config.h b/Source/Engine/Core/Config.h index 152a170b2..55fae1cd4 100644 --- a/Source/Engine/Core/Config.h +++ b/Source/Engine/Core/Config.h @@ -44,6 +44,7 @@ // Scripting API defines (see C++ scripting documentation for more info) #define API_ENUM(...) #define API_CLASS(...) +#define API_INTERFACE(...) #define API_STRUCT(...) #define API_FUNCTION(...) #define API_PROPERTY(...) @@ -52,3 +53,4 @@ #define API_PARAM(...) #define API_INJECT_CPP_CODE(...) #define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer; diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index 63638e304..580728b2c 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -108,7 +108,5 @@ class Dictionary; inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); } \ inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); } -#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) \ - public: \ - friend class type##Internal; \ - static struct ScriptingTypeInitializer TypeInitializer; +// Returns byte offset from the object pointer in vtable to the begin of the given inherited type implementation +#define VTABLE_OFFSET(type, baseType) (((intptr)static_cast((type*)1))-1) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 862b95347..e3688949b 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -62,6 +62,7 @@ ScriptingType::ScriptingType() , Fullname(nullptr, 0) , Type(ScriptingTypes::Script) , BaseTypePtr(nullptr) + , Interfaces(nullptr) { Script.Spawn = nullptr; Script.VTable = nullptr; @@ -72,7 +73,7 @@ ScriptingType::ScriptingType() Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) @@ -80,6 +81,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , Type(ScriptingTypes::Script) , BaseTypeHandle(baseType) , BaseTypePtr(nullptr) + , Interfaces(interfaces) , Size(size) { Script.Spawn = spawn; @@ -91,13 +93,14 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Script) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Script.Spawn = spawn; @@ -109,26 +112,28 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Class) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Class.Ctor = ctor; Class.Dtor = dtor; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Structure) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Struct.Ctor = ctor; @@ -140,6 +145,18 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Interface) + , BaseTypePtr(baseType) + , Interfaces(interfaces) + , Size(0) +{ +} + ScriptingType::ScriptingType(const ScriptingType& other) : ManagedClass(other.ManagedClass) , Module(other.Module) @@ -148,6 +165,7 @@ ScriptingType::ScriptingType(const ScriptingType& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) @@ -176,6 +194,8 @@ ScriptingType::ScriptingType(const ScriptingType& other) break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -188,6 +208,7 @@ ScriptingType::ScriptingType(ScriptingType&& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) @@ -220,6 +241,8 @@ ScriptingType::ScriptingType(ScriptingType&& other) break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -240,6 +263,8 @@ ScriptingType::~ScriptingType() break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -269,16 +294,40 @@ ScriptingObject* ScriptingType::GetDefaultInstance() const return Script.DefaultInstance; } +const ScriptingType::InterfaceImplementation* ScriptingType::GetInterface(const ScriptingTypeInitializer* interfaceType) const +{ + const InterfaceImplementation* interfaces = Interfaces; + if (interfaces) + { + while (interfaces->InterfaceType) + { + if (interfaces->InterfaceType == interfaceType) + return interfaces; + interfaces++; + } + } + if (BaseTypeHandle) + { + return BaseTypeHandle.GetType().GetInterface(interfaceType); + } + if (BaseTypePtr) + { + return BaseTypePtr->GetType().GetInterface(interfaceType); + } + return nullptr; +} + String ScriptingType::ToString() const { return String(Fullname.Get(), Fullname.Length()); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Script module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -289,11 +338,12 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Class module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -304,11 +354,28 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Structure module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + // Interface + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, baseType, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index acde5612f..4c41a68a8 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -97,6 +97,7 @@ enum class ScriptingTypes Structure = 1, Enum = 2, Class = 3, + Interface = 4, }; /// @@ -116,6 +117,15 @@ struct FLAXENGINE_API ScriptingType typedef void (*GetField)(void* ptr, const String& name, Variant& value); typedef void (*SetField)(void* ptr, const String& name, const Variant& value); + struct InterfaceImplementation + { + // Pointer to the type of the implemented interface. + const ScriptingTypeInitializer* InterfaceType; + + // The offset (in bytes) from the object pointer to the interface implementation. Used for casting object to the interface. + int16 VTableOffset; + }; + /// /// The managed class (cached, can be null if missing). /// @@ -151,6 +161,11 @@ struct FLAXENGINE_API ScriptingType /// const ScriptingTypeInitializer* BaseTypePtr; + /// + /// The list of interfaces implemented by this type (null if unused, list ends with null entry). + /// + const InterfaceImplementation* Interfaces; + /// /// The native size of the type value (in bytes). /// @@ -196,15 +211,6 @@ struct FLAXENGINE_API ScriptingType mutable ScriptingObject* DefaultInstance; } Script; - struct - { - // Class constructor method pointer - Ctor Ctor; - - // Class destructor method pointer - Dtor Dtor; - } Class; - struct { // Structure constructor method pointer @@ -228,13 +234,23 @@ struct FLAXENGINE_API ScriptingType // Structure field value setter SetField SetField; } Struct; + + struct + { + // Class constructor method pointer + Ctor Ctor; + + // Class destructor method pointer + Dtor Dtor; + } Class; }; ScriptingType(); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); ScriptingType& operator=(ScriptingType&& other) = delete; @@ -270,6 +286,11 @@ struct FLAXENGINE_API ScriptingType /// ScriptingObject* GetDefaultInstance() const; + /// + /// Gets the pointer to the implementation of the given interface type for this scripting type (including base types). Returns null if given interface is not implemented. + /// + const InterfaceImplementation* GetInterface(const ScriptingTypeInitializer* interfaceType) const; + String ToString() const; }; @@ -278,9 +299,10 @@ struct FLAXENGINE_API ScriptingType /// struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle { - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); }; /// diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 60074883c..06cb55257 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -22,6 +22,7 @@ namespace Flax.Build.Bindings public virtual bool IsClass => false; public virtual bool IsStruct => false; public virtual bool IsEnum => false; + public virtual bool IsInterface => false; public virtual bool IsValueType => false; public virtual bool IsScriptingObject => false; public virtual bool IsPod => false; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index b70cc8e63..7de3f96fe 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -13,6 +13,7 @@ namespace Flax.Build.Bindings { public static readonly string Enum = "API_ENUM"; public static readonly string Class = "API_CLASS"; + public static readonly string Interface = "API_INTERFACE"; public static readonly string Struct = "API_STRUCT"; public static readonly string Function = "API_FUNCTION"; public static readonly string Property = "API_PROPERTY"; @@ -27,6 +28,7 @@ namespace Flax.Build.Bindings Enum, Class, Struct, + Interface, }; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index d7711a13a..b9096b3bb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -947,9 +947,7 @@ namespace Flax.Build.Bindings var classInfo = typeInfo as ClassInfo; var structureInfo = typeInfo as StructureInfo; var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; - if (baseType != null && baseType.Type == "ISerializable") - baseType = null; - else if (classInfo != null && classInfo.IsBaseTypeHidden) + if (classInfo != null && classInfo.IsBaseTypeHidden) baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); @@ -1038,6 +1036,25 @@ namespace Flax.Build.Bindings contents.Append('}').AppendLine(); } + private static string GenerateCppInterfaceInheritanceTable(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassStructInfo typeInfo, string typeNameNative) + { + var interfacesPtr = "nullptr"; + var interfaces = typeInfo.Interfaces; + if (interfaces != null) + { + interfacesPtr = typeNameNative + "_Interfaces"; + contents.Append("static const ScriptingType::InterfaceImplementation ").Append(interfacesPtr).AppendLine("[] = {"); + for (int i = 0; i < interfaces.Count; i++) + { + var interfaceInfo = interfaces[i]; + contents.Append(" { &").Append(interfaceInfo.NativeName).Append("::TypeInitializer, (int16)VTABLE_OFFSET(").Append(typeInfo.NativeName).Append(", ").Append(interfaceInfo.NativeName).AppendLine(") },"); + } + contents.AppendLine(" { nullptr, 0 },"); + contents.AppendLine("};"); + } + return interfacesPtr; + } + private static void GenerateCppClass(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassInfo classInfo) { var classTypeNameNative = classInfo.FullNameNative; @@ -1311,6 +1328,9 @@ namespace Flax.Build.Bindings contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); + // Interfaces + var interfacesTable = GenerateCppInterfaceInheritanceTable(buildData, contents, moduleInfo, classInfo, classTypeNameNative); + // Type initializer contents.Append($"ScriptingTypeInitializer {classTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), "); @@ -1330,8 +1350,9 @@ namespace Flax.Build.Bindings } else { - contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor"); + contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, nullptr"); } + contents.Append(", ").Append(interfacesTable); contents.Append(");"); contents.AppendLine(); @@ -1526,6 +1547,42 @@ namespace Flax.Build.Bindings } } + private static void GenerateCppInterface(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, InterfaceInfo interfaceInfo) + { + var interfaceTypeNameNative = interfaceInfo.FullNameNative; + var interfaceTypeNameManaged = interfaceInfo.FullNameManaged; + var interfaceTypeNameManagedInternalCall = interfaceTypeNameManaged.Replace('+', '/'); + var interfaceTypeNameInternal = interfaceInfo.NativeName; + if (interfaceInfo.Parent != null && !(interfaceInfo.Parent is FileInfo)) + interfaceTypeNameInternal = interfaceInfo.Parent.FullNameNative + '_' + interfaceTypeNameInternal; + + contents.AppendLine(); + contents.AppendFormat("class {0}Internal", interfaceTypeNameInternal).AppendLine(); + contents.Append('{').AppendLine(); + contents.AppendLine("public:"); + + // Runtime initialization (internal methods binding) + contents.AppendLine(" static void InitRuntime()"); + contents.AppendLine(" {"); + contents.AppendLine(" }").AppendLine(); + + contents.Append('}').Append(';').AppendLine(); + contents.AppendLine(); + + // Type initializer + contents.Append($"ScriptingTypeInitializer {interfaceTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); + contents.Append($"StringAnsiView(\"{interfaceTypeNameManaged}\", {interfaceTypeNameManaged.Length}), "); + contents.Append($"&{interfaceTypeNameInternal}Internal::InitRuntime"); + contents.Append(");"); + contents.AppendLine(); + + // Nested types + foreach (var apiTypeInfo in interfaceInfo.Children) + { + GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); + } + } + private static bool GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) { if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.IsInBuild) @@ -1537,6 +1594,8 @@ namespace Flax.Build.Bindings GenerateCppClass(buildData, contents, moduleInfo, classInfo); else if (type is StructureInfo structureInfo) GenerateCppStruct(buildData, contents, moduleInfo, structureInfo); + else if (type is InterfaceInfo interfaceInfo) + GenerateCppInterface(buildData, contents, moduleInfo, interfaceInfo); else if (type is InjectCppCodeInfo injectCppCodeInfo) contents.AppendLine(injectCppCodeInfo.Code); else diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index f164d2744..b21a4a135 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -383,6 +383,95 @@ namespace Flax.Build.Bindings return name; } + private static void ParseInheritance(ref ParsingContext context, ClassStructInfo desc, out bool isFinal) + { + desc.BaseType = null; + desc.BaseTypeInheritance = AccessLevel.Private; + + var token = context.Tokenizer.NextToken(); + isFinal = token.Value == "final"; + if (isFinal) + token = context.Tokenizer.NextToken(); + + if (token.Type == TokenType.Colon) + { + while (token.Type != TokenType.LeftCurlyBrace) + { + var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); + switch (accessToken.Value) + { + case "public": + desc.BaseTypeInheritance = AccessLevel.Public; + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + case "protected": + desc.BaseTypeInheritance = AccessLevel.Protected; + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + case "private": + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + default: + token = accessToken; + break; + } + + var baseTypeInfo = new TypeInfo + { + Type = token.Value, + }; + if (token.Value.Length > 2 && token.Value[0] == 'I' && char.IsUpper(token.Value[1])) + { + // Interface + if (desc.InterfaceNames == null) + desc.InterfaceNames = new List(); + desc.InterfaceNames.Add(baseTypeInfo); + token = context.Tokenizer.NextToken(); + continue; + } + + if (desc.BaseType != null) + { + // Allow for multiple base classes, just the first one needs to be a valid base type + break; + throw new Exception($"Invalid '{desc.Name}' inheritance (only single base class is allowed for scripting types, excluding interfaces)."); + } + desc.BaseType = baseTypeInfo; + token = context.Tokenizer.NextToken(); + if (token.Type == TokenType.LeftCurlyBrace) + { + break; + } + if (token.Type == TokenType.LeftAngleBracket) + { + var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); + token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); + desc.BaseType.GenericArgs = new List + { + new TypeInfo + { + Type = genericType.Value, + } + }; + + // TODO: find better way to resolve this (custom base type attribute?) + if (desc.BaseType.Type == "ShaderAssetTypeBase") + { + desc.BaseType = desc.BaseType.GenericArgs[0]; + } + + token = context.Tokenizer.NextToken(); + } + } + token = context.Tokenizer.PreviousToken(); + } + else + { + // No base type + token = context.Tokenizer.PreviousToken(); + } + } + private static ClassInfo ParseClass(ref ParsingContext context) { var desc = new ClassInfo @@ -410,64 +499,8 @@ namespace Flax.Build.Bindings // Read name desc.Name = desc.NativeName = ParseName(ref context); - // Read class inheritance - token = context.Tokenizer.NextToken(); - var isFinal = token.Value == "final"; - if (isFinal) - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.Colon) - { - // Current class does have inheritance defined - var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); - switch (accessToken.Value) - { - case "public": - desc.BaseTypeInheritance = AccessLevel.Public; - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "protected": - desc.BaseTypeInheritance = AccessLevel.Protected; - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "private": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - } - - desc.BaseType = new TypeInfo - { - Type = token.Value, - }; - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.LeftAngleBracket) - { - var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); - context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List - { - new TypeInfo - { - Type = genericType.Value, - } - }; - - // TODO: find better way to resolve this (custom base type attribute?) - if (desc.BaseType.Type == "ShaderAssetTypeBase") - { - desc.BaseType = desc.BaseType.GenericArgs[0]; - } - } - else - { - token = context.Tokenizer.PreviousToken(); - } - } - else - { - // No base type - token = context.Tokenizer.PreviousToken(); - desc.BaseType = null; - } + // Read inheritance + ParseInheritance(ref context, desc, out var isFinal); // Process tag parameters foreach (var tag in tagParams) @@ -523,6 +556,68 @@ namespace Flax.Build.Bindings return desc; } + private static InterfaceInfo ParseInterface(ref ParsingContext context) + { + var desc = new InterfaceInfo + { + Children = new List(), + Access = context.CurrentAccessLevel, + }; + + // Read the documentation comment + desc.Comment = ParseComment(ref context); + + // Read parameters from the tag + var tagParams = ParseTagParameters(ref context); + + // Read 'class' keyword + var token = context.Tokenizer.NextToken(); + if (token.Value != "class") + throw new Exception($"Invalid API_INTERFACE usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + + // Read name + desc.Name = desc.NativeName = ParseName(ref context); + if (desc.Name.Length < 2 || desc.Name[0] != 'I' || !char.IsUpper(desc.Name[1])) + throw new Exception($"Invalid API_INTERFACE name '{desc.Name}' (it must start with 'I' character followed by the uppercase character)."); + + // Read inheritance + ParseInheritance(ref context, desc, out _); + + // Process tag parameters + foreach (var tag in tagParams) + { + switch (tag.Tag.ToLower()) + { + case "public": + desc.Access = AccessLevel.Public; + break; + case "protected": + desc.Access = AccessLevel.Protected; + break; + case "private": + desc.Access = AccessLevel.Private; + break; + case "inbuild": + desc.IsInBuild = true; + break; + case "attributes": + desc.Attributes = tag.Value; + break; + case "name": + desc.Name = tag.Value; + break; + case "namespace": + desc.Namespace = tag.Value; + break; + default: + Log.Warning($"Unknown or not supported tag parameter {tag} used on interface {desc.Name} at line {context.Tokenizer.CurrentLine}"); + break; + } + } + + return desc; + } + private static FunctionInfo ParseFunction(ref ParsingContext context) { var desc = new FunctionInfo @@ -875,55 +970,8 @@ namespace Flax.Build.Bindings // Read name desc.Name = desc.NativeName = ParseName(ref context); - // Read structure inheritance - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.Colon) - { - // Current class does have inheritance defined - var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); - switch (accessToken.Value) - { - case "public": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "protected": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "private": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - default: - token = accessToken; - break; - } - - desc.BaseType = new TypeInfo - { - Type = token.Value, - }; - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.LeftAngleBracket) - { - var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); - context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List - { - new TypeInfo - { - Type = genericType.Value, - } - }; - } - else - { - token = context.Tokenizer.PreviousToken(); - } - } - else - { - // No base type - token = context.Tokenizer.PreviousToken(); - } + // Read inheritance + ParseInheritance(ref context, desc, out _); // Process tag parameters foreach (var tag in tagParams) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 5b58a8d8f..00cf0c7c3 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -254,6 +254,16 @@ namespace Flax.Build.Bindings var injectCppCodeInfo = ParseInjectCppCode(ref context); fileInfo.AddChild(injectCppCodeInfo); } + else if (string.Equals(token.Value, ApiTokens.Interface, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested interfaces in scripting API"); + + var interfaceInfo = ParseInterface(ref context); + scopeType = interfaceInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) { if (context.ScopeInfo is ClassInfo classInfo) diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index 3ac5b37fc..a83ac2b55 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; using System.Linq; @@ -8,7 +8,7 @@ namespace Flax.Build.Bindings /// /// The native class information for bindings generator. /// - public class ClassInfo : ApiTypeInfo + public class ClassInfo : ClassStructInfo { private static readonly HashSet InBuildScriptingObjectTypes = new HashSet { @@ -22,9 +22,6 @@ namespace Flax.Build.Bindings "Actor", }; - public AccessLevel Access; - public TypeInfo BaseType; - public AccessLevel BaseTypeInheritance; public bool IsBaseTypeHidden; public bool IsStatic; public bool IsSealed; @@ -52,7 +49,7 @@ namespace Flax.Build.Bindings base.Init(buildData); // Internal base types are usually hidden from bindings (used in core-only internally) - IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType.Type == "ISerializable"; + IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType == null; // Cache if it it Scripting Object type if (InBuildScriptingObjectTypes.Contains(Name)) diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs new file mode 100644 index 000000000..dcb2d2e9e --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure information for bindings generator. + /// + public abstract class ClassStructInfo : ApiTypeInfo + { + public AccessLevel Access; + public AccessLevel BaseTypeInheritance; + public TypeInfo BaseType; + public List Interfaces; // Optional + public List InterfaceNames; // Optional + + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + if (Interfaces == null && InterfaceNames != null && InterfaceNames.Count != 0) + { + Interfaces = new List(); + for (var i = 0; i < InterfaceNames.Count; i++) + { + var interfaceName = InterfaceNames[i]; + var apiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, interfaceName, this); + if (apiTypeInfo is InterfaceInfo interfaceInfo) + { + Interfaces.Add(interfaceInfo); + } + } + if (Interfaces.Count == 0) + Interfaces = null; + } + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs new file mode 100644 index 000000000..e0225f288 --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure interface information for bindings generator. + /// + public class InterfaceInfo : ClassStructInfo + { + public override bool IsInterface => true; + + public override void AddChild(ApiTypeInfo apiTypeInfo) + { + apiTypeInfo.Namespace = null; + + base.AddChild(apiTypeInfo); + } + + public override string ToString() + { + return "interface " + Name; + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index ca0a02247..baf80a6fe 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -8,10 +8,8 @@ namespace Flax.Build.Bindings /// /// The native structure information for bindings generator. /// - public class StructureInfo : ApiTypeInfo + public class StructureInfo : ClassStructInfo { - public AccessLevel Access; - public TypeInfo BaseType; public List Fields; public List Functions; public bool IsAutoSerialization; @@ -27,7 +25,7 @@ namespace Flax.Build.Bindings { base.Init(buildData); - if (ForceNoPod) + if (ForceNoPod || (InterfaceNames != null && InterfaceNames.Count != 0)) { _isPod = false; return; diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index a8221455f..9d415beed 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -69,12 +69,14 @@ + + From c5568c8eae5291fe959908d1dfb78d47bef0f5d2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Jan 2021 14:19:51 +0100 Subject: [PATCH 019/222] Add support for loading JsonAsset instance objects if they implement ISerializable interface --- Source/Engine/Content/JsonAsset.cpp | 78 ++++++++++++--------- Source/Engine/Content/JsonAsset.h | 4 +- Source/Engine/Core/ISerializable.h | 60 ++++++++++++++++ Source/Engine/Serialization/ISerializable.h | 56 +-------------- 4 files changed, 110 insertions(+), 88 deletions(-) create mode 100644 Source/Engine/Core/ISerializable.h diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 3779181b7..4c50d94bb 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -10,7 +10,10 @@ #include "Engine/Core/Log.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Content/Factories/JsonAssetFactory.h" +#include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/JsonParseException.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Utilities/StringConverter.h" JsonAssetBase::JsonAssetBase(const SpawnParams& params, const AssetInfo* info) : Asset(params, info) @@ -175,51 +178,54 @@ void JsonAssetBase::onRename(const StringView& newPath) REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset"); -//////////////////////////////////////////////////////////////////////////////////// - -#include "Engine/Physics/PhysicalMaterial.h" - -// Unmanaged json asset types that are serialized to JsonAsset and should be created by auto by asset. -// This allows to reuse JsonAsset without creating dedicated asset types. It has been designed for lightweight resources. - -typedef ISerializable* (*UnmanagedJsonInstanceCreator)(); - -template -ISerializable* Create() -{ - return New(); -} - -// Key: managed class typename, Value: unmanaged instance spawner function -Dictionary UnmanagedTypes(32); - -void InitUnmanagedJsonTypes() -{ - UnmanagedTypes[TEXT("FlaxEngine.PhysicalMaterial")] = &Create; -} - -//////////////////////////////////////////////////////////////////////////////////// - JsonAsset::JsonAsset(const SpawnParams& params, const AssetInfo* info) : JsonAssetBase(params, info) , Instance(nullptr) { - if (UnmanagedTypes.IsEmpty()) - InitUnmanagedJsonTypes(); } Asset::LoadResult JsonAsset::loadAsset() { // Base auto result = JsonAssetBase::loadAsset(); - if (result != LoadResult::Ok) + if (result != LoadResult::Ok || IsInternalType()) return result; - UnmanagedJsonInstanceCreator instanceSpawner = nullptr; - if (UnmanagedTypes.TryGet(DataTypeName, instanceSpawner)) + // Try to scripting type for this data + const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length()); + const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length())); + if (typeHandle) { - Instance = instanceSpawner(); - Instance->Deserialize(*Data, nullptr); + auto& type = typeHandle.GetType(); + switch (type.Type) + { + case ScriptingTypes::Class: + { + // Ensure that object can deserialized + const ScriptingType::InterfaceImplementation* interfaces = type.GetInterface(&ISerializable::TypeInitializer); + if (!interfaces) + { + LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); + break; + } + + // Allocate object + const auto instance = Allocator::Allocate(type.Size); + if (!instance) + return LoadResult::Failed; + Instance = instance; + _dtor = type.Class.Dtor; + type.Class.Ctor(instance); + + // Deserialize object + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = DataEngineBuild; + ((ISerializable*)((byte*)instance + interfaces->VTableOffset))->Deserialize(*Data, modifier.Value); + // TODO: delete object when containing BinaryModule gets unloaded + break; + } + default: ; + } } return result; @@ -230,5 +236,11 @@ void JsonAsset::unload(bool isReloading) // Base JsonAssetBase::unload(isReloading); - SAFE_DELETE(Instance); + if (Instance) + { + _dtor(Instance); + Allocator::Free(Instance); + Instance = nullptr; + _dtor = nullptr; + } } diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index d5f785418..a6d452ff0 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -78,13 +78,15 @@ protected: API_CLASS(NoSpawn) class JsonAsset : public JsonAssetBase { DECLARE_ASSET_HEADER(JsonAsset); +private: + ScriptingType::Dtor _dtor; public: /// /// The deserialized unmanaged object instance (e.g. PhysicalMaterial). /// - ISerializable* Instance; + void* Instance; protected: diff --git a/Source/Engine/Core/ISerializable.h b/Source/Engine/Core/ISerializable.h new file mode 100644 index 000000000..cfc5e1976 --- /dev/null +++ b/Source/Engine/Core/ISerializable.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Serialization/JsonFwd.h" +#include "Engine/Core/Compiler.h" +#include "Engine/Core/Config.h" + +class JsonWriter; +class ISerializeModifier; + +/// +/// Interface for objects that can be serialized/deserialized to/from JSON format. +/// +API_INTERFACE() class FLAXENGINE_API ISerializable +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(ISerializable); +public: + + typedef rapidjson_flax::Document SerializeDocument; + + /// + /// Serialization output stream + /// + typedef rapidjson_flax::Value DeserializeStream; + + /// + /// Serialization input stream + /// + typedef JsonWriter SerializeStream; + +public: + + /// + /// Finalizes an instance of the class. + /// + virtual ~ISerializable() = default; + + /// + /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. + /// + /// The output stream. + /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. + virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; + + /// + /// Deserialize object from the input stream + /// + /// The input stream. + /// The deserialization modifier object. Always valid. + virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; + + /// + /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. + /// + /// The input stream. + /// The input stream member to lookup. + /// The deserialization modifier object. Always valid. + void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier); +}; diff --git a/Source/Engine/Serialization/ISerializable.h b/Source/Engine/Serialization/ISerializable.h index cc7ebd0d7..537f64ee9 100644 --- a/Source/Engine/Serialization/ISerializable.h +++ b/Source/Engine/Serialization/ISerializable.h @@ -2,57 +2,5 @@ #pragma once -#include "JsonFwd.h" -#include "Engine/Core/Compiler.h" - -class JsonWriter; -class ISerializeModifier; - -/// -/// Interface for objects that can be serialized/deserialized to/from JSON format. -/// -class FLAXENGINE_API ISerializable -{ -public: - - typedef rapidjson_flax::Document SerializeDocument; - - /// - /// Serialization output stream - /// - typedef rapidjson_flax::Value DeserializeStream; - - /// - /// Serialization input stream - /// - typedef JsonWriter SerializeStream; - -public: - - /// - /// Finalizes an instance of the class. - /// - virtual ~ISerializable() = default; - - /// - /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. - /// - /// The output stream. - /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. - virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; - - /// - /// Deserialize object from the input stream - /// - /// The input stream. - /// The deserialization modifier object. Always valid. - virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; - - /// - /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. - /// - /// The input stream. - /// The input stream member to lookup. - /// The deserialization modifier object. Always valid. - void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier); -}; +// ISerializable moved to Core module +#include "Engine/Core/ISerializable.h" From cd8a27ca2630ac283a035c5b42d0b16802eb4647 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Jan 2021 14:20:00 +0100 Subject: [PATCH 020/222] Fix typo --- Source/Shaders/Lighting.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index f626afa0b..62b1e7aa4 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -4,7 +4,7 @@ #define __LIGHTING__ #if !defined(USE_GBUFFER_CUSTOM_DATA) -#error "Canot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." +#error "Cannot calculate lighting without custom data in GBuffer. Define USE_GBUFFER_CUSTOM_DATA." #endif #include "./Flax/LightingCommon.hlsl" From 953f8da84db61a81ab88d1ff8dd794fd8b1f7cef Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Jan 2021 14:25:21 +0100 Subject: [PATCH 021/222] Update copyright year --- Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/EnumInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/EventInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/FieldInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/FileInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/FunctionInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/LangType.cs | 2 +- Source/Tools/Flax.Build/Bindings/MemberInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/ModuleInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/PropertyInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/StructureInfo.cs | 2 +- Source/Tools/Flax.Build/Bindings/TypeInfo.cs | 2 +- Source/Tools/Flax.Build/Build/Sdk.cs | 2 +- Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs | 2 +- Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs | 2 +- Source/Tools/Flax.Build/Utilities/Tokenizer.cs | 2 +- Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 06cb55257..814983e9b 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs index eeb750255..bf9fc8132 100644 --- a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Bindings/EventInfo.cs b/Source/Tools/Flax.Build/Bindings/EventInfo.cs index 7d98979b4..de93332ac 100644 --- a/Source/Tools/Flax.Build/Bindings/EventInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EventInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs index 29c8b0617..509c1d6e6 100644 --- a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/FileInfo.cs b/Source/Tools/Flax.Build/Bindings/FileInfo.cs index 38f2e491f..25e1e1944 100644 --- a/Source/Tools/Flax.Build/Bindings/FileInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FileInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; diff --git a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs index 81ea02796..953fd7ed4 100644 --- a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs b/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs index 01ab2e292..4a8673660 100644 --- a/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs index 7deee98c6..dfdb551f6 100644 --- a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/LangType.cs b/Source/Tools/Flax.Build/Bindings/LangType.cs index d74560bc6..7f5f2da03 100644 --- a/Source/Tools/Flax.Build/Bindings/LangType.cs +++ b/Source/Tools/Flax.Build/Bindings/LangType.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; diff --git a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs index 63034f57a..3e09b9f0b 100644 --- a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 554d46c01..6626145a9 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs index b36821511..f53578b4e 100644 --- a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index baf80a6fe..be24bb1f0 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 8af061200..c1ac79ae7 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Build/Sdk.cs b/Source/Tools/Flax.Build/Build/Sdk.cs index 816404679..450d8f43f 100644 --- a/Source/Tools/Flax.Build/Build/Sdk.cs +++ b/Source/Tools/Flax.Build/Build/Sdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs index f6a7816cf..d02f440ae 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.IO; diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs index d2d0278bf..ec83fb90f 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.IO; diff --git a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs index 70d0d7fc1..6913ec31a 100644 --- a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs +++ b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs b/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs index 0347a88a8..ec9f71091 100644 --- a/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs +++ b/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; From b38ccb0fbd93df57f481c9cf73d6a43d0230dea1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 4 Jan 2021 14:30:33 +0100 Subject: [PATCH 022/222] Remove submodules --- .gitmodules | 6 ------ Source/Platforms/PS4 | 1 - Source/Platforms/XboxScarlett | 1 - 3 files changed, 8 deletions(-) delete mode 160000 Source/Platforms/PS4 delete mode 160000 Source/Platforms/XboxScarlett diff --git a/.gitmodules b/.gitmodules index 36d11c4bd..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +0,0 @@ -[submodule "Source/Platforms/PS4"] - path = Source/Platforms/PS4 - url = https://gitlab.flaxengine.com/flax/flaxengine-ps4.git -[submodule "Source/Platforms/XboxScarlett"] - path = Source/Platforms/XboxScarlett - url = https://gitlab.flaxengine.com/flax/flaxengine-xboxscarlett.git diff --git a/Source/Platforms/PS4 b/Source/Platforms/PS4 deleted file mode 160000 index b9e29ede6..000000000 --- a/Source/Platforms/PS4 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b9e29ede69d31f93cbdd012d6e6c4ad8120dc7c0 diff --git a/Source/Platforms/XboxScarlett b/Source/Platforms/XboxScarlett deleted file mode 160000 index 7691bbaf2..000000000 --- a/Source/Platforms/XboxScarlett +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7691bbaf2a7e47459d9fb18fd1a8833cdbd7517a From be319c446dac20eb505e6e97ab475d02a4904605 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Jan 2021 14:14:34 +0100 Subject: [PATCH 023/222] Refactor settings types to use scripting API --- .../Settings/AndroidPlatformSettings.cs | 30 -- .../Editor/Content/Settings/AudioSettings.cs | 34 --- .../Editor/Content/Settings/BuildSettings.cs | 67 +---- .../Editor/Content/Settings/GameSettings.cs | 23 +- .../Content/Settings/GraphicsSettings.cs | 61 ----- .../Editor/Content/Settings/InputSettings.cs | 5 +- .../Content/Settings/LayersAndTagsSettings.cs | 5 +- .../Content/Settings/LinuxPlatformSettings.cs | 60 ---- .../Content/Settings/PhysicsSettings.cs | 97 +------ .../Editor/Content/Settings/TimeSettings.cs | 48 ---- .../Content/Settings/UWPPlatformSettings.cs | 97 ------- .../Settings/WindowsPlatformSettings.cs | 90 ------ Source/Editor/Cooker/GameCooker.cpp | 42 --- .../Platform/Android/AndroidPlatformTools.cpp | 20 +- .../Platform/Linux/LinuxPlatformTools.cpp | 6 +- .../Cooker/Platform/UWP/UWPPlatformTools.cpp | 17 +- .../Platform/Windows/WindowsPlatformTools.cpp | 6 +- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 39 +-- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 5 +- Source/Editor/Cooker/Steps/ValidateStep.cpp | 13 +- Source/Editor/Editor.Build.cs | 2 +- .../Editor/Managed/ManagedEditor.Internal.cpp | 18 +- Source/Editor/Utilities/EditorUtilities.cpp | 6 +- Source/Engine/Audio/Audio.cpp | 22 +- Source/Engine/Audio/AudioSettings.h | 19 +- .../Engine/Audio/OpenAL/AudioBackendOAL.cpp | 2 +- .../Audio/XAudio2/AudioBackendXAudio2.cpp | 2 +- Source/Engine/Content/JsonAsset.cpp | 2 + Source/Engine/Content/JsonAsset.h | 6 +- Source/Engine/Core/Config/BuildSettings.h | 32 ++- Source/Engine/Core/Config/GameSettings.cpp | 259 ++++++++++-------- Source/Engine/Core/Config/GameSettings.h | 56 +++- Source/Engine/Core/Config/GraphicsSettings.h | 29 +- .../Engine/Core/Config/LayersTagsSettings.h | 39 +-- Source/Engine/Core/Config/Settings.h | 85 ++---- Source/Engine/Core/Config/TimeSettings.h | 35 +-- Source/Engine/Core/Math/Math.cpp | 4 +- Source/Engine/Engine/Base/GameBase.cpp | 7 +- Source/Engine/Engine/Engine.cpp | 17 +- Source/Engine/Engine/Linux/LinuxGame.cpp | 4 +- Source/Engine/Engine/Time.cpp | 28 +- Source/Engine/Engine/Time.h | 2 + Source/Engine/Engine/Windows/WindowsGame.cpp | 4 +- .../DirectX/DX11/GPUDeviceDX11.cpp | 2 +- .../DirectX/DX12/GPUDeviceDX12.cpp | 2 +- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 2 +- Source/Engine/Input/Input.cpp | 6 + Source/Engine/Input/Input.h | 1 - Source/Engine/Input/InputSettings.h | 24 +- Source/Engine/Level/Actor.cpp | 17 +- Source/Engine/Level/Level.cpp | 32 +++ Source/Engine/Level/Level.h | 25 ++ Source/Engine/Navigation/NavMeshBuilder.cpp | 4 +- Source/Engine/Navigation/Navigation.cpp | 6 + .../Engine/Navigation/NavigationSettings.cs | 8 + Source/Engine/Navigation/NavigationSettings.h | 24 +- Source/Engine/Physics/Colliders/Collider.cpp | 2 +- Source/Engine/Physics/PhysicalMaterial.cpp | 52 ---- Source/Engine/Physics/Physics.cpp | 216 ++++++++++++--- Source/Engine/Physics/Physics.h | 5 + Source/Engine/Physics/PhysicsSettings.cpp | 82 ------ Source/Engine/Physics/PhysicsSettings.h | 161 ++++++----- Source/Engine/Physics/Utilities.h | 32 +-- .../Android/AndroidPlatformSettings.h | 24 +- .../Platform/Linux/LinuxPlatformSettings.h | 41 +-- Source/Engine/Platform/Platform.Build.cs | 10 + .../Engine/Platform/UWP/UWPPlatformSettings.h | 27 +- .../Windows/WindowsPlatformSettings.h | 45 +-- Source/Engine/Terrain/Terrain.cpp | 2 +- .../Bindings/BindingsGenerator.CSharp.cs | 14 + .../Bindings/BindingsGenerator.Cpp.cs | 11 +- .../Bindings/BindingsGenerator.Parsing.cs | 3 +- .../Flax.Build/Bindings/BindingsGenerator.cs | 45 +-- Source/Tools/Flax.Build/Bindings/FieldInfo.cs | 5 + Source/Tools/Flax.Build/Build/Module.cs | 11 + 75 files changed, 955 insertions(+), 1431 deletions(-) delete mode 100644 Source/Editor/Content/Settings/AndroidPlatformSettings.cs delete mode 100644 Source/Editor/Content/Settings/AudioSettings.cs delete mode 100644 Source/Editor/Content/Settings/GraphicsSettings.cs delete mode 100644 Source/Editor/Content/Settings/LinuxPlatformSettings.cs delete mode 100644 Source/Editor/Content/Settings/TimeSettings.cs delete mode 100644 Source/Editor/Content/Settings/UWPPlatformSettings.cs delete mode 100644 Source/Editor/Content/Settings/WindowsPlatformSettings.cs create mode 100644 Source/Engine/Navigation/NavigationSettings.cs delete mode 100644 Source/Engine/Physics/PhysicalMaterial.cpp delete mode 100644 Source/Engine/Physics/PhysicsSettings.cpp diff --git a/Source/Editor/Content/Settings/AndroidPlatformSettings.cs b/Source/Editor/Content/Settings/AndroidPlatformSettings.cs deleted file mode 100644 index 2c540109f..000000000 --- a/Source/Editor/Content/Settings/AndroidPlatformSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Android platform settings asset archetype. Allows to edit asset via editor. - /// - public class AndroidPlatformSettings : SettingsBase - { - /// - /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. - /// - [EditorOrder(0), EditorDisplay("General"), Tooltip("The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.")] - public string PackageName = "com.${COMPANY_NAME}.${PROJECT_NAME}"; - - /// - /// The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file. - /// - [EditorOrder(100), EditorDisplay("General"), Tooltip("The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file.")] - public string[] Permissions; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - } -} diff --git a/Source/Editor/Content/Settings/AudioSettings.cs b/Source/Editor/Content/Settings/AudioSettings.cs deleted file mode 100644 index f011cad46..000000000 --- a/Source/Editor/Content/Settings/AudioSettings.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The audio payback engine settings container. Allows to edit asset via editor. - /// - public sealed class AudioSettings : SettingsBase - { - /// - /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. - /// - [DefaultValue(false)] - [EditorOrder(0), EditorDisplay("General"), Tooltip("If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine.")] - public bool DisableAudio; - - /// - /// The doppler doppler effect factor. Scale for source and listener velocities. Default is 1. - /// - [DefaultValue(1.0f)] - [EditorOrder(100), EditorDisplay("General"), Limit(0, 10.0f, 0.01f), Tooltip("The doppler doppler effect factor. Scale for source and listener velocities. Default is 1.")] - public float DopplerFactor = 1.0f; - - /// - /// True if mute all audio playback when game has no use focus. - /// - [DefaultValue(true)] - [EditorOrder(200), EditorDisplay("General", "Mute On Focus Loss"), Tooltip("If checked, engine will mute all audio playback when game has no use focus.")] - public bool MuteOnFocusLoss = true; - } -} diff --git a/Source/Editor/Content/Settings/BuildSettings.cs b/Source/Editor/Content/Settings/BuildSettings.cs index e74a5fd41..87530f1fb 100644 --- a/Source/Editor/Content/Settings/BuildSettings.cs +++ b/Source/Editor/Content/Settings/BuildSettings.cs @@ -1,77 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; -using System.ComponentModel; using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The game building settings container. Allows to edit asset via editor. - /// - public sealed class BuildSettings : SettingsBase + partial class BuildSettings { - /// - /// The maximum amount of assets to include into a single assets package. Assets will be split into several packages if need to. - /// - [DefaultValue(4096)] - [EditorOrder(10), Limit(32, short.MaxValue), EditorDisplay("General", "Max assets per package"), Tooltip("The maximum amount of assets to include into a single assets package. Assets will be split into several packages if need to.")] - public int MaxAssetsPerPackage = 4096; - - /// - /// The maximum size of the single assets package (in megabytes). Assets will be split into several packages if need to. - /// - [DefaultValue(1024)] - [EditorOrder(20), Limit(16, short.MaxValue), EditorDisplay("General", "Max package size (in MB)"), Tooltip("The maximum size of the single assets package (in megabytes). Assets will be split into several packages if need to.")] - public int MaxPackageSizeMB = 1024; - - /// - /// The game content cooking Keys. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building. - /// - [DefaultValue(0)] - [EditorOrder(30), EditorDisplay("General"), Tooltip("The game content cooking Keys. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building.")] - public int ContentKey = 0; - - /// - /// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett). - /// - [DefaultValue(false)] - [EditorOrder(40), EditorDisplay("General"), Tooltip("If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett).")] - public bool ForDistribution; - - /// - /// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC. - /// - [DefaultValue(false)] - [EditorOrder(50), EditorDisplay("General"), Tooltip("If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC.")] - public bool SkipPackaging; - - /// - /// The additional assets to include into build (into root assets set). - /// - [EditorOrder(1000), EditorDisplay("Additional Data"), Tooltip("The additional assets to include into build (into root assets set).")] - public Asset[] AdditionalAssets; - - /// - /// The additional folders with assets to include into build (into root assets set). List of paths relative to the project directory (or absolute). - /// - [EditorOrder(1010), EditorDisplay("Additional Data"), Tooltip("The additional folders with assets to include into build (to root assets set). List of paths relative to the project directory (or absolute).")] - public string[] AdditionalAssetFolders; - - /// - /// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. - /// - [DefaultValue(false)] - [EditorOrder(2000), EditorDisplay("Content", "Shaders No Optimize"), Tooltip("Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.")] - public bool ShadersNoOptimize; - - /// - /// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend). - /// - [DefaultValue(false)] - [EditorOrder(2010), EditorDisplay("Content"), Tooltip("Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend).")] - public bool ShadersGenerateDebugData; - /// /// The build presets. /// diff --git a/Source/Editor/Content/Settings/GameSettings.cs b/Source/Editor/Content/Settings/GameSettings.cs index 28c13c905..eaca85c55 100644 --- a/Source/Editor/Content/Settings/GameSettings.cs +++ b/Source/Editor/Content/Settings/GameSettings.cs @@ -7,32 +7,11 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The game settings asset archetype. Allows to edit asset via editor. - /// - public sealed class GameSettings : SettingsBase + partial class GameSettings { internal const string PS4PlatformSettingsTypename = "FlaxEditor.Content.Settings.PS4PlatformSettings"; internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings"; - /// - /// The product full name. - /// - [EditorOrder(0), EditorDisplay("General"), Tooltip("The name of your product.")] - public string ProductName; - - /// - /// The company full name. - /// - [EditorOrder(10), EditorDisplay("General"), Tooltip("The name of your company or organization.")] - public string CompanyName; - - /// - /// The copyright note used for content signing (eg. source code header). - /// - [EditorOrder(15), EditorDisplay("General"), Tooltip("The copyright note used for content signing (eg. source code header).")] - public string CopyrightNotice; - /// /// The default application icon. /// diff --git a/Source/Editor/Content/Settings/GraphicsSettings.cs b/Source/Editor/Content/Settings/GraphicsSettings.cs deleted file mode 100644 index 731bc750d..000000000 --- a/Source/Editor/Content/Settings/GraphicsSettings.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The graphics rendering settings container. Allows to edit asset via editor. To modify those settings at runtime use . - /// - /// - public sealed class GraphicsSettings : SettingsBase - { - /// - /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. - /// - [EditorOrder(20), EditorDisplay("General", "Use V-Sync"), Tooltip("Enables rendering synchronization with the refresh rate of the display device to avoid \"tearing\" artifacts.")] - public bool UseVSync = false; - - /// - /// Anti Aliasing quality setting. - /// - [EditorOrder(1000), EditorDisplay("Quality", "AA Quality"), Tooltip("Anti Aliasing quality.")] - public Quality AAQuality = Quality.Medium; - - /// - /// Screen Space Reflections quality. - /// - [EditorOrder(1100), EditorDisplay("Quality", "SSR Quality"), Tooltip("Screen Space Reflections quality.")] - public Quality SSRQuality = Quality.Medium; - - /// - /// Screen Space Ambient Occlusion quality setting. - /// - [EditorOrder(1200), EditorDisplay("Quality", "SSAO Quality"), Tooltip("Screen Space Ambient Occlusion quality setting.")] - public Quality SSAOQuality = Quality.Medium; - - /// - /// Volumetric Fog quality setting. - /// - [EditorOrder(1250), EditorDisplay("Quality", "Volumetric Fog Quality"), Tooltip("Volumetric Fog quality setting.")] - public Quality VolumetricFogQuality = Quality.High; - - /// - /// The shadows quality. - /// - [EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")] - public Quality ShadowsQuality = Quality.Medium; - - /// - /// The shadow maps quality (textures resolution). - /// - [EditorOrder(1310), EditorDisplay("Quality", "Shadow Maps Quality"), Tooltip("The shadow maps quality (textures resolution).")] - public Quality ShadowMapsQuality = Quality.Medium; - - /// - /// Enables cascades splits blending for directional light shadows. - /// - [EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")] - public bool AllowCSMBlending = false; - } -} diff --git a/Source/Editor/Content/Settings/InputSettings.cs b/Source/Editor/Content/Settings/InputSettings.cs index 69a8dfba3..03ed4b7c7 100644 --- a/Source/Editor/Content/Settings/InputSettings.cs +++ b/Source/Editor/Content/Settings/InputSettings.cs @@ -4,10 +4,7 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The input settings container. Allows to edit asset via editor. - /// - public sealed class InputSettings : SettingsBase + partial class InputSettings { /// /// Maps a discrete button or key press events to a "friendly name" that will later be bound to event-driven behavior. The end effect is that pressing (and/or releasing) a key, mouse button, or keypad button. diff --git a/Source/Editor/Content/Settings/LayersAndTagsSettings.cs b/Source/Editor/Content/Settings/LayersAndTagsSettings.cs index 5f3884ee1..31e8dfff8 100644 --- a/Source/Editor/Content/Settings/LayersAndTagsSettings.cs +++ b/Source/Editor/Content/Settings/LayersAndTagsSettings.cs @@ -6,10 +6,7 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The layers and objects tags settings. Allows to edit asset via editor. - /// - public sealed class LayersAndTagsSettings : SettingsBase + partial class LayersAndTagsSettings { /// /// The tag names. diff --git a/Source/Editor/Content/Settings/LinuxPlatformSettings.cs b/Source/Editor/Content/Settings/LinuxPlatformSettings.cs deleted file mode 100644 index 8b26e77d4..000000000 --- a/Source/Editor/Content/Settings/LinuxPlatformSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Linux platform settings asset archetype. Allows to edit asset via editor. - /// - public class LinuxPlatformSettings : SettingsBase - { - /// - /// The default game window mode. - /// - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The default game window mode.")] - public GameWindowMode WindowMode = GameWindowMode.Windowed; - - /// - /// The default game window width (in pixels). - /// - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The default game window width (in pixels).")] - public int ScreenWidth = 1280; - - /// - /// The default game window height (in pixels). - /// - [EditorOrder(30), EditorDisplay("Window"), Tooltip("The default game window height (in pixels).")] - public int ScreenHeight = 720; - - /// - /// Enables resizing the game window by the user. - /// - [EditorOrder(40), EditorDisplay("Window"), Tooltip("Enables resizing the game window by the user.")] - public bool ResizableWindow = false; - - /// - /// Enables game running when application window loses focus. - /// - [EditorOrder(1010), EditorDisplay("Other", "Run In Background"), Tooltip("Enables game running when application window loses focus.")] - public bool RunInBackground = false; - - /// - /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. - /// - [EditorOrder(1020), EditorDisplay("Other"), Tooltip("Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once.")] - public bool ForceSingleInstance = false; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - - /// - /// Enables support for Vulkan. Disabling it reduces compiled shaders count. - /// - [EditorOrder(2020), EditorDisplay("Graphics", "Support Vulkan"), Tooltip("Enables support for Vulkan. Disabling it reduces compiled shaders count.")] - public bool SupportVulkan = true; - } -} diff --git a/Source/Editor/Content/Settings/PhysicsSettings.cs b/Source/Editor/Content/Settings/PhysicsSettings.cs index 540b11772..d217512b3 100644 --- a/Source/Editor/Content/Settings/PhysicsSettings.cs +++ b/Source/Editor/Content/Settings/PhysicsSettings.cs @@ -1,112 +1,17 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System.ComponentModel; using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The physics simulation settings container. Allows to edit asset via editor. - /// - public sealed class PhysicsSettings : SettingsBase + partial class PhysicsSettings { - /// - /// The default gravity force value (in cm^2/s). - /// - [DefaultValue(typeof(Vector3), "0,-981.0,0")] - [EditorOrder(0), EditorDisplay("Simulation"), Tooltip("The default gravity force value (in cm^2/s).")] - public Vector3 DefaultGravity = new Vector3(0, -981.0f, 0); - - /// - /// 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. - /// - [DefaultValue(true)] - [EditorOrder(10), EditorDisplay("Simulation"), Tooltip("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.")] - public bool QueriesHitTriggers = true; - - /// - /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. - /// - [DefaultValue(5.0f)] - [EditorOrder(20), EditorDisplay("Simulation"), Limit(-1, 10), Tooltip("Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable.")] - public float TriangleMeshTriangleMinAreaThreshold = 5.0f; - - /// - /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity - /// - [DefaultValue(200.0f)] - [EditorOrder(30), EditorDisplay("Simulation"), Limit(0), Tooltip("Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity")] - public float BounceThresholdVelocity = 200.0f; - - /// - /// Default friction combine mode, controls how friction is computed for multiple materials. - /// - [DefaultValue(PhysicsCombineMode.Average)] - [EditorOrder(40), EditorDisplay("Simulation"), Tooltip("Default friction combine mode, controls how friction is computed for multiple materials.")] - public PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode.Average; - - /// - /// Default restitution combine mode, controls how restitution is computed for multiple materials. - /// - [DefaultValue(PhysicsCombineMode.Average)] - [EditorOrder(50), EditorDisplay("Simulation"), Tooltip("Default restitution combine mode, controls how restitution is computed for multiple materials.")] - public PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode.Average; - - /// - /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for PhysX to check it internally. - /// - [DefaultValue(false)] - [EditorOrder(70), EditorDisplay("Simulation", "Disable CCD"), Tooltip("If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for PhysX to check it internally.")] - public bool DisableCCD; - - /// - /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. - /// - [DefaultValue(false)] - [EditorOrder(80), EditorDisplay("Simulation"), Tooltip("Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts.")] - public bool EnableAdaptiveForce; - - /// - /// The maximum allowed delta time (in seconds) for the physics simulation step. - /// - [DefaultValue(1.0f / 10.0f)] - [EditorOrder(1000), EditorDisplay("Framerate"), Limit(0.0013f, 2.0f), Tooltip("The maximum allowed delta time (in seconds) for the physics simulation step.")] - public float MaxDeltaTime = 1.0f / 10.0f; - - /// - /// Whether to substep the physics simulation. - /// - [DefaultValue(false)] - [EditorOrder(1005), EditorDisplay("Framerate"), Tooltip("Whether to substep the physics simulation.")] - public bool EnableSubstepping; - - /// - /// Delta time (in seconds) for an individual simulation substep. - /// - [DefaultValue(1.0f / 120.0f)] - [EditorOrder(1010), EditorDisplay("Framerate"), Limit(0.0013f, 1.0f), Tooltip("Delta time (in seconds) for an individual simulation substep.")] - public float SubstepDeltaTime = 1.0f / 120.0f; - - /// - /// The maximum number of substeps for physics simulation. - /// - [DefaultValue(5)] - [EditorOrder(1020), EditorDisplay("Framerate"), Limit(1, 16), Tooltip("The maximum number of substeps for physics simulation.")] - public int MaxSubsteps = 5; - /// /// The collision layers masks. Used to define layer-based collision detection. /// [EditorOrder(1040), EditorDisplay("Layers Matrix"), CustomEditor(typeof(FlaxEditor.CustomEditors.Dedicated.LayersMatrixEditor))] public uint[] LayerMasks = new uint[32]; - /// - /// Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders. - /// - [DefaultValue(false)] - [EditorOrder(1100), EditorDisplay("Other", "Support Cooking At Runtime"), Tooltip("Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders.")] - public bool SupportCookingAtRuntime; - /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/Content/Settings/TimeSettings.cs b/Source/Editor/Content/Settings/TimeSettings.cs deleted file mode 100644 index af352f5e1..000000000 --- a/Source/Editor/Content/Settings/TimeSettings.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The time settings asset archetype. Allows to edit asset via editor. - /// - public sealed class TimeSettings : SettingsBase - { - /// - /// The target amount of the game logic updates per second (script updates frequency). Use 0 for infinity. - /// - [DefaultValue(30.0f)] - [EditorOrder(1), Limit(0, 1000), EditorDisplay(null, "Update FPS"), Tooltip("Target amount of the game logic updates per second (script updates frequency). Use 0 for infinity.")] - public float UpdateFPS = 30.0f; - - /// - /// The target amount of the physics simulation updates per second (also fixed updates frequency). Use 0 for infinity. - /// - [DefaultValue(60.0f)] - [EditorOrder(2), Limit(0, 1000), EditorDisplay(null, "Physics FPS"), Tooltip("Target amount of the physics simulation updates per second (also fixed updates frequency). Use 0 for infinity.")] - public float PhysicsFPS = 60.0f; - - /// - /// The target amount of the frames rendered per second (actual game FPS). Use 0 for infinity. - /// - [DefaultValue(60.0f)] - [EditorOrder(3), Limit(0, 1000), EditorDisplay(null, "Draw FPS"), Tooltip("Target amount of the frames rendered per second (actual game FPS). Use 0 for infinity.")] - public float DrawFPS = 60.0f; - - /// - /// The game time scale factor. Default is 1. - /// - [DefaultValue(1.0f)] - [EditorOrder(10), Limit(0, 1000.0f, 0.1f), Tooltip("Game time scaling factor. Default is 1 for real-time simulation.")] - public float TimeScale = 1.0f; - - /// - /// The maximum allowed delta time (in seconds) for the game logic update step. - /// - [DefaultValue(1.0f / 10.0f)] - [EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), Tooltip("The maximum allowed delta time (in seconds) for the game logic update step.")] - public float MaxUpdateDeltaTime = 1.0f / 10.0f; - } -} diff --git a/Source/Editor/Content/Settings/UWPPlatformSettings.cs b/Source/Editor/Content/Settings/UWPPlatformSettings.cs deleted file mode 100644 index 5c5696936..000000000 --- a/Source/Editor/Content/Settings/UWPPlatformSettings.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Universal Windows Platform (UWP) platform settings asset archetype. Allows to edit asset via editor. - /// - public class UWPPlatformSettings : SettingsBase - { - /// - /// The preferred launch windowing mode. - /// - public enum WindowMode - { - /// - /// The full screen mode - /// - FullScreen = 0, - - /// - /// The view size. - /// - ViewSize = 1, - } - - /// - /// The display orientation modes. Can be combined as flags. - /// - [Flags] - public enum DisplayOrientations - { - /// - /// The none. - /// - None = 0, - - /// - /// The landscape. - /// - Landscape = 1, - - /// - /// The landscape flipped. - /// - LandscapeFlipped = 2, - - /// - /// The portrait. - /// - Portrait = 4, - - /// - /// The portrait flipped. - /// - PortraitFlipped = 8, - } - - /// - /// The preferred launch windowing mode. Always fullscreen on Xbox. - /// - [DefaultValue(WindowMode.FullScreen)] - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The preferred launch windowing mode. Always fullscreen on Xbox.")] - public WindowMode PreferredLaunchWindowingMode = WindowMode.FullScreen; - - /// - /// The display orientation modes. Can be combined as flags. - /// - [DefaultValue(DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped)] - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The display orientation modes. Can be combined as flags.")] - public DisplayOrientations AutoRotationPreferences = DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped; - - /// - /// The location of the package certificate (relative to the project). - /// - [DefaultValue("")] - [EditorOrder(1010), EditorDisplay("Other"), Tooltip("The location of the package certificate (relative to the project).")] - public string CertificateLocation = string.Empty; - - /// - /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. - /// - [DefaultValue(true)] - [EditorOrder(2000), EditorDisplay("Graphics", "Support DirectX 11"), Tooltip("Enables support for DirectX 11. Disabling it reduces compiled shaders count.")] - public bool SupportDX11 = true; - - /// - /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2010), EditorDisplay("Graphics", "Support DirectX 10"), Tooltip("Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count.")] - public bool SupportDX10 = false; - } -} diff --git a/Source/Editor/Content/Settings/WindowsPlatformSettings.cs b/Source/Editor/Content/Settings/WindowsPlatformSettings.cs deleted file mode 100644 index dc873e9f4..000000000 --- a/Source/Editor/Content/Settings/WindowsPlatformSettings.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Windows platform settings asset archetype. Allows to edit asset via editor. - /// - public class WindowsPlatformSettings : SettingsBase - { - /// - /// The default game window mode. - /// - [DefaultValue(GameWindowMode.Windowed)] - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The default game window mode.")] - public GameWindowMode WindowMode = GameWindowMode.Windowed; - - /// - /// The default game window width (in pixels). - /// - [DefaultValue(1280)] - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The default game window width (in pixels).")] - public int ScreenWidth = 1280; - - /// - /// The default game window height (in pixels). - /// - [DefaultValue(720)] - [EditorOrder(30), EditorDisplay("Window"), Tooltip("The default game window height (in pixels).")] - public int ScreenHeight = 720; - - /// - /// Enables resizing the game window by the user. - /// - [DefaultValue(false)] - [EditorOrder(40), EditorDisplay("Window"), Tooltip("Enables resizing the game window by the user.")] - public bool ResizableWindow = false; - - /// - /// Enables game running when application window loses focus. - /// - [DefaultValue(false)] - [EditorOrder(1010), EditorDisplay("Other", "Run In Background"), Tooltip("Enables game running when application window loses focus.")] - public bool RunInBackground = false; - - /// - /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. - /// - [DefaultValue(false)] - [EditorOrder(1020), EditorDisplay("Other"), Tooltip("Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once.")] - public bool ForceSingleInstance = false; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [DefaultValue(null)] - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - - /// - /// Enables support for DirectX 12. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2000), EditorDisplay("Graphics", "Support DirectX 12"), Tooltip("Enables support for DirectX 12. Disabling it reduces compiled shaders count.")] - public bool SupportDX12 = false; - - /// - /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. - /// - [DefaultValue(true)] - [EditorOrder(2010), EditorDisplay("Graphics", "Support DirectX 11"), Tooltip("Enables support for DirectX 11. Disabling it reduces compiled shaders count.")] - public bool SupportDX11 = true; - - /// - /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2020), EditorDisplay("Graphics", "Support DirectX 10"), Tooltip("Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count.")] - public bool SupportDX10 = false; - - /// - /// Enables support for Vulkan. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2030), EditorDisplay("Graphics", "Support Vulkan"), Tooltip("Enables support for Vulkan. Disabling it reduces compiled shaders count.")] - public bool SupportVulkan = false; - } -} diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 1fdf0755f..d68ca587a 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -43,53 +43,11 @@ #endif #if PLATFORM_TOOLS_XBOX_SCARLETT #include "Platforms/XboxScarlett/Editor/PlatformTools/XboxScarlettPlatformTools.h" -#include "Platforms/XboxScarlett/Engine/Platform/XboxScarlettPlatformSettings.h" #endif #if PLATFORM_TOOLS_ANDROID #include "Platform/Android/AndroidPlatformTools.h" -#include "Engine/Platform/Android/AndroidPlatformSettings.h" #endif -void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data) -{ -#define LOAD_SETTINGS(nodeName, settingsType) \ - { \ - Guid id = JsonTools::GetGuid(data, nodeName); \ - if (id.IsValid()) \ - { \ - AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset) \ - { \ - if (!subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ - } \ - else \ - { LOG(Warning, "Cannot load " nodeName " settings"); } \ - } \ - } -#if PLATFORM_TOOLS_WINDOWS - LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); -#endif -#if PLATFORM_TOOLS_UWP || PLATFORM_TOOLS_XBOX_ONE - LOAD_SETTINGS("UWPPlatform", UWPPlatformSettings); -#endif -#if PLATFORM_TOOLS_LINUX - LOAD_SETTINGS("LinuxPlatform", LinuxPlatformSettings); -#endif -#if PLATFORM_TOOLS_PS4 - LOAD_SETTINGS("PS4Platform", PS4PlatformSettings); -#endif -#if PLATFORM_TOOLS_XBOX_SCARLETT - LOAD_SETTINGS("XboxScarlettPlatform", XboxScarlettPlatformSettings); -#endif -#if PLATFORM_TOOLS_ANDROID - LOAD_SETTINGS("AndroidPlatform", AndroidPlatformSettings); -#endif -} - namespace GameCookerImpl { MMethod* Internal_OnEvent = nullptr; diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index bce6aa860..b687e5a2b 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -12,6 +12,10 @@ #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); namespace { @@ -106,7 +110,8 @@ void AndroidPlatformTools::OnBuildStarted(CookingData& data) bool AndroidPlatformTools::OnPostProcess(CookingData& data) { - const auto platformSettings = AndroidPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = AndroidPlatformSettings::Get(); const auto platformDataPath = data.GetPlatformBinariesRoot(); const auto assetsPath = data.OutputPath; const auto jniLibsPath = data.OriginalOutputPath / TEXT("app/jniLibs"); @@ -125,11 +130,11 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Setup package name (eg. com.company.project) String packageName = platformSettings->PackageName; { - String productName = GameSettings::ProductName; + String productName = gameSettings->ProductName; productName.Replace(TEXT(" "), TEXT("")); productName.Replace(TEXT("."), TEXT("")); productName.Replace(TEXT("-"), TEXT("")); - String companyName = GameSettings::CompanyName; + String companyName = gameSettings->CompanyName; companyName.Replace(TEXT(" "), TEXT("")); companyName.Replace(TEXT("."), TEXT("")); companyName.Replace(TEXT("-"), TEXT("")); @@ -235,7 +240,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidPermissions}"), permissions); EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidAttributes}"), attributes); const String stringsPath = data.OriginalOutputPath / TEXT("app/src/main/res/values/strings.xml"); - EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), GameSettings::ProductName); + EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), gameSettings->ProductName); // Deploy native binaries to the output location (per-ABI) const String abiBinariesPath = jniLibsPath / abi; @@ -256,7 +261,8 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // TODO: expose event to inject custom gradle and manifest options or custom binaries into app - if (BuildSettings::Instance()->SkipPackaging) + const auto buildSettings = BuildSettings::Get(); + if (buildSettings->SkipPackaging) { return false; } @@ -286,7 +292,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) #else const Char* gradlew = TEXT("gradlew"); #endif - const bool distributionPackage = BuildSettings::Instance()->ForDistribution; + const bool distributionPackage = buildSettings->ForDistribution; const String gradleCommand = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug")); const int32 result = Platform::RunProcess(gradleCommand, data.OriginalOutputPath, envVars, true); if (result != 0) @@ -297,7 +303,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Copy result package const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk")); - const String outputApk = data.OriginalOutputPath / GameSettings::ProductName + TEXT(".apk"); + const String outputApk = data.OriginalOutputPath / gameSettings->ProductName + TEXT(".apk"); if (FileSystem::CopyFile(outputApk, apk)) { LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk); diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index eccc299bb..0f650da2c 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -9,6 +9,10 @@ #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); const Char* LinuxPlatformTools::GetDisplayName() const { @@ -32,7 +36,7 @@ ArchitectureType LinuxPlatformTools::GetArchitecture() const bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) { - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); const auto outputPath = data.OutputPath; // Copy binaries diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp index f6c846fed..7f7851941 100644 --- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp @@ -11,6 +11,10 @@ #include "Engine/Serialization/FileWriteStream.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Engine/Globals.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); bool UWPPlatformTools::UseAOT() const { @@ -37,7 +41,8 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool isXboxOne = data.Platform == BuildPlatform::XboxOne; const auto platformDataPath = Globals::StartupFolder / TEXT("Source/Platforms"); const auto uwpDataPath = platformDataPath / (isXboxOne ? TEXT("XboxOne") : TEXT("UWP")) / TEXT("Binaries"); - const auto platformSettings = UWPPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = UWPPlatformSettings::Get(); Array fileTemplate; // Copy binaries @@ -66,7 +71,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } } - const auto projectName = GameSettings::ProductName; + const auto projectName = gameSettings->ProductName; auto defaultNamespace = projectName; ScriptsBuilder::FilterNamespaceText(defaultNamespace); const StringAnsi projectGuid = "{3A9A2246-71DD-4567-9ABF-3E040310E30E}"; @@ -102,7 +107,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) // Generate new temp cert if missing if (!FileSystem::FileExists(dstCertificatePath)) { - if (EditorUtilities::GenerateCertificate(GameSettings::CompanyName, dstCertificatePath)) + if (EditorUtilities::GenerateCertificate(gameSettings->CompanyName, dstCertificatePath)) { LOG(Warning, "Failed to create certificate."); } @@ -159,8 +164,8 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) auto now = DateTime::Now(); file->WriteTextFormatted( (char*)fileTemplate.Get() - , GameSettings::ProductName.ToStringAnsi() - , GameSettings::CompanyName.ToStringAnsi() + , gameSettings->ProductName.ToStringAnsi() + , gameSettings->CompanyName.ToStringAnsi() , now.GetYear() ); hasError = file->HasError(); @@ -382,7 +387,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) file->WriteTextFormatted( (char*)fileTemplate.Get() , projectName.ToStringAnsi() // {0} Display Name - , GameSettings::CompanyName.ToStringAnsi() // {1} Company Name + , gameSettings->CompanyName.ToStringAnsi() // {1} Company Name , productId.ToStringAnsi() // {2} Product ID , defaultNamespace.ToStringAnsi() // {3} Default Namespace ); diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 35ed27639..348930dd5 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -8,6 +8,10 @@ #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); const Char* WindowsPlatformTools::GetDisplayName() const { @@ -31,7 +35,7 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) { - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); const auto& outputPath = data.OutputPath; // Apply executable icon diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 396626fc1..af6f003b8 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -160,8 +160,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data) // Invalidate shaders and assets with shaders if need to rebuild them bool invalidateShaders = false; - const bool shadersNoOptimize = BuildSettings::Instance()->ShadersNoOptimize; - const bool shadersGenerateDebugData = BuildSettings::Instance()->ShadersGenerateDebugData; + const auto buildSettings = BuildSettings::Get(); + const bool shadersNoOptimize = buildSettings->ShadersNoOptimize; + const bool shadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; if (shadersNoOptimize != Settings.Global.ShadersNoOptimize) { LOG(Info, "ShadersNoOptimize option has been modified."); @@ -175,7 +176,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_WINDOWS if (data.Platform == BuildPlatform::Windows32 || data.Platform == BuildPlatform::Windows64) { - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); const bool modified = Settings.Windows.SupportDX11 != settings->SupportDX11 || Settings.Windows.SupportDX10 != settings->SupportDX10 || @@ -190,7 +191,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_UWP if (data.Platform == BuildPlatform::UWPx86 || data.Platform == BuildPlatform::UWPx64) { - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); const bool modified = Settings.UWP.SupportDX11 != settings->SupportDX11 || Settings.UWP.SupportDX10 != settings->SupportDX10; @@ -204,7 +205,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_LINUX if (data.Platform == BuildPlatform::LinuxX64) { - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); const bool modified = Settings.Linux.SupportVulkan != settings->SupportVulkan; if (modified) @@ -369,7 +370,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::Windows64: { const char* platformDefineName = "PLATFORM_WINDOWS"; - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); if (settings->SupportDX12) { COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE); @@ -393,7 +394,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::UWPx64: { const char* platformDefineName = "PLATFORM_UWP"; - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); if (settings->SupportDX11) { COMPILE_PROFILE(DirectX_SM5, SHADER_FILE_CHUNK_INTERNAL_D3D_SM5_CACHE); @@ -415,7 +416,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::LinuxX64: { const char* platformDefineName = "PLATFORM_LINUX"; - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); if (settings->SupportVulkan) { COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); @@ -881,7 +882,8 @@ bool CookAssetsStep::Perform(CookingData& data) data.StepProgress(TEXT("Loading build cache"), 0); // Prepare - const auto buildSettings = BuildSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto buildSettings = BuildSettings::Get(); const int32 contentKey = buildSettings->ContentKey == 0 ? rand() : buildSettings->ContentKey; AssetsRegistry.Clear(); AssetPathsMapping.Clear(); @@ -892,22 +894,21 @@ bool CookAssetsStep::Perform(CookingData& data) // Update build settings { - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); cache.Settings.Windows.SupportDX11 = settings->SupportDX11; cache.Settings.Windows.SupportDX10 = settings->SupportDX10; cache.Settings.Windows.SupportVulkan = settings->SupportVulkan; } { - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); cache.Settings.UWP.SupportDX11 = settings->SupportDX11; cache.Settings.UWP.SupportDX10 = settings->SupportDX10; } { - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); cache.Settings.Linux.SupportVulkan = settings->SupportVulkan; } { - const auto buildSettings = BuildSettings::Instance(); cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize; cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; } @@ -1004,7 +1005,7 @@ bool CookAssetsStep::Perform(CookingData& data) // Create build game header { GameHeaderFlags gameFlags = GameHeaderFlags::None; - if (!GameSettings::NoSplashScreen) + if (!gameSettings->NoSplashScreen) gameFlags |= GameHeaderFlags::ShowSplashScreen; // Open file @@ -1022,17 +1023,17 @@ bool CookAssetsStep::Perform(CookingData& data) Array bytes; bytes.Resize(808 + sizeof(Guid)); Platform::MemoryClear(bytes.Get(), bytes.Count()); - int32 length = sizeof(Char) * GameSettings::ProductName.Length(); - Platform::MemoryCopy(bytes.Get() + 0, GameSettings::ProductName.Get(), length); + int32 length = sizeof(Char) * gameSettings->ProductName.Length(); + Platform::MemoryCopy(bytes.Get() + 0, gameSettings->ProductName.Get(), length); bytes[length] = 0; bytes[length + 1] = 0; - length = sizeof(Char) * GameSettings::CompanyName.Length(); - Platform::MemoryCopy(bytes.Get() + 400, GameSettings::CompanyName.Get(), length); + length = sizeof(Char) * gameSettings->CompanyName.Length(); + Platform::MemoryCopy(bytes.Get() + 400, gameSettings->CompanyName.Get(), length); bytes[length + 400] = 0; bytes[length + 401] = 0; *(int32*)(bytes.Get() + 800) = (int32)gameFlags; *(int32*)(bytes.Get() + 804) = contentKey; - *(Guid*)(bytes.Get() + 808) = GameSettings::SplashScreen; + *(Guid*)(bytes.Get() + 808) = gameSettings->SplashScreen; Encryption::EncryptBytes(bytes.Get(), bytes.Count()); stream->WriteArray(bytes); diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b5ea2d268..9ce3a8a5d 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -12,6 +12,7 @@ bool DeployDataStep::Perform(CookingData& data) { data.StepProgress(TEXT("Deploying engine data"), 0); const String depsRoot = data.GetPlatformBinariesRoot(); + const auto gameSettings = GameSettings::Get(); // Setup output folders and copy required data const auto contentDir = data.OutputPath / TEXT("Content"); @@ -74,7 +75,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/VolumetricFog")); data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial")); data.AddRootEngineAsset(TEXT("Engine/DefaultTerrainMaterial")); - if (!GameSettings::NoSplashScreen && !GameSettings::SplashScreen.IsValid()) + if (!gameSettings->NoSplashScreen && !gameSettings->SplashScreen.IsValid()) data.AddRootEngineAsset(TEXT("Engine/Textures/Logo")); data.AddRootEngineAsset(TEXT("Engine/Textures/NormalTexture")); data.AddRootEngineAsset(TEXT("Engine/Textures/BlackTexture")); @@ -98,7 +99,7 @@ bool DeployDataStep::Perform(CookingData& data) // Register game assets data.StepProgress(TEXT("Deploying game data"), 50); - auto& buildSettings = *BuildSettings::Instance(); + auto& buildSettings = *BuildSettings::Get(); for (auto& e : buildSettings.AdditionalAssets) data.AddRootAsset(e.GetID()); Array files; diff --git a/Source/Editor/Cooker/Steps/ValidateStep.cpp b/Source/Editor/Cooker/Steps/ValidateStep.cpp index 219301544..625957fae 100644 --- a/Source/Editor/Cooker/Steps/ValidateStep.cpp +++ b/Source/Editor/Cooker/Steps/ValidateStep.cpp @@ -38,18 +38,23 @@ bool ValidateStep::Perform(CookingData& data) #endif // Load game settings (may be modified via editor) - GameSettings::Load(); + if (GameSettings::Load()) + { + data.Error(TEXT("Failed to load game settings.")); + return true; + } data.AddRootAsset(Globals::ProjectContentFolder / TEXT("GameSettings.json")); // Validate game settings + auto gameSettings = GameSettings::Get(); { - if (GameSettings::ProductName.IsEmpty()) + if (gameSettings->ProductName.IsEmpty()) { data.Error(TEXT("Missing product name.")); return true; } - if (GameSettings::CompanyName.IsEmpty()) + if (gameSettings->CompanyName.IsEmpty()) { data.Error(TEXT("Missing company name.")); return true; @@ -58,7 +63,7 @@ bool ValidateStep::Perform(CookingData& data) // TODO: validate version AssetInfo info; - if (!Content::GetAssetInfo(GameSettings::FirstScene, info)) + if (!Content::GetAssetInfo(gameSettings->FirstScene, info)) { data.Error(TEXT("Missing first scene.")); return true; diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index a767ecac9..4d7d33d65 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -26,7 +26,7 @@ public class Editor : EditorModule // Platform Tools inside external platform implementation location options.PrivateDefinitions.Add(macro); options.SourcePaths.Add(externalPath); - options.SourceFiles.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", platform, "Engine", "Platform", platform + "PlatformSettings.cs")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", platform, "Engine", "Platform", platform + "PlatformSettings.cs")); } } } diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 4084c7f01..28d4df32a 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -24,7 +24,7 @@ #include "Engine/ContentImporters/ImportAudio.h" #include "Engine/ContentImporters/CreateCollisionData.h" #include "Engine/ContentImporters/CreateJson.h" -#include "Engine/Core/Config/LayersTagsSettings.h" +#include "Engine/Level/Level.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Cache.h" #include "Engine/CSG/CSGBuilder.h" @@ -302,22 +302,20 @@ namespace CustomEditorsUtilInternal } } -namespace LayersAndTagsSettingsInternal +namespace LayersAndTagsSettingsInternal1 { MonoArray* GetCurrentTags() { - auto settings = LayersAndTagsSettings::Instance(); - return MUtils::ToArray(settings->Tags); + return MUtils::ToArray(Level::Tags); } MonoArray* GetCurrentLayers() { - const auto settings = LayersAndTagsSettings::Instance(); - return MUtils::ToArray(Span(settings->Layers, Math::Max(1, settings->GetNonEmptyLayerNamesCount()))); + return MUtils::ToArray(Span(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount()))); } } -namespace GameSettingsInternal +namespace GameSettingsInternal1 { void Apply() { @@ -1054,9 +1052,9 @@ public: ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions); ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions); ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal::GetCurrentTags); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal::Apply); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal1::GetCurrentTags); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal1::GetCurrentLayers); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateVisualScript", &CreateVisualScript); diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index 1571c39d1..541c8892a 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -507,7 +507,11 @@ bool EditorUtilities::GetApplicationImage(const Guid& imageId, TextureData& imag AssetReference icon = Content::LoadAsync(imageId); if (icon == nullptr) { - icon = Content::LoadAsync(GameSettings::Icon); + const auto gameSettings = GameSettings::Get(); + if (gameSettings) + { + icon = Content::LoadAsync(gameSettings->Icon); + } } if (icon == nullptr) { diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp index 1dd0393b4..51f674e73 100644 --- a/Source/Engine/Audio/Audio.cpp +++ b/Source/Engine/Audio/Audio.cpp @@ -44,9 +44,14 @@ Array Audio::Devices; Action Audio::DevicesChanged; Action Audio::ActiveDeviceChanged; AudioBackend* AudioBackend::Instance = nullptr; -float MasterVolume = 1.0f; -float Volume = 1.0f; -int32 ActiveDeviceIndex = -1; + +namespace +{ + float MasterVolume = 1.0f; + float Volume = 1.0f; + int32 ActiveDeviceIndex = -1; + bool MuteOnFocusLoss = true; +} class AudioService : public EngineService { @@ -77,6 +82,11 @@ namespace } } +void AudioSettings::Apply() +{ + ::MuteOnFocusLoss = MuteOnFocusLoss; +} + AudioDevice* Audio::GetActiveDevice() { return &Devices[ActiveDeviceIndex]; @@ -161,7 +171,7 @@ void Audio::OnRemoveSource(AudioSource* source) bool AudioService::Init() { - const auto settings = AudioSettings::Instance(); + const auto settings = AudioSettings::Get(); const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio; // Pick a backend to use @@ -225,11 +235,9 @@ void AudioService::Update() { PROFILE_CPU(); - const auto settings = AudioSettings::Instance(); - // Update the master volume float masterVolume = MasterVolume; - if (settings->MuteOnFocusLoss && !Engine::HasFocus) + if (MuteOnFocusLoss && !Engine::HasFocus) { // Mute audio if app has no user focus masterVolume = 0.0f; diff --git a/Source/Engine/Audio/AudioSettings.h b/Source/Engine/Audio/AudioSettings.h index e976c9953..b5efe9524 100644 --- a/Source/Engine/Audio/AudioSettings.h +++ b/Source/Engine/Audio/AudioSettings.h @@ -8,35 +8,38 @@ /// /// Audio settings container. /// -/// -class AudioSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AudioSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings); public: /// /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. /// + API_FIELD(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"General\")") bool DisableAudio = false; /// /// The doppler effect factor. Scale for source and listener velocities. Default is 1. /// + API_FIELD(Attributes="EditorOrder(100), DefaultValue(1.0f), EditorDisplay(\"General\")") float DopplerFactor = 1.0f; /// /// True if mute all audio playback when game has no use focus. /// + API_FIELD(Attributes="EditorOrder(200), DefaultValue(true), EditorDisplay(\"General\", \"Mute On Focus Loss\")") bool MuteOnFocusLoss = true; 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. + /// + static AudioSettings* Get(); + // [SettingsBase] - void RestoreDefault() final override - { - DisableAudio = false; - DopplerFactor = 1.0f; - MuteOnFocusLoss = true; - } + void Apply() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index e06ab35aa..89c8f3d02 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -878,7 +878,7 @@ bool AudioBackendOAL::Base_Init() // Init ALC::AL_EXT_float32 = ALC::IsExtensionSupported("AL_EXT_float32"); - SetDopplerFactor(AudioSettings::Instance()->DopplerFactor); + SetDopplerFactor(AudioSettings::Get()->DopplerFactor); ALC::RebuildContexts(true); Audio::SetActiveDeviceIndex(activeDeviceIndex); diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index a201253ac..aac3979ce 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -794,7 +794,7 @@ void AudioBackendXAudio2::Base_Update() } // Update dirty voices - const float dopplerFactor = AudioSettings::Instance()->DopplerFactor; + const float dopplerFactor = AudioSettings::Get()->DopplerFactor; X3DAUDIO_DSP_SETTINGS dsp = { 0 }; dsp.DstChannelCount = XAudio2::Channels; dsp.pMatrixCoefficients = XAudio2::MatrixCoefficients; diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 6618336d4..821c43f87 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -214,6 +214,7 @@ Asset::LoadResult JsonAsset::loadAsset() if (!instance) return LoadResult::Failed; Instance = instance; + InstanceType = typeHandle; _dtor = type.Class.Dtor; type.Class.Ctor(instance); @@ -238,6 +239,7 @@ void JsonAsset::unload(bool isReloading) if (Instance) { + InstanceType = ScriptingTypeHandle(); _dtor(Instance); Allocator::Free(Instance); Instance = nullptr; diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index c790bb6b9..b9edd67ea 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -74,7 +74,6 @@ protected: /// /// Generic type of Json-format asset. It provides the managed representation of this resource data so it can be accessed via C# API. /// -/// API_CLASS(NoSpawn) class JsonAsset : public JsonAssetBase { DECLARE_ASSET_HEADER(JsonAsset); @@ -83,6 +82,11 @@ private: public: + /// + /// The scripting type of the deserialized unmanaged object instance (e.g. PhysicalMaterial). + /// + ScriptingTypeHandle InstanceType; + /// /// The deserialized unmanaged object instance (e.g. PhysicalMaterial). /// diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index ac0a36f02..529171639 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -10,71 +10,73 @@ /// /// The game building rendering settings. /// -class BuildSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings); public: /// /// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to. /// - int32 MaxAssetsPerPackage = 1024; + API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, 32, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")") + int32 MaxAssetsPerPackage = 4096; /// /// The maximum size of the single assets package (in megabytes). Asset packages will split into several packages if need to. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, 16, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")") int32 MaxPackageSizeMB = 1024; /// /// The game content cooking keycode. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building. /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), Limit(1, 16, ushort.MaxValue), EditorDisplay(\"General\")") int32 ContentKey = 0; /// /// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett). /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"General\")") bool ForDistribution = false; /// /// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC. /// + API_FIELD(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"General\")") bool SkipPackaging = false; /// /// The list of additional assets to include into build (into root assets set). /// + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Additional Data\")") Array> AdditionalAssets; /// /// The list of additional folders with assets to include into build (into root assets set). Paths relative to the project directory (or absolute). /// + API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Additional Data\")") Array AdditionalAssetFolders; /// /// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Content\", \"Shaders No Optimize\")") bool ShadersNoOptimize = false; /// /// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend). /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Content\")") bool ShadersGenerateDebugData = false; public: - // [SettingsBase] - void RestoreDefault() final override - { - MaxAssetsPerPackage = 1024; - MaxPackageSizeMB = 1024; - ContentKey = 0; - ForDistribution = false; - SkipPackaging = false; - AdditionalAssets.Clear(); - AdditionalAssetFolders.Clear(); - ShadersNoOptimize = false; - ShadersGenerateDebugData = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static BuildSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(MaxAssetsPerPackage); diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 9899ced75..5ea356d61 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -2,6 +2,7 @@ #include "GameSettings.h" #include "Engine/Serialization/JsonTools.h" +#include "Engine/Scripting/ScriptingType.h" #include "Engine/Physics/PhysicsSettings.h" #include "Engine/Core/Log.h" #include "LayersTagsSettings.h" @@ -17,30 +18,6 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" -#include "Engine/Engine/Time.h" - -String GameSettings::ProductName; -String GameSettings::CompanyName; -String GameSettings::CopyrightNotice; -Guid GameSettings::Icon; -Guid GameSettings::FirstScene; -bool GameSettings::NoSplashScreen = false; -Guid GameSettings::SplashScreen; -Dictionary GameSettings::CustomSettings; - -Array Settings::Containers(32); - -#if USE_EDITOR -extern void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data); -#endif - -void TimeSettings::Apply() -{ - Time::UpdateFPS = UpdateFPS; - Time::PhysicsFPS = PhysicsFPS; - Time::DrawFPS = DrawFPS; - Time::TimeScale = TimeScale; -} class GameSettingsService : public EngineService { @@ -49,9 +26,6 @@ public: GameSettingsService() : EngineService(TEXT("GameSettings"), -70) { - GameSettings::Icon = Guid::Empty; - GameSettings::FirstScene = Guid::Empty; - GameSettings::SplashScreen = Guid::Empty; } bool Init() override @@ -60,62 +34,141 @@ public: } }; +IMPLEMENT_SETTINGS_GETTER(BuildSettings, GameCooking); +IMPLEMENT_SETTINGS_GETTER(GraphicsSettings, Graphics); +IMPLEMENT_SETTINGS_GETTER(LayersAndTagsSettings, LayersAndTags); +IMPLEMENT_SETTINGS_GETTER(TimeSettings, Time); +IMPLEMENT_SETTINGS_GETTER(AudioSettings, Audio); +IMPLEMENT_SETTINGS_GETTER(PhysicsSettings, Physics); +IMPLEMENT_SETTINGS_GETTER(InputSettings, Input); + +#if !USE_EDITOR +#if PLATFORM_WINDOWS +IMPLEMENT_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); +#elif PLATFORM_UWP || PLATFORM_XBOX_ONE +IMPLEMENT_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); +#elif PLATFORM_LINUX +IMPLEMENT_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); +#elif PLATFORM_PS4 +IMPLEMENT_SETTINGS_GETTER(PS4PlatformSettings, PS4Platform); +#elif PLATFORM_XBOX_SCARLETT +IMPLEMENT_SETTINGS_GETTER(XboxScarlettPlatformSettings, XboxScarlettPlatform); +#elif PLATFORM_ANDROID +IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); +#else +#error Unknown platform +#endif +#endif + GameSettingsService GameSettingsServiceInstance; +AssetReference GameSettingsAsset; + +GameSettings* GameSettings::Get() +{ + if (!GameSettingsAsset) + { + // Load root game settings asset. + // It may be missing in editor during dev but must be ready in the build game. + const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); + GameSettingsAsset = Content::LoadAsync(assetPath); + if (GameSettingsAsset == nullptr) + { + LOG(Error, "Missing game settings asset."); + return nullptr; + } + if (GameSettingsAsset->WaitForLoaded()) + { + return nullptr; + } + if (GameSettingsAsset->InstanceType != GameSettings::TypeInitializer) + { + LOG(Error, "Invalid game settings asset data type."); + return nullptr; + } + } + auto asset = GameSettingsAsset.Get(); + if (asset && asset->WaitForLoaded()) + asset = nullptr; + return asset ? (GameSettings*)asset->Instance : nullptr; +} bool GameSettings::Load() { + // Load main settings asset + auto settings = Get(); + if (!settings) + { + return true; + } + + // Preload all settings assets +#define PRELOAD_SETTINGS(type) \ + { \ + if (settings->type) \ + { \ + Content::LoadAsync(settings->type); \ + } \ + else \ + { \ + LOG(Warning, "Missing {0} settings", TEXT(#type)); \ + } \ + } + PRELOAD_SETTINGS(Time); + PRELOAD_SETTINGS(Audio); + PRELOAD_SETTINGS(LayersAndTags); + PRELOAD_SETTINGS(Physics); + PRELOAD_SETTINGS(Input); + PRELOAD_SETTINGS(Graphics); + PRELOAD_SETTINGS(Navigation); + PRELOAD_SETTINGS(GameCooking); +#undef PRELOAD_SETTINGS + + // Apply the game settings to the engine + settings->Apply(); + + return false; +} + +void GameSettings::Apply() +{ + // TODO: impl this +#define APPLY_SETTINGS(type) \ + { \ + type* obj = type::Get(); \ + if (obj) \ + { \ + obj->Apply(); \ + } \ + else \ + { \ + LOG(Warning, "Missing {0} settings", TEXT(#type)); \ + } \ + } + APPLY_SETTINGS(TimeSettings); + APPLY_SETTINGS(AudioSettings); + APPLY_SETTINGS(LayersAndTagsSettings); + APPLY_SETTINGS(PhysicsSettings); + APPLY_SETTINGS(InputSettings); + APPLY_SETTINGS(GraphicsSettings); + APPLY_SETTINGS(NavigationSettings); + APPLY_SETTINGS(BuildSettings); + APPLY_SETTINGS(PlatformSettings); +#undef APPLY_SETTINGS +} + +void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Load properties + ProductName = JsonTools::GetString(stream, "ProductName"); + CompanyName = JsonTools::GetString(stream, "CompanyName"); + CopyrightNotice = JsonTools::GetString(stream, "CopyrightNotice"); + Icon = JsonTools::GetGuid(stream, "Icon"); + FirstScene = JsonTools::GetGuid(stream, "FirstScene"); + NoSplashScreen = JsonTools::GetBool(stream, "NoSplashScreen", NoSplashScreen); + SplashScreen = JsonTools::GetGuid(stream, "SplashScreen"); CustomSettings.Clear(); - -#if USE_EDITOR -#define END_POINT(msg) LOG(Warning, msg " Using default values."); return false -#else -#define END_POINT(msg) LOG(Fatal, msg); return true -#endif - -#define LOAD_SETTINGS(nodeName, settingsType) \ - { \ - Guid id = JsonTools::GetGuid(data, nodeName); \ - if (id.IsValid()) \ - { \ - AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset && !subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ - else \ - { LOG(Warning, "Cannot load " nodeName " settings"); } \ - } \ - else \ - { LOG(Warning, "Missing " nodeName " settings"); } \ - } - - // Load root game settings asset. - // It may be missing in editor during dev but must be ready in the build game. - const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); - AssetReference asset = Content::LoadAsync(assetPath); - if (asset == nullptr) - { - END_POINT("Missing game settings asset."); - } - if (asset->WaitForLoaded() - || asset->DataTypeName != TEXT("FlaxEditor.Content.Settings.GameSettings") - || asset->Data == nullptr) - { - END_POINT("Cannot load game settings asset."); - } - auto& data = *asset->Data; - - // Load settings - ProductName = JsonTools::GetString(data, "ProductName"); - CompanyName = JsonTools::GetString(data, "CompanyName"); - CopyrightNotice = JsonTools::GetString(data, "CopyrightNotice"); - Icon = JsonTools::GetGuid(data, "Icon"); - FirstScene = JsonTools::GetGuid(data, "FirstScene"); - NoSplashScreen = JsonTools::GetBool(data, "NoSplashScreen", NoSplashScreen); - SplashScreen = JsonTools::GetGuid(data, "SplashScreen"); - const auto customSettings = data.FindMember("CustomSettings"); - if (customSettings != data.MemberEnd()) + const auto customSettings = stream.FindMember("CustomSettings"); + if (customSettings != stream.MemberEnd()) { auto& items = customSettings->value; for (auto it = items.MemberBegin(); it != items.MemberEnd(); ++it) @@ -129,39 +182,21 @@ bool GameSettings::Load() } } - // Load child settings - LOAD_SETTINGS("Time", TimeSettings); - LOAD_SETTINGS("Physics", PhysicsSettings); - LOAD_SETTINGS("LayersAndTags", LayersAndTagsSettings); - LOAD_SETTINGS("Graphics", GraphicsSettings); - LOAD_SETTINGS("GameCooking", BuildSettings); - LOAD_SETTINGS("Input", InputSettings); - LOAD_SETTINGS("Audio", AudioSettings); - LOAD_SETTINGS("Navigation", NavigationSettings); + // Settings containers + DESERIALIZE(Time); + DESERIALIZE(Audio); + DESERIALIZE(LayersAndTags); + DESERIALIZE(Physics); + DESERIALIZE(Input); + DESERIALIZE(Graphics); + DESERIALIZE(Navigation); + DESERIALIZE(GameCooking); - // Load platform settings -#if PLATFORM_WINDOWS - LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); -#endif -#if PLATFORM_UWP - LOAD_SETTINGS("UWPPlatform", UWPPlatformSettings); -#endif -#if PLATFORM_LINUX - LOAD_SETTINGS("LinuxPlatform", LinuxPlatformSettings); -#endif -#if PLATFORM_PS4 - LOAD_SETTINGS("PS4Platform", PS4PlatformSettings); -#endif -#if PLATFORM_XBOX_SCARLETT - LOAD_SETTINGS("XboxScarlettPlatform", XboxScarlettPlatformSettings); -#endif -#if PLATFORM_ANDROID - LOAD_SETTINGS("AndroidPlatform", AndroidPlatformSettings); -#endif -#if USE_EDITOR - LoadPlatformSettingsEditor(data); -#endif - - return false; -#undef END_POINT + // Per-platform settings containers + DESERIALIZE(WindowsPlatform); + DESERIALIZE(UWPPlatform); + DESERIALIZE(LinuxPlatform); + DESERIALIZE(PS4Platform); + DESERIALIZE(XboxScarlettPlatform); + DESERIALIZE(AndroidPlatform); } diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 6870d8c22..16e1fe7b5 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -2,60 +2,96 @@ #pragma once +#include "Settings.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Dictionary.h" /// -/// Main engine configuration service. Loads and applies game configuration. +/// The main game engine configuration service. Loads and applies game configuration. /// -class GameSettings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GameSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(GameSettings); public: /// /// The product full name. /// - static String ProductName; + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String ProductName; /// /// The company full name. /// - static String CompanyName; + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"General\")") + String CompanyName; /// /// The copyright note used for content signing (eg. source code header). /// - static String CopyrightNotice; + API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")") + String CopyrightNotice; /// /// The default application icon. /// - static Guid Icon; + Guid Icon = Guid::Empty; /// /// Reference to the first scene to load on a game startup. /// - static Guid FirstScene; + Guid FirstScene = Guid::Empty; /// /// True if skip showing splash screen image on the game startup. /// - static bool NoSplashScreen; + bool NoSplashScreen = false; /// /// Reference to the splash screen image to show on a game startup. /// - static Guid SplashScreen; + Guid SplashScreen = Guid::Empty; /// /// The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair). /// - static Dictionary CustomSettings; + Dictionary CustomSettings; + +public: + + // Settings containers + Guid Time; + Guid Audio; + Guid LayersAndTags; + Guid Physics; + Guid Input; + Guid Graphics; + Guid Navigation; + Guid GameCooking; + + // Per-platform settings containers + Guid WindowsPlatform; + Guid UWPPlatform; + Guid LinuxPlatform; + Guid PS4Platform; + Guid XboxScarlettPlatform; + Guid AndroidPlatform; + +public: + + /// + /// Gets the instance of the game settings asset (null if missing). Object returned by this method is always loaded with valid data to use. + /// + static GameSettings* Get(); /// /// Loads the game settings (including other settings such as Physics, Input, etc.). /// /// True if failed, otherwise false. static bool Load(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 4e120fdff..17c342e89 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -9,67 +9,68 @@ /// /// Graphics rendering settings. /// -class GraphicsSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GraphicsSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); public: /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(false), EditorDisplay(\"General\", \"Use V-Sync\")") bool UseVSync = false; /// /// Anti Aliasing quality setting. /// + API_FIELD(Attributes="EditorOrder(1000), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"AA Quality\")") Quality AAQuality = Quality::Medium; /// /// Screen Space Reflections quality setting. /// + API_FIELD(Attributes="EditorOrder(1100), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"SSR Quality\")") Quality SSRQuality = Quality::Medium; /// /// Screen Space Ambient Occlusion quality setting. /// + API_FIELD(Attributes="EditorOrder(1200), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"SSAO Quality\")") Quality SSAOQuality = Quality::Medium; /// /// Volumetric Fog quality setting. /// + API_FIELD(Attributes="EditorOrder(1250), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") Quality VolumetricFogQuality = Quality::High; /// /// The shadows quality. /// + API_FIELD(Attributes="EditorOrder(1300), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\")") Quality ShadowsQuality = Quality::Medium; /// /// The shadow maps quality (textures resolution). /// + API_FIELD(Attributes="EditorOrder(1310), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\")") Quality ShadowMapsQuality = Quality::Medium; /// /// Enables cascades splits blending for directional light shadows. /// + API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; 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. + /// + static GraphicsSettings* Get(); + // [SettingsBase] void Apply() override; - - void RestoreDefault() final override - { - UseVSync = false; - AAQuality = Quality::Medium; - SSRQuality = Quality::Medium; - SSAOQuality = Quality::Medium; - VolumetricFogQuality = Quality::High; - ShadowsQuality = Quality::Medium; - ShadowMapsQuality = Quality::Medium; - AllowCSMBlending = false; - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(UseVSync); diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index ee09d9250..44bca9231 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -7,8 +7,9 @@ /// /// Layers and objects tags settings. /// -class LayersAndTagsSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LayersAndTagsSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(LayersAndTagsSettings); public: /// @@ -24,43 +25,11 @@ public: public: /// - /// Gets or adds the tag (returns the tag index). + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// - /// The tag. - /// The tag index. - int32 GetOrAddTag(const String& tag) - { - int32 index = Tags.Find(tag); - if (index == INVALID_INDEX) - { - index = Tags.Count(); - Tags.Add(tag); - } - return index; - } - - /// - /// Gets the amount of non empty layer names (from the beginning, trims the last ones). - /// - /// The layers count. - int32 GetNonEmptyLayerNamesCount() const - { - int32 result = 31; - while (result >= 0 && Layers[result].IsEmpty()) - result--; - return result + 1; - } - -public: + static LayersAndTagsSettings* Get(); // [SettingsBase] - void RestoreDefault() override - { - Tags.Clear(); - for (int32 i = 0; i < 32; i++) - Layers[i].Clear(); - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { const auto tags = stream.FindMember("Tags"); diff --git a/Source/Engine/Core/Config/Settings.h b/Source/Engine/Core/Config/Settings.h index fe594d5ae..2822f4a6a 100644 --- a/Source/Engine/Core/Config/Settings.h +++ b/Source/Engine/Core/Config/Settings.h @@ -2,81 +2,46 @@ #pragma once -#include "Engine/Core/Singleton.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Serialization/ISerializable.h" /// /// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game. /// -class FLAXENGINE_API Settings +API_CLASS(Abstract) class FLAXENGINE_API SettingsBase : public ISerializable { +DECLARE_SCRIPTING_TYPE_MINIMAL(SettingsBase); public: /// - /// The settings containers. - /// - static Array Containers; - - /// - /// Restores the default settings for all the registered containers. - /// - static void RestoreDefaultAll() - { - for (int32 i = 0; i < Containers.Count(); i++) - Containers[i]->RestoreDefault(); - } - -private: - - // Disable copy/move - Settings(const Settings&) = delete; - Settings& operator=(const Settings&) = delete; - -protected: - - Settings() - { - Containers.Add(this); - } - -public: - - virtual ~Settings() = default; - -public: - - typedef ISerializable::DeserializeStream DeserializeStream; - - /// - /// Applies the settings to the target services. + /// Applies the settings to the target system. /// virtual void Apply() { } - /// - /// Restores the default settings. - /// - virtual void RestoreDefault() = 0; +public: - /// - /// Deserializes the settings container. - /// - /// The input data stream. - /// The deserialization modifier object. Always valid. - virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; -}; - -/// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game. -/// -template -class SettingsBase : public Settings, public Singleton -{ -protected: - - SettingsBase() + // [ISerializable] + void Serialize(SerializeStream& stream, const void* otherObj) override { + // Not supported (Editor C# edits settings data) } }; + +// Helper utility define for settings getter implementation code +#define IMPLEMENT_SETTINGS_GETTER(type, field) \ + type* type::Get() \ + { \ + static type DefaultInstance; \ + type* result = &DefaultInstance; \ + const auto gameSettings = GameSettings::Get(); \ + if (gameSettings) \ + { \ + const auto asset = Content::Load(gameSettings->field); \ + if (asset && asset->Instance && asset->InstanceType == type::TypeInitializer) \ + { \ + result = static_cast(asset->Instance); \ + } \ + } \ + return result; \ + } diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 4b4deaaf8..7e383380f 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -3,60 +3,53 @@ #pragma once #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" /// /// Time and game simulation settings container. /// -class TimeSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API TimeSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(TimeSettings); public: /// /// The target amount of the game logic updates per second (script updates frequency). /// + API_FIELD(Attributes="EditorOrder(1), DefaultValue(30.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")") float UpdateFPS = 30.0f; /// /// The target amount of the physics simulation updates per second (also fixed updates frequency). /// + API_FIELD(Attributes="EditorOrder(2), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")") float PhysicsFPS = 60.0f; /// /// The target amount of the frames rendered per second (actual game FPS). /// + API_FIELD(Attributes="EditorOrder(3), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")") float DrawFPS = 60.0f; /// /// The game time scale factor. Default is 1. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(1.0f), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")") float TimeScale = 1.0f; /// /// The maximum allowed delta time (in seconds) for the game logic update step. /// - float MaxUpdateDeltaTime = (1.0f / 10.0f); + API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.1f), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") + float MaxUpdateDeltaTime = 0.1f; 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. + /// + static TimeSettings* Get(); + // [SettingsBase] void Apply() override; - - void RestoreDefault() override - { - UpdateFPS = 30.0f; - PhysicsFPS = 60.0f; - DrawFPS = 60.0f; - TimeScale = 1.0f; - MaxUpdateDeltaTime = 1.0f / 10.0f; - } - - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(UpdateFPS); - DESERIALIZE(PhysicsFPS); - DESERIALIZE(DrawFPS); - DESERIALIZE(TimeScale); - DESERIALIZE(MaxUpdateDeltaTime); - } + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/Math/Math.cpp b/Source/Engine/Core/Math/Math.cpp index 4075bcbda..d0642d7ca 100644 --- a/Source/Engine/Core/Math/Math.cpp +++ b/Source/Engine/Core/Math/Math.cpp @@ -5,8 +5,8 @@ void Math::SinCos(float angle, float& sine, float& cosine) { - sine = sin(angle); - cosine = cos(angle); + sine = Math::Sin(angle); + cosine = Math::Cos(angle); } uint32 Math::FloorLog2(uint32 value) diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index 6c02ce1d1..c313b0737 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -119,7 +119,10 @@ bool GameBase::Init() } // Preload first scene asset data - GameBaseImpl::FirstScene = GameSettings::FirstScene; + const auto gameSettings = GameSettings::Get(); + if (!gameSettings) + return true; + GameBaseImpl::FirstScene = gameSettings->FirstScene; return false; } @@ -261,8 +264,8 @@ void GameBaseImpl::OnSplashScreenEnd() // Load the first scene LOG(Info, "Loading the first scene"); + const auto sceneId = FirstScene ? FirstScene.GetID() : Guid::Empty; FirstScene.Unlink(); - const auto sceneId = GameSettings::FirstScene; if (Level::LoadSceneAsync(sceneId)) { LOG(Fatal, "Cannot load the first scene."); diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index ebf268957..f9313b1b4 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -51,6 +51,9 @@ namespace EngineImpl { bool IsReady = false; +#if !USE_EDITOR + bool RunInBackground = false; +#endif String CommandLine = nullptr; int32 Fps = 0, FpsAccumulatedFrames = 0; double FpsAccumulated = 0.0; @@ -133,6 +136,9 @@ int32 Engine::Main(const Char* cmdLine) Platform::BeforeRun(); EngineImpl::InitMainWindow(); Application::BeforeRun(); +#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX) + EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; +#endif Log::Logger::WriteFloor(); LOG_FLUSH(); Time::OnBeforeRun(); @@ -279,11 +285,7 @@ void Engine::OnUpdate() bool isGameRunning = true; if (mainWindow && !mainWindow->IsFocused()) { -#if PLATFORM_WINDOWS || PLATFORM_LINUX - isGameRunning = PlatformSettings::Instance()->RunInBackground; -#else - isGameRunning = false; -#endif + isGameRunning = EngineImpl::RunInBackground; } Time::SetGamePaused(!isGameRunning); #endif @@ -393,8 +395,11 @@ const String& Engine::GetCommandLine() JsonAsset* Engine::GetCustomSettings(const StringView& key) { + const auto settings = GameSettings::Get(); + if (!settings) + return nullptr; Guid assetId = Guid::Empty; - GameSettings::CustomSettings.TryGet(key, assetId); + settings->CustomSettings.TryGet(key, assetId); return Content::LoadAsync(assetId); } diff --git a/Source/Engine/Engine/Linux/LinuxGame.cpp b/Source/Engine/Engine/Linux/LinuxGame.cpp index 43bfe4a6e..1f59a66a9 100644 --- a/Source/Engine/Engine/Linux/LinuxGame.cpp +++ b/Source/Engine/Engine/Linux/LinuxGame.cpp @@ -16,7 +16,7 @@ void LinuxGame::InitMainWindowSettings(CreateWindowSettings& settings) { // TODO: restore window size and fullscreen mode from the cached local settings saved after previous session - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); auto windowMode = platformSettings->WindowMode; // Use command line switches @@ -54,7 +54,7 @@ void LinuxGame::InitMainWindowSettings(CreateWindowSettings& settings) bool LinuxGame::Init() { - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); // Create mutex if need to if (platformSettings->ForceSingleInstance) diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 1afe7e05a..f26f4f759 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -3,17 +3,19 @@ #include "Time.h" #include "EngineService.h" #include "Engine/Core/Math/Math.h" -#include "Engine/Core/Config/TimeSettings.h" #include "Engine/Platform/Platform.h" -#include "Engine/Physics/PhysicsSettings.h" +#include "Engine/Core/Config/TimeSettings.h" +#include "Engine/Serialization/Serialization.h" namespace { bool FixedDeltaTimeEnable; float FixedDeltaTimeValue; + float MaxUpdateDeltaTime = 0.1f; } bool Time::_gamePaused = false; +float Time::_physicsMaxDeltaTime = 0.1f; DateTime Time::StartupTime; float Time::UpdateFPS = 30.0f; float Time::PhysicsFPS = 60.0f; @@ -43,6 +45,24 @@ public: TimeService TimeServiceInstance; +void TimeSettings::Apply() +{ + Time::UpdateFPS = UpdateFPS; + Time::PhysicsFPS = PhysicsFPS; + Time::DrawFPS = DrawFPS; + Time::TimeScale = TimeScale; + ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; +} + +void TimeSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(UpdateFPS); + DESERIALIZE(PhysicsFPS); + DESERIALIZE(DrawFPS); + DESERIALIZE(TimeScale); + DESERIALIZE(MaxUpdateDeltaTime); +} + void Time::TickData::OnBeforeRun(float targetFps, double currentTime) { Time = UnscaledTime = TimeSpan::Zero(); @@ -219,7 +239,7 @@ void Time::OnBeforeRun() bool Time::OnBeginUpdate() { - if (Update.OnTickBegin(UpdateFPS, TimeSettings::Instance()->MaxUpdateDeltaTime)) + if (Update.OnTickBegin(UpdateFPS, MaxUpdateDeltaTime)) { Current = &Update; return true; @@ -229,7 +249,7 @@ bool Time::OnBeginUpdate() bool Time::OnBeginPhysics() { - if (Physics.OnTickBegin(PhysicsFPS, PhysicsSettings::Instance()->MaxDeltaTime)) + if (Physics.OnTickBegin(PhysicsFPS, _physicsMaxDeltaTime)) { Current = &Physics; return true; diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index 0e8c25806..0d44afa22 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -15,6 +15,7 @@ API_CLASS(Static) class FLAXENGINE_API Time DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time); friend class Engine; friend class TimeService; + friend class PhysicsSettings; public: /// @@ -100,6 +101,7 @@ public: private: static bool _gamePaused; + static float _physicsMaxDeltaTime; public: diff --git a/Source/Engine/Engine/Windows/WindowsGame.cpp b/Source/Engine/Engine/Windows/WindowsGame.cpp index da82cc7fc..4b7496481 100644 --- a/Source/Engine/Engine/Windows/WindowsGame.cpp +++ b/Source/Engine/Engine/Windows/WindowsGame.cpp @@ -11,7 +11,7 @@ void WindowsGame::InitMainWindowSettings(CreateWindowSettings& settings) { // TODO: restore window size and fullscreen mode from the cached local settings saved after previous session - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); auto windowMode = platformSettings->WindowMode; // Use command line switches @@ -45,7 +45,7 @@ void WindowsGame::InitMainWindowSettings(CreateWindowSettings& settings) bool WindowsGame::Init() { - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); // Create mutex if need to if (platformSettings->ForceSingleInstance) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 64151749b..e9da74a99 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -91,7 +91,7 @@ GPUDevice* GPUDeviceDX11::Create() else if (CommandLine::Options.D3D11) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #if !USE_EDITOR && PLATFORM_WINDOWS - auto winSettings = WindowsPlatformSettings::Instance(); + auto winSettings = WindowsPlatformSettings::Get(); if (!winSettings->SupportDX11 && !winSettings->SupportDX10) { // Skip if there is no support diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 29d417834..317a6e9aa 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -45,7 +45,7 @@ GPUDevice* GPUDeviceDX12::Create() selectedAdapter.Description.VendorId = GPU_VENDOR_ID_AMD; #else #if !USE_EDITOR && PLATFORM_WINDOWS - auto winSettings = WindowsPlatformSettings::Instance(); + auto winSettings = WindowsPlatformSettings::Get(); if (!winSettings->SupportDX12) { // Skip if there is no support diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 33df0db47..85fc3dcd0 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1076,7 +1076,7 @@ GPUDeviceVulkan::GPUDeviceVulkan(ShaderProfile shaderProfile, GPUAdapterVulkan* GPUDevice* GPUDeviceVulkan::Create() { #if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX) - auto settings = PlatformSettings::Instance(); + auto settings = PlatformSettings::Get(); if (!settings->SupportVulkan) { // Skip if there is no support diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 1dfa0e5fe..be11f091d 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -98,6 +98,12 @@ Delegate Input::ActionTriggered; Array Input::ActionMappings; Array Input::AxisMappings; +void InputSettings::Apply() +{ + Input::ActionMappings = ActionMappings; + Input::AxisMappings = AxisMappings; +} + int32 Input::GetGamepadsCount() { return Gamepads.Count(); diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h index 33852f9ec..4ef7bfffb 100644 --- a/Source/Engine/Input/Input.h +++ b/Source/Engine/Input/Input.h @@ -21,7 +21,6 @@ class InputDevice; API_CLASS(Static) class FLAXENGINE_API Input { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Input); -public: /// /// Gets the mouse (null if platform does not support mouse or it is not connected). diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h index df1f4d60c..7771028e8 100644 --- a/Source/Engine/Input/InputSettings.h +++ b/Source/Engine/Input/InputSettings.h @@ -5,14 +5,13 @@ #include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/JsonTools.h" #include "VirtualInput.h" -#include "Input.h" /// /// Input settings container. /// -/// -class InputSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API InputSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(InputSettings); public: /// @@ -27,19 +26,14 @@ public: public: - // [SettingsBase] - void Apply() override - { - Input::ActionMappings = ActionMappings; - Input::AxisMappings = AxisMappings; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static InputSettings* Get(); + + // [SettingsBase] + void Apply() override; - void RestoreDefault() override - { - ActionMappings.Resize(0); - AxisMappings.Resize(0); - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { const auto actionMappings = stream.FindMember("ActionMappings"); diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 81a5b3155..900d802ed 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -8,7 +8,6 @@ #include "Prefabs/Prefab.h" #include "Prefabs/PrefabManager.h" #include "Engine/Core/Log.h" -#include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Scripting/Script.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Threading/Threading.h" @@ -386,21 +385,19 @@ Actor* Actor::GetChildByPrefabObjectId(const Guid& prefabObjectId) const bool Actor::HasTag(const StringView& tag) const { - return HasTag() && tag == LayersAndTagsSettings::Instance()->Tags[_tag]; + return HasTag() && tag == Level::Tags[_tag]; } const String& Actor::GetLayerName() const { - const auto settings = LayersAndTagsSettings::Instance(); - return settings->Layers[_layer]; + return Level::Layers[_layer]; } const String& Actor::GetTag() const { if (HasTag()) { - const auto settings = LayersAndTagsSettings::Instance(); - return settings->Tags[_tag]; + return Level::Tags[_tag]; } return String::Empty; } @@ -420,13 +417,13 @@ void Actor::SetTagIndex(int32 tagIndex) if (tagIndex == ACTOR_TAG_INVALID) { } - else if (LayersAndTagsSettings::Instance()->Tags.IsEmpty()) + else if (Level::Tags.IsEmpty()) { tagIndex = ACTOR_TAG_INVALID; } else { - tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, LayersAndTagsSettings::Instance()->Tags.Count() - 1); + tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, Level::Tags.Count() - 1); } if (tagIndex == _tag) return; @@ -444,7 +441,7 @@ void Actor::SetTag(const StringView& tagName) } else { - tagIndex = LayersAndTagsSettings::Instance()->Tags.Find(tagName); + tagIndex = Level::Tags.Find(tagName); if (tagIndex == -1) { LOG(Error, "Cannot change actor tag. Given value is invalid."); @@ -971,7 +968,7 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) if (tag->value.IsString() && tag->value.GetStringLength()) { const String tagName = tag->value.GetText(); - _tag = LayersAndTagsSettings::Instance()->GetOrAddTag(tagName); + _tag = Level::GetOrAddTag(tagName); } } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 18d9d4a52..7d257e104 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Core/ObjectsRemovalService.h" +#include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" @@ -97,6 +98,7 @@ public: { } + bool Init() override; void Update() override; void LateUpdate() override; void FixedUpdate() override; @@ -124,6 +126,8 @@ Delegate Level::SceneUnloaded; Action Level::ScriptsReloadStart; Action Level::ScriptsReload; Action Level::ScriptsReloadEnd; +Array Level::Tags; +String Level::Layers[32]; bool LevelImpl::spawnActor(Actor* actor, Actor* parent) { @@ -158,6 +162,15 @@ bool LevelImpl::deleteActor(Actor* actor) return false; } +bool LevelService::Init() +{ + auto& settings = *LayersAndTagsSettings::Get(); + Level::Tags = settings.Tags; + for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++) + Level::Layers[i] = settings.Layers[i]; + return false; +} + void LevelService::Update() { PROFILE_CPU(); @@ -642,6 +655,25 @@ void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid scen } } +int32 Level::GetOrAddTag(const StringView& tag) +{ + int32 index = Tags.Find(tag); + if (index == INVALID_INDEX) + { + index = Tags.Count(); + Tags.AddOne() = tag; + } + return index; +} + +int32 Level::GetNonEmptyLayerNamesCount() +{ + int32 result = 31; + while (result >= 0 && Layers[result].IsEmpty()) + result--; + return result + 1; +} + void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) { PROFILE_CPU(); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index b1ee090f4..a1e1d73cd 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -420,6 +420,31 @@ public: /// Output array with only parents static void ConstructParentActorsTreeList(const Array& input, Array& output); +public: + + /// + /// The tags names. + /// + static Array Tags; + + /// + /// The layers names. + /// + static String Layers[32]; + + /// + /// Gets or adds the tag (returns the tag index). + /// + /// The tag. + /// The tag index. + static int32 GetOrAddTag(const StringView& tag); + + /// + /// Gets the amount of non empty layer names (from the beginning, trims the last ones). + /// + /// The layers count. + static int32 GetNonEmptyLayerNamesCount(); + private: // Actor API diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index b2679f861..caa2513d2 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -461,13 +461,13 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou float GetTileSize() { - auto& settings = *NavigationSettings::Instance(); + auto& settings = *NavigationSettings::Get(); return settings.CellSize * settings.TileSize; } void InitConfig(rcConfig& config) { - auto& settings = *NavigationSettings::Instance(); + auto& settings = *NavigationSettings::Get(); config.cs = settings.CellSize; config.ch = settings.CellHeight; diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 143449547..6decb4be7 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -1,8 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Navigation.h" +#include "NavigationSettings.h" #include "NavMeshRuntime.h" #include "NavMeshBuilder.h" +#include "Engine/Core/Config/GameSettings.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" #include "Engine/Threading/Threading.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Engine/EngineService.h" @@ -55,6 +59,8 @@ void* rcAllocDefault(size_t size, rcAllocHint) return Allocator::Allocate(size); } +IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation); + bool NavigationService::Init() { // Link memory allocation calls to use engine default allocator diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs new file mode 100644 index 000000000..ff6f2aa4a --- /dev/null +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -0,0 +1,8 @@ +// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +namespace FlaxEditor.Content.Settings +{ + partial class NavigationSettings + { + } +} diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index d7c3aa205..fe4189955 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -9,7 +9,7 @@ /// /// The navigation system settings container. /// -API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); public: @@ -94,24 +94,12 @@ public: public: - // [SettingsBase] - void RestoreDefault() final override - { - CellHeight = 10.0f; - CellSize = 30.0f; - TileSize = 64; - MinRegionArea = 0; - MergeRegionArea = 20; - MaxEdgeLen = 1200.0f; - MaxEdgeError = 1.3f; - DetailSamplingDist = 600.0f; - MaxDetailSamplingError = 1.0f; - WalkableRadius = 34.0f; - WalkableHeight = 144.0f; - WalkableMaxClimb = 35.0f; - WalkableMaxSlopeAngle = 60.0f; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static NavigationSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(CellHeight); diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index e73963c7c..2eeb266aa 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -232,7 +232,7 @@ void Collider::UpdateLayerBits() filterData.word0 = GetLayerMask(); // Own layer mask - filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()]; + filterData.word1 = Physics::LayerMasks[GetLayer()]; _shape->setSimulationFilterData(filterData); _shape->setQueryFilterData(filterData); diff --git a/Source/Engine/Physics/PhysicalMaterial.cpp b/Source/Engine/Physics/PhysicalMaterial.cpp deleted file mode 100644 index 9870bfc65..000000000 --- a/Source/Engine/Physics/PhysicalMaterial.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "PhysicalMaterial.h" -#include "PhysicsSettings.h" -#include "Physics.h" -#include -#include - -PhysicalMaterial::PhysicalMaterial() - : _material(nullptr) -{ -} - -PhysicalMaterial::~PhysicalMaterial() -{ - if (_material) - { - Physics::RemoveMaterial(_material); - } -} - -PxMaterial* PhysicalMaterial::GetPhysXMaterial() -{ - if (_material == nullptr && CPhysX) - { - _material = CPhysX->createMaterial(Friction, Friction, Restitution); - _material->userData = this; - - const PhysicsCombineMode useFrictionCombineMode = (OverrideFrictionCombineMode ? FrictionCombineMode : PhysicsSettings::Instance()->FrictionCombineMode); - _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); - - const PhysicsCombineMode useRestitutionCombineMode = (OverrideRestitutionCombineMode ? RestitutionCombineMode : PhysicsSettings::Instance()->RestitutionCombineMode); - _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); - } - - return _material; -} - -void PhysicalMaterial::UpdatePhysXMaterial() -{ - if (_material != nullptr) - { - _material->setStaticFriction(Friction); - _material->setDynamicFriction(Friction); - const PhysicsCombineMode useFrictionCombineMode = (OverrideFrictionCombineMode ? FrictionCombineMode : PhysicsSettings::Instance()->FrictionCombineMode); - _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); - - _material->setRestitution(Restitution); - const PhysicsCombineMode useRestitutionCombineMode = (OverrideRestitutionCombineMode ? RestitutionCombineMode : PhysicsSettings::Instance()->RestitutionCombineMode); - _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); - } -} diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 2daa47010..98237948f 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Physics.h" +#include "PhysicalMaterial.h" #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "Engine/Platform/CPUInfo.h" @@ -13,6 +14,8 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Engine/Time.h" #include #include #if WITH_PVD @@ -110,34 +113,43 @@ struct ActionData }; PxPhysics* CPhysX = nullptr; -PhysXAllocator PhysXAllocatorCallback; -PhysXError PhysXErrorCallback; -PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; #if WITH_PVD PxPvd* CPVD = nullptr; #endif -PxTolerancesScale ToleranceScale; -SimulationEventCallback EventsCallback; -void* ScratchMemory = nullptr; -FixedStepper* Stepper = nullptr; -CriticalSection FlushLocker; -Array NewActors; -Array Actions; -Array DeadActors; -Array DeadMaterials; -Array _deadObjects; -Array DeadColliders; -Array DeadJoints; -bool _isDuringSimulation = false; -PxFoundation* _foundation = nullptr; + +namespace +{ + PhysXAllocator PhysXAllocatorCallback; + PhysXError PhysXErrorCallback; + PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; + PxTolerancesScale ToleranceScale; + SimulationEventCallback EventsCallback; + void* ScratchMemory = nullptr; + FixedStepper* Stepper = nullptr; + CriticalSection FlushLocker; + Array NewActors; + Array Actions; + Array DeadActors; + Array DeadMaterials; + Array _deadObjects; + Array DeadColliders; + Array DeadJoints; + bool _queriesHitTriggers = true; + bool _isDuringSimulation = false; + PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average; + PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average; + PxFoundation* _foundation = nullptr; #if COMPILE_WITH_PHYSICS_COOKING -PxCooking* Cooking = nullptr; + PxCooking* Cooking = nullptr; #endif -PxScene* PhysicsScene = nullptr; -PxMaterial* DefaultMaterial = nullptr; -PxControllerManager* ControllerManager = nullptr; -PxCpuDispatcher* CpuDispatcher = nullptr; + PxScene* PhysicsScene = nullptr; + PxMaterial* DefaultMaterial = nullptr; + PxControllerManager* ControllerManager = nullptr; + PxCpuDispatcher* CpuDispatcher = nullptr; +} + bool Physics::AutoSimulation = true; +uint32 Physics::LayerMasks[32]; class PhysicsService : public EngineService { @@ -146,6 +158,8 @@ public: PhysicsService() : EngineService(TEXT("Physics"), 0) { + for (int32 i = 0; i < 32; i++) + Physics::LayerMasks[i] = MAX_uint32; } bool Init() override; @@ -155,12 +169,146 @@ public: PhysicsService PhysicsServiceInstance; +PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) +{ +#if WITH_PVD + PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; +#else + PxShapeFlags flags = static_cast(0); +#endif + + if (isEnabled) + { + if (isTrigger) + { + flags |= PxShapeFlag::eTRIGGER_SHAPE; + if (_queriesHitTriggers) + flags |= PxShapeFlag::eSCENE_QUERY_SHAPE; + } + else + { + flags = PxShapeFlag::eSIMULATION_SHAPE | PxShapeFlag::eSCENE_QUERY_SHAPE; + } + } + + return flags; +} + +void PhysicsSettings::Apply() +{ + Time::_physicsMaxDeltaTime = MaxDeltaTime; + _queriesHitTriggers = QueriesHitTriggers; + _frictionCombineMode = FrictionCombineMode; + _restitutionCombineMode = RestitutionCombineMode; + Platform::MemoryCopy(Physics::LayerMasks, LayerMasks, sizeof(LayerMasks)); + Physics::SetGravity(DefaultGravity); + Physics::SetBounceThresholdVelocity(BounceThresholdVelocity); + Physics::SetEnableCCD(!DisableCCD); + + // TODO: setting eADAPTIVE_FORCE requires PxScene setup (physx docs: This flag is not mutable, and must be set in PxSceneDesc at scene creation.) + // TODO: update all shapes filter data + // TODO: update all shapes flags + + /* + { + get all actors and then: + + const PxU32 numShapes = actor->getNbShapes(); + PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes); + actor->getShapes(shapes, numShapes); + for (PxU32 i = 0; i < numShapes; i++) + { + .. + } + SAMPLE_FREE(shapes); + }*/ +} + +PhysicsSettings::PhysicsSettings() +{ + for (int32 i = 0; i < 32; i++) + LayerMasks[i] = MAX_uint32; +} + +void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(DefaultGravity); + DESERIALIZE(TriangleMeshTriangleMinAreaThreshold); + DESERIALIZE(BounceThresholdVelocity); + DESERIALIZE(FrictionCombineMode); + DESERIALIZE(RestitutionCombineMode); + DESERIALIZE(DisableCCD); + DESERIALIZE(EnableAdaptiveForce); + DESERIALIZE(MaxDeltaTime); + DESERIALIZE(EnableSubstepping); + DESERIALIZE(SubstepDeltaTime); + DESERIALIZE(MaxSubsteps); + DESERIALIZE(QueriesHitTriggers); + DESERIALIZE(SupportCookingAtRuntime); + + const auto layers = stream.FindMember("LayerMasks"); + if (layers != stream.MemberEnd()) + { + auto& layersArray = layers->value; + ASSERT(layersArray.IsArray()); + for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) + { + LayerMasks[i] = layersArray[i].GetUint(); + } + } +} + +PhysicalMaterial::PhysicalMaterial() + : _material(nullptr) +{ +} + +PhysicalMaterial::~PhysicalMaterial() +{ + if (_material) + { + Physics::RemoveMaterial(_material); + } +} + +PxMaterial* PhysicalMaterial::GetPhysXMaterial() +{ + if (_material == nullptr && CPhysX) + { + _material = CPhysX->createMaterial(Friction, Friction, Restitution); + _material->userData = this; + + const PhysicsCombineMode useFrictionCombineMode = OverrideFrictionCombineMode ? FrictionCombineMode : _frictionCombineMode; + _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); + + const PhysicsCombineMode useRestitutionCombineMode = OverrideRestitutionCombineMode ? RestitutionCombineMode : _restitutionCombineMode; + _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); + } + + return _material; +} + +void PhysicalMaterial::UpdatePhysXMaterial() +{ + if (_material != nullptr) + { + _material->setStaticFriction(Friction); + _material->setDynamicFriction(Friction); + const PhysicsCombineMode useFrictionCombineMode = OverrideFrictionCombineMode ? FrictionCombineMode : _frictionCombineMode; + _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); + + _material->setRestitution(Restitution); + const PhysicsCombineMode useRestitutionCombineMode = OverrideRestitutionCombineMode ? RestitutionCombineMode : _restitutionCombineMode; + _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); + } +} + bool PhysicsService::Init() { #define CHECK_INIT(value, msg) if(!value) { LOG(Error, msg); return true; } auto cpuInfo = Platform::GetCPUInfo(); - auto settings = PhysicsSettings::Instance(); + auto& settings = *PhysicsSettings::Get(); // Send info LOG(Info, "Setup NVIDIA PhysX {0}.{1}.{2}", PX_PHYSICS_VERSION_MAJOR, PX_PHYSICS_VERSION_MINOR, PX_PHYSICS_VERSION_BUGFIX); @@ -220,7 +368,7 @@ bool PhysicsService::Init() #if COMPILE_WITH_PHYSICS_COOKING #if !USE_EDITOR - if (settings->SupportCookingAtRuntime) + if (settings.SupportCookingAtRuntime) #endif { // Init cooking @@ -235,16 +383,16 @@ bool PhysicsService::Init() // Create scene description PxSceneDesc sceneDesc(CPhysX->getTolerancesScale()); - sceneDesc.gravity = C2P(settings->DefaultGravity); + sceneDesc.gravity = C2P(settings.DefaultGravity); sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS; //sceneDesc.flags |= PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS; // TODO: set it? - if (!settings->DisableCCD) + if (!settings.DisableCCD) sceneDesc.flags |= PxSceneFlag::eENABLE_CCD; - if (settings->EnableAdaptiveForce) + if (settings.EnableAdaptiveForce) sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE; sceneDesc.simulationEventCallback = &EventsCallback; sceneDesc.filterShader = PhysiXFilterShader; - sceneDesc.bounceThresholdVelocity = settings->BounceThresholdVelocity; + sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; if (sceneDesc.cpuDispatcher == nullptr) { CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp(cpuInfo.ProcessorCoreCount - 1, 1, 4)); @@ -366,7 +514,7 @@ void Physics::SetGravity(const Vector3& value) bool Physics::GetEnableCCD() { - return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings_DisableCCD; + return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings::Get()->DisableCCD; } void Physics::SetEnableCCD(const bool value) @@ -377,7 +525,7 @@ void Physics::SetEnableCCD(const bool value) float Physics::GetBounceThresholdVelocity() { - return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings_BounceThresholdVelocity; + return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity; } void Physics::SetBounceThresholdVelocity(const float value) @@ -390,13 +538,13 @@ void Physics::Simulate(float dt) { ASSERT(IsInMainThread() && !_isDuringSimulation); ASSERT(CPhysX); - const auto settings = PhysicsSettings::Instance(); + const auto& settings = *PhysicsSettings::Get(); // Flush the old/new objects and the other requests before the simulation FlushRequests(); // Clamp delta - dt = Math::Clamp(dt, 0.0f, settings->MaxDeltaTime); + dt = Math::Clamp(dt, 0.0f, settings.MaxDeltaTime); // Prepare util objects if (ScratchMemory == nullptr) @@ -407,10 +555,10 @@ void Physics::Simulate(float dt) { Stepper = New(); } - if (settings->EnableSubstepping) + if (settings.EnableSubstepping) { // Use substeps - Stepper->Setup(settings->SubstepDeltaTime, settings->MaxSubsteps); + Stepper->Setup(settings.SubstepDeltaTime, settings.MaxSubsteps); } else { diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index bdc1326d3..05b64cb0f 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -155,6 +155,11 @@ public: /// API_PROPERTY() static void SetBounceThresholdVelocity(float value); + /// + /// The collision layers masks. Used to define layer-based collision detection. + /// + static uint32 LayerMasks[32]; + public: /// diff --git a/Source/Engine/Physics/PhysicsSettings.cpp b/Source/Engine/Physics/PhysicsSettings.cpp deleted file mode 100644 index be8bff331..000000000 --- a/Source/Engine/Physics/PhysicsSettings.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "PhysicsSettings.h" -#include "Engine/Serialization/Serialization.h" -#include "Physics.h" - -void PhysicsSettings::Apply() -{ - // Set simulation parameters - Physics::SetGravity(DefaultGravity); - Physics::SetBounceThresholdVelocity(BounceThresholdVelocity); - Physics::SetEnableCCD(!DisableCCD); - - // TODO: setting eADAPTIVE_FORCE requires PxScene setup (physx docs: This flag is not mutable, and must be set in PxSceneDesc at scene creation.) - - // Update all shapes - - // TODO: update all shapes filter data - // TODO: update all shapes flags - - /* - { - get all actors and then: - - const PxU32 numShapes = actor->getNbShapes(); - PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes); - actor->getShapes(shapes, numShapes); - for (PxU32 i = 0; i < numShapes; i++) - { - .. - } - SAMPLE_FREE(shapes); - }*/ -} - -void PhysicsSettings::RestoreDefault() -{ - DefaultGravity = PhysicsSettings_DefaultGravity; - TriangleMeshTriangleMinAreaThreshold = PhysicsSettings_TriangleMeshTriangleMinAreaThreshold; - BounceThresholdVelocity = PhysicsSettings_BounceThresholdVelocity; - FrictionCombineMode = PhysicsSettings_FrictionCombineMode; - RestitutionCombineMode = PhysicsSettings_RestitutionCombineMode; - DisableCCD = PhysicsSettings_DisableCCD; - EnableAdaptiveForce = PhysicsSettings_EnableAdaptiveForce; - MaxDeltaTime = PhysicsSettings_MaxDeltaTime; - EnableSubstepping = PhysicsSettings_EnableSubstepping; - SubstepDeltaTime = PhysicsSettings_SubstepDeltaTime; - MaxSubsteps = PhysicsSettings_MaxSubsteps; - QueriesHitTriggers = PhysicsSettings_QueriesHitTriggers; - SupportCookingAtRuntime = PhysicsSettings_SupportCookingAtRuntime; - - for (int32 i = 0; i < 32; i++) - LayerMasks[i] = MAX_uint32; -} - -void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - DESERIALIZE(DefaultGravity); - DESERIALIZE(TriangleMeshTriangleMinAreaThreshold); - DESERIALIZE(BounceThresholdVelocity); - DESERIALIZE(FrictionCombineMode); - DESERIALIZE(RestitutionCombineMode); - DESERIALIZE(DisableCCD); - DESERIALIZE(EnableAdaptiveForce); - DESERIALIZE(MaxDeltaTime); - DESERIALIZE(EnableSubstepping); - DESERIALIZE(SubstepDeltaTime); - DESERIALIZE(MaxSubsteps); - DESERIALIZE(QueriesHitTriggers); - DESERIALIZE(SupportCookingAtRuntime); - - const auto layers = stream.FindMember("LayerMasks"); - if (layers != stream.MemberEnd()) - { - auto& layersArray = layers->value; - ASSERT(layersArray.IsArray()); - for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) - { - LayerMasks[i] = layersArray[i].GetUint(); - } - } -} diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index 3bf19bcb8..214c86558 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -6,105 +6,91 @@ #include "Engine/Core/Math/Vector3.h" #include "Types.h" -// Default values for the physics settings - -#define PhysicsSettings_DefaultGravity Vector3(0, -981.0f, 0) -#define PhysicsSettings_TriangleMeshTriangleMinAreaThreshold (5.0f) -#define PhysicsSettings_BounceThresholdVelocity (200.0f) -#define PhysicsSettings_FrictionCombineMode PhysicsCombineMode::Average -#define PhysicsSettings_RestitutionCombineMode PhysicsCombineMode::Average -#define PhysicsSettings_DisableCCD false -#define PhysicsSettings_EnableAdaptiveForce false -#define PhysicsSettings_MaxDeltaTime (1.0f / 10.0f) -#define PhysicsSettings_EnableSubstepping false -#define PhysicsSettings_SubstepDeltaTime (1.0f / 120.0f) -#define PhysicsSettings_MaxSubsteps 5 -#define PhysicsSettings_QueriesHitTriggers true -#define PhysicsSettings_SupportCookingAtRuntime false - /// /// Physics simulation settings container. /// -/// -class PhysicsSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API PhysicsSettings : public SettingsBase { -public: - - /// - /// Initializes a new instance of the class. - /// - PhysicsSettings() - { - for (int32 i = 0; i < 32; i++) - LayerMasks[i] = MAX_uint32; - } - +DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicsSettings); public: /// /// The default gravity force value (in cm^2/s). /// - Vector3 DefaultGravity = PhysicsSettings_DefaultGravity; - - /// - /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. - /// - float TriangleMeshTriangleMinAreaThreshold = PhysicsSettings_TriangleMeshTriangleMinAreaThreshold; - - /// - /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity - /// - float BounceThresholdVelocity = PhysicsSettings_BounceThresholdVelocity; - - /// - /// Default friction combine mode, controls how friction is computed for multiple materials. - /// - PhysicsCombineMode FrictionCombineMode = PhysicsSettings_FrictionCombineMode; - - /// - /// Default restitution combine mode, controls how restitution is computed for multiple materials. - /// - PhysicsCombineMode RestitutionCombineMode = PhysicsSettings_RestitutionCombineMode; - - /// - /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for physx to check it internally. - /// - bool DisableCCD = PhysicsSettings_DisableCCD; - - /// - /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. - /// - bool EnableAdaptiveForce = PhysicsSettings_EnableAdaptiveForce; - - /// - /// The maximum allowed delta time (in seconds) for the physics simulation step. - /// - float MaxDeltaTime = PhysicsSettings_MaxDeltaTime; - - /// - /// Whether to substep the physics simulation. - /// - bool EnableSubstepping = PhysicsSettings_EnableSubstepping; - - /// - /// Delta time (in seconds) for an individual simulation substep. - /// - float SubstepDeltaTime = PhysicsSettings_SubstepDeltaTime; - - /// - /// The maximum number of substeps for physics simulation. - /// - int32 MaxSubsteps = PhysicsSettings_MaxSubsteps; + API_FIELD(Attributes="EditorOrder(0), DefaultValue(typeof(Vector3), \"0,-981.0,0\"), EditorDisplay(\"Simulation\")") + Vector3 DefaultGravity = Vector3(0, -981.0f, 0); /// /// 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. /// - bool QueriesHitTriggers = PhysicsSettings_QueriesHitTriggers; + API_FIELD(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Framerate\")") + bool QueriesHitTriggers = true; + + /// + /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(5.0f), EditorDisplay(\"Simulation\")") + float TriangleMeshTriangleMinAreaThreshold = 5.0f; + + /// + /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity + /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(200.0f), EditorDisplay(\"Simulation\")") + float BounceThresholdVelocity = 200.0f; + + /// + /// Default friction combine mode, controls how friction is computed for multiple materials. + /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(PhysicsCombineMode.Average), EditorDisplay(\"Simulation\")") + PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode::Average; + + /// + /// Default restitution combine mode, controls how restitution is computed for multiple materials. + /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(PhysicsCombineMode.Average), EditorDisplay(\"Simulation\")") + PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode::Average; + + /// + /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for physx to check it internally. + /// + API_FIELD(Attributes="EditorOrder(70), DefaultValue(false), EditorDisplay(\"Simulation\")") + bool DisableCCD = false; + + /// + /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. + /// + API_FIELD(Attributes="EditorOrder(80), DefaultValue(false), EditorDisplay(\"Simulation\")") + bool EnableAdaptiveForce = false; + + /// + /// The maximum allowed delta time (in seconds) for the physics simulation step. + /// + API_FIELD(Attributes="EditorOrder(1000), DefaultValue(1.0f / 10.0f), EditorDisplay(\"Framerate\")") + float MaxDeltaTime = 1.0f / 10.0f; + + /// + /// Whether to substep the physics simulation. + /// + API_FIELD(Attributes="EditorOrder(1005), DefaultValue(false), EditorDisplay(\"Framerate\")") + bool EnableSubstepping = false; + + /// + /// Delta time (in seconds) for an individual simulation substep. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(1.0f / 120.0f), EditorDisplay(\"Framerate\")") + float SubstepDeltaTime = 1.0f / 120.0f; + + /// + /// The maximum number of substeps for physics simulation. + /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(5), EditorDisplay(\"Framerate\")") + int32 MaxSubsteps = 5; /// /// Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders. /// - bool SupportCookingAtRuntime = PhysicsSettings_SupportCookingAtRuntime; + API_FIELD(Attributes="EditorOrder(1100), DefaultValue(false), EditorDisplay(\"Other\")") + bool SupportCookingAtRuntime = false; public: @@ -115,8 +101,17 @@ public: public: + /// + /// Initializes a new instance of the class. + /// + PhysicsSettings(); + + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static PhysicsSettings* Get(); + // [SettingsBase] void Apply() override; - void RestoreDefault() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Physics/Utilities.h b/Source/Engine/Physics/Utilities.h index 11914277f..34ee7f66d 100644 --- a/Source/Engine/Physics/Utilities.h +++ b/Source/Engine/Physics/Utilities.h @@ -14,9 +14,6 @@ #include #include #include -#include "PhysicsSettings.h" - -//////////////////////////// Conversion between PhysX and Flax types inline PxVec2& C2P(const Vector2& v) { @@ -43,8 +40,6 @@ inline PxBounds3& C2P(const BoundingBox& v) return *(PxBounds3*)&v; } -//////////////////////////// - inline Vector2& P2C(const PxVec2& v) { return *(Vector2*)&v; @@ -79,29 +74,4 @@ inline Vector3 P2C(const PxExtendedVec3& v) #endif } -//////////////////////////// - -inline PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) -{ -#if WITH_PVD - PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; -#else - PxShapeFlags flags = static_cast(0); -#endif - - if (isEnabled) - { - if (isTrigger) - { - flags |= PxShapeFlag::eTRIGGER_SHAPE; - if (PhysicsSettings::Instance()->QueriesHitTriggers) - flags |= PxShapeFlag::eSCENE_QUERY_SHAPE; - } - else - { - flags = PxShapeFlag::eSIMULATION_SHAPE | PxShapeFlag::eSCENE_QUERY_SHAPE; - } - } - - return flags; -} +extern PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled); diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index 1c761259b..8805b2180 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -9,41 +9,37 @@ /// /// Android platform settings. /// -/// -class AndroidPlatformSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AndroidPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings); public: /// /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. /// - String PackageName; + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); /// /// The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file. /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")") Array Permissions; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// + API_FIELD(Attributes="EditorOrder(1030), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") Guid OverrideIcon; public: - AndroidPlatformSettings() - { - RestoreDefault(); - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static AndroidPlatformSettings* Get(); // [SettingsBase] - void RestoreDefault() final override - { - PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); - Permissions.Clear(); - OverrideIcon = Guid::Empty; - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(PackageName); diff --git a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h index 4c602224a..7da0ec406 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h +++ b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h @@ -9,66 +9,67 @@ /// /// Linux platform settings. /// -/// -class LinuxPlatformSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LinuxPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings); public: /// /// The default game window mode. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(GameWindowMode.Windowed), EditorDisplay(\"Window\")") GameWindowMode WindowMode = GameWindowMode::Windowed; /// /// The default game window width (in pixels). /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1280), EditorDisplay(\"Window\")") int32 ScreenWidth = 1280; /// /// The default game window height (in pixels). /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(720), EditorDisplay(\"Window\")") int32 ScreenHeight = 720; - /// - /// Enables game running when application window loses focus. - /// - bool RunInBackground = false; - /// /// Enables resizing the game window by the user. /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Window\")") bool ResizableWindow = false; + /// + /// Enables game running when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + bool RunInBackground = false; + /// /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(false), EditorDisplay(\"Other\")") bool ForceSingleInstance = false; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// - Guid OverrideIcon = Guid::Empty; + API_FIELD(Attributes="EditorOrder(1030), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.AssetRefEditor\"), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") + Guid OverrideIcon; /// /// Enables support for Vulkan. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\")") bool SupportVulkan = true; public: - // [SettingsBase] - void RestoreDefault() final override - { - WindowMode = GameWindowMode::Windowed; - ScreenWidth = 1280; - ScreenHeight = 720; - RunInBackground = false; - ResizableWindow = false; - ForceSingleInstance = false; - OverrideIcon = Guid::Empty; - SupportVulkan = true; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static LinuxPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(WindowMode); diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs index b7a2baa97..72a706685 100644 --- a/Source/Engine/Platform/Platform.Build.cs +++ b/Source/Engine/Platform/Platform.Build.cs @@ -62,5 +62,15 @@ public class Platform : EngineModule break; default: throw new InvalidPlatformException(options.Platform.Target); } + if (options.Target.IsEditor) + { + // Include platform settings headers + options.SourceFiles.Add(Path.Combine(FolderPath, "Windows", "WindowsPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "UWP", "UWPPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "Linux", "LinuxPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "Android", "AndroidPlatformSettings.h")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform", "XboxScarlettPlatformSettings.h")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Platform", "PS4PlatformSettings.h")); + } } } diff --git a/Source/Engine/Platform/UWP/UWPPlatformSettings.h b/Source/Engine/Platform/UWP/UWPPlatformSettings.h index 706cad89c..e600cead7 100644 --- a/Source/Engine/Platform/UWP/UWPPlatformSettings.h +++ b/Source/Engine/Platform/UWP/UWPPlatformSettings.h @@ -9,15 +9,15 @@ /// /// Universal Windows Platform settings. /// -/// -class UWPPlatformSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API UWPPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings); public: /// /// The preferred launch windowing mode. /// - enum class WindowMode + API_ENUM() enum class WindowMode { /// /// The full screen mode @@ -33,7 +33,7 @@ public: /// /// The display orientation modes. Can be combined as flags. /// - enum class DisplayOrientations + API_ENUM(Attributes="Flags") enum class DisplayOrientations { /// /// The none. @@ -71,40 +71,41 @@ public: /// /// The preferred launch windowing mode. Always fullscreen on Xbox. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(WindowMode.FullScreen), EditorDisplay(\"Window\")") WindowMode PreferredLaunchWindowingMode = WindowMode::FullScreen; /// /// The display orientation modes. Can be combined as flags. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(DisplayOrientations.All), EditorDisplay(\"Window\")") DisplayOrientations AutoRotationPreferences = DisplayOrientations::All; /// /// The location of the package certificate (relative to the project). /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(\"\"), EditorDisplay(\"Other\")") String CertificateLocation; /// /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\", \"Support DirectX 11\")") bool SupportDX11 = true; /// /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") bool SupportDX10 = false; public: - // [SettingsBase] - void RestoreDefault() final override - { - PreferredLaunchWindowingMode = WindowMode::FullScreen; - AutoRotationPreferences = DisplayOrientations::All; - CertificateLocation.Clear(); - SupportDX11 = true; - SupportDX10 = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static UWPPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(PreferredLaunchWindowingMode); diff --git a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h index 3da8dc2b8..48a39444a 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h +++ b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h @@ -9,84 +9,85 @@ /// /// Windows platform settings. /// -/// -class WindowsPlatformSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WindowsPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings); public: /// /// The default game window mode. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(GameWindowMode.Windowed), EditorDisplay(\"Window\")") GameWindowMode WindowMode = GameWindowMode::Windowed; /// /// The default game window width (in pixels). /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1280), EditorDisplay(\"Window\")") int32 ScreenWidth = 1280; /// /// The default game window height (in pixels). /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(720), EditorDisplay(\"Window\")") int32 ScreenHeight = 720; - /// - /// Enables game running when application window loses focus. - /// - bool RunInBackground = false; - /// /// Enables resizing the game window by the user. /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Window\")") bool ResizableWindow = false; + /// + /// Enables game running when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + bool RunInBackground = false; + /// /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(false), EditorDisplay(\"Other\")") bool ForceSingleInstance = false; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// + API_FIELD(Attributes="EditorOrder(1030), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.AssetRefEditor\"), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") Guid OverrideIcon; /// /// Enables support for DirectX 12. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 12\")") bool SupportDX12 = false; /// /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(true), EditorDisplay(\"Graphics\", \"Support DirectX 11\")") bool SupportDX11 = true; /// /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2020), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") bool SupportDX10 = false; /// /// Enables support for Vulkan. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2030), DefaultValue(false), EditorDisplay(\"Graphics\")") bool SupportVulkan = false; public: - // [SettingsBase] - void RestoreDefault() final override - { - WindowMode = GameWindowMode::Windowed; - ScreenWidth = 1280; - ScreenHeight = 720; - RunInBackground = false; - ResizableWindow = false; - ForceSingleInstance = false; - OverrideIcon = Guid::Empty; - SupportDX12 = false; - SupportDX11 = true; - SupportDX10 = false; - SupportVulkan = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static WindowsPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(WindowMode); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index d15dd4cfa..9dabe1af0 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -69,7 +69,7 @@ void Terrain::UpdateLayerBits() filterData.word0 = GetLayerMask(); // Own layer mask - filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()]; + filterData.word1 = Physics::LayerMasks[GetLayer()]; // Update the shapes layer bits for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 966737e95..2c3e1c837 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -66,6 +66,14 @@ namespace Flax.Build.Bindings return null; } + // Special case for Engine TEXT macro + if (value.Contains("TEXT(\"")) + return value.Replace("TEXT(\"", "(\""); + + // Special case for value constructors + if (value.Contains('(') && value.Contains(')')) + return "new " + value; + // Convert from C++ to C# switch (value) { @@ -611,6 +619,12 @@ namespace Flax.Build.Bindings contents.Append(returnValueType).Append(' ').Append(fieldInfo.Name); if (!useUnmanaged) { + if (fieldInfo.DefaultValue != null) + { + var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo); + if (!string.IsNullOrEmpty(defaultValue)) + contents.Append(" = ").Append(defaultValue); + } contents.AppendLine(";"); continue; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index b9096b3bb..c8a6a55df 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1310,7 +1310,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" }").AppendLine(); - if (!useScripting) + if (!useScripting && !classInfo.IsAbstract) { // Constructor contents.AppendLine(" static void Ctor(void* ptr)"); @@ -1350,7 +1350,14 @@ namespace Flax.Build.Bindings } else { - contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, nullptr"); + if (classInfo.IsAbstract) + contents.Append("nullptr, nullptr, "); + else + contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, "); + if (classInfo.BaseType != null) + contents.Append($"&{classInfo.BaseType}::TypeInitializer"); + else + contents.Append("nullptr"); } contents.Append(", ").Append(interfacesTable); contents.Append(");"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index da337e746..916916995 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1038,8 +1038,7 @@ namespace Flax.Build.Bindings var token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); if (token.Type == TokenType.Equal) { - //context.Tokenizer.SkipUntil(TokenType.SemiColon, out var defaultValue); - context.Tokenizer.SkipUntil(TokenType.SemiColon); + context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue); } else if (token.Type == TokenType.LeftBracket) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index ea8cfb559..419820f56 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -87,6 +87,8 @@ namespace Flax.Build.Bindings if (headerFiles.Count == 0) return moduleInfo; + // TODO: use aynsc tasks or thread pool to load and parse multiple header files at once using multi-threading + // Find and load files with API tags string[] headerFilesContents = null; //using (new ProfileEventScope("LoadHeaderFiles")) @@ -94,9 +96,8 @@ namespace Flax.Build.Bindings var anyApi = false; for (int i = 0; i < headerFiles.Count; i++) { - // Skip scripting types definitions file - if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal) || - headerFiles[i].EndsWith("EditorContextAPI.h", StringComparison.Ordinal)) + // Skip scripting tags definitions file + if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal)) continue; // Check if file contains any valid API tag @@ -320,24 +321,36 @@ namespace Flax.Build.Bindings token = tokenizer.NextToken(true); while (token.Type != TokenType.Newline) { - condition += token.Value; + var tokenValue = token.Value.Trim(); + if (tokenValue.Length == 0) + { + token = tokenizer.NextToken(true); + continue; + } + + // Very simple defines processing + tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions); + tokenValue = tokenValue.Replace("false", "0"); + tokenValue = tokenValue.Replace("true", "1"); + tokenValue = tokenValue.Replace("||", "|"); + if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") + tokenValue = "0"; + + condition += tokenValue; token = tokenizer.NextToken(true); } - // Replace contents with defines - condition = condition.Trim(); - condition = ReplacePreProcessorDefines(condition, context.PreprocessorDefines.Keys); - condition = ReplacePreProcessorDefines(condition, moduleOptions.PublicDefinitions); - condition = ReplacePreProcessorDefines(condition, moduleOptions.PrivateDefinitions); - condition = ReplacePreProcessorDefines(condition, moduleOptions.CompileEnv.PreprocessorDefinitions); - condition = condition.Replace("false", "0"); - condition = condition.Replace("true", "1"); + // Filter condition + condition = condition.Replace("1|1", "1"); + condition = condition.Replace("1|0", "1"); + condition = condition.Replace("0|1", "1"); - // Check condition - // TODO: support expressions in preprocessor defines in API headers? + // Skip chunk of code of condition fails if (condition != "1") { - // Skip chunk of code ParsePreprocessorIf(fileInfo, tokenizer, ref token); } @@ -404,7 +417,7 @@ namespace Flax.Build.Bindings { foreach (var define in defines) { - if (text.Contains(define)) + if (string.Equals(text, define, StringComparison.Ordinal)) text = text.Replace(define, "1"); } return text; diff --git a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs index 509c1d6e6..b1488ed09 100644 --- a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs @@ -12,6 +12,9 @@ namespace Flax.Build.Bindings public bool NoArray; public FunctionInfo Getter; public FunctionInfo Setter; + public string DefaultValue; + + public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue); public override string ToString() { @@ -19,6 +22,8 @@ namespace Flax.Build.Bindings if (IsStatic) result += "static "; result += Type + " " + Name; + if (HasDefaultValue) + result += " = " + DefaultValue; return result; } } diff --git a/Source/Tools/Flax.Build/Build/Module.cs b/Source/Tools/Flax.Build/Build/Module.cs index dae700728..726cbf946 100644 --- a/Source/Tools/Flax.Build/Build/Module.cs +++ b/Source/Tools/Flax.Build/Build/Module.cs @@ -111,5 +111,16 @@ namespace Flax.Build // By default deploy all C++ header files files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.AllDirectories)); } + + /// + /// Adds the file to the build sources if exists. + /// + /// The options. + /// The source file path. + protected void AddSourceFileIfExists(BuildOptions options, string path) + { + if (File.Exists(path)) + options.SourceFiles.Add(path); + } } } From 199683897aeb016923bfc011a81981d50acec7bb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Jan 2021 14:15:22 +0100 Subject: [PATCH 024/222] Update DirectXShaderCompiler to 1.6 --- .../DirectX/ShaderCompilerDX.cpp | 87 +-- .../Binaries/ThirdParty/x64/dxcompiler.dll | 4 +- .../Binaries/ThirdParty/x64/dxcompiler.lib | 4 +- .../Windows/Binaries/ThirdParty/x64/dxil.dll | 4 +- .../ThirdParty/DirectXShaderCompiler/dxcapi.h | 518 +++++++++++++----- 5 files changed, 448 insertions(+), 169 deletions(-) diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index a7f2fc01c..1f58dce93 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -12,10 +12,6 @@ #include #include -#ifndef DXIL_FOURCC -#define DXIL_FOURCC(ch0, ch1, ch2, ch3) ((uint32)(uint8)(ch0) | (uint32)(uint8)(ch1) << 8 | (uint32)(uint8)(ch2) << 16 | (uint32)(uint8)(ch3) << 24) -#endif - /// /// Helper class to include source for DX shaders compiler. /// @@ -76,7 +72,7 @@ public: ShaderCompilerDX::ShaderCompilerDX(ShaderProfile profile) : ShaderCompiler(profile) { - IDxcCompiler2* compiler = nullptr; + IDxcCompiler3* compiler = nullptr; IDxcLibrary* library = nullptr; IDxcContainerReflection* containerReflection = nullptr; if (FAILED(DxcCreateInstance(CLSID_DxcCompiler, __uuidof(compiler), reinterpret_cast(&compiler))) || @@ -216,7 +212,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD // Prepare auto options = _context->Options; - auto compiler = (IDxcCompiler2*)_compiler; + auto compiler = (IDxcCompiler3*)_compiler; auto library = (IDxcLibrary*)_library; auto containerReflection = (IDxcContainerReflection*)_containerReflection; auto type = meta.GetStage(); @@ -248,18 +244,27 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD ComPtr textBlob; if (FAILED(library->CreateBlobWithEncodingFromPinned((LPBYTE)options->Source, options->SourceLength, CP_UTF8, &textBlob))) return true; + DxcBuffer textBuffer; + textBuffer.Ptr = textBlob->GetBufferPointer(); + textBuffer.Size = textBlob->GetBufferSize(); + textBuffer.Encoding = DXC_CP_ACP; const StringAsUTF16<> entryPoint(meta.Name.Get(), meta.Name.Length()); Array definesStrings; - Array defines; - Array> args; + Array> args; if (_context->Options->NoOptimize) - args.Add(TEXT("-Od")); + args.Add(DXC_ARG_SKIP_OPTIMIZATIONS); else - args.Add(TEXT("-O3")); + args.Add(DXC_ARG_OPTIMIZATION_LEVEL3); if (_context->Options->TreatWarningsAsErrors) - args.Add(TEXT("-WX")); + args.Add(DXC_ARG_WARNINGS_ARE_ERRORS); if (_context->Options->GenerateDebugData) - args.Add(TEXT("-Zi")); + args.Add(DXC_ARG_DEBUG); + args.Add(TEXT("-T")); + args.Add(targetProfile); + args.Add(TEXT("-E")); + args.Add(entryPoint.Get()); + args.Add(options->TargetName.Get()); + Array> argsFull; // Compile all shader function permutations for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++) @@ -278,33 +283,37 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD // Convert defines from char* to Char* const int32 macrosCount = _macros.Count() - 1; - definesStrings.Resize(macrosCount * 2); - defines.Resize(macrosCount); + definesStrings.Resize(macrosCount); for (int32 i = 0; i < macrosCount; i++) { auto& macro = _macros[i]; - auto& define = defines[i]; - auto& defineName = definesStrings[i * 2]; - auto& defineValue = definesStrings[i * 2 + 1]; - defineName = macro.Name; - defineValue = macro.Definition; - define.Name = defineName.GetText(); - define.Value = defineValue.Get(); + auto& define = definesStrings[i]; + define = macro.Name; + if (macro.Definition && *macro.Definition) + { + define += TEXT("="); + define += macro.Definition; + } + } + + // Build full list of arguments + argsFull.Clear(); + for (auto& e : args) + argsFull.Add(e); + for (auto& d : definesStrings) + { + argsFull.Add(TEXT("-D")); + argsFull.Add(*d); } // Compile - ComPtr results; + ComPtr results; HRESULT result = compiler->Compile( - textBlob.Get(), - options->TargetName.Get(), - entryPoint.Get(), - targetProfile, - (LPCWSTR*)args.Get(), - args.Count(), - defines.Get(), - defines.Count(), + &textBuffer, + (LPCWSTR*)argsFull.Get(), + argsFull.Count(), &include, - &results); + IID_PPV_ARGS(&results)); if (SUCCEEDED(result) && results) results->GetStatus(&result); if (FAILED(result)) @@ -338,11 +347,19 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD // Generate debug information { // Disassemble compiled shader - ComPtr disassembly; - if (FAILED(compiler->Disassemble(shaderBuffer, &disassembly))) + ComPtr disassembly; + DxcBuffer shaderDxcBuffer; + shaderDxcBuffer.Ptr = shaderBuffer->GetBufferPointer(); + shaderDxcBuffer.Size = shaderBuffer->GetBufferSize(); + shaderDxcBuffer.Encoding = DXC_CP_ACP; + if (FAILED(compiler->Disassemble(&shaderDxcBuffer, IID_PPV_ARGS(&disassembly)))) + return true; + ComPtr disassemblyBlob; + ComPtr disassemblyPath; + if (FAILED(disassembly->GetOutput(DXC_OUT_DISASSEMBLY, IID_PPV_ARGS(disassemblyBlob.GetAddressOf()), disassemblyPath.GetAddressOf()))) return true; ComPtr disassemblyUtf8; - if (FAILED(library->GetBlobAsUtf8(disassembly, &disassemblyUtf8))) + if (FAILED(library->GetBlobAsUtf8(disassemblyBlob, &disassemblyUtf8))) return true; // Extract debug info @@ -356,7 +373,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD LOG(Error, "IDxcContainerReflection::Load failed."); return true; } - const uint32 dxilPartKind = DXIL_FOURCC('D', 'X', 'I', 'L'); + const uint32 dxilPartKind = DXC_PART_DXIL; uint32 dxilPartIndex = ~0u; if (FAILED(containerReflection->FindFirstPartKind(dxilPartKind, &dxilPartIndex))) { diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll index bf7ec271d..0f4d9ef52 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5c8d027a25bc36830b6241b0d2031a74973a00d96b3db67745e45fcfbf1d910 -size 15377800 +oid sha256:b7490aa95b516e002b30d9cf7034f4fc796d087660d028cb076fdf15ac9efe5e +size 18515848 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib index 21852672f..748275336 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7bf3b219942ae175796d79ca63f83efabeca54fc6eab4f67703bc4946df0b2b -size 2344 +oid sha256:02d6f118c010e5a1a65c30d622871525d92e8f78a80e322d3a2d855bb502d0e2 +size 2002 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll index 9afd5f8bc..411d5e15f 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17df4ea0a53a18dcb80c924217a54d617c1b6166e7677501a8decf86d08b9de7 -size 1119176 +oid sha256:5c9bbaa764ec71e5087b13767a20086ccebc6127a46473a1076977b28bbc24c8 +size 1867776 diff --git a/Source/ThirdParty/DirectXShaderCompiler/dxcapi.h b/Source/ThirdParty/DirectXShaderCompiler/dxcapi.h index 342c2e782..4e4297e50 100644 --- a/Source/ThirdParty/DirectXShaderCompiler/dxcapi.h +++ b/Source/ThirdParty/DirectXShaderCompiler/dxcapi.h @@ -35,23 +35,6 @@ struct IMalloc; struct IDxcIncludeHandler; -/// -/// Creates a single uninitialized object of the class associated with a specified CLSID. -/// -/// -/// The CLSID associated with the data and code that will be used to create the object. -/// -/// -/// A reference to the identifier of the interface to be used to communicate -/// with the object. -/// -/// -/// Address of pointer variable that receives the interface pointer requested -/// in riid. Upon successful return, *ppv contains the requested interface -/// pointer. Upon failure, *ppv contains NULL. -/// -/// While this function is similar to CoCreateInstance, there is no COM involvement. -/// typedef HRESULT (__stdcall *DxcCreateInstanceProc)( _In_ REFCLSID rclsid, _In_ REFIID riid, @@ -102,6 +85,56 @@ DXC_API_IMPORT HRESULT __stdcall DxcCreateInstance2( _Out_ LPVOID* ppv ); +// For convenience, equivalent definitions to CP_UTF8 and CP_UTF16. +#define DXC_CP_UTF8 65001 +#define DXC_CP_UTF16 1200 +// Use DXC_CP_ACP for: Binary; ANSI Text; Autodetect UTF with BOM +#define DXC_CP_ACP 0 + +// This flag indicates that the shader hash was computed taking into account source information (-Zss) +#define DXC_HASHFLAG_INCLUDES_SOURCE 1 + +// Hash digest type for ShaderHash +typedef struct DxcShaderHash { + UINT32 Flags; // DXC_HASHFLAG_* + BYTE HashDigest[16]; +} DxcShaderHash; + +#define DXC_FOURCC(ch0, ch1, ch2, ch3) ( \ + (UINT32)(UINT8)(ch0) | (UINT32)(UINT8)(ch1) << 8 | \ + (UINT32)(UINT8)(ch2) << 16 | (UINT32)(UINT8)(ch3) << 24 \ + ) +#define DXC_PART_PDB DXC_FOURCC('I', 'L', 'D', 'B') +#define DXC_PART_PDB_NAME DXC_FOURCC('I', 'L', 'D', 'N') +#define DXC_PART_PRIVATE_DATA DXC_FOURCC('P', 'R', 'I', 'V') +#define DXC_PART_ROOT_SIGNATURE DXC_FOURCC('R', 'T', 'S', '0') +#define DXC_PART_DXIL DXC_FOURCC('D', 'X', 'I', 'L') +#define DXC_PART_REFLECTION_DATA DXC_FOURCC('S', 'T', 'A', 'T') +#define DXC_PART_SHADER_HASH DXC_FOURCC('H', 'A', 'S', 'H') +#define DXC_PART_INPUT_SIGNATURE DXC_FOURCC('I', 'S', 'G', '1') +#define DXC_PART_OUTPUT_SIGNATURE DXC_FOURCC('O', 'S', 'G', '1') +#define DXC_PART_PATCH_CONSTANT_SIGNATURE DXC_FOURCC('P', 'S', 'G', '1') + +// Some option arguments are defined here for continuity with D3DCompile interface +#define DXC_ARG_DEBUG L"-Zi" +#define DXC_ARG_SKIP_VALIDATION L"-Vd" +#define DXC_ARG_SKIP_OPTIMIZATIONS L"-Od" +#define DXC_ARG_PACK_MATRIX_ROW_MAJOR L"-Zpr" +#define DXC_ARG_PACK_MATRIX_COLUMN_MAJOR L"-Zpc" +#define DXC_ARG_AVOID_FLOW_CONTROL L"-Gfa" +#define DXC_ARG_PREFER_FLOW_CONTROL L"-Gfp" +#define DXC_ARG_ENABLE_STRICTNESS L"-Ges" +#define DXC_ARG_ENABLE_BACKWARDS_COMPATIBILITY L"-Gec" +#define DXC_ARG_IEEE_STRICTNESS L"-Gis" +#define DXC_ARG_OPTIMIZATION_LEVEL0 L"-O0" +#define DXC_ARG_OPTIMIZATION_LEVEL1 L"-O1" +#define DXC_ARG_OPTIMIZATION_LEVEL2 L"-O2" +#define DXC_ARG_OPTIMIZATION_LEVEL3 L"-O3" +#define DXC_ARG_WARNINGS_ARE_ERRORS L"-WX" +#define DXC_ARG_RESOURCES_MAY_ALIAS L"-res_may_alias" +#define DXC_ARG_ALL_RESOURCES_BOUND L"-all_resources_bound" +#define DXC_ARG_DEBUG_NAME_FOR_SOURCE L"-Zss" +#define DXC_ARG_DEBUG_NAME_FOR_BINARY L"-Zsb" // IDxcBlob is an alias of ID3D10Blob and ID3DBlob struct __declspec(uuid("8BA5FB08-5195-40e2-AC58-0D989C3A0102")) @@ -122,70 +155,144 @@ public: DECLARE_CROSS_PLATFORM_UUIDOF(IDxcBlobEncoding) }; -struct __declspec(uuid("e5204dc7-d18c-4c3c-bdfb-851673980fe7")) -IDxcLibrary : public IUnknown { - virtual HRESULT STDMETHODCALLTYPE SetMalloc(_In_opt_ IMalloc *pMalloc) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateBlobFromBlob( - _In_ IDxcBlob *pBlob, UINT32 offset, UINT32 length, _COM_Outptr_ IDxcBlob **ppResult) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateBlobFromFile( - LPCWSTR pFileName, _In_opt_ UINT32* codePage, - _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingFromPinned( - _In_bytecount_(size) LPCVOID pText, UINT32 size, UINT32 codePage, - _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingOnHeapCopy( - _In_bytecount_(size) LPCVOID pText, UINT32 size, UINT32 codePage, - _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingOnMalloc( - _In_bytecount_(size) LPCVOID pText, IMalloc *pIMalloc, UINT32 size, UINT32 codePage, - _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateIncludeHandler( - _COM_Outptr_ IDxcIncludeHandler **ppResult) = 0; - virtual HRESULT STDMETHODCALLTYPE CreateStreamFromBlobReadOnly( - _In_ IDxcBlob *pBlob, _COM_Outptr_ IStream **ppStream) = 0; - virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf8( - _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; - virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf16( - _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; +// Notes on IDxcBlobUtf16 and IDxcBlobUtf8 +// These guarantee null-terminated text and the stated encoding. +// GetBufferSize() will return the size in bytes, including null-terminator +// GetStringLength() will return the length in characters, excluding the null-terminator +// Name strings will use IDxcBlobUtf16, while other string output blobs, +// such as errors/warnings, preprocessed HLSL, or other text will be based +// on the -encoding option. - DECLARE_CROSS_PLATFORM_UUIDOF(IDxcLibrary) +// The API will use this interface for output name strings +struct __declspec(uuid("A3F84EAB-0FAA-497E-A39C-EE6ED60B2D84")) +IDxcBlobUtf16 : public IDxcBlobEncoding { +public: + virtual LPCWSTR STDMETHODCALLTYPE GetStringPointer(void) = 0; + virtual SIZE_T STDMETHODCALLTYPE GetStringLength(void) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcBlobUtf16) }; +struct __declspec(uuid("3DA636C9-BA71-4024-A301-30CBF125305B")) +IDxcBlobUtf8 : public IDxcBlobEncoding { +public: + virtual LPCSTR STDMETHODCALLTYPE GetStringPointer(void) = 0; + virtual SIZE_T STDMETHODCALLTYPE GetStringLength(void) = 0; -struct __declspec(uuid("CEDB484A-D4E9-445A-B991-CA21CA157DC2")) -IDxcOperationResult : public IUnknown { - virtual HRESULT STDMETHODCALLTYPE GetStatus(_Out_ HRESULT *pStatus) = 0; - virtual HRESULT STDMETHODCALLTYPE GetResult(_COM_Outptr_result_maybenull_ IDxcBlob **pResult) = 0; - virtual HRESULT STDMETHODCALLTYPE GetErrorBuffer(_COM_Outptr_result_maybenull_ IDxcBlobEncoding **pErrors) = 0; - - DECLARE_CROSS_PLATFORM_UUIDOF(IDxcOperationResult) + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcBlobUtf8) }; struct __declspec(uuid("7f61fc7d-950d-467f-b3e3-3c02fb49187c")) IDxcIncludeHandler : public IUnknown { virtual HRESULT STDMETHODCALLTYPE LoadSource( - _In_ LPCWSTR pFilename, // Candidate filename. + _In_z_ LPCWSTR pFilename, // Candidate filename. _COM_Outptr_result_maybenull_ IDxcBlob **ppIncludeSource // Resultant source object for included file, nullptr if not found. ) = 0; DECLARE_CROSS_PLATFORM_UUIDOF(IDxcIncludeHandler) }; +// Structure for supplying bytes or text input to Dxc APIs. +// Use Encoding = 0 for non-text bytes, ANSI text, or unknown with BOM. +typedef struct DxcBuffer { + LPCVOID Ptr; + SIZE_T Size; + UINT Encoding; +} DxcText; + struct DxcDefine { LPCWSTR Name; _Maybenull_ LPCWSTR Value; }; +struct __declspec(uuid("73EFFE2A-70DC-45F8-9690-EFF64C02429D")) +IDxcCompilerArgs : public IUnknown { + // Pass GetArguments() and GetCount() to Compile + virtual LPCWSTR* STDMETHODCALLTYPE GetArguments() = 0; + virtual UINT32 STDMETHODCALLTYPE GetCount() = 0; + + // Add additional arguments or defines here, if desired. + virtual HRESULT STDMETHODCALLTYPE AddArguments( + _In_opt_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments to add + _In_ UINT32 argCount // Number of arguments to add + ) = 0; + virtual HRESULT STDMETHODCALLTYPE AddArgumentsUTF8( + _In_opt_count_(argCount)LPCSTR *pArguments, // Array of pointers to UTF-8 arguments to add + _In_ UINT32 argCount // Number of arguments to add + ) = 0; + virtual HRESULT STDMETHODCALLTYPE AddDefines( + _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines + _In_ UINT32 defineCount // Number of defines + ) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcCompilerArgs) +}; + +////////////////////////// +// Legacy Interfaces +///////////////////////// + +// NOTE: IDxcUtils replaces IDxcLibrary +struct __declspec(uuid("e5204dc7-d18c-4c3c-bdfb-851673980fe7")) +IDxcLibrary : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE SetMalloc(_In_opt_ IMalloc *pMalloc) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateBlobFromBlob( + _In_ IDxcBlob *pBlob, UINT32 offset, UINT32 length, _COM_Outptr_ IDxcBlob **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateBlobFromFile( + _In_z_ LPCWSTR pFileName, _In_opt_ UINT32* codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingFromPinned( + _In_bytecount_(size) LPCVOID pText, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingOnHeapCopy( + _In_bytecount_(size) LPCVOID pText, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingOnMalloc( + _In_bytecount_(size) LPCVOID pText, IMalloc *pIMalloc, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateIncludeHandler( + _COM_Outptr_ IDxcIncludeHandler **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateStreamFromBlobReadOnly( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IStream **ppStream) = 0; + virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf8( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf16( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcLibrary) +}; + +// NOTE: IDxcResult replaces IDxcOperationResult +struct __declspec(uuid("CEDB484A-D4E9-445A-B991-CA21CA157DC2")) +IDxcOperationResult : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE GetStatus(_Out_ HRESULT *pStatus) = 0; + + // GetResult returns the main result of the operation. + // This corresponds to: + // DXC_OUT_OBJECT - Compile() with shader or library target + // DXC_OUT_DISASSEMBLY - Disassemble() + // DXC_OUT_HLSL - Compile() with -P + // DXC_OUT_ROOT_SIGNATURE - Compile() with rootsig_* target + virtual HRESULT STDMETHODCALLTYPE GetResult(_COM_Outptr_result_maybenull_ IDxcBlob **ppResult) = 0; + + // GetErrorBuffer Corresponds to DXC_OUT_ERRORS. + virtual HRESULT STDMETHODCALLTYPE GetErrorBuffer(_COM_Outptr_result_maybenull_ IDxcBlobEncoding **ppErrors) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcOperationResult) +}; + +// NOTE: IDxcCompiler3 replaces IDxcCompiler and IDxcCompiler2 struct __declspec(uuid("8c210bf3-011f-4422-8d70-6f9acb8db617")) IDxcCompiler : public IUnknown { // Compile a single entry point to the target shader model virtual HRESULT STDMETHODCALLTYPE Compile( _In_ IDxcBlob *pSource, // Source text to compile - _In_opt_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. - _In_ LPCWSTR pEntryPoint, // entry point name - _In_ LPCWSTR pTargetProfile, // shader profile to compile - _In_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. + _In_opt_z_ LPCWSTR pEntryPoint, // entry point name + _In_z_ LPCWSTR pTargetProfile, // shader profile to compile + _In_opt_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments _In_ UINT32 argCount, // Number of arguments - _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines + _In_count_(defineCount) + const DxcDefine *pDefines, // Array of defines _In_ UINT32 defineCount, // Number of defines _In_opt_ IDxcIncludeHandler *pIncludeHandler, // user-provided interface to handle #include directives (optional) _COM_Outptr_ IDxcOperationResult **ppResult // Compiler output status, buffer, and errors @@ -194,10 +301,11 @@ IDxcCompiler : public IUnknown { // Preprocess source text virtual HRESULT STDMETHODCALLTYPE Preprocess( _In_ IDxcBlob *pSource, // Source text to preprocess - _In_opt_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. - _In_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. + _In_opt_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments _In_ UINT32 argCount, // Number of arguments - _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines + _In_count_(defineCount) + const DxcDefine *pDefines, // Array of defines _In_ UINT32 defineCount, // Number of defines _In_opt_ IDxcIncludeHandler *pIncludeHandler, // user-provided interface to handle #include directives (optional) _COM_Outptr_ IDxcOperationResult **ppResult // Preprocessor output status, buffer, and errors @@ -212,21 +320,23 @@ IDxcCompiler : public IUnknown { DECLARE_CROSS_PLATFORM_UUIDOF(IDxcCompiler) }; +// NOTE: IDxcCompiler3 replaces IDxcCompiler and IDxcCompiler2 struct __declspec(uuid("A005A9D9-B8BB-4594-B5C9-0E633BEC4D37")) IDxcCompiler2 : public IDxcCompiler { // Compile a single entry point to the target shader model with debug information. virtual HRESULT STDMETHODCALLTYPE CompileWithDebug( _In_ IDxcBlob *pSource, // Source text to compile - _In_opt_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. - _In_ LPCWSTR pEntryPoint, // Entry point name - _In_ LPCWSTR pTargetProfile, // Shader profile to compile - _In_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. + _In_opt_z_ LPCWSTR pEntryPoint, // Entry point name + _In_z_ LPCWSTR pTargetProfile, // Shader profile to compile + _In_opt_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments _In_ UINT32 argCount, // Number of arguments - _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines + _In_count_(defineCount) + const DxcDefine *pDefines, // Array of defines _In_ UINT32 defineCount, // Number of defines _In_opt_ IDxcIncludeHandler *pIncludeHandler, // user-provided interface to handle #include directives (optional) _COM_Outptr_ IDxcOperationResult **ppResult, // Compiler output status, buffer, and errors - _Outptr_opt_result_z_ LPWSTR *ppDebugBlobName,// Suggested file name for debug blob. + _Outptr_opt_result_z_ LPWSTR *ppDebugBlobName,// Suggested file name for debug blob. (Must be HeapFree()'d!) _COM_Outptr_opt_ IDxcBlob **ppDebugBlob // Debug blob ) = 0; @@ -238,28 +348,181 @@ IDxcLinker : public IUnknown { public: // Register a library with name to ref it later. virtual HRESULT RegisterLibrary( - _In_opt_ LPCWSTR pLibName, // Name of the library. - _In_ IDxcBlob *pLib // Library blob. + _In_opt_ LPCWSTR pLibName, // Name of the library. + _In_ IDxcBlob *pLib // Library blob. ) = 0; // Links the shader and produces a shader blob that the Direct3D runtime can // use. virtual HRESULT STDMETHODCALLTYPE Link( - _In_opt_ LPCWSTR pEntryName, // Entry point name - _In_ LPCWSTR pTargetProfile, // shader profile to link - _In_count_(libCount) - const LPCWSTR *pLibNames, // Array of library names to link - UINT32 libCount, // Number of libraries to link - _In_count_(argCount) - const LPCWSTR *pArguments, // Array of pointers to arguments - _In_ UINT32 argCount, // Number of arguments - _COM_Outptr_ IDxcOperationResult * - *ppResult // Linker output status, buffer, and errors + _In_opt_ LPCWSTR pEntryName, // Entry point name + _In_ LPCWSTR pTargetProfile, // shader profile to link + _In_count_(libCount) + const LPCWSTR *pLibNames, // Array of library names to link + _In_ UINT32 libCount, // Number of libraries to link + _In_opt_count_(argCount) const LPCWSTR *pArguments, // Array of pointers to arguments + _In_ UINT32 argCount, // Number of arguments + _COM_Outptr_ + IDxcOperationResult **ppResult // Linker output status, buffer, and errors ) = 0; DECLARE_CROSS_PLATFORM_UUIDOF(IDxcLinker) }; +///////////////////////// +// Latest interfaces. Please use these +//////////////////////// + +// NOTE: IDxcUtils replaces IDxcLibrary +struct __declspec(uuid("4605C4CB-2019-492A-ADA4-65F20BB7D67F")) +IDxcUtils : public IUnknown { + // Create a sub-blob that holds a reference to the outer blob and points to its memory. + virtual HRESULT STDMETHODCALLTYPE CreateBlobFromBlob( + _In_ IDxcBlob *pBlob, UINT32 offset, UINT32 length, _COM_Outptr_ IDxcBlob **ppResult) = 0; + + // For codePage, use 0 (or DXC_CP_ACP) for raw binary or ANSI code page + + // Creates a blob referencing existing memory, with no copy. + // User must manage the memory lifetime separately. + // (was: CreateBlobWithEncodingFromPinned) + virtual HRESULT STDMETHODCALLTYPE CreateBlobFromPinned( + _In_bytecount_(size) LPCVOID pData, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + // Create blob, taking ownership of memory allocated with supplied allocator. + // (was: CreateBlobWithEncodingOnMalloc) + virtual HRESULT STDMETHODCALLTYPE MoveToBlob( + _In_bytecount_(size) LPCVOID pData, IMalloc *pIMalloc, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + //// + // New blobs and copied contents are allocated with the current allocator + + // Copy blob contents to memory owned by the new blob. + // (was: CreateBlobWithEncodingOnHeapCopy) + virtual HRESULT STDMETHODCALLTYPE CreateBlob( + _In_bytecount_(size) LPCVOID pData, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + // (was: CreateBlobFromFile) + virtual HRESULT STDMETHODCALLTYPE LoadFile( + _In_z_ LPCWSTR pFileName, _In_opt_ UINT32* pCodePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + virtual HRESULT STDMETHODCALLTYPE CreateReadOnlyStreamFromBlob( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IStream **ppStream) = 0; + + // Create default file-based include handler + virtual HRESULT STDMETHODCALLTYPE CreateDefaultIncludeHandler( + _COM_Outptr_ IDxcIncludeHandler **ppResult) = 0; + + // Convert or return matching encoded text blobs + virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf8( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobUtf8 **pBlobEncoding) = 0; + virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf16( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobUtf16 **pBlobEncoding) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetDxilContainerPart( + _In_ const DxcBuffer *pShader, + _In_ UINT32 DxcPart, + _Outptr_result_nullonfailure_ void **ppPartData, + _Out_ UINT32 *pPartSizeInBytes) = 0; + + // Create reflection interface from serialized Dxil container, or DXC_PART_REFLECTION_DATA. + // TBD: Require part header for RDAT? (leaning towards yes) + virtual HRESULT STDMETHODCALLTYPE CreateReflection( + _In_ const DxcBuffer *pData, REFIID iid, void **ppvReflection) = 0; + + virtual HRESULT STDMETHODCALLTYPE BuildArguments( + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in errors and include handlers. + _In_opt_z_ LPCWSTR pEntryPoint, // Entry point name. (-E) + _In_z_ LPCWSTR pTargetProfile, // Shader profile to compile. (-T) + _In_opt_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments + _In_ UINT32 argCount, // Number of arguments + _In_count_(defineCount) + const DxcDefine *pDefines, // Array of defines + _In_ UINT32 defineCount, // Number of defines + _COM_Outptr_ IDxcCompilerArgs **ppArgs // Arguments you can use with Compile() method + ) = 0; + + // Takes the shader PDB and returns the hash and the container inside it + virtual HRESULT STDMETHODCALLTYPE GetPDBContents( + _In_ IDxcBlob *pPDBBlob, _COM_Outptr_ IDxcBlob **ppHash, _COM_Outptr_ IDxcBlob **ppContainer) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcUtils) +}; + +// For use with IDxcResult::[Has|Get]Output dxcOutKind argument +// Note: text outputs returned from version 2 APIs are UTF-8 or UTF-16 based on -encoding option +typedef enum DXC_OUT_KIND { + DXC_OUT_NONE = 0, + DXC_OUT_OBJECT = 1, // IDxcBlob - Shader or library object + DXC_OUT_ERRORS = 2, // IDxcBlobUtf8 or IDxcBlobUtf16 + DXC_OUT_PDB = 3, // IDxcBlob + DXC_OUT_SHADER_HASH = 4, // IDxcBlob - DxcShaderHash of shader or shader with source info (-Zsb/-Zss) + DXC_OUT_DISASSEMBLY = 5, // IDxcBlobUtf8 or IDxcBlobUtf16 - from Disassemble + DXC_OUT_HLSL = 6, // IDxcBlobUtf8 or IDxcBlobUtf16 - from Preprocessor or Rewriter + DXC_OUT_TEXT = 7, // IDxcBlobUtf8 or IDxcBlobUtf16 - other text, such as -ast-dump or -Odump + DXC_OUT_REFLECTION = 8, // IDxcBlob - RDAT part with reflection data + DXC_OUT_ROOT_SIGNATURE = 9, // IDxcBlob - Serialized root signature output + DXC_OUT_EXTRA_OUTPUTS = 10,// IDxcExtraResults - Extra outputs + + DXC_OUT_FORCE_DWORD = 0xFFFFFFFF +} DXC_OUT_KIND; + +struct __declspec(uuid("58346CDA-DDE7-4497-9461-6F87AF5E0659")) +IDxcResult : public IDxcOperationResult { + virtual BOOL STDMETHODCALLTYPE HasOutput(_In_ DXC_OUT_KIND dxcOutKind) = 0; + virtual HRESULT STDMETHODCALLTYPE GetOutput(_In_ DXC_OUT_KIND dxcOutKind, + _In_ REFIID iid, _COM_Outptr_opt_result_maybenull_ void **ppvObject, + _COM_Outptr_ IDxcBlobUtf16 **ppOutputName) = 0; + + virtual UINT32 GetNumOutputs() = 0; + virtual DXC_OUT_KIND GetOutputByIndex(UINT32 Index) = 0; + virtual DXC_OUT_KIND PrimaryOutput() = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcResult) +}; + +// Special names for extra output that should get written to specific streams +#define DXC_EXTRA_OUTPUT_NAME_STDOUT L"*stdout*" +#define DXC_EXTRA_OUTPUT_NAME_STDERR L"*stderr*" + +struct __declspec(uuid("319b37a2-a5c2-494a-a5de-4801b2faf989")) +IDxcExtraOutputs : public IUnknown { + + virtual UINT32 STDMETHODCALLTYPE GetOutputCount() = 0; + virtual HRESULT STDMETHODCALLTYPE GetOutput(_In_ UINT32 uIndex, + _In_ REFIID iid, _COM_Outptr_opt_result_maybenull_ void **ppvObject, + _COM_Outptr_opt_result_maybenull_ IDxcBlobUtf16 **ppOutputType, + _COM_Outptr_opt_result_maybenull_ IDxcBlobUtf16 **ppOutputName) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcExtraOutputs) +}; + +struct __declspec(uuid("228B4687-5A6A-4730-900C-9702B2203F54")) +IDxcCompiler3 : public IUnknown { + // Compile a single entry point to the target shader model, + // Compile a library to a library target (-T lib_*), + // Compile a root signature (-T rootsig_*), or + // Preprocess HLSL source (-P) + virtual HRESULT STDMETHODCALLTYPE Compile( + _In_ const DxcBuffer *pSource, // Source text to compile + _In_opt_count_(argCount) LPCWSTR *pArguments, // Array of pointers to arguments + _In_ UINT32 argCount, // Number of arguments + _In_opt_ IDxcIncludeHandler *pIncludeHandler, // user-provided interface to handle #include directives (optional) + _In_ REFIID riid, _Out_ LPVOID *ppResult // IDxcResult: status, buffer, and errors + ) = 0; + + // Disassemble a program. + virtual HRESULT STDMETHODCALLTYPE Disassemble( + _In_ const DxcBuffer *pObject, // Program to disassemble: dxil container or bitcode. + _In_ REFIID riid, _Out_ LPVOID *ppResult // IDxcResult: status, disassembly text, and errors + ) = 0; + + DECLARE_CROSS_PLATFORM_UUIDOF(IDxcCompiler3) +}; + static const UINT32 DxcValidatorFlags_Default = 0; static const UINT32 DxcValidatorFlags_InPlaceEdit = 1; // Validator is allowed to update shader blob in-place. static const UINT32 DxcValidatorFlags_RootSignatureOnly = 2; @@ -356,80 +619,79 @@ IDxcVersionInfo2 : public IDxcVersionInfo { // Note: __declspec(selectany) requires 'extern' // On Linux __declspec(selectany) is removed and using 'extern' results in link error. #ifdef _MSC_VER -#define EXTERN extern +#define CLSID_SCOPE __declspec(selectany) extern #else -#define EXTERN +#define CLSID_SCOPE #endif -// {73e22d93-e6ce-47f3-b5bf-f0664f39c1b0} -__declspec(selectany) EXTERN const CLSID CLSID_DxcCompiler = { - 0x73e22d93, - 0xe6ce, - 0x47f3, - { 0xb5, 0xbf, 0xf0, 0x66, 0x4f, 0x39, 0xc1, 0xb0 } -}; +CLSID_SCOPE const CLSID CLSID_DxcCompiler = { + 0x73e22d93, + 0xe6ce, + 0x47f3, + {0xb5, 0xbf, 0xf0, 0x66, 0x4f, 0x39, 0xc1, 0xb0}}; // {EF6A8087-B0EA-4D56-9E45-D07E1A8B7806} -__declspec(selectany) EXTERN const GUID CLSID_DxcLinker = { +CLSID_SCOPE const GUID CLSID_DxcLinker = { 0xef6a8087, 0xb0ea, 0x4d56, - {0x9e, 0x45, 0xd0, 0x7e, 0x1a, 0x8b, 0x78, 0x6} -}; + {0x9e, 0x45, 0xd0, 0x7e, 0x1a, 0x8b, 0x78, 0x6}}; // {CD1F6B73-2AB0-484D-8EDC-EBE7A43CA09F} -__declspec(selectany) EXTERN const CLSID CLSID_DxcDiaDataSource = { - 0xcd1f6b73, - 0x2ab0, - 0x484d, - { 0x8e, 0xdc, 0xeb, 0xe7, 0xa4, 0x3c, 0xa0, 0x9f } -}; +CLSID_SCOPE const CLSID CLSID_DxcDiaDataSource = { + 0xcd1f6b73, + 0x2ab0, + 0x484d, + {0x8e, 0xdc, 0xeb, 0xe7, 0xa4, 0x3c, 0xa0, 0x9f}}; + +// {3E56AE82-224D-470F-A1A1-FE3016EE9F9D} +CLSID_SCOPE const CLSID CLSID_DxcCompilerArgs = { + 0x3e56ae82, + 0x224d, + 0x470f, + {0xa1, 0xa1, 0xfe, 0x30, 0x16, 0xee, 0x9f, 0x9d}}; // {6245D6AF-66E0-48FD-80B4-4D271796748C} -__declspec(selectany) EXTERN const GUID CLSID_DxcLibrary = { - 0x6245d6af, - 0x66e0, - 0x48fd, - { 0x80, 0xb4, 0x4d, 0x27, 0x17, 0x96, 0x74, 0x8c } -}; +CLSID_SCOPE const GUID CLSID_DxcLibrary = { + 0x6245d6af, + 0x66e0, + 0x48fd, + {0x80, 0xb4, 0x4d, 0x27, 0x17, 0x96, 0x74, 0x8c}}; + +CLSID_SCOPE const GUID CLSID_DxcUtils = CLSID_DxcLibrary; // {8CA3E215-F728-4CF3-8CDD-88AF917587A1} -__declspec(selectany) EXTERN const GUID CLSID_DxcValidator = { - 0x8ca3e215, - 0xf728, - 0x4cf3, - { 0x8c, 0xdd, 0x88, 0xaf, 0x91, 0x75, 0x87, 0xa1 } -}; +CLSID_SCOPE const GUID CLSID_DxcValidator = { + 0x8ca3e215, + 0xf728, + 0x4cf3, + {0x8c, 0xdd, 0x88, 0xaf, 0x91, 0x75, 0x87, 0xa1}}; // {D728DB68-F903-4F80-94CD-DCCF76EC7151} -__declspec(selectany) EXTERN const GUID CLSID_DxcAssembler = { - 0xd728db68, - 0xf903, - 0x4f80, - { 0x94, 0xcd, 0xdc, 0xcf, 0x76, 0xec, 0x71, 0x51 } -}; +CLSID_SCOPE const GUID CLSID_DxcAssembler = { + 0xd728db68, + 0xf903, + 0x4f80, + {0x94, 0xcd, 0xdc, 0xcf, 0x76, 0xec, 0x71, 0x51}}; // {b9f54489-55b8-400c-ba3a-1675e4728b91} -__declspec(selectany) EXTERN const GUID CLSID_DxcContainerReflection = { - 0xb9f54489, - 0x55b8, - 0x400c, - { 0xba, 0x3a, 0x16, 0x75, 0xe4, 0x72, 0x8b, 0x91 } -}; +CLSID_SCOPE const GUID CLSID_DxcContainerReflection = { + 0xb9f54489, + 0x55b8, + 0x400c, + {0xba, 0x3a, 0x16, 0x75, 0xe4, 0x72, 0x8b, 0x91}}; // {AE2CD79F-CC22-453F-9B6B-B124E7A5204C} -__declspec(selectany) EXTERN const GUID CLSID_DxcOptimizer = { +CLSID_SCOPE const GUID CLSID_DxcOptimizer = { 0xae2cd79f, 0xcc22, 0x453f, - {0x9b, 0x6b, 0xb1, 0x24, 0xe7, 0xa5, 0x20, 0x4c} -}; + {0x9b, 0x6b, 0xb1, 0x24, 0xe7, 0xa5, 0x20, 0x4c}}; // {94134294-411f-4574-b4d0-8741e25240d2} -__declspec(selectany) EXTERN const GUID CLSID_DxcContainerBuilder = { - 0x94134294, - 0x411f, - 0x4574, - { 0xb4, 0xd0, 0x87, 0x41, 0xe2, 0x52, 0x40, 0xd2 } -}; +CLSID_SCOPE const GUID CLSID_DxcContainerBuilder = { + 0x94134294, + 0x411f, + 0x4574, + {0xb4, 0xd0, 0x87, 0x41, 0xe2, 0x52, 0x40, 0xd2}}; #endif From f5b9a95522524eb381d05bc8e8e0c6f57ca0c7fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Jan 2021 14:15:32 +0100 Subject: [PATCH 025/222] Fix null character at shader sources at during game cooking --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index af6f003b8..7f2824c42 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -323,6 +323,8 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass auto sourceLength = sourceChunk->Size(); Encryption::DecryptBytes((byte*)source, sourceLength); source[sourceLength - 1] = 0; + while (sourceLength > 2 && source[sourceLength - 1] == 0) + sourceLength--; // Init shader cache output stream // TODO: reuse MemoryWriteStream per cooking process to reduce dynamic memory allocations From 1e383fde5ced50105966b9eb031d80bec502449d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Jan 2021 14:15:42 +0100 Subject: [PATCH 026/222] Fix using AssetRefEditor for editing asset reference on Guid property --- .../CustomEditors/Editors/AssetRefEditor.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index 49950dae8..7a0e5819c 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors public class AssetRefEditor : CustomEditor { private CustomElement _element; - private ScriptType _type; + private ScriptType _valueType; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -44,7 +44,8 @@ namespace FlaxEditor.CustomEditors.Editors { if (!HasDifferentTypes) { - _type = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + var assetType = _valueType; float height = 48; var attributes = Values.GetAttributes(); @@ -58,14 +59,14 @@ namespace FlaxEditor.CustomEditors.Editors { var customType = TypeUtils.GetType(assetReference.TypeName); if (customType != ScriptType.Null) - _type = customType; + assetType = customType; else Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName)); } } _element = layout.Custom(); - _element.CustomControl.AssetType = _type; + _element.CustomControl.AssetType = assetType; _element.CustomControl.Height = height; _element.CustomControl.SelectedItemChanged += OnSelectedItemChanged; } @@ -73,11 +74,11 @@ namespace FlaxEditor.CustomEditors.Editors private void OnSelectedItemChanged() { - if (typeof(AssetItem).IsAssignableFrom(_type.Type)) + if (typeof(AssetItem).IsAssignableFrom(_valueType.Type)) SetValue(_element.CustomControl.SelectedItem); - else if (_type.Type == typeof(Guid)) + else if (_valueType.Type == typeof(Guid)) SetValue(_element.CustomControl.SelectedID); - else if (_type.Type == typeof(SceneReference)) + else if (_valueType.Type == typeof(SceneReference)) SetValue(new SceneReference(_element.CustomControl.SelectedID)); else SetValue(_element.CustomControl.SelectedAsset); From 31bab88a5bdddc75e4bb0cd6ea3ad2427eb4e914 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Jan 2021 14:15:49 +0100 Subject: [PATCH 027/222] Fix PlatformSettings typedef on Android --- Source/Engine/Platform/Android/AndroidPlatformSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index 8805b2180..0526849a9 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -48,7 +48,7 @@ public: } }; -#if PLATFORM_LINUX +#if PLATFORM_ANDROID typedef AndroidPlatformSettings PlatformSettings; #endif From d7224670c5af688a1c0df5950a1a47e2a4b1c6ac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 5 Jan 2021 14:16:17 +0100 Subject: [PATCH 028/222] Fix PCF shadow uvs vector trunc warning --- Source/Shaders/ShadowsSampling.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Shaders/ShadowsSampling.hlsl b/Source/Shaders/ShadowsSampling.hlsl index ffd757e42..6bea0678c 100644 --- a/Source/Shaders/ShadowsSampling.hlsl +++ b/Source/Shaders/ShadowsSampling.hlsl @@ -518,7 +518,7 @@ float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, UNROLL for(int i = 0; i < FilterSizeCube; i++) { - float2 samplePos = shadowMapUVs + sideVector * PCFDiscSamples[i].x + upVector * PCFDiscSamples[i].y; + float2 samplePos = shadowMapUVs + sideVector.xy * PCFDiscSamples[i].x + upVector.xy * PCFDiscSamples[i].y; result += shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, samplePos, shadowPosition.z); } result *= (1.0f / FilterSizeCube); From d20cbf434fb732182c936bc557e8dec618f6a437 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Jan 2021 13:49:26 +0100 Subject: [PATCH 029/222] Add default values initialization for structures and new array entries when resizing --- .../CustomEditors/Editors/ArrayEditor.cs | 10 +++++ .../CustomEditors/Editors/ListEditor.cs | 2 +- Source/Editor/Scripting/TypeUtils.cs | 12 +++++- Source/Editor/Utilities/Utils.cs | 40 +++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index f12dadde1..335fd64a7 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -2,6 +2,7 @@ using System; using System.Collections; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors @@ -48,6 +49,15 @@ namespace FlaxEditor.CustomEditors.Editors Array.Copy(array, oldSize - 1, newValues, i, 1); } } + else if (newSize > 0) + { + // Initialize new entries with default values + var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType)); + for (int i = 0; i < newSize; i++) + { + newValues.SetValue(defaultValue, i); + } + } SetValue(newValues); } diff --git a/Source/Editor/CustomEditors/Editors/ListEditor.cs b/Source/Editor/CustomEditors/Editors/ListEditor.cs index 305d2e2c9..19ed9675b 100644 --- a/Source/Editor/CustomEditors/Editors/ListEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ListEditor.cs @@ -58,7 +58,7 @@ namespace FlaxEditor.CustomEditors.Editors } else if (newSize > 0) { - // Fill new entries + // Fill new entries with default value var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType); for (int i = oldSize; i < newSize; i++) { diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 70108f06f..60f64ab8e 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -59,11 +59,19 @@ namespace FlaxEditor.Scripting if (type.Type == typeof(MaterialSceneTextures)) return MaterialSceneTextures.BaseColor; if (type.IsValueType) - return type.CreateInstance(); + { + var value = type.CreateInstance(); + Utilities.Utils.InitDefaultValues(value); + return value; + } if (new ScriptType(typeof(object)).IsAssignableFrom(type)) return null; if (type.CanCreateInstance) - return type.CreateInstance(); + { + var value = type.CreateInstance(); + Utilities.Utils.InitDefaultValues(value); + return value; + } throw new NotSupportedException("Cannot create default value for type " + type); } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5f1636239..cd9935da3 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; @@ -1732,5 +1733,44 @@ namespace FlaxEditor.Utilities distance = 0; return false; } + + /// + /// Initializes the object fields and properties with their default values based on . + /// + /// The object. + public static void InitDefaultValues(object obj) + { + var scriptType = TypeUtils.GetObjectType(obj); + if (!scriptType) + return; + var isStructure = scriptType.IsStructure; + + var fields = scriptType.GetFields(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; i < fields.Length; i++) + { + var field = fields[i]; + var attr = field.GetAttribute(); + if (attr != null) + { + field.SetValue(obj, attr.Value); + } + else if (isStructure) + { + // C# doesn't support default values for structure members so initialize them + field.SetValue(obj, TypeUtils.GetDefaultValue(field.ValueType)); + } + } + + var properties = scriptType.GetProperties(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; i < properties.Length; i++) + { + var property = properties[i]; + var attr = property.GetAttribute(); + if (attr != null) + { + property.SetValue(obj, attr.Value); + } + } + } } } From a6d5fb318afd453a01257b1463f84a86c5a7e561 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Jan 2021 14:14:08 +0100 Subject: [PATCH 030/222] Fix default field value parsing to skip whitespaces --- .../Bindings/BindingsGenerator.Parsing.cs | 2 +- Source/Tools/Flax.Build/Utilities/Tokenizer.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 916916995..1c4cadb4a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1038,7 +1038,7 @@ namespace Flax.Build.Bindings var token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); if (token.Type == TokenType.Equal) { - context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue); + context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false); } else if (token.Type == TokenType.LeftBracket) { diff --git a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs index 6913ec31a..e73ddcbca 100644 --- a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs +++ b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs @@ -460,6 +460,24 @@ namespace Flax.Build } } + /// + /// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token). + /// + /// The expected token type. + /// The output contents of the skipped tokens. + /// When false, all white-space tokens will be ignored. + public void SkipUntil(TokenType tokenType, out string context, bool includeWhitespaces) + { + context = string.Empty; + while (NextToken(true).Type != tokenType) + { + var token = CurrentToken; + if (!includeWhitespaces && (token.Type == TokenType.Newline || token.Type == TokenType.Whitespace)) + continue; + context += token.Value; + } + } + /// /// Disposes the . /// From 40c7fe3f778584ffbbddb7d3916fac038ddd7248 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Jan 2021 14:14:35 +0100 Subject: [PATCH 031/222] Add support for automatic DefaultValue attributes generation for fields in scripting API --- .../Bindings/BindingsGenerator.CSharp.cs | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 2c3e1c837..a25e00fbb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -394,14 +394,36 @@ namespace Flax.Build.Bindings } } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged) + private static bool IsDefaultValueSupported(string value) + { + // TEXT macro (eg. TEXT("text")) + // TODO: support string for default value attribute + if (value.Contains("TEXT(\"")) + return false; + + // Value constructors (eg. Vector2(1, 2)) + // TODO: support value constructores for default value attribute + if (value.Contains('(') && value.Contains(')')) + return false; + + // Constants (eg. Vector2::Zero) + // TODO: support constants for default value attribute + if (value.Contains("::")) + return false; + + return true; + } + + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged, string defaultValue = null) { var writeTooltip = true; + var writeDefaultValue = true; if (!string.IsNullOrEmpty(attributes)) { // Write attributes contents.Append(indent).Append('[').Append(attributes).Append(']').AppendLine(); writeTooltip = !attributes.Contains("Tooltip(") && !attributes.Contains("HideInEditor"); + writeDefaultValue = !attributes.Contains("DefaultValue("); } if (useUnmanaged) @@ -425,16 +447,22 @@ namespace Flax.Build.Bindings contents.Append(indent).Append("[Tooltip(\"").Append(tooltip).Append("\")]").AppendLine(); } } + if (!string.IsNullOrEmpty(defaultValue) && writeDefaultValue && IsDefaultValueSupported(defaultValue)) + { + // Write default value attribute + defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo); + contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine(); + } } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged) + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged, string defaultValue = null) { - GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged, defaultValue); } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, MemberInfo memberInfo, bool useUnmanaged) + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null) { - GenerateCSharpAttributes(buildData, contents, indent, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue); } private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo) @@ -506,7 +534,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, eventInfo, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged); contents.Append(indent); if (eventInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -605,7 +633,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, fieldInfo, useUnmanaged, fieldInfo.DefaultValue); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -672,7 +700,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, propertyInfo, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, propertyInfo, useUnmanaged); contents.Append(indent); if (propertyInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -725,7 +753,7 @@ namespace Flax.Build.Bindings { contents.AppendLine(); GenerateCSharpComment(contents, indent, functionInfo.Comment); - GenerateCSharpAttributes(buildData, contents, indent, functionInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, functionInfo, true); contents.Append(indent); if (functionInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -837,7 +865,7 @@ namespace Flax.Build.Bindings foreach (var comment in fieldInfo.Comment) contents.Append(indent).Append(comment).AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, fieldInfo.IsStatic); + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic, fieldInfo.DefaultValue); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -861,7 +889,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); foreach (var comment in fieldInfo.Comment) contents.Append(indent).Append(comment).AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, fieldInfo.IsStatic); + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -967,7 +995,7 @@ namespace Flax.Build.Bindings foreach (var comment in entryInfo.Comment) contents.Append(indent).Append(comment).AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, entryInfo.Attributes, entryInfo.Comment, true, false); + GenerateCSharpAttributes(buildData, contents, indent, enumInfo, entryInfo.Attributes, entryInfo.Comment, true, false); contents.Append(indent).Append(entryInfo.Name); if (!string.IsNullOrEmpty(entryInfo.Value)) contents.Append(" = ").Append(entryInfo.Value); From dba43c4e9fbb5f6007f6ad82cb45d5c8af7fc03b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Jan 2021 15:33:17 +0100 Subject: [PATCH 032/222] Add support for using `ObsoleteAttribute` to upgrade old C# asset/script data format after refactor --- .../JsonCustomSerializers/ExtendedDefaultContractResolver.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index f6a62dd54..cc07597e8 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -118,9 +118,11 @@ namespace FlaxEngine.Json.JsonCustomSerializers if (noSerialize) continue; + var isObsolete = attributes.Any(x => x is ObsoleteAttribute); + var jsonProperty = CreateProperty(p, memberSerialization); jsonProperty.Writable = true; - jsonProperty.Readable = true; + jsonProperty.Readable = !isObsolete; if (_flaxType.IsAssignableFrom(p.PropertyType)) { From 27ed23c1b9e8019bf000845f57712f5e09348b0a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Jan 2021 14:28:46 +0100 Subject: [PATCH 033/222] Add support for multiple navmeshes on a scene --- Source/Engine/Level/Scene/Scene.cpp | 88 +++-- Source/Engine/Level/Scene/Scene.h | 37 +- .../{NavigationScene.cpp => NavMesh.cpp} | 133 ++++--- Source/Engine/Navigation/NavMesh.h | 76 ++++ .../Engine/Navigation/NavMeshBoundsVolume.cpp | 7 +- Source/Engine/Navigation/NavMeshBuilder.cpp | 274 +++++++------ Source/Engine/Navigation/NavMeshBuilder.h | 7 +- Source/Engine/Navigation/NavMeshRuntime.cpp | 276 ++++++++++++- Source/Engine/Navigation/NavMeshRuntime.h | 86 ++++- Source/Engine/Navigation/Navigation.cpp | 364 +++++++----------- Source/Engine/Navigation/Navigation.h | 4 +- Source/Engine/Navigation/NavigationScene.h | 94 ----- .../Engine/Navigation/NavigationSettings.cs | 66 ++++ Source/Engine/Navigation/NavigationSettings.h | 77 ++-- Source/Engine/Navigation/NavigationTypes.h | 49 ++- 15 files changed, 1012 insertions(+), 626 deletions(-) rename Source/Engine/Navigation/{NavigationScene.cpp => NavMesh.cpp} (52%) create mode 100644 Source/Engine/Navigation/NavMesh.h delete mode 100644 Source/Engine/Navigation/NavigationScene.h diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index 2d48b9f14..b86610ab9 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -8,7 +8,10 @@ #include "Engine/Physics/Colliders/MeshCollider.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/ActorsCache.h" -#include "Engine/Navigation/NavigationScene.h" +#include "Engine/Navigation/NavigationSettings.h" +#include "Engine/Navigation/NavMeshBoundsVolume.h" +#include "Engine/Navigation/NavMesh.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" REGISTER_JSON_ASSET(SceneAsset, "FlaxEngine.SceneAsset"); @@ -27,7 +30,6 @@ Scene::Scene(const SpawnParams& params) , Ticking(this) , LightmapsData(this) , CSGData(this) - , Navigation(::New(this)) { // Default name _name = TEXT("Scene"); @@ -42,7 +44,6 @@ Scene::Scene(const SpawnParams& params) Scene::~Scene() { - Delete(Navigation); } LightmapSettings Scene::GetLightmapSettings() const @@ -55,6 +56,31 @@ void Scene::SetLightmapSettings(const LightmapSettings& value) Info.LightmapSettings = value; } +BoundingBox Scene::GetNavigationBounds() +{ + if (NavigationVolumes.IsEmpty()) + return BoundingBox::Empty; + PROFILE_CPU_NAMED("GetNavigationBounds"); + auto box = NavigationVolumes[0]->GetBox(); + for (int32 i = 1; i < NavigationVolumes.Count(); i++) + BoundingBox::Merge(box, NavigationVolumes[i]->GetBox(), box); + return box; +} + +NavMeshBoundsVolume* Scene::FindNavigationBoundsOverlap(const BoundingBox& bounds) +{ + NavMeshBoundsVolume* result = nullptr; + for (int32 i = 0; i < NavigationVolumes.Count(); i++) + { + if (NavigationVolumes[i]->GetBox().Intersects(bounds)) + { + result = NavigationVolumes[i]; + break; + } + } + return result; +} + void Scene::ClearLightmaps() { LightmapsData.ClearLightmaps(); @@ -216,12 +242,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) // Update scene info object SaveTime = DateTime::NowUTC(); -#if USE_EDITOR - // Save navmesh tiles to asset (if modified) - if (Navigation->IsDataDirty) - Navigation->SaveNavMesh(); -#endif - LightmapsData.SaveLightmaps(Info.Lightmaps); Info.Serialize(stream, other ? &other->Info : nullptr); @@ -230,8 +250,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) stream.JKEY("CSG"); stream.Object(&CSGData, other ? &other->CSGData : nullptr); } - - SERIALIZE_MEMBER(NavMesh, Navigation->DataAsset); } void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -243,7 +261,37 @@ void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) LightmapsData.LoadLightmaps(Info.Lightmaps); CSGData.DeserializeIfExists(stream, "CSG", modifier); - DESERIALIZE_MEMBER(NavMesh, Navigation->DataAsset); + // [Deprecated on 13.01.2021, expires on 13.01.2023] + if (modifier->EngineBuild <= 6215 && NavigationMeshes.IsEmpty()) + { + const auto e = SERIALIZE_FIND_MEMBER(stream, "NavMesh"); + if (e != stream.MemberEnd()) + { + // Upgrade from old single hidden navmesh data into NavMesh actors on a scene + AssetReference dataAsset; + Serialization::Deserialize(e->value, dataAsset, modifier); + const auto settings = NavigationSettings::Get(); + if (dataAsset && settings->NavMeshes.HasItems()) + { + auto navMesh = New(); + navMesh->SetStaticFlags(StaticFlags::FullyStatic); + navMesh->SetName(TEXT("NavMesh.") + settings->NavMeshes[0].Name); + navMesh->DataAsset = dataAsset; + navMesh->Properties = settings->NavMeshes[0]; + if (IsDuringPlay()) + { + navMesh->SetParent(this, false); + } + else + { + navMesh->_parent = this; + navMesh->_scene = this; + Children.Add(navMesh); + navMesh->CreateManaged(); + } + } + } + } } void Scene::OnDeleteObject() @@ -309,22 +357,6 @@ void Scene::EndPlay() Actor::EndPlay(); } -void Scene::OnEnable() -{ - // Base - Actor::OnEnable(); - - Navigation->OnEnable(); -} - -void Scene::OnDisable() -{ - Navigation->OnDisable(); - - // Base - Actor::OnDisable(); -} - void Scene::OnTransformChanged() { // Base diff --git a/Source/Engine/Level/Scene/Scene.h b/Source/Engine/Level/Scene/Scene.h index 628726e4b..d3c1502c6 100644 --- a/Source/Engine/Level/Scene/Scene.h +++ b/Source/Engine/Level/Scene/Scene.h @@ -12,8 +12,9 @@ class MeshCollider; class Level; -class NavigationScene; class ReloadScriptsAction; +class NavMeshBoundsVolume; +class NavMesh; /// /// The scene root object that contains a hierarchy of actors. @@ -69,11 +70,6 @@ public: /// CSG::SceneCSGData CSGData; - /// - /// The navigation scene (always valid). - /// - NavigationScene* Navigation; - /// /// Gets the lightmap settings (per scene). /// @@ -85,6 +81,31 @@ public: /// API_PROPERTY() void SetLightmapSettings(const LightmapSettings& value); +public: + + /// + /// The list of registered navigation bounds volumes (in the scene). + /// + Array NavigationVolumes; + + /// + /// The list of registered navigation meshes (in the scene). + /// + Array NavigationMeshes; + + /// + /// Gets the total navigation volumes bounds. + /// + /// The navmesh bounds. + BoundingBox GetNavigationBounds(); + + /// + /// Finds the navigation volume bounds that have intersection with the given world-space bounding box. + /// + /// The bounds. + /// The intersecting volume or null if none found. + NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds); + public: /// @@ -148,12 +169,10 @@ public: protected: - // [Scene] + // [Actor] void PostLoad() override; void PostSpawn() override; void BeginPlay(SceneBeginData* data) override; - void OnEnable() override; - void OnDisable() override; void OnTransformChanged() override; }; diff --git a/Source/Engine/Navigation/NavigationScene.cpp b/Source/Engine/Navigation/NavMesh.cpp similarity index 52% rename from Source/Engine/Navigation/NavigationScene.cpp rename to Source/Engine/Navigation/NavMesh.cpp index 2138db009..ef90e2989 100644 --- a/Source/Engine/Navigation/NavigationScene.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -1,61 +1,33 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "NavigationScene.h" -#include "Navigation.h" +#include "NavMesh.h" #include "NavMeshRuntime.h" -#include "NavMeshBoundsVolume.h" -#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Level/Scene/Scene.h" -#include "Engine/Content/Assets/RawDataAsset.h" -#include "Engine/Core/Log.h" -#if USE_EDITOR -#include "Editor/Editor.h" -#endif +#include "Engine/Serialization/Serialization.h" #if COMPILE_WITH_ASSETS_IMPORTER #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/Serialization/MemoryWriteStream.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#endif #endif -NavigationScene::NavigationScene(::Scene* scene) - : Scene(scene) +NavMesh::NavMesh(const SpawnParams& params) + : Actor(params) , IsDataDirty(false) { - DataAsset.Loaded.Bind(this); + DataAsset.Loaded.Bind(this); } -BoundingBox NavigationScene::GetNavigationBounds() -{ - if (Volumes.IsEmpty()) - return BoundingBox::Empty; - - PROFILE_CPU_NAMED("GetNavigationBounds"); - - auto box = Volumes[0]->GetBox(); - for (int32 i = 1; i < Volumes.Count(); i++) - BoundingBox::Merge(box, Volumes[i]->GetBox(), box); - return box; -} - -NavMeshBoundsVolume* NavigationScene::FindNavigationBoundsOverlap(const BoundingBox& bounds) -{ - NavMeshBoundsVolume* result = nullptr; - - for (int32 i = 0; i < Volumes.Count(); i++) - { - if (Volumes[i]->GetBox().Intersects(bounds)) - { - result = Volumes[i]; - break; - } - } - - return result; -} - -void NavigationScene::SaveNavMesh() +void NavMesh::SaveNavMesh() { #if COMPILE_WITH_ASSETS_IMPORTER + // Skip if scene is missing + const auto scene = GetScene(); + if (!scene) + return; + #if USE_EDITOR // Skip if game is running in editor (eg. game scripts update dynamic navmesh) if (Editor::IsPlayMode) @@ -77,7 +49,7 @@ void NavigationScene::SaveNavMesh() Guid assetId = DataAsset.GetID(); if (!assetId.IsValid()) assetId = Guid::New(); - const String assetPath = Scene->GetDataFolderPath() / TEXT("NavMesh") + ASSET_FILES_EXTENSION_WITH_DOT; + const String assetPath = scene->GetDataFolderPath() / TEXT("NavMesh") + Properties.Name + ASSET_FILES_EXTENSION_WITH_DOT; // Generate navmesh tiles data const int32 streamInitialCapacity = Math::RoundUpToPowerOf2((Data.Tiles.Count() + 1) * 1024); @@ -99,32 +71,46 @@ void NavigationScene::SaveNavMesh() #endif } -void NavigationScene::OnEnable() +void NavMesh::ClearData() { - auto navMesh = NavMeshRuntime::Get(); - CHECK(navMesh); + if (Data.Tiles.HasItems()) + { + IsDataDirty = true; + Data.TileSize = 0.0f; + Data.Tiles.Resize(0); + } +} + +NavMeshRuntime* NavMesh::GetRuntime(bool createIfMissing) const +{ + return NavMeshRuntime::Get(Properties, createIfMissing); +} + +void NavMesh::AddTiles() +{ + auto navMesh = NavMeshRuntime::Get(Properties, true); navMesh->AddTiles(this); } -void NavigationScene::OnDisable() +void NavMesh::RemoveTiles() { - auto navMesh = NavMeshRuntime::Get(); + auto navMesh = NavMeshRuntime::Get(Properties, false); if (navMesh) navMesh->RemoveTiles(this); } -void NavigationScene::OnDataAssetLoaded() +void NavMesh::OnDataAssetLoaded() { // Skip if already has data (prevent reloading navmesh on saving) if (Data.Tiles.HasItems()) return; - const bool isEnabled = Scene->IsDuringPlay() && Scene->IsActiveInHierarchy(); + const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy(); // Remove added tiles if (isEnabled) { - OnDisable(); + RemoveTiles(); } // Load navmesh tiles @@ -136,6 +122,49 @@ void NavigationScene::OnDataAssetLoaded() // Add loaded tiles if (isEnabled) { - OnEnable(); + AddTiles(); } } + +void NavMesh::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + +#if USE_EDITOR + // Save navmesh tiles to asset (if modified) + if (IsDataDirty) + SaveNavMesh(); +#endif + + SERIALIZE_GET_OTHER_OBJ(NavMesh); + SERIALIZE(DataAsset); + SERIALIZE(Properties); +} + +void NavMesh::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE(DataAsset); + DESERIALIZE(Properties); +} + +void NavMesh::OnEnable() +{ + // Base + Actor::OnEnable(); + + GetScene()->NavigationMeshes.Add(this); + AddTiles(); +} + +void NavMesh::OnDisable() +{ + RemoveTiles(); + GetScene()->NavigationMeshes.Remove(this); + + // Base + Actor::OnDisable(); +} diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h new file mode 100644 index 000000000..bd0eab703 --- /dev/null +++ b/Source/Engine/Navigation/NavMesh.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "NavMeshData.h" +#include "NavigationTypes.h" +#include "Engine/Content/AssetReference.h" +#include "Engine/Content/Assets/RawDataAsset.h" +#include "Engine/Level/Actor.h" + +class NavMeshBoundsVolume; +class NavMeshRuntime; + +/// +/// The navigation mesh actor that holds a navigation data for a scene. +/// +API_CLASS() class FLAXENGINE_API NavMesh : public Actor +{ +DECLARE_SCENE_OBJECT(NavMesh); +public: + + /// + /// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization. + /// + bool IsDataDirty; + + /// + /// The navmesh tiles data. + /// + NavMeshData Data; + + /// + /// The cached navmesh data asset. + /// + AssetReference DataAsset; + + /// + /// The navigation mesh properties. + /// + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Nav Mesh\")") NavMeshProperties Properties; + +public: + + /// + /// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time). + /// + void SaveNavMesh(); + + /// + /// Clears the data. + /// + void ClearData(); + + /// + /// Gets the navmesh runtime object that matches with properties. + /// + NavMeshRuntime* GetRuntime(bool createIfMissing = true) const; + +private: + + void AddTiles(); + void RemoveTiles(); + void OnDataAssetLoaded(); + +public: + + // [Actor] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + + // [Actor] + void OnEnable() override; + void OnDisable() override; +}; diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp index 34a881ede..a3f02249a 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp @@ -2,7 +2,6 @@ #include "NavMeshBoundsVolume.h" #include "Engine/Level/Scene/Scene.h" -#include "NavigationScene.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/Managed/ManagedEditor.h" @@ -16,15 +15,15 @@ NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params) void NavMeshBoundsVolume::OnEnable() { - GetScene()->Navigation->Volumes.Add(this); - // Base Actor::OnEnable(); + + GetScene()->NavigationVolumes.Add(this); } void NavMeshBoundsVolume::OnDisable() { - GetScene()->Navigation->Volumes.Remove(this); + GetScene()->NavigationVolumes.Remove(this); // Base Actor::OnDisable(); diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index caa2513d2..dca9a445c 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -3,6 +3,11 @@ #if COMPILE_WITH_NAV_MESH_BUILDER #include "NavMeshBuilder.h" +#include "NavMesh.h" +#include "NavigationSettings.h" +#include "NavMeshBoundsVolume.h" +#include "NavLink.h" +#include "NavMeshRuntime.h" #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/VectorInt.h" #include "Engine/Physics/Colliders/BoxCollider.h" @@ -15,12 +20,6 @@ #include "Engine/Level/Level.h" #include "Engine/Level/SceneQuery.h" #include "Engine/Core/Log.h" -#include "NavigationScene.h" -#include "NavigationSettings.h" -#include "NavMeshBoundsVolume.h" -#include "NavLink.h" -#include "Navigation.h" -#include "NavMeshRuntime.h" #include #include #include @@ -113,11 +112,9 @@ struct NavigationSceneRasterization { PROFILE_CPU_NAMED("BoxCollider"); - OrientedBoundingBox box = boxCollider->GetOrientedBox(); - + const OrientedBoundingBox box = boxCollider->GetOrientedBox(); vb.Resize(8); box.GetCorners(vb.Get()); - ib.Add(BoxTrianglesIndicesCache, 36); e.RasterizeTriangles(); @@ -127,7 +124,7 @@ struct NavigationSceneRasterization PROFILE_CPU_NAMED("MeshCollider"); auto collisionData = meshCollider->CollisionData.Get(); - if (!collisionData || collisionData->WaitForLoaded(1000.0f)) + if (!collisionData || collisionData->WaitForLoaded()) return true; collisionData->ExtractGeometry(vb, ib); @@ -181,7 +178,7 @@ void RasterizeGeometry(const BoundingBox& tileBounds, rcContext* context, rcConf // Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location // Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use -bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds) +bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds) { // Build initial tile bounds (with infinite extent) tileBounds.Minimum.X = (float)x * tileSize; @@ -194,9 +191,9 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi // Check if any navmesh volume intersects with the tile bool foundAnyVolume = false; Vector2 rangeY; - for (int32 i = 0; i < scene->Volumes.Count(); i++) + for (int32 i = 0; i < scene->NavigationVolumes.Count(); i++) { - const auto volume = scene->Volumes[i]; + const auto volume = scene->NavigationVolumes[i]; const auto& volumeBounds = volume->GetBox(); if (volumeBounds.Intersects(tileBounds)) { @@ -224,27 +221,27 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi return foundAnyVolume; } -void RemoveTile(NavMeshRuntime* navMesh, NavigationScene* scene, int32 x, int32 y, int32 layer) +void RemoveTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, int32 layer) { - ScopeLock lock(navMesh->Locker); + ScopeLock lock(runtime->Locker); // Find tile data and remove it - for (int32 i = 0; i < scene->Data.Tiles.Count(); i++) + for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++) { - auto& tile = scene->Data.Tiles[i]; + auto& tile = navMesh->Data.Tiles[i]; if (tile.PosX == x && tile.PosY == y && tile.Layer == layer) { - scene->Data.Tiles.RemoveAt(i); - scene->IsDataDirty = true; + navMesh->Data.Tiles.RemoveAt(i); + navMesh->IsDataDirty = true; break; } } // Remove tile from navmesh - navMesh->RemoveTile(x, y, layer); + runtime->RemoveTile(x, y, layer); } -bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config) +bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config) { rcContext context; int32 layer = 0; @@ -355,7 +352,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou if (polyMesh->nverts == 0) { // Empty tile - RemoveTile(NavMeshRuntime::Get(), scene, x, y, layer); + RemoveTile(navMesh, runtime, x, y, layer); return false; } @@ -438,11 +435,11 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou { PROFILE_CPU_NAMED("Navigation.CreateTile"); - ScopeLock lock(NavMeshRuntime::Get()->Locker); + ScopeLock lock(runtime->Locker); // Add tile data - scene->IsDataDirty = true; - auto& tile = scene->Data.Tiles.AddOne(); + navMesh->IsDataDirty = true; + auto& tile = navMesh->Data.Tiles.AddOne(); tile.PosX = x; tile.PosY = y; tile.Layer = layer; @@ -451,7 +448,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou tile.Data.Copy(navData, navDataSize); // Add tile to navmesh - NavMeshRuntime::Get()->AddTile(scene, tile); + runtime->AddTile(navMesh, tile); } dtFree(navData); @@ -465,16 +462,17 @@ float GetTileSize() return settings.CellSize * settings.TileSize; } -void InitConfig(rcConfig& config) +void InitConfig(rcConfig& config, NavMesh* navMesh) { auto& settings = *NavigationSettings::Get(); + auto& navMeshProperties = navMesh->Properties; config.cs = settings.CellSize; config.ch = settings.CellHeight; - config.walkableSlopeAngle = settings.WalkableMaxSlopeAngle; - config.walkableHeight = (int)(settings.WalkableHeight / config.ch + 0.99f); - config.walkableClimb = (int)(settings.WalkableMaxClimb / config.ch); - config.walkableRadius = (int)(settings.WalkableRadius / config.cs + 0.99f); + config.walkableSlopeAngle = navMeshProperties.Agent.MaxSlopeAngle; + config.walkableHeight = (int)(navMeshProperties.Agent.Height / config.ch + 0.99f); + config.walkableClimb = (int)(navMeshProperties.Agent.StepHeight / config.ch); + config.walkableRadius = (int)(navMeshProperties.Agent.Radius / config.cs + 0.99f); config.maxEdgeLen = (int)(settings.MaxEdgeLen / config.cs); config.maxSimplificationError = settings.MaxEdgeError; config.minRegionArea = rcSqr(settings.MinRegionArea); @@ -506,7 +504,9 @@ class NavMeshTileBuildTask : public ThreadPoolTask { public: - NavigationScene* Scene; + Scene* Scene; + NavMesh* NavMesh; + NavMeshRuntime* Runtime; BoundingBox TileBounds; int32 X; int32 Y; @@ -520,7 +520,7 @@ public: { PROFILE_CPU_NAMED("BuildNavMeshTile"); - if (GenerateTile(Scene, X, Y, TileBounds, TileSize, Config)) + if (GenerateTile(NavMesh, Runtime, X, Y, TileBounds, TileSize, Config)) { LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y); } @@ -557,7 +557,7 @@ void OnSceneUnloading(Scene* scene, const Guid& sceneId) for (int32 i = 0; i < NavBuildTasks.Count(); i++) { auto task = NavBuildTasks[i]; - if (task->Scene == scene->Navigation) + if (task->Scene == scene) { NavBuildTasksLocker.Unlock(); @@ -600,15 +600,16 @@ float NavMeshBuilder::GetNavMeshBuildingProgress() return result; } -void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize) +void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize) { + NavMeshRuntime* runtime = navMesh->GetRuntime(); NavBuildTasksLocker.Lock(); // Skip if this tile is already during cooking for (int32 i = 0; i < NavBuildTasks.Count(); i++) { const auto task = NavBuildTasks[i]; - if (task->X == x && task->Y == y) + if (task->X == x && task->Y == y && task->Runtime == runtime) { NavBuildTasksLocker.Unlock(); return; @@ -617,7 +618,9 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, // Create task auto task = New(); - task->Scene = scene; + task->Scene = navMesh->GetScene(); + task->NavMesh = navMesh; + task->Runtime = runtime; task->X = x; task->Y = y; task->TileBounds = tileBounds; @@ -632,70 +635,10 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, task->Start(); } -void BuildWholeScene(NavigationScene* scene) +void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBounds) { const float tileSize = GetTileSize(); - const auto navMesh = NavMeshRuntime::Get(); - - // Compute total navigation area bounds - const BoundingBox worldBounds = scene->GetNavigationBounds(); - - // Align total bounds to tile size - BoundingBox worldBoundsAligned; - worldBoundsAligned.Minimum = Vector3::Floor(worldBounds.Minimum / tileSize) * tileSize; - worldBoundsAligned.Maximum = Vector3::Ceil(worldBounds.Maximum / tileSize) * tileSize; - - // Calculate tiles range for the given navigation world bounds (aligned to tiles size) - const Int3 tilesMin = Int3(worldBoundsAligned.Minimum / tileSize); - const Int3 tilesMax = Int3(worldBoundsAligned.Maximum / tileSize); - const int32 tilesX = tilesMax.X - tilesMin.X; - const int32 tilesY = tilesMax.Z - tilesMin.Z; - - { - PROFILE_CPU_NAMED("Prepare"); - - // Prepare navmesh - navMesh->RemoveTiles(scene); - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); - - // Prepare scene data - scene->Data.TileSize = tileSize; - scene->Data.Tiles.Clear(); - scene->Data.Tiles.EnsureCapacity(tilesX * tilesX); - scene->IsDataDirty = true; - } - - // Initialize nav mesh configuration - rcConfig config; - InitConfig(config); - - // Generate all tiles that intersect with the navigation volume bounds - { - PROFILE_CPU_NAMED("StartBuildingTiles"); - - for (int32 y = tilesMin.Z; y < tilesMax.Z; y++) - { - for (int32 x = tilesMin.X; x < tilesMax.X; x++) - { - BoundingBox tileBounds; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) - { - BuildTileAsync(scene, x, y, config, tileBounds, tileSize); - } - else - { - RemoveTile(navMesh, scene, x, y, 0); - } - } - } - } -} - -void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) -{ - const float tileSize = GetTileSize(); - const auto navMesh = NavMeshRuntime::Get(); + NavMeshRuntime* runtime = navMesh->GetRuntime(); // Align dirty bounds to tile size BoundingBox dirtyBoundsAligned; @@ -703,8 +646,8 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBounds.Maximum / tileSize) * tileSize; // Calculate tiles range for the given navigation dirty bounds (aligned to tiles size) - const Int3 tilesMin = Int3(dirtyBoundsAligned.Minimum / tileSize); - const Int3 tilesMax = Int3(dirtyBoundsAligned.Maximum / tileSize); + const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize); + const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize); const int32 tilesX = tilesMax.X - tilesMin.X; const int32 tilesY = tilesMax.Z - tilesMin.Z; @@ -712,28 +655,28 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) PROFILE_CPU_NAMED("Prepare"); // Prepare scene data and navmesh - if (Math::NotNearEqual(scene->Data.TileSize, tileSize)) + if (Math::NotNearEqual(navMesh->Data.TileSize, tileSize)) { - navMesh->RemoveTiles(scene); - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); + runtime->RemoveTiles(navMesh); + runtime->SetTileSize(tileSize); + runtime->EnsureCapacity(tilesX * tilesY); - scene->Data.TileSize = tileSize; - scene->Data.Tiles.Clear(); - scene->Data.Tiles.EnsureCapacity(tilesX * tilesX); - scene->IsDataDirty = true; + navMesh->Data.TileSize = tileSize; + navMesh->Data.Tiles.Clear(); + navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX); + navMesh->IsDataDirty = true; } else { // Prepare navmesh - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); + runtime->SetTileSize(tileSize); + runtime->EnsureCapacity(tilesX * tilesY); } } // Initialize nav mesh configuration rcConfig config; - InitConfig(config); + InitConfig(config, navMesh); // Generate all tiles that intersect with the navigation volume bounds { @@ -746,17 +689,103 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) BoundingBox tileBounds; if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) { - BuildTileAsync(scene, x, y, config, tileBounds, tileSize); + BuildTileAsync(navMesh, x, y, config, tileBounds, tileSize); } else { - RemoveTile(navMesh, scene, x, y, 0); + RemoveTile(navMesh, runtime, x, y, 0); } } } } } +void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds) +{ + auto settings = NavigationSettings::Get(); + + // Sync navmeshes + for (auto& navMeshProperties : settings->NavMeshes) + { + NavMesh* navMesh = nullptr; + for (auto e : scene->NavigationMeshes) + { + if (e->Properties.Name == navMeshProperties.Name) + { + navMesh = e; + break; + } + } + if (navMesh) + { + // Sync settings + auto runtime = navMesh->GetRuntime(false); + navMesh->Properties = navMeshProperties; + if (runtime) + runtime->Properties = navMeshProperties; + } + else if (settings->AutoAddMissingNavMeshes) + { + // Spawn missing navmesh + navMesh = New(); + navMesh->SetStaticFlags(StaticFlags::FullyStatic); + navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name); + navMesh->Properties = navMeshProperties; + navMesh->SetParent(scene, false); + } + } + + // Build all navmeshes on the scene + for (NavMesh* navMesh : scene->NavigationMeshes) + { + BuildDirtyBounds(scene, navMesh, dirtyBounds); + } + + // Remove unused navmeshes + if (settings->AutoRemoveMissingNavMeshes) + { + for (NavMesh* navMesh : scene->NavigationMeshes) + { + // Skip used navmeshes + if (navMesh->Data.Tiles.HasItems()) + continue; + + // Skip navmeshes during async building + int32 usageCount = 0; + NavBuildTasksLocker.Lock(); + for (int32 i = 0; i < NavBuildTasks.Count(); i++) + { + if (NavBuildTasks[i]->NavMesh == navMesh) + usageCount++; + } + NavBuildTasksLocker.Unlock(); + if (usageCount != 0) + continue; + + navMesh->DeleteObject(); + } + } +} + +void BuildWholeScene(Scene* scene) +{ + // Compute total navigation area bounds + const BoundingBox worldBounds = scene->GetNavigationBounds(); + + BuildDirtyBounds(scene, worldBounds); +} + +void ClearNavigation(Scene* scene) +{ + const bool autoRemoveMissingNavMeshes = NavigationSettings::Get()->AutoRemoveMissingNavMeshes; + for (NavMesh* navMesh : scene->NavigationMeshes) + { + navMesh->ClearData(); + if (autoRemoveMissingNavMeshes) + navMesh->DeleteObject(); + } +} + void NavMeshBuilder::Update() { ScopeLock lock(NavBuildQueueLocker); @@ -769,15 +798,12 @@ void NavMeshBuilder::Update() if (now - req.Time >= 0) { NavBuildQueue.RemoveAt(i--); - auto scene = req.Scene->Navigation; + const auto scene = req.Scene.Get(); // Early out if scene has no bounds volumes to define nav mesh area - if (scene->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - // Cleanup if no navigation to use - scene->Data.TileSize = 0; - scene->Data.Tiles.Resize(0); - scene->IsDataDirty = true; + ClearNavigation(scene); continue; } @@ -797,9 +823,9 @@ void NavMeshBuilder::Update() void NavMeshBuilder::Build(Scene* scene, float timeoutMs) { // Early out if scene is not using navigation - if (scene->Navigation->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - scene->Navigation->ClearData(); + ClearNavigation(scene); return; } @@ -828,9 +854,9 @@ void NavMeshBuilder::Build(Scene* scene, float timeoutMs) void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs) { // Early out if scene is not using navigation - if (scene->Navigation->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - scene->Navigation->ClearData(); + ClearNavigation(scene); return; } diff --git a/Source/Engine/Navigation/NavMeshBuilder.h b/Source/Engine/Navigation/NavMeshBuilder.h index 4e59027ce..27dab4273 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.h +++ b/Source/Engine/Navigation/NavMeshBuilder.h @@ -4,9 +4,14 @@ #if COMPILE_WITH_NAV_MESH_BUILDER +#include "Engine/Core/Compiler.h" + class Scene; -class NavMeshBuilder +/// +/// The navigation mesh building utility. +/// +class FLAXENGINE_API NavMeshBuilder { public: diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index e160a11a6..4ca3b7041 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -1,18 +1,23 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NavMeshRuntime.h" -#include "NavigationScene.h" +#include "NavMesh.h" #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" #include #include +#include #define MAX_NODES 2048 #define USE_DATA_LINK 0 #define USE_NAV_MESH_ALLOC 1 -NavMeshRuntime::NavMeshRuntime() +#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f +#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f + +NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties) + : Properties(properties) { _navMesh = nullptr; _navMeshQuery = dtAllocNavMeshQuery(); @@ -30,6 +35,150 @@ int32 NavMeshRuntime::GetTilesCapacity() const return _navMesh ? _navMesh->getMaxTiles() : 0; } +bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + + return dtStatusSucceed(query->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X)); +} + +bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const +{ + resultPath.Clear(); + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + dtPolyRef endPoly = 0; + query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr); + if (!endPoly) + { + return false; + } + + dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; + int32 pathSize; + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + if (dtStatusFailed(findPathStatus)) + { + return false; + } + + // Check for special case, where path has not been found, and starting polygon was the one closest to the target + if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) + { + // In this case we find a point on starting polygon, that's closest to destination and store it as path end + resultPath.Resize(2); + resultPath[0] = startPosition; + resultPath[1] = startPosition; + query->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X); + } + else + { + int straightPathCount = 0; + resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); + const auto findStraightPathStatus = query->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); + if (dtStatusFailed(findStraightPathStatus)) + { + return false; + } + resultPath.Resize(straightPathCount); + } + + return true; +} + +bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X); + if (!startPoly) + { + return false; + } + + return true; +} + +bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + + dtRaycastHit hit; + hit.path = nullptr; + hit.maxPath = 0; + const bool result = dtStatusSucceed(query->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit)); + if (hit.t >= MAX_float) + { + hitInfo.Position = endPosition; + hitInfo.Distance = 0; + } + else + { + hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t; + hitInfo.Distance = hit.t; + } + hitInfo.Normal = *(Vector3*)&hit.hitNormal; + + return result; +} + void NavMeshRuntime::SetTileSize(float tileSize) { ScopeLock lock(Locker); @@ -132,13 +281,13 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) } } -void NavMeshRuntime::AddTiles(NavigationScene* scene) +void NavMeshRuntime::AddTiles(NavMesh* navMesh) { // Skip if no data - ASSERT(scene); - if (scene->Data.Tiles.IsEmpty()) + ASSERT(navMesh); + if (navMesh->Data.Tiles.IsEmpty()) return; - auto& data = scene->Data; + auto& data = navMesh->Data; PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles"); @@ -164,14 +313,14 @@ void NavMeshRuntime::AddTiles(NavigationScene* scene) // Add new tiles for (auto& tileData : data.Tiles) { - AddTileInternal(scene, tileData); + AddTileInternal(navMesh, tileData); } } -void NavMeshRuntime::AddTile(NavigationScene* scene, NavMeshTileData& tileData) +void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData) { - ASSERT(scene); - auto& data = scene->Data; + ASSERT(navMesh); + auto& data = navMesh->Data; PROFILE_CPU_NAMED("NavMeshRuntime.AddTile"); @@ -195,17 +344,17 @@ void NavMeshRuntime::AddTile(NavigationScene* scene, NavMeshTileData& tileData) EnsureCapacity(1); // Add new tile - AddTileInternal(scene, tileData); + AddTileInternal(navMesh, tileData); } bool IsTileFromScene(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData) { - return tile.Scene == (NavigationScene*)customData; + return tile.NavMesh == (NavMesh*)customData; } -void NavMeshRuntime::RemoveTiles(NavigationScene* scene) +void NavMeshRuntime::RemoveTiles(NavMesh* navMesh) { - RemoveTiles(IsTileFromScene, scene); + RemoveTiles(IsTileFromScene, navMesh); } void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer) @@ -274,6 +423,101 @@ void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMe } } +#if COMPILE_WITH_DEBUG_DRAW + +#include "Engine/Debug/DebugDraw.h" + +void DrawPoly(NavMeshRuntime* navMesh, const dtMeshTile& tile, const dtPoly& poly) +{ + const unsigned int ip = (unsigned int)(&poly - tile.polys); + const dtPolyDetail& pd = tile.detailMeshes[ip]; + const Color color = navMesh->Properties.Color; + const float drawOffsetY = 10.0f + ((float)GetHash(color) / (float)MAX_uint32) * 10.0f; // Apply some offset to prevent Z-fighting for different navmeshes + const Color fillColor = color * 0.5f; + const Color edgesColor = Color::FromHSV(color.ToHSV() + Vector3(20.0f, 0, -0.1f), color.A); + + for (int i = 0; i < pd.triCount; i++) + { + Vector3 v[3]; + const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4]; + + for (int k = 0; k < 3; k++) + { + if (t[k] < poly.vertCount) + { + v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3]; + } + else + { + v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3]; + } + } + + v[0].Y += drawOffsetY; + v[1].Y += drawOffsetY; + v[2].Y += drawOffsetY; + + DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], fillColor, 0, true); + } + + for (int k = 0; k < pd.triCount; k++) + { + const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4]; + Vector3 v[3]; + + for (int m = 0; m < 3; m++) + { + if (t[m] < poly.vertCount) + v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3]; + else + v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3]; + } + + v[0].Y += drawOffsetY; + v[1].Y += drawOffsetY; + v[2].Y += drawOffsetY; + + for (int m = 0, n = 2; m < 3; n = m++) + { + // Skip inner detail edges + if (((t[3] >> (n * 2)) & 0x3) == 0) + continue; + + DEBUG_DRAW_LINE(v[n], v[m], edgesColor, 0, true); + } + } +} + +void NavMeshRuntime::DebugDraw() +{ + ScopeLock lock(Locker); + + const dtNavMesh* dtNavMesh = GetNavMesh(); + const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; + if (tilesCount == 0) + return; + + for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) + { + const dtMeshTile* tile = dtNavMesh->getTile(tileIndex); + if (!tile->header) + continue; + + //DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue); + + for (int i = 0; i < tile->header->polyCount; i++) + { + const dtPoly* poly = &tile->polys[i]; + if (poly->getType() != DT_POLYTYPE_GROUND) + continue; + + DrawPoly(this, *tile, *poly); + } + } +} + +#endif + void NavMeshRuntime::Dispose() { if (_navMesh) @@ -284,7 +528,7 @@ void NavMeshRuntime::Dispose() _tiles.Resize(0); } -void NavMeshRuntime::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData) +void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData) { // Check if that tile has been added to navmesh NavMeshTile* tile = nullptr; @@ -313,7 +557,7 @@ void NavMeshRuntime::AddTileInternal(NavigationScene* scene, NavMeshTileData& ti ASSERT(tile); // Copy tile properties - tile->Scene = scene; + tile->NavMesh = navMesh; tile->X = tileData.PosX; tile->Y = tileData.PosY; tile->Layer = tileData.Layer; diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h index 6c538ae79..5d1b8ca63 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -5,27 +5,41 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Platform/CriticalSection.h" #include "NavMeshData.h" +#include "NavigationTypes.h" -class NavigationScene; class dtNavMesh; class dtNavMeshQuery; +class NavMesh; -class NavMeshTile +/// +/// The navigation mesh tile data. +/// +class FLAXENGINE_API NavMeshTile { public: int32 X; int32 Y; int32 Layer; - NavigationScene* Scene; + NavMesh* NavMesh; BytesContainer Data; }; -class NavMeshRuntime +/// +/// The navigation mesh runtime object that builds the navmesh from all loaded scenes. +/// +class FLAXENGINE_API NavMeshRuntime { public: - static NavMeshRuntime* Get(); + // Gets the navigation mesh runtime for a given navmesh name. Return null if missing. + static NavMeshRuntime* Get(const StringView& navMeshName); + + // Gets the navigation mesh runtime for a given agent properties trying to pick the best matching navmesh. + static NavMeshRuntime* Get(const NavAgentProperties& agentProperties); + + // Gets the navigation mesh runtime for a given navmesh properties. + static NavMeshRuntime* Get(const NavMeshProperties& navMeshProperties, bool createIfMissing = false); private: @@ -36,7 +50,7 @@ private: public: - NavMeshRuntime(); + NavMeshRuntime(const NavMeshProperties& properties); ~NavMeshRuntime(); public: @@ -46,6 +60,11 @@ public: /// CriticalSection Locker; + /// + /// The navigation mesh properties. + /// + NavMeshProperties Properties; + /// /// Gets the size of the tile (in world-units). Returns zero if not initialized yet. /// @@ -66,6 +85,43 @@ public: int32 GetTilesCapacity() const; +public: + + /// + /// Finds the distance from the specified start position to the nearest polygon wall. + /// + /// The start position. + /// The result hit information. Valid only when query succeed. + /// The maximum distance to search for wall (search radius). + /// True if ray hits an matching object, otherwise false. + bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const; + + /// + /// Finds the path between the two positions presented as a list of waypoints stored in the corners array. + /// + /// The start position. + /// The end position. + /// The result path. + /// True if found valid path between given two points (it may be partial), otherwise false if failed. + bool FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const; + + /// + /// Projects the point to nav mesh surface (finds the nearest polygon). + /// + /// The source point. + /// The result position on the navmesh (valid only if method returns true). + /// True if found valid location on the navmesh, otherwise false. + bool ProjectPoint(const Vector3& point, Vector3& result) const; + + /// + /// Casts a 'walkability' ray along the surface of the navigation mesh from the start position toward the end position. + /// + /// The start position. + /// The end position. + /// The result hit information. Valid only when query succeed. + /// True if ray hits an matching object, otherwise false. + bool RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const; + public: /// @@ -83,21 +139,21 @@ public: /// /// Adds the tiles from the given scene to the runtime navmesh. /// - /// The navigation scene. - void AddTiles(NavigationScene* scene); + /// The navigation mesh. + void AddTiles(NavMesh* navMesh); /// /// Adds the tile from the given scene to the runtime navmesh. /// - /// The navigation scene. + /// The navigation mesh. /// The tile data. - void AddTile(NavigationScene* scene, NavMeshTileData& tileData); + void AddTile(NavMesh* navMesh, NavMeshTileData& tileData); /// /// Removes all the tiles from the navmesh that has been added from the given navigation scene. /// - /// The scene. - void RemoveTiles(NavigationScene* scene); + /// The navigation mesh. + void RemoveTiles(NavMesh* navMesh); /// /// Removes the tile from the navmesh. @@ -114,6 +170,10 @@ public: /// The user data passed to the callback method. void RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData); +#if COMPILE_WITH_DEBUG_DRAW + void DebugDraw(); +#endif + /// /// Releases the navmesh. /// @@ -121,5 +181,5 @@ public: private: - void AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData); + void AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData); }; diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 6decb4be7..d146e9c1e 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -11,21 +11,90 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" -#include +#include "Engine/Serialization/Serialization.h" #include -#include - -#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f -#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f +#include namespace { - NavMeshRuntime* _navMesh; + Array> NavMeshes; } -NavMeshRuntime* NavMeshRuntime::Get() +NavMeshRuntime* NavMeshRuntime::Get(const StringView& navMeshName) { - return ::_navMesh; + NavMeshRuntime* result = nullptr; + for (auto navMesh : NavMeshes) + { + if (navMesh->Properties.Name == navMeshName) + { + result = navMesh; + break; + } + } + return result; +} + +NavMeshRuntime* NavMeshRuntime::Get(const NavAgentProperties& agentProperties) +{ + NavMeshRuntime* result = nullptr; + // TODO: maybe build lookup table for agentProperties -> navMesh to improve perf on frequent calls? + float bestAgentRadiusDiff = -MAX_float; + float bestAgentHeightDiff = -MAX_float; + bool bestIsValid = false; + for (auto navMesh : NavMeshes) + { + const auto& navMeshProperties = navMesh->Properties; + const float agentRadiusDiff = navMeshProperties.Agent.Radius - agentProperties.Radius; + const float agentHeightDiff = navMeshProperties.Agent.Height - agentProperties.Height; + const bool isValid = agentRadiusDiff >= 0.0f && agentHeightDiff >= 0.0f; + + // NavMesh must be valid for an agent and be first valid or have better properties than the best matching result so far + if (isValid + && + ( + !bestIsValid + || + (agentRadiusDiff + agentHeightDiff < bestAgentRadiusDiff + bestAgentHeightDiff) + ) + ) + { + result = navMesh; + bestIsValid = true; + bestAgentRadiusDiff = agentRadiusDiff; + bestAgentHeightDiff = agentHeightDiff; + } + } + return result; +} + +NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties, bool createIfMissing) +{ + NavMeshRuntime* result = nullptr; + for (auto navMesh : NavMeshes) + { + if (navMesh->Properties == navMeshProperties) + { + result = navMesh; + break; + } + } + if (!result && createIfMissing) + { + // Create a new navmesh + result = New(navMeshProperties); + NavMeshes.Add(result); + } + return result; +} + +bool NavAgentProperties::operator==(const NavAgentProperties& other) const +{ + return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle); +} + +bool NavMeshProperties::operator==(const NavMeshProperties& other) const +{ + return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent; } class NavigationService : public EngineService @@ -59,17 +128,59 @@ void* rcAllocDefault(size_t size, rcAllocHint) return Allocator::Allocate(size); } +NavigationSettings::NavigationSettings() +{ + NavMeshes.Resize(1); + auto& navMesh = NavMeshes[0]; + navMesh.Name = TEXT("Default"); +} + IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation); +void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(AutoAddMissingNavMeshes); + DESERIALIZE(AutoRemoveMissingNavMeshes); + DESERIALIZE(CellHeight); + DESERIALIZE(CellSize); + DESERIALIZE(TileSize); + DESERIALIZE(MinRegionArea); + DESERIALIZE(MergeRegionArea); + DESERIALIZE(MaxEdgeLen); + DESERIALIZE(MaxEdgeError); + DESERIALIZE(DetailSamplingDist); + DESERIALIZE(MaxDetailSamplingError); + if (modifier->EngineBuild >= 6215) + { + DESERIALIZE(NavMeshes); + } + else + { + // [Deprecated on 12.01.2021, expires on 12.01.2022] + float WalkableRadius = 34.0f; + float WalkableHeight = 144.0f; + float WalkableMaxClimb = 35.0f; + float WalkableMaxSlopeAngle = 60.0f; + DESERIALIZE(WalkableRadius); + DESERIALIZE(WalkableHeight); + DESERIALIZE(WalkableMaxClimb); + DESERIALIZE(WalkableMaxSlopeAngle); + NavMeshes.Resize(1); + auto& navMesh = NavMeshes[0]; + navMesh.Name = TEXT("Default"); + navMesh.Agent.Radius = WalkableRadius; + navMesh.Agent.Height = WalkableHeight; + navMesh.Agent.StepHeight = WalkableMaxClimb; + navMesh.Agent.MaxSlopeAngle = WalkableMaxSlopeAngle; + } +} + bool NavigationService::Init() { // Link memory allocation calls to use engine default allocator dtAllocSetCustom(dtAllocDefault, Allocator::Free); rcAllocSetCustom(rcAllocDefault, Allocator::Free); - // Create global nav mesh - _navMesh = New(); - return false; } @@ -84,156 +195,42 @@ void NavigationService::Update() void NavigationService::Dispose() { - if (_navMesh) + // Release nav meshes + for (auto navMesh : NavMeshes) { - _navMesh->Dispose(); - Delete(_navMesh); - _navMesh = nullptr; + navMesh->Dispose(); + Delete(navMesh); } + NavMeshes.Clear(); + NavMeshes.ClearDelete(); } bool Navigation::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - - return dtStatusSucceed(_navMesh->GetNavMeshQuery()->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X)); + return NavMeshes.First()->FindDistanceToWall(startPosition, hitInfo, maxDistance); } bool Navigation::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) { - resultPath.Clear(); - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - dtPolyRef endPoly = 0; - query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr); - if (!endPoly) - { - return false; - } - - dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; - int32 pathSize; - const auto findPathStatus = _navMesh->GetNavMeshQuery()->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); - if (dtStatusFailed(findPathStatus)) - { - return false; - } - - // Check for special case, where path has not been found, and starting polygon was the one closest to the target - if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) - { - // In this case we find a point on starting polygon, that's closest to destination and store it as path end - resultPath.Resize(2); - resultPath[0] = startPosition; - resultPath[1] = startPosition; - _navMesh->GetNavMeshQuery()->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X); - } - else - { - int straightPathCount = 0; - resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); - const auto findStraightPathStatus = _navMesh->GetNavMeshQuery()->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); - if (dtStatusFailed(findStraightPathStatus)) - { - return false; - } - resultPath.Resize(straightPathCount); - } - - return true; + return NavMeshes.First()->FindPath(startPosition, endPosition, resultPath); } bool Navigation::ProjectPoint(const Vector3& point, Vector3& result) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X); - if (!startPoly) - { - return false; - } - - return true; + return NavMeshes.First()->ProjectPoint(point, result); } bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - - dtRaycastHit hit; - hit.path = nullptr; - hit.maxPath = 0; - const bool result = dtStatusSucceed(_navMesh->GetNavMeshQuery()->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit)); - if (hit.t >= MAX_float) - { - hitInfo.Position = endPosition; - hitInfo.Distance = 0; - } - else - { - hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t; - hitInfo.Distance = hit.t; - } - hitInfo.Normal = *(Vector3*)&hit.hitNormal; - - return result; + return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo); } #if COMPILE_WITH_NAV_MESH_BUILDER @@ -260,96 +257,13 @@ void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, floa #endif -#if USE_EDITOR - -#include "Engine/Debug/DebugDraw.h" - -void DrawPoly(const dtMeshTile& tile, const dtPoly& poly) -{ - const unsigned int ip = (unsigned int)(&poly - tile.polys); - const dtPolyDetail& pd = tile.detailMeshes[ip]; - const float DrawOffsetY = 20.0f; - - for (int i = 0; i < pd.triCount; i++) - { - Vector3 v[3]; - const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4]; - - for (int k = 0; k < 3; k++) - { - if (t[k] < poly.vertCount) - { - v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3]; - } - else - { - v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3]; - } - } - - v[0].Y += DrawOffsetY; - v[1].Y += DrawOffsetY; - v[2].Y += DrawOffsetY; - - DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], Color::Green * 0.5f, 0, true); - } - - for (int k = 0; k < pd.triCount; k++) - { - const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4]; - Vector3 v[3]; - - for (int m = 0; m < 3; m++) - { - if (t[m] < poly.vertCount) - v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3]; - else - v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3]; - } - - v[0].Y += DrawOffsetY; - v[1].Y += DrawOffsetY; - v[2].Y += DrawOffsetY; - - for (int m = 0, n = 2; m < 3; n = m++) - { - // Skip inner detail edges - if (((t[3] >> (n * 2)) & 0x3) == 0) - continue; - - DEBUG_DRAW_LINE(v[n], v[m], Color::YellowGreen, 0, true); - } - } -} +#if COMPILE_WITH_DEBUG_DRAW void Navigation::DrawNavMesh() { - if (!_navMesh) - return; - - ScopeLock lock(_navMesh->Locker); - - const dtNavMesh* dtNavMesh = _navMesh->GetNavMesh(); - const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; - if (tilesCount == 0) - return; - - for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) + for (auto navMesh : NavMeshes) { - const dtMeshTile* tile = dtNavMesh->getTile(tileIndex); - if (!tile->header) - continue; - - //DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue); - - for (int i = 0; i < tile->header->polyCount; i++) - { - const dtPoly* poly = &tile->polys[i]; - if (poly->getType() != DT_POLYTYPE_GROUND) - continue; - - DrawPoly(*tile, *poly); - } + navMesh->DebugDraw(); } } diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index ba15a9ff7..2557e31e8 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -6,8 +6,6 @@ class Scene; -#define NAV_MESH_PATH_MAX_SIZE 200 - /// /// The navigation service used for path finding and agents navigation system. /// @@ -88,7 +86,7 @@ public: #endif -#if USE_EDITOR +#if COMPILE_WITH_DEBUG_DRAW /// /// Draws the navigation for all the scenes (uses DebugDraw interface). diff --git a/Source/Engine/Navigation/NavigationScene.h b/Source/Engine/Navigation/NavigationScene.h deleted file mode 100644 index ecdca0744..000000000 --- a/Source/Engine/Navigation/NavigationScene.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "NavMeshData.h" -#include "Engine/Content/AssetReference.h" -#include "Engine/Content/Assets/RawDataAsset.h" - -class Scene; -class NavMeshBoundsVolume; - -/// -/// Scene object navigation data. -/// -class NavigationScene -{ - friend Scene; - -public: - - /// - /// Initializes a new instance of the class. - /// - /// The scene. - NavigationScene(Scene* scene); - -public: - - /// - /// The parent scene. - /// - Scene* Scene; - - /// - /// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization. - /// - bool IsDataDirty; - - /// - /// The navmesh tiles data. - /// - NavMeshData Data; - - /// - /// The cached navmesh data asset. - /// - AssetReference DataAsset; - -public: - - /// - /// The list of registered navigation bounds volumes (in the scene). - /// - Array Volumes; - - /// - /// Gets the total navigation volumes bounds. - /// - /// The navmesh bounds. - BoundingBox GetNavigationBounds(); - - /// - /// Finds the navigation volume bounds that have intersection with the given world-space bounding box. - /// - /// The bounds. - /// The intersecting volume or null if none found. - NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds); - -public: - - /// - /// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time). - /// - void SaveNavMesh(); - - /// - /// Clears the data. - /// - void ClearData() - { - if (Data.Tiles.HasItems()) - { - IsDataDirty = true; - Data.TileSize = 0.0f; - Data.Tiles.Resize(0); - } - } - -protected: - - void OnEnable(); - void OnDisable(); - void OnDataAssetLoaded(); -}; diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs index ff6f2aa4a..da300b3f1 100644 --- a/Source/Engine/Navigation/NavigationSettings.cs +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -1,8 +1,74 @@ // Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. +using System; +using FlaxEngine; + namespace FlaxEditor.Content.Settings { partial class NavigationSettings { + // [Deprecated on 12.01.2021, expires on 12.01.2022] + private void UpgradeToNavMeshes() + { + if (NavMeshes != null && NavMeshes.Length == 1) + return; + NavMeshes = new NavMeshProperties[1]; + ref var navMesh = ref NavMeshes[0]; + navMesh.Name = "Default"; + navMesh.Color = Color.Green; + navMesh.Rotation = Quaternion.Identity; + navMesh.Agent.Radius = 34.0f; + navMesh.Agent.Height = 144.0f; + navMesh.Agent.StepHeight = 35.0f; + navMesh.Agent.MaxSlopeAngle = 60.0f; + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableRadius + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.Radius = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableHeight + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.Height = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableMaxClimb + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.StepHeight = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableMaxSlopeAngle + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.MaxSlopeAngle = value; + } + } } } diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index fe4189955..e225a2f07 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -4,7 +4,7 @@ #include "NavigationTypes.h" #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" +#include "Engine/Core/Collections/Array.h" /// /// The navigation system settings container. @@ -12,108 +12,93 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); +public: + + /// + /// If checked, enables automatic navmesh actors spawning on a scenes that are using it during navigation building. + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Navigation\")") + bool AutoAddMissingNavMeshes = true; + + /// + /// If checked, enables automatic navmesh actors removing from a scenes that are not using it during navigation building. + /// + API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Navigation\")") + bool AutoRemoveMissingNavMeshes = true; + public: /// /// The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times. /// - API_FIELD(Attributes="DefaultValue(10.0f), Limit(1, 400), EditorOrder(10), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(1, 400), EditorOrder(210), EditorDisplay(\"Nav Mesh Options\")") float CellHeight = 10.0f; /// /// The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the horizontal axes but longer build times. /// - API_FIELD(Attributes="DefaultValue(30.0f), Limit(1, 400), EditorOrder(20), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(1, 400), EditorOrder(220), EditorDisplay(\"Nav Mesh Options\")") float CellSize = 30.0f; /// /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. /// - API_FIELD(Attributes="DefaultValue(64), Limit(8, 4096), EditorOrder(30), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(8, 4096), EditorOrder(230), EditorDisplay(\"Nav Mesh Options\")") int32 TileSize = 64; /// /// The minimum number of cells allowed to form isolated island areas. /// - API_FIELD(Attributes="DefaultValue(0), Limit(0, 100), EditorOrder(40), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(0, 100), EditorOrder(240), EditorDisplay(\"Nav Mesh Options\")") int32 MinRegionArea = 0; /// /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. /// - API_FIELD(Attributes="DefaultValue(20), Limit(0, 100), EditorOrder(50), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(0, 100), EditorOrder(250), EditorDisplay(\"Nav Mesh Options\")") int32 MergeRegionArea = 20; /// /// The maximum allowed length for contour edges along the border of the mesh. /// - API_FIELD(Attributes="DefaultValue(1200.0f), Limit(100), EditorOrder(60), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")") + API_FIELD(Attributes="Limit(100), EditorOrder(260), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")") float MaxEdgeLen = 1200.0f; /// /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. /// - API_FIELD(Attributes="DefaultValue(1.3f), Limit(0.1f, 4), EditorOrder(70), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(0.1f, 4), EditorOrder(270), EditorDisplay(\"Nav Mesh Options\")") float MaxEdgeError = 1.3f; /// /// The sampling distance to use when generating the detail mesh. /// - API_FIELD(Attributes="DefaultValue(600.0f), Limit(1), EditorOrder(80), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")") + API_FIELD(Attributes="Limit(1), EditorOrder(280), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")") float DetailSamplingDist = 600.0f; /// /// The maximum distance the detail mesh surface should deviate from heightfield data. /// - API_FIELD(Attributes="DefaultValue(1.0f), Limit(0, 3), EditorOrder(90), EditorDisplay(\"Nav Mesh Options\")") + API_FIELD(Attributes="Limit(0, 3), EditorOrder(290), EditorDisplay(\"Nav Mesh Options\")") float MaxDetailSamplingError = 1.0f; - /// - /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. - /// - API_FIELD(Attributes="DefaultValue(34.0f), Limit(0), EditorOrder(1000), EditorDisplay(\"Agent Options\")") - float WalkableRadius = 34.0f; +public: /// - /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. + /// The configuration for navmeshes. /// - API_FIELD(Attributes="DefaultValue(144.0f), Limit(0), EditorOrder(1010), EditorDisplay(\"Agent Options\")") - float WalkableHeight = 144.0f; - - /// - /// The maximum ledge height that is considered to still be traversable. - /// - API_FIELD(Attributes="DefaultValue(35.0f), Limit(0), EditorOrder(1020), EditorDisplay(\"Agent Options\")") - float WalkableMaxClimb = 35.0f; - - /// - /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. - /// - API_FIELD(Attributes="DefaultValue(60.0f), Limit(0, 89.0f), EditorOrder(1030), EditorDisplay(\"Agent Options\")") - float WalkableMaxSlopeAngle = 60.0f; + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\")") + Array NavMeshes; public: + NavigationSettings(); + /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// static NavigationSettings* Get(); // [SettingsBase] - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(CellHeight); - DESERIALIZE(CellSize); - DESERIALIZE(TileSize); - DESERIALIZE(MinRegionArea); - DESERIALIZE(MergeRegionArea); - DESERIALIZE(MaxEdgeLen); - DESERIALIZE(MaxEdgeError); - DESERIALIZE(DetailSamplingDist); - DESERIALIZE(MaxDetailSamplingError); - DESERIALIZE(WalkableRadius); - DESERIALIZE(WalkableHeight); - DESERIALIZE(WalkableMaxClimb); - DESERIALIZE(WalkableMaxSlopeAngle); - } + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index a5672f342..f00c1683e 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -7,61 +7,88 @@ #include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/ISerializable.h" + +#define NAV_MESH_PATH_MAX_SIZE 200 /// /// The navigation system agent properties container for navmesh building and querying. /// -API_STRUCT() struct FLAXENGINE_API NavAgentProperties +API_STRUCT() struct FLAXENGINE_API NavAgentProperties : ISerializable { +API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); /// /// The radius of the agent used for navigation. Agents can't pass through gaps of less than twice the radius. /// - API_FIELD() float Radius = 34.0f; + API_FIELD(Attributes="EditorOrder(0)") + float Radius = 34.0f; /// /// The height of the agent used for navigation. Agents can't enter areas with ceilings lower than this value. /// - API_FIELD() float Height = 144.0f; + API_FIELD(Attributes="EditorOrder(10)") + float Height = 144.0f; /// /// The step height used for navigation. Defines the maximum ledge height that is considered to still be traversable by the agent. /// - API_FIELD() float StepHeight = 35.0f; + API_FIELD(Attributes="EditorOrder(20)") + float StepHeight = 35.0f; /// /// The maximum slope (in degrees) that is considered walkable for navigation. Agents can't go up or down slopes higher than this value. /// - API_FIELD() float MaxSlopeAngle = 60.0f; + API_FIELD(Attributes="EditorOrder(30)") + float MaxSlopeAngle = 60.0f; + + bool operator==(const NavAgentProperties& other) const; + + bool operator!=(const NavAgentProperties& other) const + { + return !operator==(other); + } }; /// /// The navigation mesh properties container for navmesh building. /// -API_STRUCT() struct FLAXENGINE_API NavMeshProperties +API_STRUCT() struct FLAXENGINE_API NavMeshProperties : ISerializable { +API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties); /// - /// The navmesh type name (for debugging). + /// The navmesh type name. Identifies different types of the navmeshes, used to sync navmesh properties with settings asset. /// - API_FIELD() String Name; + API_FIELD(Attributes="EditorOrder(0)") + String Name; /// /// The navmesh type color (for debugging). /// - API_FIELD() Color Color; + API_FIELD(Attributes="EditorOrder(10)") + Color Color = Color::Green; /// /// The navmesh rotation applied to navigation surface. Used during building to the rotate scene geometry and to revert back result during path finding queries. Can be used to generate navmesh on walls. /// - API_FIELD() Quaternion Rotation; + API_FIELD(Attributes="EditorOrder(20)") + Quaternion Rotation = Quaternion::Identity; /// /// The properties of the agent used to generate walkable navigation surface. /// - API_FIELD() NavAgentProperties Agent; + API_FIELD(Attributes="EditorOrder(30)") + NavAgentProperties Agent; + + bool operator==(const NavMeshProperties& other) const; + + bool operator!=(const NavMeshProperties& other) const + { + return !operator==(other); + } }; /// From 29c3e2d54bb54372cb80cb25695e407e5ebebc9e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Jan 2021 14:28:58 +0100 Subject: [PATCH 034/222] Fix Color doc comments --- Source/Engine/Core/Math/Color.cpp | 35 +++++++++++++++++++++++ Source/Engine/Core/Math/Color.cs | 47 +------------------------------ Source/Engine/Core/Math/Color.h | 40 ++++++-------------------- 3 files changed, 44 insertions(+), 78 deletions(-) diff --git a/Source/Engine/Core/Math/Color.cpp b/Source/Engine/Core/Math/Color.cpp index 168db71a5..5a00a52e5 100644 --- a/Source/Engine/Core/Math/Color.cpp +++ b/Source/Engine/Core/Math/Color.cpp @@ -137,6 +137,26 @@ String Color::ToHexString() const return String(result, 6); } +bool Color::IsTransparent() const +{ + return Math::IsZero(R + G + B + A); +} + +bool Color::HasOpacity() const +{ + return !Math::IsOne(A); +} + +bool Color::NearEqual(const Color& a, const Color& b) +{ + return Math::NearEqual(a.R, b.R) && Math::NearEqual(a.G, b.G) && Math::NearEqual(a.B, b.B) && Math::NearEqual(a.A, b.A); +} + +bool Color::NearEqual(const Color& a, const Color& b, float epsilon) +{ + return Math::NearEqual(a.R, b.R, epsilon) && Math::NearEqual(a.G, b.G, epsilon) && Math::NearEqual(a.B, b.B, epsilon) && Math::NearEqual(a.A, b.A, epsilon); +} + Vector3 Color::ToVector3() const { return Vector3(R, G, B); @@ -158,6 +178,21 @@ Vector3 Color::ToHSV() const return Vector3(hue, saturation, value); } +void Color::Lerp(const Color& start, const Color& end, float amount, Color& result) +{ + result.R = Math::Lerp(start.R, end.R, amount); + result.G = Math::Lerp(start.G, end.G, amount); + result.B = Math::Lerp(start.B, end.B, amount); + result.A = Math::Lerp(start.A, end.A, amount); +} + +Color Color::Lerp(const Color& start, const Color& end, float amount) +{ + Color result; + Lerp(start, end, amount, result); + return result; +} + Color Color::LinearToSrgb(const Color& linear) { #define LINEAR_TO_SRGB(value) value < 0.00313067f ? value * 12.92f : Math::Pow(value, (1.0f / 2.4f)) * 1.055f - 0.055f diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index 5aee4c428..a98f35126 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -716,7 +716,7 @@ namespace FlaxEngine } /// - /// Converts the color to HSV color space (returned as vector). + /// Gets Hue[0-360], Saturation[0-1] and Value[0-1] from RGB color. /// /// The HSV color. public Vector3 ToHSV() @@ -743,51 +743,6 @@ namespace FlaxEngine return new Vector3(hue, saturation, value); } - /// - /// Convert color from the RGB color space to HSV color space. - /// - /// Color of the RGB. - /// The output Hue. - /// The output Saturation. - /// The output Value. - public static void RGBToHSV(Color rgbColor, out float h, out float s, out float v) - { - if ((rgbColor.B > rgbColor.G) && (rgbColor.B > rgbColor.R)) - RGBToHSVHelper(4f, rgbColor.B, rgbColor.R, rgbColor.G, out h, out s, out v); - else if (rgbColor.G <= rgbColor.R) - RGBToHSVHelper(0f, rgbColor.R, rgbColor.G, rgbColor.B, out h, out s, out v); - else - RGBToHSVHelper(2f, rgbColor.G, rgbColor.B, rgbColor.R, out h, out s, out v); - } - - private static void RGBToHSVHelper(float offset, float dominantcolor, float colorone, float colortwo, out float h, out float s, out float v) - { - v = dominantcolor; - if (Mathf.IsZero(v)) - { - s = 0f; - h = 0f; - } - else - { - var single = colorone <= colortwo ? colorone : colortwo; - float vv = v - single; - if (Mathf.IsZero(vv)) - { - s = 0f; - h = offset + (colorone - colortwo); - } - else - { - s = vv / v; - h = offset + (colorone - colortwo) / vv; - } - h = h / 6f; - if (h < 0f) - h = h + 1f; - } - } - /// /// Adjusts the contrast of a color. /// diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index d11097d5c..3eee6d9f8 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -166,7 +166,7 @@ public: /// Creates RGB color from Hue[0-360], Saturation[0-1] and Value[0-1] packed to XYZ vector. /// /// The HSV color. - /// The alpha value. Default is 1. + /// The alpha value. Default is 1. /// The RGB color. static Color FromHSV(const Vector3& hsv, float alpha = 1.0f); @@ -264,28 +264,15 @@ public: } // Returns true if color is fully transparent (all components are equal zero). - bool IsTransparent() const - { - return Math::IsZero(R + G + B + A); - } + bool IsTransparent() const; // Returns true if color has opacity channel in use (different from 1). - bool HasOpacity() const - { - return !Math::IsOne(A); - } + bool HasOpacity() const; public: - static bool NearEqual(const Color& a, const Color& b) - { - return Math::NearEqual(a.R, b.R) && Math::NearEqual(a.G, b.G) & Math::NearEqual(a.B, b.B) && Math::NearEqual(a.A, b.A); - } - - static bool NearEqual(const Color& a, const Color& b, float epsilon) - { - return Math::NearEqual(a.R, b.R, epsilon) && Math::NearEqual(a.G, b.G, epsilon) & Math::NearEqual(a.B, b.B, epsilon) && Math::NearEqual(a.A, b.A, epsilon); - } + static bool NearEqual(const Color& a, const Color& b); + static bool NearEqual(const Color& a, const Color& b, float epsilon); public: @@ -296,7 +283,7 @@ public: Vector4 ToVector4() const; /// - /// Gets Hue[0-1], Saturation[0-1] and Value[0-360] from RGB color. + /// Gets Hue[0-360], Saturation[0-1] and Value[0-1] from RGB color. /// /// HSV color Vector3 ToHSV() const; @@ -308,13 +295,7 @@ public: /// The end color. /// The value between 0 and 1 indicating the weight of interpolation. /// When the method completes, contains the linear interpolation of the two colors. - static void Lerp(const Color& start, const Color& end, float amount, Color& result) - { - result.R = Math::Lerp(start.R, end.R, amount); - result.G = Math::Lerp(start.G, end.G, amount); - result.B = Math::Lerp(start.B, end.B, amount); - result.A = Math::Lerp(start.A, end.A, amount); - } + static void Lerp(const Color& start, const Color& end, float amount, Color& result); /// /// Performs a linear interpolation between two colors. @@ -323,12 +304,7 @@ public: /// The end color. /// The value between 0 and 1 indicating the weight of interpolation. /// The linear interpolation of the two colors. - static Color Lerp(const Color& start, const Color& end, float amount) - { - Color result; - Lerp(start, end, amount, result); - return result; - } + static Color Lerp(const Color& start, const Color& end, float amount); // Converts a [0.0, 1.0] linear value into a [0.0, 1.0] sRGB value. static Color LinearToSrgb(const Color& linear); From 88bddfb1417e7e33f02b0ae2b1f89e00848c587c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Jan 2021 14:49:50 +0100 Subject: [PATCH 035/222] Add navmesh name to logs for better debugging --- Source/Engine/Navigation/NavMeshRuntime.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index 4ca3b7041..017d35383 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -223,7 +223,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) newCapacity = 32; } - LOG(Info, "Resizing navmesh from {0} to {1} tiles capacity", capacity, newCapacity); + LOG(Info, "Resizing navmesh {2} from {0} to {1} tiles capacity", capacity, newCapacity, Properties.Name); // Ensure to have size assigned ASSERT(_tileSize != 0); @@ -238,7 +238,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) _navMesh = dtAllocNavMesh(); if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) { - LOG(Fatal, "Failed to initialize nav mesh."); + LOG(Error, "Failed to initialize navmesh {0}.", Properties.Name); } // Prepare parameters @@ -255,7 +255,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) // Initialize nav mesh if (dtStatusFailed(_navMesh->init(¶ms))) { - LOG(Fatal, "Navmesh init failed."); + LOG(Error, "Navmesh {0} init failed.", Properties.Name); return; } @@ -276,7 +276,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) #endif if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) { - LOG(Warning, "Could not add tile to navmesh."); + LOG(Warning, "Could not add tile to navmesh {0}.", Properties.Name); } } } @@ -298,7 +298,7 @@ void NavMeshRuntime::AddTiles(NavMesh* navMesh) { if (Math::NotNearEqual(data.TileSize, _tileSize)) { - LOG(Warning, "Cannot add navigation scene tiles to the navmesh. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize); + LOG(Warning, "Cannot add navigation scene tiles to the navmesh {2}. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize, Properties.Name); return; } } @@ -331,7 +331,7 @@ void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData) { if (Math::NotNearEqual(data.TileSize, _tileSize)) { - LOG(Warning, "Cannot add navigation scene tile to the navmesh. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize); + LOG(Warning, "Cannot add navigation scene tile to the navmesh {2}. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize, Properties.Name); return; } } @@ -375,7 +375,7 @@ void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer) if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { - LOG(Warning, "Failed to remove tile from navmesh."); + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); } for (int32 i = 0; i < _tiles.Count(); i++) @@ -408,13 +408,13 @@ void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMe const auto tileRef = _navMesh->getTileRefAt(tile.X, tile.Y, tile.Layer); if (tileRef == 0) { - LOG(Warning, "Missing navmesh tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer); + LOG(Warning, "Missing navmesh {3} tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer, Properties.Name); } else { if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { - LOG(Warning, "Failed to remove tile from navmesh."); + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); } } @@ -579,6 +579,6 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData #endif if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) { - LOG(Warning, "Could not add tile to navmesh."); + LOG(Warning, "Could not add tile to navmesh {0}.", Properties.Name); } } From d1a282e2288525517f9247789df9ed082b95c594 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Jan 2021 14:50:12 +0100 Subject: [PATCH 036/222] Fix removing unused navmesh tiles on whole scene rebuild --- Source/Engine/Navigation/NavMeshBuilder.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index dca9a445c..7e440648f 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -635,7 +635,7 @@ void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const task->Start(); } -void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBounds) +void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBounds, bool rebuild) { const float tileSize = GetTileSize(); NavMeshRuntime* runtime = navMesh->GetRuntime(); @@ -655,12 +655,15 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo PROFILE_CPU_NAMED("Prepare"); // Prepare scene data and navmesh - if (Math::NotNearEqual(navMesh->Data.TileSize, tileSize)) + rebuild |= Math::NotNearEqual(navMesh->Data.TileSize, tileSize); + if (rebuild) { + // Remove all tiles from navmesh runtime runtime->RemoveTiles(navMesh); runtime->SetTileSize(tileSize); runtime->EnsureCapacity(tilesX * tilesY); + // Remove all tiles from navmesh data navMesh->Data.TileSize = tileSize; navMesh->Data.Tiles.Clear(); navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX); @@ -668,7 +671,7 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo } else { - // Prepare navmesh + // Ensure to have enough memory for tiles runtime->SetTileSize(tileSize); runtime->EnsureCapacity(tilesX * tilesY); } @@ -700,7 +703,7 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo } } -void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds) +void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild) { auto settings = NavigationSettings::Get(); @@ -738,7 +741,7 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds) // Build all navmeshes on the scene for (NavMesh* navMesh : scene->NavigationMeshes) { - BuildDirtyBounds(scene, navMesh, dirtyBounds); + BuildDirtyBounds(scene, navMesh, dirtyBounds, rebuild); } // Remove unused navmeshes @@ -772,7 +775,7 @@ void BuildWholeScene(Scene* scene) // Compute total navigation area bounds const BoundingBox worldBounds = scene->GetNavigationBounds(); - BuildDirtyBounds(scene, worldBounds); + BuildDirtyBounds(scene, worldBounds, true); } void ClearNavigation(Scene* scene) @@ -814,7 +817,7 @@ void NavMeshBuilder::Update() } else { - BuildDirtyBounds(scene, req.DirtyBounds); + BuildDirtyBounds(scene, req.DirtyBounds, false); } } } From af692605ab77924292049ba22b588eea8a1a8988 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Jan 2021 15:21:50 +0100 Subject: [PATCH 037/222] Add option to hide navmesh in editor debug view --- Source/Engine/Navigation/NavMesh.h | 9 +++++++++ Source/Engine/Navigation/Navigation.cpp | 26 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h index bd0eab703..f1a901967 100644 --- a/Source/Engine/Navigation/NavMesh.h +++ b/Source/Engine/Navigation/NavMesh.h @@ -34,6 +34,15 @@ public: /// AssetReference DataAsset; +#if USE_EDITOR + + /// + /// If checked, the navmesh will be drawn in debug view when showing navigation data. + /// + API_FIELD(Attributes="EditorOrder(-10), EditorDisplay(\"Nav Mesh\")") bool ShowDebugDraw = true; + +#endif + /// /// The navigation mesh properties. /// diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index d146e9c1e..7f8ca5e9a 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -8,7 +8,12 @@ #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" #include "Engine/Threading/Threading.h" +#if USE_EDITOR +#include "Engine/Level/Level.h" #include "Engine/Level/Scene/Scene.h" +#endif +#include "NavMesh.h" + #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" @@ -263,6 +268,27 @@ void Navigation::DrawNavMesh() { for (auto navMesh : NavMeshes) { +#if USE_EDITOR + // Skip drawing if any of the navmeshes on scene has disabled ShowDebugDraw option + bool skip = false; + for (auto scene : Level::Scenes) + { + for (auto e : scene->NavigationMeshes) + { + if (e->Properties == navMesh->Properties) + { + if (!e->ShowDebugDraw) + { + skip = true; + } + break; + } + } + } + if (skip) + continue; +#endif + navMesh->DebugDraw(); } } From b2a2652b5653345feebdeea69dcddd471c14ec53 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Jan 2021 15:35:53 +0100 Subject: [PATCH 038/222] Fix not using hardcoded order for showing Actor main properties in editor --- .../CustomEditors/Dedicated/ActorEditor.cs | 37 ------------------- .../CustomEditors/Editors/GenericEditor.cs | 13 +++---- Source/Engine/Level/Actor.cpp | 10 +++++ Source/Engine/Level/Actor.h | 24 ++++-------- Source/Engine/Navigation/NavMesh.h | 4 +- 5 files changed, 26 insertions(+), 62 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index ad974c55a..924f5aeb3 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -24,43 +24,6 @@ namespace FlaxEditor.CustomEditors.Dedicated { private Guid _linkedPrefabId; - /// - protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item) - { - // Note: we cannot specify actor properties editor types directly because we want to keep editor classes in FlaxEditor assembly - int order = item.Order?.Order ?? int.MinValue; - switch (order) - { - // Override static flags editor - case -80: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorStaticFlagsEditor)); - break; - - // Override layer editor - case -69: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorLayerEditor)); - break; - - // Override tag editor - case -68: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTagEditor)); - break; - - // Override position/scale editor - case -30: - case -10: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.PositionScaleEditor)); - break; - - // Override orientation editor - case -20: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.OrientationEditor)); - break; - } - - base.SpawnProperty(itemLayout, itemValues, item); - } - /// protected override List GetItemsForType(ScriptType type) { diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 485a9b735..9e7295e7e 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -517,23 +517,22 @@ namespace FlaxEditor.CustomEditors.Editors if (item.Header != null) itemLayout.Header(item.Header.Text); - // Peek values - ValueContainer itemValues; try { - itemValues = item.GetValues(Values); + // Peek values + ValueContainer itemValues = item.GetValues(Values); + + // Spawn property editor + SpawnProperty(itemLayout, itemValues, item); } catch (Exception ex) { - Editor.LogWarning("Failed to get object values for item " + item); + Editor.LogWarning("Failed to setup values and UI for item " + item); Editor.LogWarning(ex.Message); Editor.LogWarning(ex.StackTrace); return; } - // Spawn property editor - SpawnProperty(itemLayout, itemValues, item); - // Expand all parent groups if need to if (item.ExpandGroups) { diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 900d802ed..30ca06daf 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -597,6 +597,11 @@ void Actor::SetDirection(const Vector3& value) SetOrientation(orientation); } +void Actor::ResetLocalTransform() +{ + SetLocalTransform(Transform::Identity); +} + void Actor::SetLocalTransform(const Transform& value) { CHECK(!value.IsNanOrInfinity()); @@ -1258,6 +1263,11 @@ Script* Actor::GetScriptByPrefabObjectId(const Guid& prefabObjectId) const return result; } +bool Actor::IsPrefabRoot() const +{ + return _isPrefabRoot != 0; +} + Actor* Actor::FindActor(const StringView& name) const { Actor* result = nullptr; diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 334021c28..88da10f0e 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -79,7 +79,7 @@ public: /// /// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-69)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-69), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorLayerEditor\")") FORCE_INLINE int32 GetLayer() const { return _layer; @@ -123,7 +123,7 @@ public: /// /// Gets the name of the tag. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTagEditor\")") const String& GetTag() const; /// @@ -355,7 +355,7 @@ public: /// /// Gets the actor static fags. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-80)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-80), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorStaticFlagsEditor\")") FORCE_INLINE StaticFlags GetStaticFlags() const { return _staticFlags; @@ -500,10 +500,7 @@ public: /// /// Resets the actor local transform. /// - FORCE_INLINE void ResetLocalTransform() - { - SetLocalTransform(Transform::Identity); - } + void ResetLocalTransform(); /// /// Gets local transform of the actor in parent actor space. @@ -523,7 +520,7 @@ public: /// /// Gets local position of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionScaleEditor\")") FORCE_INLINE Vector3 GetLocalPosition() const { return _localTransform.Translation; @@ -538,7 +535,7 @@ public: /// /// Gets local rotation of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") FORCE_INLINE Quaternion GetLocalOrientation() const { return _localTransform.Orientation; @@ -553,7 +550,7 @@ public: /// /// Gets local scale vector of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Vector3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Vector3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionScaleEditor\")") FORCE_INLINE Vector3 GetLocalScale() const { return _localTransform.Scale; @@ -708,15 +705,10 @@ public: /// The script or null. Script* GetScriptByPrefabObjectId(const Guid& prefabObjectId) const; -public: - /// /// Gets a value indicating whether this actor is a prefab instance root object. /// - API_PROPERTY() FORCE_INLINE bool IsPrefabRoot() const - { - return _isPrefabRoot != 0; - } + API_PROPERTY() bool IsPrefabRoot() const; public: diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h index f1a901967..c9096d323 100644 --- a/Source/Engine/Navigation/NavMesh.h +++ b/Source/Engine/Navigation/NavMesh.h @@ -39,14 +39,14 @@ public: /// /// If checked, the navmesh will be drawn in debug view when showing navigation data. /// - API_FIELD(Attributes="EditorOrder(-10), EditorDisplay(\"Nav Mesh\")") bool ShowDebugDraw = true; + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Nav Mesh\")") bool ShowDebugDraw = true; #endif /// /// The navigation mesh properties. /// - API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Nav Mesh\")") NavMeshProperties Properties; + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Nav Mesh\")") NavMeshProperties Properties; public: From 93aa265b209b12a3df79e52d31adb7050b6a5654 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Jan 2021 13:08:53 +0100 Subject: [PATCH 039/222] Add support for rotated navmeshes --- Source/Engine/Navigation/NavMeshBuilder.cpp | 116 +++++++++++++------- Source/Engine/Navigation/NavMeshRuntime.cpp | 72 +++++++++--- 2 files changed, 137 insertions(+), 51 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 7e440648f..e9c8cb578 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -55,7 +55,8 @@ struct OffMeshLink struct NavigationSceneRasterization { - BoundingBox TileBounds; + BoundingBox TileBoundsNavMesh; + Matrix WorldToNavMesh; rcContext* Context; rcConfig* Config; rcHeightfield* Heightfield; @@ -63,9 +64,12 @@ struct NavigationSceneRasterization Array VertexBuffer; Array IndexBuffer; Array* OffMeshLinks; + const bool IsWorldToNavMeshIdentity; - NavigationSceneRasterization(const BoundingBox& tileBounds, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) - : TileBounds(tileBounds) + NavigationSceneRasterization(const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) + : TileBoundsNavMesh(tileBoundsNavMesh) + , WorldToNavMesh(worldToNavMesh) + , IsWorldToNavMeshIdentity(worldToNavMesh.IsIdentity()) { Context = context; Config = config; @@ -82,23 +86,47 @@ struct NavigationSceneRasterization return; // Rasterize triangles - for (int32 i0 = 0; i0 < ib.Count();) + if (IsWorldToNavMeshIdentity) { - auto v0 = vb[ib[i0++]]; - auto v1 = vb[ib[i0++]]; - auto v2 = vb[ib[i0++]]; + // Faster path + for (int32 i0 = 0; i0 < ib.Count();) + { + auto v0 = vb[ib[i0++]]; + auto v1 = vb[ib[i0++]]; + auto v2 = vb[ib[i0++]]; - auto n = Vector3::Cross(v0 - v1, v0 - v2); - n.Normalize(); - const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; - rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + auto n = Vector3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } + } + else + { + // Transform vertices from world space into the navmesh space + const Matrix worldToNavMesh = WorldToNavMesh; + for (int32 i0 = 0; i0 < ib.Count();) + { + auto v0 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + auto v1 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + auto v2 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + + auto n = Vector3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : RC_NULL_AREA; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } } } static bool Walk(Actor* actor, NavigationSceneRasterization& e) { // Early out if object is not intersecting with the tile bounds or is not using navigation - if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation) || !actor->GetBox().Intersects(e.TileBounds)) + if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation)) + return true; + BoundingBox actorBoxNavMesh; + BoundingBox::Transform(actor->GetBox(), e.WorldToNavMesh, actorBoxNavMesh); + if (!actorBoxNavMesh.Intersects(e.TileBoundsNavMesh)) return true; // Prepare buffers (for triangles) @@ -138,7 +166,9 @@ struct NavigationSceneRasterization for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { const auto patch = terrain->GetPatch(patchIndex); - if (!patch->GetBounds().Intersects(e.TileBounds)) + BoundingBox patchBoundsNavMesh; + BoundingBox::Transform(patch->GetBounds(), e.WorldToNavMesh, patchBoundsNavMesh); + if (!patchBoundsNavMesh.Intersects(e.TileBoundsNavMesh)) continue; patch->ExtractCollisionGeometry(vb, ib); @@ -152,7 +182,9 @@ struct NavigationSceneRasterization OffMeshLink link; link.Start = navLink->GetTransform().LocalToWorld(navLink->Start); + Vector3::Transform(link.Start, e.WorldToNavMesh, link.Start); link.End = navLink->GetTransform().LocalToWorld(navLink->End); + Vector3::Transform(link.End, e.WorldToNavMesh, link.End); link.Radius = navLink->Radius; link.BiDir = navLink->BiDirectional; link.Id = GetHash(navLink->GetID()); @@ -167,26 +199,26 @@ struct NavigationSceneRasterization } }; -void RasterizeGeometry(const BoundingBox& tileBounds, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) +void RasterizeGeometry(const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) { PROFILE_CPU_NAMED("RasterizeGeometry"); - NavigationSceneRasterization rasterization(tileBounds, context, config, heightfield, offMeshLinks); + NavigationSceneRasterization rasterization(tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks); Function treeWalkFunction(NavigationSceneRasterization::Walk); SceneQuery::TreeExecute(treeWalkFunction, rasterization); } // Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location // Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use -bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds) +bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh) { // Build initial tile bounds (with infinite extent) - tileBounds.Minimum.X = (float)x * tileSize; - tileBounds.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Minimum.Z = (float)y * tileSize; - tileBounds.Maximum.X = tileBounds.Minimum.X + tileSize; - tileBounds.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Maximum.Z = tileBounds.Minimum.Z + tileSize; + tileBoundsNavMesh.Minimum.X = (float)x * tileSize; + tileBoundsNavMesh.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; + tileBoundsNavMesh.Minimum.Z = (float)y * tileSize; + tileBoundsNavMesh.Maximum.X = tileBoundsNavMesh.Minimum.X + tileSize; + tileBoundsNavMesh.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; + tileBoundsNavMesh.Maximum.Z = tileBoundsNavMesh.Minimum.Z + tileSize; // Check if any navmesh volume intersects with the tile bool foundAnyVolume = false; @@ -195,7 +227,9 @@ bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, Boundi { const auto volume = scene->NavigationVolumes[i]; const auto& volumeBounds = volume->GetBox(); - if (volumeBounds.Intersects(tileBounds)) + BoundingBox volumeBoundsNavMesh; + BoundingBox::Transform(volumeBounds, worldToNavMesh, volumeBoundsNavMesh); + if (volumeBoundsNavMesh.Intersects(tileBoundsNavMesh)) { if (foundAnyVolume) { @@ -214,8 +248,8 @@ bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, Boundi if (foundAnyVolume) { // Build proper tile bounds - tileBounds.Minimum.Y = rangeY.X; - tileBounds.Maximum.Y = rangeY.Y; + tileBoundsNavMesh.Minimum.Y = rangeY.X; + tileBoundsNavMesh.Maximum.Y = rangeY.Y; } return foundAnyVolume; @@ -241,18 +275,18 @@ void RemoveTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, int runtime->RemoveTile(x, y, layer); } -bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config) +bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize, rcConfig& config) { rcContext context; int32 layer = 0; // Expand tile bounds by a certain margin const float tileBorderSize = (1.0f + (float)config.borderSize) * config.cs; - tileBounds.Minimum -= tileBorderSize; - tileBounds.Maximum += tileBorderSize; + tileBoundsNavMesh.Minimum -= tileBorderSize; + tileBoundsNavMesh.Maximum += tileBorderSize; - rcVcopy(config.bmin, &tileBounds.Minimum.X); - rcVcopy(config.bmax, &tileBounds.Maximum.X); + rcVcopy(config.bmin, &tileBoundsNavMesh.Minimum.X); + rcVcopy(config.bmax, &tileBoundsNavMesh.Maximum.X); rcHeightfield* heightfield = rcAllocHeightfield(); if (!heightfield) @@ -267,7 +301,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B } Array offMeshLinks; - RasterizeGeometry(tileBounds, &context, &config, heightfield, &offMeshLinks); + RasterizeGeometry(tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield); @@ -507,7 +541,8 @@ public: Scene* Scene; NavMesh* NavMesh; NavMeshRuntime* Runtime; - BoundingBox TileBounds; + BoundingBox TileBoundsNavMesh; + Matrix WorldToNavMesh; int32 X; int32 Y; float TileSize; @@ -520,7 +555,7 @@ public: { PROFILE_CPU_NAMED("BuildNavMeshTile"); - if (GenerateTile(NavMesh, Runtime, X, Y, TileBounds, TileSize, Config)) + if (GenerateTile(NavMesh, Runtime, X, Y, TileBoundsNavMesh, WorldToNavMesh, TileSize, Config)) { LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y); } @@ -600,7 +635,7 @@ float NavMeshBuilder::GetNavMeshBuildingProgress() return result; } -void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize) +void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize) { NavMeshRuntime* runtime = navMesh->GetRuntime(); NavBuildTasksLocker.Lock(); @@ -623,7 +658,8 @@ void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const task->Runtime = runtime; task->X = x; task->Y = y; - task->TileBounds = tileBounds; + task->TileBoundsNavMesh = tileBoundsNavMesh; + task->WorldToNavMesh = worldToNavMesh; task->TileSize = tileSize; task->Config = config; NavBuildTasks.Add(task); @@ -639,8 +675,12 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo { const float tileSize = GetTileSize(); NavMeshRuntime* runtime = navMesh->GetRuntime(); + Matrix worldToNavMesh; + Matrix::RotationQuaternion(runtime->Properties.Rotation, worldToNavMesh); // Align dirty bounds to tile size + BoundingBox dirtyBoundsNavMesh; + BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh); BoundingBox dirtyBoundsAligned; dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBounds.Minimum / tileSize) * tileSize; dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBounds.Maximum / tileSize) * tileSize; @@ -689,10 +729,10 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo { for (int32 x = tilesMin.X; x < tilesMax.X; x++) { - BoundingBox tileBounds; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) + BoundingBox tileBoundsNavMesh; + if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBoundsNavMesh, worldToNavMesh)) { - BuildTileAsync(navMesh, x, y, config, tileBounds, tileSize); + BuildTileAsync(navMesh, x, y, config, tileBoundsNavMesh, worldToNavMesh, tileSize); } else { diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index 017d35383..95b07924c 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -3,6 +3,7 @@ #include "NavMeshRuntime.h" #include "NavMesh.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Matrix.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" #include @@ -48,14 +49,27 @@ bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit dtQueryFilter filter; Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 startPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); if (!startPoly) { return false; } - return dtStatusSucceed(query->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X)); + if (!dtStatusSucceed(query->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X))) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + Vector3::Transform(hitInfo.Position, invRotation, hitInfo.Position); + Vector3::Transform(hitInfo.Normal, invRotation, hitInfo.Normal); + + return true; } bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const @@ -72,14 +86,18 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo dtQueryFilter filter; Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); if (!startPoly) { return false; } dtPolyRef endPoly = 0; - query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr); + query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); if (!endPoly) { return false; @@ -87,31 +105,37 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; int32 pathSize; - const auto findPathStatus = query->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); if (dtStatusFailed(findPathStatus)) { return false; } + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + // Check for special case, where path has not been found, and starting polygon was the one closest to the target if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) { // In this case we find a point on starting polygon, that's closest to destination and store it as path end resultPath.Resize(2); resultPath[0] = startPosition; - resultPath[1] = startPosition; - query->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X); + resultPath[1] = startPositionNavMesh; + query->closestPointOnPolyBoundary(startPoly, &endPositionNavMesh.X, &resultPath[1].X); + Vector3::Transform(resultPath[1], invRotation, resultPath[1]); } else { int straightPathCount = 0; resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); - const auto findStraightPathStatus = query->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); + const auto findStraightPathStatus = query->findStraightPath(&startPositionNavMesh.X, &endPositionNavMesh.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); if (dtStatusFailed(findStraightPathStatus)) { return false; } resultPath.Resize(straightPathCount); + for (auto& pos : resultPath) + Vector3::Transform(pos, invRotation, pos); } return true; @@ -130,13 +154,20 @@ bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const dtQueryFilter filter; Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 pointNavMesh; + Vector3::Transform(point, Properties.Rotation, pointNavMesh); + dtPolyRef startPoly = 0; - query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X); + query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &result.X); if (!startPoly) { return false; } + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + Vector3::Transform(result, invRotation, result); + return true; } @@ -153,8 +184,12 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos dtQueryFilter filter; Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); if (!startPoly) { return false; @@ -163,7 +198,7 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos dtRaycastHit hit; hit.path = nullptr; hit.maxPath = 0; - const bool result = dtStatusSucceed(query->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit)); + const bool result = dtStatusSucceed(query->raycast(startPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, 0, &hit)); if (hit.t >= MAX_float) { hitInfo.Position = endPosition; @@ -427,7 +462,7 @@ void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMe #include "Engine/Debug/DebugDraw.h" -void DrawPoly(NavMeshRuntime* navMesh, const dtMeshTile& tile, const dtPoly& poly) +void DrawPoly(NavMeshRuntime* navMesh, const Matrix& navMeshToWorld, const dtMeshTile& tile, const dtPoly& poly) { const unsigned int ip = (unsigned int)(&poly - tile.polys); const dtPolyDetail& pd = tile.detailMeshes[ip]; @@ -457,6 +492,10 @@ void DrawPoly(NavMeshRuntime* navMesh, const dtMeshTile& tile, const dtPoly& pol v[1].Y += drawOffsetY; v[2].Y += drawOffsetY; + Vector3::Transform(v[0], navMeshToWorld, v[0]); + Vector3::Transform(v[1], navMeshToWorld, v[1]); + Vector3::Transform(v[2], navMeshToWorld, v[2]); + DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], fillColor, 0, true); } @@ -477,6 +516,10 @@ void DrawPoly(NavMeshRuntime* navMesh, const dtMeshTile& tile, const dtPoly& pol v[1].Y += drawOffsetY; v[2].Y += drawOffsetY; + Vector3::Transform(v[0], navMeshToWorld, v[0]); + Vector3::Transform(v[1], navMeshToWorld, v[1]); + Vector3::Transform(v[2], navMeshToWorld, v[2]); + for (int m = 0, n = 2; m < 3; n = m++) { // Skip inner detail edges @@ -496,6 +539,9 @@ void NavMeshRuntime::DebugDraw() const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; if (tilesCount == 0) return; + Matrix worldToNavMesh, navMeshToWorld; + Matrix::RotationQuaternion(Properties.Rotation, worldToNavMesh); + Matrix::Invert(worldToNavMesh, navMeshToWorld); for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) { @@ -511,7 +557,7 @@ void NavMeshRuntime::DebugDraw() if (poly->getType() != DT_POLYTYPE_GROUND) continue; - DrawPoly(this, *tile, *poly); + DrawPoly(this, navMeshToWorld, *tile, *poly); } } } From 7835259a839008b1788354991ec6962cf384b09d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Jan 2021 13:11:32 +0100 Subject: [PATCH 040/222] Add `TestPath` utility to navigation system --- Source/Engine/Navigation/NavMeshRuntime.cpp | 46 +++++++++++++++++++++ Source/Engine/Navigation/NavMeshRuntime.h | 8 ++++ Source/Engine/Navigation/Navigation.cpp | 7 ++++ Source/Engine/Navigation/Navigation.h | 8 ++++ 4 files changed, 69 insertions(+) diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index 95b07924c..d81f46e06 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -141,6 +141,52 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo return true; } +bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPosition) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + dtPolyRef endPoly = 0; + query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); + if (!endPoly) + { + return false; + } + + dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; + int32 pathSize; + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + if (dtStatusFailed(findPathStatus)) + { + return false; + } + + if (dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) + { + return false; + } + + return true; +} + bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const { ScopeLock lock(Locker); diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h index 5d1b8ca63..7da4be0e2 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -105,6 +105,14 @@ public: /// True if found valid path between given two points (it may be partial), otherwise false if failed. bool FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const; + /// + /// Tests the path between the two positions (non-partial). + /// + /// The start position. + /// The end position. + /// True if found valid path between given two points, otherwise false if failed. + bool TestPath(const Vector3& startPosition, const Vector3& endPosition) const; + /// /// Projects the point to nav mesh surface (finds the nearest polygon). /// diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 7f8ca5e9a..29b081150 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -224,6 +224,13 @@ bool Navigation::FindPath(const Vector3& startPosition, const Vector3& endPositi return NavMeshes.First()->FindPath(startPosition, endPosition, resultPath); } +bool Navigation::TestPath(const Vector3& startPosition, const Vector3& endPosition) +{ + if (NavMeshes.IsEmpty()) + return false; + return NavMeshes.First()->TestPath(startPosition, endPosition); +} + bool Navigation::ProjectPoint(const Vector3& point, Vector3& result) { if (NavMeshes.IsEmpty()) diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index 2557e31e8..dcd6b9fbb 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -32,6 +32,14 @@ public: /// True if found valid path between given two points (it may be partial), otherwise false if failed. API_FUNCTION() static bool FindPath(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) Array& resultPath); + /// + /// Tests the path between the two positions (non-partial). + /// + /// The start position. + /// The end position. + /// True if found valid path between given two points, otherwise false if failed. + API_FUNCTION() static bool TestPath(const Vector3& startPosition, const Vector3& endPosition); + /// /// Projects the point to nav mesh surface (finds the nearest polygon). /// From 98d272a9030a5ccba358caaed0297d24032636b2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Jan 2021 14:18:22 +0100 Subject: [PATCH 041/222] Add support for SphereCollider and CapsuleCollider for navmesh --- Source/Engine/Navigation/NavMeshBuilder.cpp | 95 ++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index e9c8cb578..4d62b03b8 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -11,6 +11,8 @@ #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/VectorInt.h" #include "Engine/Physics/Colliders/BoxCollider.h" +#include "Engine/Physics/Colliders/SphereCollider.h" +#include "Engine/Physics/Colliders/CapsuleCollider.h" #include "Engine/Physics/Colliders/MeshCollider.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Terrain/TerrainPatch.h" @@ -119,6 +121,77 @@ struct NavigationSceneRasterization } } + static void TriangulateBox(Array& vb, Array& ib, const OrientedBoundingBox& box) + { + vb.Resize(8); + box.GetCorners(vb.Get()); + ib.Add(BoxTrianglesIndicesCache, 36); + } + + static void TriangulateBox(Array& vb, Array& ib, const BoundingBox& box) + { + vb.Resize(8); + box.GetCorners(vb.Get()); + ib.Add(BoxTrianglesIndicesCache, 36); + } + + static void TriangulateSphere(Array& vb, Array& ib, const BoundingSphere& sphere) + { + const int32 sphereResolution = 12; + const int32 verticalSegments = sphereResolution; + const int32 horizontalSegments = sphereResolution * 2; + + // Generate vertices for unit sphere + Vector3 vertices[(verticalSegments + 1) * (horizontalSegments + 1)]; + int32 vertexCount = 0; + for (int32 j = 0; j <= horizontalSegments; j++) + vertices[vertexCount++] = Vector3(0, -1, 0); + for (int32 i = 1; i < verticalSegments; i++) + { + const float latitude = (float)i * PI / verticalSegments - PI / 2.0f; + const float dy = Math::Sin(latitude); + const float dxz = Math::Cos(latitude); + auto& firstHorizontalVertex = vertices[vertexCount++]; + firstHorizontalVertex = Vector3(0, dy, dxz); + for (int32 j = 1; j < horizontalSegments; j++) + { + const float longitude = (float)j * 2.0f * PI / horizontalSegments; + const float dx = Math::Sin(longitude) * dxz; + const float dz = Math::Cos(longitude) * dxz; + vertices[vertexCount++] = Vector3(dx, dy, dz); + } + vertices[vertexCount++] = firstHorizontalVertex; + } + for (int32 j = 0; j <= horizontalSegments; j++) + vertices[vertexCount++] = Vector3(0, 1, 0); + + // Transform vertices into world space vertex buffer + vb.Resize(vertexCount); + for (int32 i = 0; i < vertexCount; i++) + vb[i] = sphere.Center + vertices[i] * sphere.Radius; + + // Generate index buffer + const int32 stride = horizontalSegments + 1; + int32 indexCount = 0; + ib.Resize(verticalSegments * (horizontalSegments + 1) * 6); + for (int32 i = 0; i < verticalSegments; i++) + { + const int32 nextI = i + 1; + for (int32 j = 0; j <= horizontalSegments; j++) + { + const int32 nextJ = (j + 1) % stride; + + ib[indexCount++] = i * stride + j; + ib[indexCount++] = nextI * stride + j; + ib[indexCount++] = i * stride + nextJ; + + ib[indexCount++] = i * stride + nextJ; + ib[indexCount++] = nextI * stride + j; + ib[indexCount++] = nextI * stride + nextJ; + } + } + } + static bool Walk(Actor* actor, NavigationSceneRasterization& e) { // Early out if object is not intersecting with the tile bounds or is not using navigation @@ -141,9 +214,25 @@ struct NavigationSceneRasterization PROFILE_CPU_NAMED("BoxCollider"); const OrientedBoundingBox box = boxCollider->GetOrientedBox(); - vb.Resize(8); - box.GetCorners(vb.Get()); - ib.Add(BoxTrianglesIndicesCache, 36); + TriangulateBox(vb, ib, box); + + e.RasterizeTriangles(); + } + else if (const auto* sphereCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("SphereCollider"); + + const BoundingSphere sphere = sphereCollider->GetSphere(); + TriangulateSphere(vb, ib, sphere); + + e.RasterizeTriangles(); + } + else if (const auto* capsuleCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("CapsuleCollider"); + + const BoundingBox box = capsuleCollider->GetBox(); + TriangulateBox(vb, ib, box); e.RasterizeTriangles(); } From f8624ae768abb49d0798d3d87ea3adc65b10f65b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Jan 2021 14:18:40 +0100 Subject: [PATCH 042/222] Fix crash during navmesh build when actor gets removed --- Source/Engine/Navigation/NavMeshBuilder.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 4d62b03b8..a12a72c69 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -628,7 +628,7 @@ class NavMeshTileBuildTask : public ThreadPoolTask public: Scene* Scene; - NavMesh* NavMesh; + ScriptingObjectReference NavMesh; NavMeshRuntime* Runtime; BoundingBox TileBoundsNavMesh; Matrix WorldToNavMesh; @@ -644,6 +644,11 @@ public: { PROFILE_CPU_NAMED("BuildNavMeshTile"); + const auto navMesh = NavMesh.Get(); + if (!navMesh) + { + return false; + } if (GenerateTile(NavMesh, Runtime, X, Y, TileBoundsNavMesh, WorldToNavMesh, TileSize, Config)) { LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y); From d7949b54056249ef04edff934995a01b43132e52 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Jan 2021 14:29:27 +0100 Subject: [PATCH 043/222] Remove deprecated comment --- Source/Engine/Navigation/NavMeshBuilder.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index a12a72c69..b0e41504b 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -281,9 +281,6 @@ struct NavigationSceneRasterization e.OffMeshLinks->Add(link); } - // TODO: nav mesh for capsule collider - // TODO: nav mesh for sphere collider - return true; } }; From 2c2c1af97f7e0538ea30415e345b50ea348ccddf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Jan 2021 11:59:04 +0100 Subject: [PATCH 044/222] Add NavAgentMask --- .../Dedicated/NavAgentMaskEditor.cs | 92 +++++++++++++++++++ Source/Engine/Navigation/Navigation.cpp | 36 ++++++++ .../Engine/Navigation/NavigationSettings.cs | 12 +++ Source/Engine/Navigation/NavigationTypes.h | 24 +++++ Source/Engine/Serialization/JsonSerializer.cs | 1 + 5 files changed, 165 insertions(+) create mode 100644 Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs diff --git a/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs b/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs new file mode 100644 index 000000000..ab0151bb9 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Linq; +using FlaxEditor.Content.Settings; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(NavAgentMask)), DefaultEditor] + internal class NavAgentMaskEditor : CustomEditor + { + private CheckBox[] _checkBoxes; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + var settings = GameSettings.Load(); + if (settings.NavMeshes == null || settings.NavMeshes.Length == 0) + { + layout.Label("Missing navmesh settings"); + return; + } + + _checkBoxes = new CheckBox[settings.NavMeshes.Length]; + for (int i = 0; i < settings.NavMeshes.Length; i++) + { + ref var navmesh = ref settings.NavMeshes[i]; + var property = layout.AddPropertyItem(navmesh.Name, navmesh.Agent.ToString()); + property.Labels.Last().TextColorHighlighted = navmesh.Color; + var checkbox = property.Checkbox().CheckBox; + UpdateCheckbox(checkbox, i); + checkbox.Tag = i; + checkbox.StateChanged += OnCheckboxStateChanged; + _checkBoxes[i] = checkbox; + } + } + + /// + protected override void Deinitialize() + { + _checkBoxes = null; + + base.Deinitialize(); + } + + /// + public override void Refresh() + { + if (_checkBoxes != null) + { + for (int i = 0; i < _checkBoxes.Length; i++) + { + UpdateCheckbox(_checkBoxes[i], i); + } + } + + base.Refresh(); + } + + private void OnCheckboxStateChanged(CheckBox checkBox) + { + var i = (int)checkBox.Tag; + var value = (NavAgentMask)Values[0]; + var mask = 1u << i; + value.Mask &= ~mask; + value.Mask |= checkBox.Checked ? mask : 0; + SetValue(value); + } + + private void UpdateCheckbox(CheckBox checkbox, int i) + { + for (var j = 0; j < Values.Count; j++) + { + var value = (((NavAgentMask)Values[j]).Mask & (1 << i)) != 0; + if (j == 0) + { + checkbox.Checked = value; + } + else if (checkbox.State != CheckBoxState.Intermediate) + { + if (checkbox.Checked != value) + checkbox.State = CheckBoxState.Intermediate; + } + } + } + } +} diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 29b081150..5e73f40c7 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -97,6 +97,42 @@ bool NavAgentProperties::operator==(const NavAgentProperties& other) const return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle); } +bool NavAgentMask::IsAgentSupported(int32 agentIndex) const +{ + return (Mask & (1 << agentIndex)) != 0; +} + +bool NavAgentMask::IsAgentSupported(const NavAgentProperties& agentProperties) const +{ + auto settings = NavigationSettings::Get(); + for (int32 agentIndex = 0; agentIndex < settings->NavMeshes.Count(); agentIndex++) + { + if (settings->NavMeshes[agentIndex].Agent == agentProperties) + { + return (Mask & (1 << agentIndex)) != 0; + } + } + return false; +} + +bool NavAgentMask::IsNavMeshSupported(const NavMeshProperties& navMeshProperties) const +{ + auto settings = NavigationSettings::Get(); + for (int32 agentIndex = 0; agentIndex < settings->NavMeshes.Count(); agentIndex++) + { + if (settings->NavMeshes[agentIndex] == navMeshProperties) + { + return (Mask & (1 << agentIndex)) != 0; + } + } + return false; +} + +bool NavAgentMask::operator==(const NavAgentMask& other) const +{ + return Mask == other.Mask; +} + bool NavMeshProperties::operator==(const NavMeshProperties& other) const { return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent; diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs index da300b3f1..4127ebe6b 100644 --- a/Source/Engine/Navigation/NavigationSettings.cs +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -72,3 +72,15 @@ namespace FlaxEditor.Content.Settings } } } + +namespace FlaxEngine +{ + partial struct NavAgentProperties + { + /// + public override string ToString() + { + return $"Radius: {Radius}, Height: {Height}, StepHeight: {StepHeight}, MaxSlopeAngle: {MaxSlopeAngle}"; + } + } +} diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index f00c1683e..d02b2acc4 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -91,6 +91,30 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties); } }; +/// +/// The navigation system agents selection mask (from navigation system settings). Uses 1 bit per agent type (up to 32 agents). +/// +API_STRUCT() struct FLAXENGINE_API NavAgentMask +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentMask); + + /// + /// The agents selection mask. + /// + API_FIELD() uint32 Mask = MAX_uint32; + + bool IsAgentSupported(int32 agentIndex) const; + bool IsAgentSupported(const NavAgentProperties& agentProperties) const; + bool IsNavMeshSupported(const NavMeshProperties& navMeshProperties) const; + + bool operator==(const NavAgentMask& other) const; + + bool operator!=(const NavAgentMask& other) const + { + return !operator==(other); + } +}; + /// /// The result information for navigation mesh queries. /// diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 231831442..535499442 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -120,6 +120,7 @@ namespace FlaxEngine.Json } } */ + /// /// Objects serialization tool (json format). /// From 65d3883f032223cd45e145ee36b4fa807090423d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Jan 2021 11:59:21 +0100 Subject: [PATCH 045/222] Add support for masking navmesh agents in NavMeshBoundsVolume --- .../Engine/Navigation/NavMeshBoundsVolume.cpp | 19 +++++++++++++++++++ .../Engine/Navigation/NavMeshBoundsVolume.h | 15 +++++++++++++++ Source/Engine/Navigation/NavMeshBuilder.cpp | 6 ++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp index a3f02249a..700442b2f 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp @@ -2,6 +2,7 @@ #include "NavMeshBoundsVolume.h" #include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/Managed/ManagedEditor.h" @@ -13,6 +14,24 @@ NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params) { } +void NavMeshBoundsVolume::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + BoxVolume::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(NavMeshBoundsVolume); + + SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + +void NavMeshBoundsVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + BoxVolume::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + void NavMeshBoundsVolume::OnEnable() { // Base diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.h b/Source/Engine/Navigation/NavMeshBoundsVolume.h index 2846f9e4f..897a83666 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.h +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Level/Actors/BoxVolume.h" +#include "NavigationTypes.h" /// /// A special type of volume that defines the areas of the scene in which navigation meshes are generated. @@ -10,6 +11,20 @@ API_CLASS() class FLAXENGINE_API NavMeshBoundsVolume : public BoxVolume { DECLARE_SCENE_OBJECT(NavMeshBoundsVolume); +public: + + /// + /// The agent types used by this navmesh bounds volume (from navigation settings). Can be used to generate navmesh for a certain set of agents. + /// + API_FIELD(Attributes="EditorDisplay(\"Box Volume\"), EditorOrder(10)") + NavAgentMask AgentsMask; + +public: + + // [BoxVolume] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + protected: // [BoxVolume] diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index b0e41504b..57cea5d14 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -296,7 +296,7 @@ void RasterizeGeometry(const BoundingBox& tileBoundsNavMesh, const Matrix& world // Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location // Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use -bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh) +bool GetNavMeshTileBounds(Scene* scene, NavMesh* navMesh, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh) { // Build initial tile bounds (with infinite extent) tileBoundsNavMesh.Minimum.X = (float)x * tileSize; @@ -312,6 +312,8 @@ bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, Boundi for (int32 i = 0; i < scene->NavigationVolumes.Count(); i++) { const auto volume = scene->NavigationVolumes[i]; + if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties)) + continue; const auto& volumeBounds = volume->GetBox(); BoundingBox volumeBoundsNavMesh; BoundingBox::Transform(volumeBounds, worldToNavMesh, volumeBoundsNavMesh); @@ -821,7 +823,7 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo for (int32 x = tilesMin.X; x < tilesMax.X; x++) { BoundingBox tileBoundsNavMesh; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBoundsNavMesh, worldToNavMesh)) + if (GetNavMeshTileBounds(scene, navMesh, x, y, tileSize, tileBoundsNavMesh, worldToNavMesh)) { BuildTileAsync(navMesh, x, y, config, tileBoundsNavMesh, worldToNavMesh, tileSize); } From 070faf12dae59858c6004ae9597c4683e1ea66e9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Jan 2021 12:48:22 +0100 Subject: [PATCH 046/222] Fixes for the rotated navmeshes building --- Source/Engine/Navigation/NavMeshBuilder.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 57cea5d14..5cd5a52bd 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -321,13 +321,13 @@ bool GetNavMeshTileBounds(Scene* scene, NavMesh* navMesh, int32 x, int32 y, floa { if (foundAnyVolume) { - rangeY.X = Math::Min(rangeY.X, volumeBounds.Minimum.Y); - rangeY.Y = Math::Max(rangeY.Y, volumeBounds.Maximum.Y); + rangeY.X = Math::Min(rangeY.X, volumeBoundsNavMesh.Minimum.Y); + rangeY.Y = Math::Max(rangeY.Y, volumeBoundsNavMesh.Maximum.Y); } else { - rangeY.X = volumeBounds.Minimum.Y; - rangeY.Y = volumeBounds.Maximum.Y; + rangeY.X = volumeBoundsNavMesh.Minimum.Y; + rangeY.Y = volumeBoundsNavMesh.Maximum.Y; } foundAnyVolume = true; } @@ -775,8 +775,8 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo BoundingBox dirtyBoundsNavMesh; BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh); BoundingBox dirtyBoundsAligned; - dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBounds.Minimum / tileSize) * tileSize; - dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBounds.Maximum / tileSize) * tileSize; + dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize; + dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize; // Calculate tiles range for the given navigation dirty bounds (aligned to tiles size) const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize); From 01a30faa624f332374d0db24f5edc3009bae32ad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Jan 2021 16:46:40 +0100 Subject: [PATCH 047/222] Add NavMesh Modifier Volume actor type --- .../Actors/NavModifierVolumeNode.cs | 20 +++++++ Source/Editor/SceneGraph/SceneGraphFactory.cs | 1 + .../Editor/Windows/SceneTreeWindow.Actors.cs | 3 +- Source/Editor/Windows/ToolboxWindow.cs | 3 +- .../Engine/Navigation/NavMeshBoundsVolume.h | 2 +- Source/Engine/Navigation/NavMeshBuilder.cpp | 41 ++++++++++++-- .../Engine/Navigation/NavModifierVolume.cpp | 54 +++++++++++++++++++ Source/Engine/Navigation/NavModifierVolume.h | 35 ++++++++++++ 8 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs create mode 100644 Source/Engine/Navigation/NavModifierVolume.cpp create mode 100644 Source/Engine/Navigation/NavModifierVolume.h diff --git a/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs new file mode 100644 index 000000000..38faba8ef --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Actor node for . + /// + /// + [HideInEditor] + public sealed class NavModifierVolumeNode : BoxVolumeNode + { + /// + public NavModifierVolumeNode(Actor actor) + : base(actor) + { + } + } +} diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 68cb627f0..0076742b3 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -62,6 +62,7 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(NavMeshBoundsVolume), typeof(NavMeshBoundsVolumeNode)); CustomNodesTypes.Add(typeof(BoxVolume), typeof(BoxVolumeNode)); CustomNodesTypes.Add(typeof(NavLink), typeof(NavLinkNode)); + CustomNodesTypes.Add(typeof(NavModifierVolume), typeof(NavModifierVolumeNode)); CustomNodesTypes.Add(typeof(ParticleEffect), typeof(ParticleEffectNode)); CustomNodesTypes.Add(typeof(SceneAnimationPlayer), typeof(SceneAnimationPlayerNode)); } diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs index d3fff78d8..6fafe444f 100644 --- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs +++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs @@ -97,7 +97,8 @@ namespace FlaxEditor.Windows new KeyValuePair("Audio Listener", typeof(AudioListener)), new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)), new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)), - new KeyValuePair("Nav Mesh Link", typeof(NavLink)), + new KeyValuePair("Nav Link", typeof(NavLink)), + new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)), } }, new ActorsGroup diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index d10a111b6..8c30621b0 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -165,7 +165,8 @@ namespace FlaxEditor.Windows groupOther.AddChild(CreateActorItem("Empty Actor", typeof(EmptyActor))); groupOther.AddChild(CreateActorItem("Scene Animation", typeof(SceneAnimationPlayer))); groupOther.AddChild(CreateActorItem("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume))); - groupOther.AddChild(CreateActorItem("Nav Mesh Link", typeof(NavLink))); + groupOther.AddChild(CreateActorItem("Nav Link", typeof(NavLink))); + groupOther.AddChild(CreateActorItem("Nav Modifier Volume", typeof(NavModifierVolume))); var groupGui = CreateGroupWithList(actorGroups, "GUI"); groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl))); diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.h b/Source/Engine/Navigation/NavMeshBoundsVolume.h index 897a83666..674ba4877 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.h +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.h @@ -6,7 +6,7 @@ #include "NavigationTypes.h" /// -/// A special type of volume that defines the areas of the scene in which navigation meshes are generated. +/// A special type of volume that defines the area of the scene in which navigation meshes are generated. /// API_CLASS() class FLAXENGINE_API NavMeshBoundsVolume : public BoxVolume { diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 5cd5a52bd..8d0bbafeb 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -7,6 +7,7 @@ #include "NavigationSettings.h" #include "NavMeshBoundsVolume.h" #include "NavLink.h" +#include "NavModifierVolume.h" #include "NavMeshRuntime.h" #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/VectorInt.h" @@ -55,8 +56,14 @@ struct OffMeshLink int32 Id; }; +struct Modifier +{ + BoundingBox Bounds; +}; + struct NavigationSceneRasterization { + NavMesh* NavMesh; BoundingBox TileBoundsNavMesh; Matrix WorldToNavMesh; rcContext* Context; @@ -66,18 +73,21 @@ struct NavigationSceneRasterization Array VertexBuffer; Array IndexBuffer; Array* OffMeshLinks; + Array* Modifiers; const bool IsWorldToNavMeshIdentity; - NavigationSceneRasterization(const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) + NavigationSceneRasterization(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) : TileBoundsNavMesh(tileBoundsNavMesh) , WorldToNavMesh(worldToNavMesh) , IsWorldToNavMeshIdentity(worldToNavMesh.IsIdentity()) { + NavMesh = navMesh; Context = context; Config = config; Heightfield = heightfield; WalkableThreshold = Math::Cos(config->walkableSlopeAngle * DegreesToRadians); OffMeshLinks = offMeshLinks; + Modifiers = modifiers; } void RasterizeTriangles() @@ -280,16 +290,30 @@ struct NavigationSceneRasterization e.OffMeshLinks->Add(link); } + else if (const auto* navModifierVolume = dynamic_cast(actor)) + { + if (navModifierVolume->AgentsMask.IsNavMeshSupported(e.NavMesh->Properties)) + { + PROFILE_CPU_NAMED("NavModifierVolume"); + + Modifier modifier; + OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox(); + bounds.Transform(e.WorldToNavMesh); + bounds.GetBoundingBox(modifier.Bounds); + + e.Modifiers->Add(modifier); + } + } return true; } }; -void RasterizeGeometry(const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) +void RasterizeGeometry(NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) { PROFILE_CPU_NAMED("RasterizeGeometry"); - NavigationSceneRasterization rasterization(tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks); + NavigationSceneRasterization rasterization(navMesh, tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks, modifiers); Function treeWalkFunction(NavigationSceneRasterization::Walk); SceneQuery::TreeExecute(treeWalkFunction, rasterization); } @@ -389,7 +413,8 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B } Array offMeshLinks; - RasterizeGeometry(tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks); + Array modifiers; + RasterizeGeometry(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield); @@ -415,6 +440,12 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B return true; } + // Mark areas + for (auto& modifier : modifiers) + { + rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, RC_NULL_AREA, *compactHeightfield); + } + if (!rcBuildDistanceField(&context, *compactHeightfield)) { LOG(Warning, "Could not generate navmesh: Could not build distance field."); @@ -468,7 +499,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B for (int i = 0; i < polyMesh->npolys; i++) { - polyMesh->flags[i] = polyMesh->areas[i] == RC_WALKABLE_AREA ? 1 : 0; + polyMesh->flags[i] = polyMesh->areas[i] != RC_NULL_AREA ? 1 : 0; } if (polyMesh->nverts == 0) diff --git a/Source/Engine/Navigation/NavModifierVolume.cpp b/Source/Engine/Navigation/NavModifierVolume.cpp new file mode 100644 index 000000000..65325195a --- /dev/null +++ b/Source/Engine/Navigation/NavModifierVolume.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NavModifierVolume.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#include "Editor/Managed/ManagedEditor.h" +#include "NavMeshBuilder.h" +#endif + +NavModifierVolume::NavModifierVolume(const SpawnParams& params) + : BoxVolume(params) +{ + _size = 100.0f; +} + +void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + BoxVolume::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(NavModifierVolume); + + SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + +void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + BoxVolume::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + +#if USE_EDITOR + +void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds) +{ + // Auto-rebuild modified navmesh area + if (IsDuringPlay() && IsActiveInHierarchy() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh()) + { + BoundingBox dirtyBounds; + BoundingBox::Merge(prevBounds, _box, dirtyBounds); + NavMeshBuilder::Build(GetScene(), dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs); + } +} + +Color NavModifierVolume::GetWiresColor() +{ + return Color::Red; +} + +#endif diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h new file mode 100644 index 000000000..3ca0d4b20 --- /dev/null +++ b/Source/Engine/Navigation/NavModifierVolume.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actors/BoxVolume.h" +#include "NavigationTypes.h" + +/// +/// A special type of volume that defines the area of the scene in which navigation is restricted (eg. higher traversal cost or dynamic obstacle block). +/// +API_CLASS() class FLAXENGINE_API NavModifierVolume : public BoxVolume +{ +DECLARE_SCENE_OBJECT(NavModifierVolume); +public: + + /// + /// The agent types used by this navmesh modifier volume (from navigation settings). Can be used to adjust navmesh for a certain set of agents. + /// + API_FIELD(Attributes="EditorDisplay(\"Box Volume\"), EditorOrder(10)") + NavAgentMask AgentsMask; + +public: + + // [BoxVolume] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + + // [BoxVolume] +#if USE_EDITOR + void OnBoundsChanged(const BoundingBox& prevBounds) override; + Color GetWiresColor() override; +#endif +}; From 2ae78b9afde4268874b9232a7ae9d8cf6f00738c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 10:51:55 +0100 Subject: [PATCH 048/222] Fix navmesh tiles build --- Source/Engine/Navigation/NavMeshBuilder.cpp | 29 +++++++++++++++------ Source/Engine/Navigation/NavMeshRuntime.cpp | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 8d0bbafeb..01441d831 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -590,18 +590,31 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B ScopeLock lock(runtime->Locker); - // Add tile data navMesh->IsDataDirty = true; - auto& tile = navMesh->Data.Tiles.AddOne(); - tile.PosX = x; - tile.PosY = y; - tile.Layer = layer; + NavMeshTileData* tile = nullptr; + for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++) + { + auto& e = navMesh->Data.Tiles[i]; + if (e.PosX == x && e.PosY == y && e.Layer == layer) + { + tile = &e; + break; + } + } + if (!tile) + { + // Add new tile + tile = &navMesh->Data.Tiles.AddOne(); + tile->PosX = x; + tile->PosY = y; + tile->Layer = layer; + } - // Copy data - tile.Data.Copy(navData, navDataSize); + // Copy data to the tile + tile->Data.Copy(navData, navDataSize); // Add tile to navmesh - runtime->AddTile(navMesh, tile); + runtime->AddTile(navMesh, *tile); } dtFree(navData); diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index d81f46e06..d576f3607 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -13,6 +13,7 @@ #define MAX_NODES 2048 #define USE_DATA_LINK 0 #define USE_NAV_MESH_ALLOC 1 +// TODO: try not using USE_NAV_MESH_ALLOC #define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f #define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f From c1216a4318260085bc2ed960a05fdf8be6d64e37 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 11:42:17 +0100 Subject: [PATCH 049/222] Add `NavAreaProperties` --- Source/Engine/Navigation/Navigation.cpp | 5 +++ Source/Engine/Navigation/NavigationTypes.h | 40 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 5e73f40c7..d5a516870 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -133,6 +133,11 @@ bool NavAgentMask::operator==(const NavAgentMask& other) const return Mask == other.Mask; } +bool NavAreaProperties::operator==(const NavAreaProperties& other) const +{ + return Name == other.Name && Id == other.Id && Math::NearEqual(Cost, other.Cost); +} + bool NavMeshProperties::operator==(const NavMeshProperties& other) const { return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent; diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index d02b2acc4..e604580f6 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -137,3 +137,43 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); /// API_FIELD() Vector3 Normal; }; + +/// +/// The navigation area properties container for navmesh building and navigation runtime. +/// +API_STRUCT() struct FLAXENGINE_API NavAreaProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAreaProperties); + + /// + /// The area type name. Identifies different types of the areas. + /// + API_FIELD(Attributes="EditorOrder(0)") + String Name; + + /// + /// The area type color (for debugging). Alpha channel is used to blend with navmesh color (alpha 0 to use navmesh color only). + /// + API_FIELD(Attributes="EditorOrder(10)") + Color Color = Color::Red; + + /// + /// The area id. It must be unique for the project. Valid range 0-63. Value 0 is reserved for Null areas (empty, non-navigable areas). + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 63)") + byte Id = 1; + + /// + /// The cost scale for the area traversal for agents. The higher the cost, the less likely agent wil choose the path that goes over it. For instance, areas that are harder to move like sand should have higher cost for proper path finding. + /// + API_FIELD(Attributes="EditorOrder(30), Limit(0, 100000, 0.1f)") + float Cost = 1; + + bool operator==(const NavAreaProperties& other) const; + + bool operator!=(const NavAreaProperties& other) const + { + return !operator==(other); + } +}; From ba050b9eaac2da84d4029e7f7970727c69f66f76 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 11:42:50 +0100 Subject: [PATCH 050/222] Add default spacing for CollectionEditor to 10 for cleaner UI when working with arrays and lists in Editor --- Source/Editor/CustomEditors/Editors/CollectionEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index ecbbef0f9..2e5aeade7 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -116,7 +116,7 @@ namespace FlaxEditor.CustomEditors.Editors // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; - float spacing = 0.0f; + float spacing = 10.0f; var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { From fdc8e371c4c6b783ce6b77afa3f62d7386f9cc80 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 13:18:26 +0100 Subject: [PATCH 051/222] Add support for using NavAreas for navigation --- Source/Engine/Navigation/NavMeshBuilder.cpp | 28 +++++++++++++-- Source/Engine/Navigation/NavMeshRuntime.cpp | 17 ++++++++- Source/Engine/Navigation/NavMeshRuntime.h | 6 ++++ .../Engine/Navigation/NavModifierVolume.cpp | 14 ++++++++ Source/Engine/Navigation/NavModifierVolume.h | 15 +++++++- Source/Engine/Navigation/Navigation.cpp | 36 +++++++++++++++++++ .../Engine/Navigation/NavigationSettings.cs | 30 ++++++++++++++++ Source/Engine/Navigation/NavigationSettings.h | 11 ++++-- Source/Engine/Navigation/NavigationTypes.h | 6 ++-- .../recastnavigation/DetourNavMeshQuery.h | 1 + 10 files changed, 155 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 01441d831..3026af66a 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -59,6 +59,7 @@ struct OffMeshLink struct Modifier { BoundingBox Bounds; + NavAreaProperties* NavArea; }; struct NavigationSceneRasterization @@ -300,6 +301,7 @@ struct NavigationSceneRasterization OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox(); bounds.Transform(e.WorldToNavMesh); bounds.GetBoundingBox(modifier.Bounds); + modifier.NavArea = navModifierVolume->GetNavArea(); e.Modifiers->Add(modifier); } @@ -443,7 +445,8 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B // Mark areas for (auto& modifier : modifiers) { - rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, RC_NULL_AREA, *compactHeightfield); + const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA; + rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, areaId, *compactHeightfield); } if (!rcBuildDistanceField(&context, *compactHeightfield)) @@ -564,7 +567,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B offMeshArea[i] = RC_WALKABLE_AREA; offMeshFlags[i] = 1; - // TODO: support navigation areas, navigation area type for off mesh links + // TODO: support navigation area type for off mesh links } params.offMeshConCount = linksCount; @@ -884,6 +887,27 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild { auto settings = NavigationSettings::Get(); + // Validate nav areas ids to be unique and in valid range + for (int32 i = 0; i < settings->NavAreas.Count(); i++) + { + auto& a = settings->NavAreas[i]; + if (a.Id > RC_WALKABLE_AREA) + { + LOG(Error, "Nav Area {0} uses invalid Id. Valid values are in range 0-63 only.", a.Name); + return; + } + + for (int32 j = i + 1; j < settings->NavAreas.Count(); j++) + { + auto& b = settings->NavAreas[j]; + if (a.Id == b.Id) + { + LOG(Error, "Nav Area {0} uses the same Id={1} as Nav Area {2}. Each area hast to have unique Id.", a.Name, a.Id, b.Name); + return; + } + } + } + // Sync navmeshes for (auto& navMeshProperties : settings->NavMeshes) { diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index d576f3607..abd706d25 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NavMeshRuntime.h" +#include "NavigationSettings.h" #include "NavMesh.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Matrix.h" @@ -18,6 +19,14 @@ #define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f #define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f +namespace +{ + FORCE_INLINE void InitFilter(dtQueryFilter& filter) + { + Platform::MemoryCopy(filter.m_areaCost, NavMeshRuntime::NavAreasCosts, sizeof(NavMeshRuntime::NavAreasCosts)); + } +} + NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties) : Properties(properties) { @@ -48,6 +57,7 @@ bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit } dtQueryFilter filter; + InitFilter(filter); Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); Vector3 startPositionNavMesh; @@ -85,6 +95,7 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo } dtQueryFilter filter; + InitFilter(filter); Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); Vector3 startPositionNavMesh, endPositionNavMesh; @@ -153,6 +164,7 @@ bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPo } dtQueryFilter filter; + InitFilter(filter); Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); Vector3 startPositionNavMesh, endPositionNavMesh; @@ -199,6 +211,7 @@ bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const } dtQueryFilter filter; + InitFilter(filter); Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); Vector3 pointNavMesh; @@ -229,6 +242,7 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos } dtQueryFilter filter; + InitFilter(filter); Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); Vector3 startPositionNavMesh, endPositionNavMesh; @@ -513,7 +527,8 @@ void DrawPoly(NavMeshRuntime* navMesh, const Matrix& navMeshToWorld, const dtMes { const unsigned int ip = (unsigned int)(&poly - tile.polys); const dtPolyDetail& pd = tile.detailMeshes[ip]; - const Color color = navMesh->Properties.Color; + const Color& areaColor = NavMeshRuntime::NavAreasColors[poly.getArea()]; + const Color color = Color::Lerp(navMesh->Properties.Color, areaColor, areaColor.A); const float drawOffsetY = 10.0f + ((float)GetHash(color) / (float)MAX_uint32) * 10.0f; // Apply some offset to prevent Z-fighting for different navmeshes const Color fillColor = color * 0.5f; const Color edgesColor = Color::FromHSV(color.ToHSV() + Vector3(20.0f, 0, -0.1f), color.A); diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h index 7da4be0e2..fd8dcf794 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -41,6 +41,12 @@ public: // Gets the navigation mesh runtime for a given navmesh properties. static NavMeshRuntime* Get(const NavMeshProperties& navMeshProperties, bool createIfMissing = false); + // The lookup table that maps areaId of the navmesh to the current properties (applied by the NavigationSettings). Cached to improve runtime performance. + static float NavAreasCosts[64]; +#if COMPILE_WITH_DEBUG_DRAW + static Color NavAreasColors[64]; +#endif + private: dtNavMesh* _navMesh; diff --git a/Source/Engine/Navigation/NavModifierVolume.cpp b/Source/Engine/Navigation/NavModifierVolume.cpp index 65325195a..533fae199 100644 --- a/Source/Engine/Navigation/NavModifierVolume.cpp +++ b/Source/Engine/Navigation/NavModifierVolume.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NavModifierVolume.h" +#include "NavigationSettings.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" #if USE_EDITOR @@ -15,6 +16,17 @@ NavModifierVolume::NavModifierVolume(const SpawnParams& params) _size = 100.0f; } +NavAreaProperties* NavModifierVolume::GetNavArea() const +{ + auto settings = NavigationSettings::Get(); + for (auto& navArea : settings->NavAreas) + { + if (navArea.Name == AreaName) + return &navArea; + } + return nullptr; +} + void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj) { // Base @@ -23,6 +35,7 @@ void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_GET_OTHER_OBJ(NavModifierVolume); SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); + SERIALIZE(AreaName); } void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -31,6 +44,7 @@ void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifie BoxVolume::Deserialize(stream, modifier); DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); + DESERIALIZE(AreaName); } #if USE_EDITOR diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h index 3ca0d4b20..7ee70c116 100644 --- a/Source/Engine/Navigation/NavModifierVolume.h +++ b/Source/Engine/Navigation/NavModifierVolume.h @@ -16,9 +16,22 @@ public: /// /// The agent types used by this navmesh modifier volume (from navigation settings). Can be used to adjust navmesh for a certain set of agents. /// - API_FIELD(Attributes="EditorDisplay(\"Box Volume\"), EditorOrder(10)") + API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(10)") NavAgentMask AgentsMask; + /// + /// The name of the nav area to apply within the modifiers volume. Nav area properties are picked from the Navigation Settings asset. + /// + API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(20)") + String AreaName; + +public: + + /// + /// Gets the properties of the nav area used by this volume. Null if missing or invalid area name. + /// + NavAreaProperties* GetNavArea() const; + public: // [BoxVolume] diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index d5a516870..9ff3498d7 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -92,6 +92,12 @@ NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties, return result; } +static_assert(ARRAY_COUNT(NavMeshRuntime::NavAreasCosts) == DT_MAX_AREAS, "Invalid nav areas amount limit."); +float NavMeshRuntime::NavAreasCosts[64]; +#if COMPILE_WITH_DEBUG_DRAW +Color NavMeshRuntime::NavAreasColors[64]; +#endif + bool NavAgentProperties::operator==(const NavAgentProperties& other) const { return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle); @@ -176,13 +182,42 @@ void* rcAllocDefault(size_t size, rcAllocHint) NavigationSettings::NavigationSettings() { + // Init navmeshes NavMeshes.Resize(1); auto& navMesh = NavMeshes[0]; navMesh.Name = TEXT("Default"); + + // Init nav areas + NavAreas.Resize(2); + auto& areaNull = NavAreas[0]; + areaNull.Name = TEXT("Null"); + areaNull.Color = Color::Transparent; + areaNull.Id = 0; + areaNull.Cost = MAX_float; + auto& areaWalkable = NavAreas[1]; + areaWalkable.Name = TEXT("Walkable"); + areaWalkable.Color = Color::Transparent; + areaWalkable.Id = 63; + areaWalkable.Cost = 1; } IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation); +void NavigationSettings::Apply() +{ + // Cache areas properties + for (auto& area : NavAreas) + { + if (area.Id < DT_MAX_AREAS) + { + NavMeshRuntime::NavAreasCosts[area.Id] = area.Cost; +#if USE_EDITOR + NavMeshRuntime::NavAreasColors[area.Id] = area.Color; +#endif + } + } +} + void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { DESERIALIZE(AutoAddMissingNavMeshes); @@ -219,6 +254,7 @@ void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifi navMesh.Agent.StepHeight = WalkableMaxClimb; navMesh.Agent.MaxSlopeAngle = WalkableMaxSlopeAngle; } + DESERIALIZE(NavAreas); } bool NavigationService::Init() diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs index 4127ebe6b..8a97e4adc 100644 --- a/Source/Engine/Navigation/NavigationSettings.cs +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -7,6 +7,36 @@ namespace FlaxEditor.Content.Settings { partial class NavigationSettings { + /// + /// Initializes a new instance of the class. + /// + public NavigationSettings() + { + // Init navmeshes + NavMeshes = new NavMeshProperties[1]; + ref var navMesh = ref NavMeshes[0]; + navMesh.Name = "Default"; + navMesh.Color = Color.Green; + navMesh.Rotation = Quaternion.Identity; + navMesh.Agent.Radius = 34.0f; + navMesh.Agent.Height = 144.0f; + navMesh.Agent.StepHeight = 35.0f; + navMesh.Agent.MaxSlopeAngle = 60.0f; + + // Init nav areas + NavAreas = new NavAreaProperties[2]; + ref var areaNull = ref NavAreas[0]; + areaNull.Name = "Null"; + areaNull.Color = Color.Transparent; + areaNull.Id = 0; + areaNull.Cost = float.MaxValue; + ref var areaWalkable = ref NavAreas[1]; + areaWalkable.Name = "Walkable"; + areaWalkable.Color = Color.Transparent; + areaWalkable.Id = 63; + areaWalkable.Cost = 1; + } + // [Deprecated on 12.01.2021, expires on 12.01.2022] private void UpgradeToNavMeshes() { diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index e225a2f07..0b35d5153 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -9,7 +9,7 @@ /// /// The navigation system settings container. /// -API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API NavigationSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); public: @@ -87,9 +87,15 @@ public: /// /// The configuration for navmeshes. /// - API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\")") + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\", EditorDisplayAttribute.InlineStyle)") Array NavMeshes; + /// + /// The configuration for nav areas. + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Areas\", EditorDisplayAttribute.InlineStyle)") + Array NavAreas; + public: NavigationSettings(); @@ -100,5 +106,6 @@ public: static NavigationSettings* Get(); // [SettingsBase] + void Apply() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h index e604580f6..11ebaeb1a 100644 --- a/Source/Engine/Navigation/NavigationTypes.h +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -156,18 +156,18 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavAreaProperties); /// The area type color (for debugging). Alpha channel is used to blend with navmesh color (alpha 0 to use navmesh color only). /// API_FIELD(Attributes="EditorOrder(10)") - Color Color = Color::Red; + Color Color = Color::Transparent; /// /// The area id. It must be unique for the project. Valid range 0-63. Value 0 is reserved for Null areas (empty, non-navigable areas). /// API_FIELD(Attributes="EditorOrder(20), Limit(0, 63)") - byte Id = 1; + byte Id; /// /// The cost scale for the area traversal for agents. The higher the cost, the less likely agent wil choose the path that goes over it. For instance, areas that are harder to move like sand should have higher cost for proper path finding. /// - API_FIELD(Attributes="EditorOrder(30), Limit(0, 100000, 0.1f)") + API_FIELD(Attributes="EditorOrder(30), Limit(0, float.MaxValue, 0.1f)") float Cost = 1; bool operator==(const NavAreaProperties& other) const; diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h index 1c23e4857..d9f940e2d 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h @@ -34,6 +34,7 @@ /// @ingroup detour class dtQueryFilter { +public: float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.) unsigned short m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.) unsigned short m_excludeFlags; ///< Flags for polygons that should not be visted. (Used by default implementation.) From 2bfcc15f66c776b38c78fbcd44d330471c78a955 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 15:00:35 +0100 Subject: [PATCH 052/222] Add invoking `BoxVolume::OnBoundsChanged` after transform changed --- Source/Engine/Level/Actors/BoxVolume.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Level/Actors/BoxVolume.cpp b/Source/Engine/Level/Actors/BoxVolume.cpp index 6235244bc..91912fa69 100644 --- a/Source/Engine/Level/Actors/BoxVolume.cpp +++ b/Source/Engine/Level/Actors/BoxVolume.cpp @@ -132,8 +132,10 @@ void BoxVolume::OnTransformChanged() // Base Actor::OnTransformChanged(); + const auto prevBounds = _box; OrientedBoundingBox::CreateCentered(Vector3::Zero, _size, _bounds); _bounds.Transform(_transform.GetWorld()); _bounds.GetBoundingBox(_box); BoundingSphere::FromBox(_box, _sphere); + OnBoundsChanged(prevBounds); } From f80c33f381306843ac6028848b987104aeffaf03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 15:07:01 +0100 Subject: [PATCH 053/222] Add Actor.HasStaticFlag --- Source/Engine/Level/Actor.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 88da10f0e..53f8fe163 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -367,6 +367,14 @@ public: /// The value to set. API_PROPERTY() virtual void SetStaticFlags(StaticFlags value); + /// + /// Returns true if object has given flag(s) set. + /// + FORCE_INLINE bool HasStaticFlag(StaticFlags flag) const + { + return (_staticFlags & flag) == (int)flag; + } + /// /// Adds the actor static flags. /// From e0f1f18998c5608bbe3e77206eaa9ab1696d6346 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 15:07:39 +0100 Subject: [PATCH 054/222] Move Actor static flags helper methods to be manually implemented (less bindings) --- Source/Engine/Level/Actor.cs | 60 ++++++++++++++++++++++++++++++++++++ Source/Engine/Level/Actor.h | 10 +++--- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 674842a65..260fef44e 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -6,6 +6,66 @@ namespace FlaxEngine { partial class Actor : ITransformable, ISceneObject { + /// + /// Returns true if object is fully static on the scene, otherwise false. + /// + [Unmanaged] + [Tooltip("Returns true if object is fully static on the scene, otherwise false.")] + public bool IsStatic => StaticFlags == FlaxEngine.StaticFlags.FullyStatic; + + /// + /// Returns true if object has static transform. + /// + [Unmanaged] + [Tooltip("Returns true if object has static transform.")] + public bool IsTransformStatic => (StaticFlags & StaticFlags.Transform) == StaticFlags.Transform; + + /// + /// Adds the actor static flags. + /// + /// The flags to add. + [Unmanaged] + [Tooltip("Adds the actor static flags.")] + public void AddStaticFlags(StaticFlags flags) + { + StaticFlags |= flags; + } + + /// + /// Removes the actor static flags. + /// + /// The flags to remove. + [Unmanaged] + [Tooltip("Removes the actor static flags.")] + public void RemoveStaticFlags(StaticFlags flags) + { + StaticFlags &= ~flags; + } + + /// + /// Sets a single static flag to the desire value. + /// + /// The flag to change. + /// The target value of the flag. + [Unmanaged] + [Tooltip("Sets a single static flag to the desire value.")] + public void SetStaticFlag(StaticFlags flag, bool value) + { + StaticFlags = StaticFlags & ~flag | (value ? flag : StaticFlags.None); + } + + /// + /// Returns true if object has given flag(s) set. + /// + /// The flag(s) to check. + /// True if has flag(s) set, otherwise false. + [Unmanaged] + [Tooltip("Returns true if object has given flag(s) set..")] + public bool HasStaticFlag(StaticFlags flag) + { + return (StaticFlags & flag) == flag; + } + /// /// The rotation as Euler angles in degrees. /// diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 53f8fe163..da08bc498 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -339,7 +339,7 @@ public: /// /// Returns true if object is fully static on the scene, otherwise false. /// - API_PROPERTY() FORCE_INLINE bool IsStatic() const + FORCE_INLINE bool IsStatic() const { return _staticFlags == StaticFlags::FullyStatic; } @@ -347,7 +347,7 @@ public: /// /// Returns true if object has static transform. /// - API_PROPERTY() FORCE_INLINE bool IsTransformStatic() const + FORCE_INLINE bool IsTransformStatic() const { return (_staticFlags & StaticFlags::Transform) != 0; } @@ -379,7 +379,7 @@ public: /// Adds the actor static flags. /// /// The flags to add. - API_FUNCTION() FORCE_INLINE void AddStaticFlags(StaticFlags flags) + FORCE_INLINE void AddStaticFlags(StaticFlags flags) { SetStaticFlags(_staticFlags | flags); } @@ -388,7 +388,7 @@ public: /// Removes the actor static flags. /// /// The flags to remove. - API_FUNCTION() FORCE_INLINE void RemoveStaticFlags(StaticFlags flags) + FORCE_INLINE void RemoveStaticFlags(StaticFlags flags) { SetStaticFlags(static_cast(_staticFlags & ~flags)); } @@ -398,7 +398,7 @@ public: /// /// The flag to change. /// The target value of the flag. - API_FUNCTION() FORCE_INLINE void SetStaticFlag(StaticFlags flag, bool value) + FORCE_INLINE void SetStaticFlag(StaticFlags flag, bool value) { SetStaticFlags(static_cast(_staticFlags & ~flag) | (value ? flag : StaticFlags::None)); } From d19c31555b457f8e9beaee82981c51f66989390a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 15:08:00 +0100 Subject: [PATCH 055/222] Add support for dynamic updating navmesh when moving NavModifierVolume --- .../Engine/Navigation/NavModifierVolume.cpp | 26 +++++++++++++++---- Source/Engine/Navigation/NavModifierVolume.h | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Navigation/NavModifierVolume.cpp b/Source/Engine/Navigation/NavModifierVolume.cpp index 533fae199..e72393a03 100644 --- a/Source/Engine/Navigation/NavModifierVolume.cpp +++ b/Source/Engine/Navigation/NavModifierVolume.cpp @@ -2,12 +2,12 @@ #include "NavModifierVolume.h" #include "NavigationSettings.h" +#include "NavMeshBuilder.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/Managed/ManagedEditor.h" -#include "NavMeshBuilder.h" #endif NavModifierVolume::NavModifierVolume(const SpawnParams& params) @@ -47,19 +47,35 @@ void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifie DESERIALIZE(AreaName); } -#if USE_EDITOR - void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds) { +#if COMPILE_WITH_NAV_MESH_BUILDER // Auto-rebuild modified navmesh area - if (IsDuringPlay() && IsActiveInHierarchy() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh()) + if ( + IsDuringPlay() && IsActiveInHierarchy() && HasStaticFlag(StaticFlags::Navigation) && + ( + // Build at runtime for dynamic modifiers + !HasStaticFlag(StaticFlags::Transform) +#if USE_EDITOR + // Build in editor when using auto-rebuild option + || (!Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh()) +#endif + )) { BoundingBox dirtyBounds; BoundingBox::Merge(prevBounds, _box, dirtyBounds); - NavMeshBuilder::Build(GetScene(), dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs); +#if USE_EDITOR + const float timeoutMs = ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs; +#else + const float timeoutMs = 0.0f; +#endif + NavMeshBuilder::Build(GetScene(), dirtyBounds, timeoutMs); } +#endif } +#if USE_EDITOR + Color NavModifierVolume::GetWiresColor() { return Color::Red; diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h index 7ee70c116..9b6a3e764 100644 --- a/Source/Engine/Navigation/NavModifierVolume.h +++ b/Source/Engine/Navigation/NavModifierVolume.h @@ -41,8 +41,8 @@ public: protected: // [BoxVolume] -#if USE_EDITOR void OnBoundsChanged(const BoundingBox& prevBounds) override; +#if USE_EDITOR Color GetWiresColor() override; #endif }; From ef240ce9860b9c8fcab5f5927450effda4bf7492 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 18 Jan 2021 15:53:15 +0100 Subject: [PATCH 056/222] Add culling for decals (via DrawMinScreenSize property) --- Source/Engine/Level/Actors/Decal.cpp | 10 ++++++++++ Source/Engine/Level/Actors/Decal.h | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index 92652ea7d..c7e8475b3 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -7,6 +7,7 @@ #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" Decal::Decal(const SpawnParams& params) : Actor(params) @@ -66,6 +67,13 @@ void Decal::Draw(RenderContext& renderContext) Material->IsLoaded() && Material->IsDecal()) { + const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); + const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(_sphere.Center, _sphere.Radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt; + + // Check if decal is being culled + if (Math::Square(DrawMinScreenSize * 0.5f) > screenRadiusSquared) + return; + renderContext.List->Decals.Add(this); } } @@ -80,6 +88,7 @@ void Decal::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE(Material); SERIALIZE_MEMBER(Size, _size); SERIALIZE(SortOrder); + SERIALIZE(DrawMinScreenSize); } void Decal::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -90,6 +99,7 @@ void Decal::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE(Material); DESERIALIZE_MEMBER(Size, _size); DESERIALIZE(SortOrder); + DESERIALIZE(DrawMinScreenSize); } bool Decal::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/Decal.h b/Source/Engine/Level/Actors/Decal.h index 41a5addea..78a92bfd6 100644 --- a/Source/Engine/Level/Actors/Decal.h +++ b/Source/Engine/Level/Actors/Decal.h @@ -30,9 +30,15 @@ public: /// /// The decal rendering order. The higher values are render later (on top). /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(0), EditorDisplay(\"Decal\")") + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Decal\")") int32 SortOrder = 0; + /// + /// The minimum screen size for the decal drawing. If the decal size on the screen is smaller than this value then decal will be culled. Set it to higher value to make culling more aggressive. + /// + API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Decal\")") + float DrawMinScreenSize = 0.02f; + /// /// Gets the decal bounds size (in local space). /// From f8bf87e0b100778deb437b037b3d953e636c77dc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Jan 2021 09:14:30 +0100 Subject: [PATCH 057/222] Fixes --- Source/Engine/Navigation/NavMeshRuntime.cpp | 8 +++++--- .../Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index abd706d25..3d112a39e 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -644,7 +644,10 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData if (tileRef) { // Remove any existing tile at that location - _navMesh->removeTile(tileRef, nullptr, nullptr); + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } // Reuse tile data container for (int32 i = 0; i < _tiles.Count(); i++) @@ -657,12 +660,11 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData } } } - else + if (!tile) { // Add tile tile = &_tiles.AddOne(); } - ASSERT(tile); // Copy tile properties tile->NavMesh = navMesh; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index a25e00fbb..8b679fb7a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -402,7 +402,7 @@ namespace Flax.Build.Bindings return false; // Value constructors (eg. Vector2(1, 2)) - // TODO: support value constructores for default value attribute + // TODO: support value constructors for default value attribute if (value.Contains('(') && value.Contains(')')) return false; @@ -1096,7 +1096,7 @@ namespace Flax.Build.Bindings Utilities.WriteFileIfChanged(bindings.GeneratedCSharpFilePath, contents.ToString()); // Ensure that generated file is included into build - if (useBindings && moduleBuildInfo != null && !moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) + if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) { moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath); } From 9ae79b830739f234f3f968d858c88524773eb8a2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 20 Jan 2021 11:13:09 +0100 Subject: [PATCH 058/222] Add IFunctionDependantNode for Visject nodes --- Source/Editor/Surface/Archetypes/Function.cs | 52 ++++++++++++++++++- .../Editor/Surface/IFunctionDependantNode.cs | 31 +++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 Source/Editor/Surface/IFunctionDependantNode.cs diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 7c4963072..b7584a2b8 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1387,6 +1387,9 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + /// The cached signature. This might not be loaded if called from other not initialization (eg. node initialized before this function node). Then use GetSignature method. + /// internal Signature _signature; /// @@ -1395,6 +1398,13 @@ namespace FlaxEditor.Surface.Archetypes { } + public void GetSignature(out Signature signature) + { + if (_signature.Node == null) + LoadSignature(); + signature = _signature; + } + private void SaveSignature() { using (var stream = new MemoryStream()) @@ -1568,6 +1578,13 @@ namespace FlaxEditor.Surface.Archetypes // Update node interface UpdateUI(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionEdited(this); + } }; editor.Show(this, Vector2.Zero); } @@ -1622,6 +1639,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnLoaded(); LoadSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionCreated(this); + } } /// @@ -1639,6 +1663,26 @@ namespace FlaxEditor.Surface.Archetypes // Start editing OnEditSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionCreated(this); + } + } + + /// + public override void OnDeleted() + { + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionDeleted(this); + } + + base.OnDeleted(); } /// @@ -1647,6 +1691,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnValuesChanged(); LoadSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionEdited(this); + } } /// @@ -1752,7 +1803,6 @@ namespace FlaxEditor.Surface.Archetypes { private bool _isTypesChangedEventRegistered; - /// public SetFieldNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) { diff --git a/Source/Editor/Surface/IFunctionDependantNode.cs b/Source/Editor/Surface/IFunctionDependantNode.cs new file mode 100644 index 000000000..e66c10e92 --- /dev/null +++ b/Source/Editor/Surface/IFunctionDependantNode.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.Surface +{ + /// + /// Interface for surface nodes that depend on surface function nodes collection. + /// + [HideInEditor] + public interface IFunctionsDependantNode + { + /// + /// On function created. + /// + /// The function node. + void OnFunctionCreated(SurfaceNode node); + + /// + /// On function signature changed (new name or parameters change). + /// + /// The function node. + void OnFunctionEdited(SurfaceNode node); + + /// + /// On function removed. + /// + /// The function node. + void OnFunctionDeleted(SurfaceNode node); + } +} From 57d7508e25536807b944267eeb518cab00999134 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Jan 2021 14:26:22 +0100 Subject: [PATCH 059/222] Fix some editor UI controls visible in Visual Scripting --- Source/Editor/GUI/CurveEditor.cs | 1 + Source/Editor/GUI/Tabs/Tabs.cs | 1 + Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs | 1 + Source/Editor/GUI/Timeline/Timeline.cs | 1 + Source/Editor/Progress/ProgressHandler.cs | 1 + 5 files changed, 5 insertions(+) diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 8840a7f06..3e37b591c 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -16,6 +16,7 @@ namespace FlaxEditor.GUI /// /// The base class for editors. Allows to use generic curve editor without type information at compile-time. /// + [HideInEditor] public abstract class CurveEditorBase : ContainerControl { /// diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 377c6827d..5b1317929 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -11,6 +11,7 @@ namespace FlaxEditor.GUI.Tabs /// Represents control which contains collection of . /// /// + [HideInEditor] public class Tabs : ContainerControl { /// diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index abd3e78a8..2e26b9045 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.GUI /// The generic keyframes animation editor control. /// /// + [HideInEditor] public class KeyframesEditor : ContainerControl { /// diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 149f4a3c8..e0e4083c1 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -20,6 +20,7 @@ namespace FlaxEditor.GUI.Timeline /// The timeline control that contains tracks section and headers. Can be used to create time-based media interface for camera tracks editing, audio mixing and events tracking. /// /// + [HideInEditor] public class Timeline : ContainerControl { private static readonly KeyValuePair[] FPSValues = diff --git a/Source/Editor/Progress/ProgressHandler.cs b/Source/Editor/Progress/ProgressHandler.cs index da2782d16..f008a5a03 100644 --- a/Source/Editor/Progress/ProgressHandler.cs +++ b/Source/Editor/Progress/ProgressHandler.cs @@ -8,6 +8,7 @@ namespace FlaxEditor.Progress /// /// Base class for all editor handlers used to report actions progress to the user. /// + [HideInEditor] public abstract class ProgressHandler { /// From d93a2803cc9202caecf286d22f859675f94f0a26 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Jan 2021 14:26:52 +0100 Subject: [PATCH 060/222] Fix MAssembly classes dictionary cache allocation to be during assembly load --- Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp index 50a8b689e..6c8e8f562 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp @@ -29,7 +29,6 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const MAssembl , _isDependency(false) , _isFileLocked(false) , _hasCachedClasses(false) - , _classes(options.DictionaryInitialSize) , _reloadCount(0) , _name(name) , _options(options) @@ -342,6 +341,8 @@ void MAssembly::OnLoading() Loading(this); _isLoading = true; + if (_classes.Capacity() == 0) + _classes.EnsureCapacity(_options.DictionaryInitialSize); // Pick a domain if (_domain == nullptr) From 178f0b9e3a923691176cb18e1b558a8c46b38d7d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Jan 2021 14:27:26 +0100 Subject: [PATCH 061/222] Fix Dictionary to call ctor/dtor for Buckets when needed --- Source/Engine/Core/Collections/Dictionary.h | 115 ++++++-------------- 1 file changed, 36 insertions(+), 79 deletions(-) diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index e61cfc4d9..99bad0bef 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -13,7 +13,6 @@ template API_CLASS(InBuild) class Dictionary { friend Dictionary; - public: /// @@ -23,94 +22,73 @@ public: { friend Dictionary; - public: - enum State : byte { - Empty, - Deleted, - Occupied, + Empty = 0, + Deleted = 1, + Occupied = 2, }; public: - /// The key. KeyType Key; /// The value. ValueType Value; private: - State _state; - public: - - Bucket() - : _state(Empty) - { - } - - ~Bucket() - { - } - - public: - void Free() { + if (_state == Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } _state = Empty; } void Delete() { _state = Deleted; + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); } template void Occupy(const KeyComparableType& key) { - Key = key; + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItem(&Value); _state = Occupied; } template void Occupy(const KeyComparableType& key, const ValueType& value) { - Key = key; - Value = value; + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItems(&Value, &value, 1); _state = Occupied; } template void Occupy(const KeyComparableType& key, ValueType&& value) { - Key = key; - Value = MoveTemp(value); + Memory::ConstructItems(&Key, &key, 1); + Memory::MoveItems(&Value, &value, 1); _state = Occupied; } - public: - FORCE_INLINE bool IsEmpty() const { return _state == Empty; } - FORCE_INLINE bool IsNotEmpty() const - { - return _state != Empty; - } - FORCE_INLINE bool IsDeleted() const { return _state == Deleted; } - FORCE_INLINE bool IsNotDeleted() const - { - return _state != Deleted; - } - FORCE_INLINE bool IsOccupied() const { return _state == Occupied; @@ -182,7 +160,6 @@ public: /// /// Gets the amount of the elements in the collection. /// - /// The amount of elements in the collection. FORCE_INLINE int32 Count() const { return _elementsCount; @@ -191,7 +168,6 @@ public: /// /// Gets the amount of the elements that can be contained by the collection. /// - /// The capacity of the collection. FORCE_INLINE int32 Capacity() const { return _tableSize; @@ -200,7 +176,6 @@ public: /// /// Returns true if collection is empty. /// - /// True if is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _elementsCount == 0; @@ -209,7 +184,6 @@ public: /// /// Returns true if collection has one or more elements. /// - /// True if isn't empty, otherwise false. FORCE_INLINE bool HasItems() const { return _elementsCount != 0; @@ -218,12 +192,11 @@ public: public: /// - /// The dictionary collection iterator. + /// The Dictionary collection iterator. /// struct Iterator { friend Dictionary; - private: Dictionary& _collection; @@ -254,7 +227,6 @@ public: /// /// Checks if iterator is in the end of the collection. /// - /// True if is in the end, otherwise false. FORCE_INLINE bool IsEnd() const { return _index == _collection._tableSize; @@ -263,7 +235,6 @@ public: /// /// Checks if iterator is not in the end of the collection. /// - /// True if is not in the end, otherwise false. FORCE_INLINE bool IsNotEnd() const { return _index != _collection._tableSize; @@ -286,7 +257,7 @@ public: return _index >= 0 && _index < _collection._tableSize; } - FORCE_INLINE bool operator !() const + FORCE_INLINE bool operator!() const { return !(bool)*this; } @@ -316,7 +287,7 @@ public: return *this; } - Iterator operator++(int) + Iterator operator++(int) const { Iterator i = *this; ++i; @@ -335,7 +306,7 @@ public: return *this; } - Iterator operator--(int) + Iterator operator--(int) const { Iterator i = *this; --i; @@ -428,7 +399,6 @@ public: if (pos.ObjectIndex == -1) return false; - result = _table[pos.ObjectIndex].Value; return true; } @@ -449,7 +419,6 @@ public: if (pos.ObjectIndex == -1) return nullptr; - return &_table[pos.ObjectIndex].Value; } @@ -463,11 +432,8 @@ public: if (_table) { // Free all buckets - // Note: this will not clear allocated objects space! for (int32 i = 0; i < _tableSize; i++) - { _table[i].Free(); - } _elementsCount = _deletedCount = 0; } } @@ -492,16 +458,14 @@ public: /// Enables preserving collection contents during resizing. void SetCapacity(int32 capacity, bool preserveContents = true) { - // Validate input - ASSERT(capacity >= 0); - // Check if capacity won't change if (capacity == Capacity()) return; // Cache previous state + ASSERT(capacity >= 0); Bucket* oldTable = _table; - int32 oldTableSize = _tableSize; + const int32 oldTableSize = _tableSize; // Clear elements counters const int32 oldElementsCount = _elementsCount; @@ -523,10 +487,10 @@ public: } // Allocate new table - _table = NewArray(capacity); + _table = (Bucket*)Allocator::Allocate(capacity * sizeof(Bucket)); _tableSize = capacity; - - // Check if preserve content + for (int32 i = 0; i < capacity; i++) + _table[i]._state = Bucket::Empty; if (oldElementsCount != 0 && preserveContents) { // Try to preserve all pairs in the collection @@ -548,7 +512,9 @@ public: // Delete old table if (oldTable) { - DeleteArray(oldTable, oldTableSize); + for (int32 i = 0; i < oldTableSize; i++) + oldTable[i].Free(); + Allocator::Free(oldTable); } } @@ -571,11 +537,10 @@ public: if (Capacity() >= minCapacity) return; - // TODO: improve this, better collection growing and shrinking on remove - int32 num = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; - if (num < minCapacity) - num = minCapacity; - SetCapacity(num); + int32 capacity = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; + if (capacity < minCapacity) + capacity = minCapacity; + SetCapacity(capacity); } /// @@ -676,7 +641,6 @@ public: _deletedCount++; return true; } - return false; } @@ -733,9 +697,7 @@ public: for (int32 i = 0; i < _tableSize; i++) { if (_table[i].IsOccupied() && _table[i].Key == key) - { return Iterator(*this, i); - } } } return End(); @@ -754,7 +716,6 @@ public: FindPositionResult pos; FindPosition(key, pos); - return pos.ObjectIndex != -1; } @@ -809,12 +770,9 @@ public: void Clone(const Dictionary& other) { Clear(); - SetCapacity(other.Capacity(), false); - for (auto i = other.Begin(); i != other.End(); ++i) Add(i); - ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); } @@ -909,14 +867,13 @@ protected: void FindPosition(const KeyComparableType& key, FindPositionResult& result) const { ASSERT(_table); - const int32 tableSizeMinusOne = _tableSize - 1; int32 bucketIndex = GetHash(key) & tableSizeMinusOne; int32 insertPos = -1; - int32 numChecks = 0; + int32 checksCount = 0; result.FreeSlotIndex = -1; - while (numChecks < _tableSize) + while (checksCount < _tableSize) { // Empty bucket if (_table[bucketIndex].IsEmpty()) @@ -941,8 +898,8 @@ protected: return; } - numChecks++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, numChecks)) & tableSizeMinusOne; + checksCount++; + bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, checksCount)) & tableSizeMinusOne; } result.ObjectIndex = -1; From a4763f630e9636da94c58201b86351562a69d827 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Jan 2021 14:28:02 +0100 Subject: [PATCH 062/222] Fix right-click context menu for Visject Nodes --- Source/Editor/Surface/SurfaceNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index cf39384ce..972f9e678 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -984,7 +984,7 @@ namespace FlaxEditor.Surface } // Secondary Context Menu - if (button == MouseButton.Right) + if (button == MouseButton.Right && false) { if (!IsSelected) Surface.Select(this); From c2f745397a957349aa2e3e053092a1f8ed5dbce3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Jan 2021 14:28:44 +0100 Subject: [PATCH 063/222] Fix ResizeAuto in SurfaceNode to include custom controls --- Source/Editor/Surface/SurfaceNode.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 972f9e678..8726359e9 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -185,9 +185,9 @@ namespace FlaxEditor.Surface var rightWidth = 40.0f; var boxLabelFont = Style.Current.FontSmall; var titleLabelFont = Style.Current.FontLarge; - for (int i = 0; i < Elements.Count; i++) + for (int i = 0; i < Children.Count; i++) { - if (Elements[i] is InputBox inputBox) + if (Children[i] is InputBox inputBox) { var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) @@ -195,15 +195,23 @@ namespace FlaxEditor.Surface leftWidth = Mathf.Max(leftWidth, boxWidth); leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Elements[i] is OutputBox outputBox) + else if (Children[i] is OutputBox outputBox) { rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Elements[i] is Control control) + else if (Children[i] is Control control) { - width = Mathf.Max(width, control.Width + 10); - height = Mathf.Max(height, control.Height + 10); + if (control.AnchorPreset == AnchorPresets.TopLeft) + { + width = Mathf.Max(width, control.Right + 4 - Constants.NodeMarginX); + height = Mathf.Max(height, control.Bottom + 4 - Constants.NodeMarginY - Constants.NodeHeaderSize); + } + else + { + width = Mathf.Max(width, control.Width + 4); + height = Mathf.Max(height, control.Height + 4); + } } } width = Mathf.Max(width, leftWidth + rightWidth + 10); From 4042d466d3b8c1e15f53115ac6118477d3d11c58 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 21 Jan 2021 14:40:53 +0100 Subject: [PATCH 064/222] Add support for Events in Scripting API reflection in Editor --- .../Editor/Content/Items/VisualScriptItem.cs | 6 +++++ Source/Editor/Scripting/ScriptType.cs | 24 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 86be2e3de..fd23887ec 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -50,6 +50,9 @@ namespace FlaxEditor.Content /// public bool IsMethod => false; + /// + public bool IsEvent => false; + /// public bool HasGet => true; @@ -174,6 +177,9 @@ namespace FlaxEditor.Content /// public bool IsMethod => true; + /// + public bool IsEvent => false; + /// public bool HasGet => false; diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 038c1b745..7f49bdc75 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -138,6 +138,11 @@ namespace FlaxEditor.Scripting /// public bool IsMethod => _managed is MethodInfo || (_custom?.IsMethod ?? false); + /// + /// Gets a value indicating whether this member is an event. + /// + public bool IsEvent => _managed is EventInfo || (_custom?.IsEvent ?? false); + /// /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). /// @@ -383,7 +388,7 @@ namespace FlaxEditor.Scripting } /// - /// Gets the method parameters metadata. + /// Gets the method parameters metadata (or event delegate signature parameters). /// public Parameter[] GetParameters() { @@ -416,6 +421,11 @@ namespace FlaxEditor.Scripting } return result; } + if (_managed is EventInfo eventInfo) + { + var invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke"); + return new ScriptMemberInfo(invokeMethod).GetParameters(); + } return _custom.GetParameters(); } @@ -634,6 +644,11 @@ namespace FlaxEditor.Scripting /// public static readonly ScriptType Null; + /// + /// A that is System.Void. + /// + public static readonly ScriptType Void = new ScriptType(typeof(void)); + /// /// Gets the type of the script as . /// @@ -1463,6 +1478,11 @@ namespace FlaxEditor.Scripting /// bool IsMethod { get; } + /// + /// Gets a value indicating whether this member is an event. + /// + bool IsEvent { get; } + /// /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). /// @@ -1504,7 +1524,7 @@ namespace FlaxEditor.Scripting object[] GetAttributes(bool inherit); /// - /// Gets the method parameters metadata. + /// Gets the method parameters metadata (or event delegate signature parameters). /// ScriptMemberInfo.Parameter[] GetParameters(); From cbbfb166285b3ea932f6061209726c334728cd7d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 22 Jan 2021 10:55:13 +0100 Subject: [PATCH 065/222] Add scripting API events in Visual Script --- Source/Editor/Scripting/ScriptType.cs | 8 +- Source/Editor/Surface/Archetypes/Function.cs | 307 +++++++++++++++++- Source/Editor/Surface/SurfaceNode.cs | 9 +- Source/Editor/Surface/SurfaceUtils.cs | 5 + .../Editor/Surface/VisjectSurfaceContext.cs | 9 +- Source/Editor/Surface/VisualScriptSurface.cs | 65 +++- Source/Engine/Content/Assets/VisualScript.cpp | 211 +++++++++++- Source/Engine/Content/Assets/VisualScript.h | 23 +- Source/Engine/Scripting/BinaryModule.cpp | 4 + Source/Engine/Scripting/Events.h | 36 ++ .../Bindings/BindingsGenerator.Cpp.cs | 69 +++- 11 files changed, 708 insertions(+), 38 deletions(-) create mode 100644 Source/Engine/Scripting/Events.h diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 7f49bdc75..53c84a1b5 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -53,6 +53,8 @@ namespace FlaxEditor.Scripting return fieldInfo.IsPublic; if (_managed is PropertyInfo propertyInfo) return (propertyInfo.GetMethod == null || propertyInfo.GetMethod.IsPublic) && (propertyInfo.SetMethod == null || propertyInfo.SetMethod.IsPublic); + if (_managed is EventInfo eventInfo) + return eventInfo.GetAddMethod().IsPublic; if (_custom != null) return _custom.IsPublic; return false; @@ -72,6 +74,8 @@ namespace FlaxEditor.Scripting return fieldInfo.IsStatic; if (_managed is PropertyInfo propertyInfo) return (propertyInfo.GetMethod == null || propertyInfo.GetMethod.IsStatic) && (propertyInfo.SetMethod == null || propertyInfo.SetMethod.IsStatic); + if (_managed is EventInfo eventInfo) + return eventInfo.GetAddMethod().IsStatic; if (_custom != null) return _custom.IsStatic; return false; @@ -178,7 +182,7 @@ namespace FlaxEditor.Scripting } /// - /// Gets the method parameters count (valid for methods only). + /// Gets the method parameters count (valid for methods and events only). /// public int ParametersCount { @@ -186,6 +190,8 @@ namespace FlaxEditor.Scripting { if (_managed is MethodInfo methodInfo) return methodInfo.GetParameters().Length; + if (_managed is EventInfo eventInfo) + return eventInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Length; if (_custom != null) return _custom.ParametersCount; return 0; diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index b7584a2b8..340d234ab 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -1180,6 +1181,12 @@ namespace FlaxEditor.Surface.Archetypes [TypeReference(typeof(object), nameof(IsTypeValid))] public ScriptType Type; + public Parameter(ref ScriptMemberInfo.Parameter param) + { + Name = param.Name; + Type = param.Type; + } + private static bool IsTypeValid(ScriptType type) { return SurfaceUtils.IsValidVisualScriptFunctionType(type) && !type.IsVoid; @@ -1654,6 +1661,7 @@ namespace FlaxEditor.Surface.Archetypes base.OnSpawned(); // Setup initial signature + var defaultSignature = _signature.Node == null; CheckFunctionName(ref _signature.Name); if (_signature.ReturnType == ScriptType.Null) _signature.ReturnType = new ScriptType(typeof(void)); @@ -1661,8 +1669,11 @@ namespace FlaxEditor.Surface.Archetypes SaveSignature(); UpdateUI(); - // Start editing - OnEditSignature(); + if (defaultSignature) + { + // Start editing + OnEditSignature(); + } // Send event for (int i = 0; i < Surface.Nodes.Count; i++) @@ -1890,6 +1901,254 @@ namespace FlaxEditor.Surface.Archetypes } } + private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode + { + private ComboBoxElement _combobox; + private Image _helperButton; + private bool _isBind; + private bool _isUpdateLocked = true; + private List _tooltips = new List(); + private List _functionNodesIds = new List(); + private ScriptMemberInfo.Parameter[] _signature; + + protected EventBaseNode(bool isBind, uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + _isBind = isBind; + } + + private bool IsValidFunctionSignature(ref VisualScriptFunctionNode.Signature sig) + { + if (!sig.ReturnType.IsVoid || sig.Parameters == null || sig.Parameters.Length != _signature.Length) + return false; + for (int i = 0; i < _signature.Length; i++) + { + if (_signature[i].Type != sig.Parameters[i].Type) + return false; + } + return true; + } + + private void UpdateUI() + { + if (_isUpdateLocked) + return; + _isUpdateLocked = true; + if (_combobox == null) + { + _combobox = (ComboBoxElement)_children[4]; + _combobox.TooltipText = _isBind ? "Select the function to call when the event occurs" : "Select the function to unbind from the event"; + _combobox.SelectedIndexChanged += OnSelectedChanged; + _helperButton = new Image + { + Location = _combobox.UpperRight + new Vector2(4, 3), + Size = new Vector2(12.0f), + Parent = this, + }; + _helperButton.Clicked += OnHelperButtonClicked; + } + int toSelect = -1; + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + _combobox.ClearItems(); + _tooltips.Clear(); + _functionNodesIds.Clear(); + var nodes = Surface.Nodes; + var count = _signature != null ? nodes.Count : 0; + for (int i = 0; i < count; i++) + { + if (nodes[i] is VisualScriptFunctionNode functionNode) + { + // Get if function signature matches the event signature + functionNode.GetSignature(out var functionSig); + if (IsValidFunctionSignature(ref functionSig)) + { + if (functionNode.ID == handlerFunctionNodeId) + toSelect = _functionNodesIds.Count; + _functionNodesIds.Add(functionNode.ID); + _tooltips.Add(functionNode.TooltipText); + _combobox.AddItem(functionSig.ToString()); + } + } + } + _combobox.Tooltips = _tooltips.Count != 0 ? _tooltips.ToArray() : null; + _combobox.Enabled = _tooltips.Count != 0; + _combobox.SelectedIndex = toSelect; + if (toSelect != -1) + { + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Search12); + _helperButton.Color = Color.White; + _helperButton.TooltipText = "Navigate to the handler function"; + } + else if (_isBind) + { + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add48); + _helperButton.Color = Color.Red; + _helperButton.TooltipText = "Add new handler function and bind it to this event"; + _helperButton.Enabled = _signature != null; + } + else + { + _helperButton.Enabled = false; + } + ResizeAuto(); + _isUpdateLocked = false; + } + + private void OnHelperButtonClicked(Image img, MouseButton mouseButton) + { + if (mouseButton != MouseButton.Left) + return; + if (_combobox.SelectedIndex != -1) + { + // Focus selected function + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + var handlerFunctionNode = Surface.FindNode(handlerFunctionNodeId); + Surface.FocusNode(handlerFunctionNode); + } + else if (_isBind) + { + // Create new function that matches the event signature + var surfaceBounds = Surface.AllNodesBounds; + Surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Vector2(200, 150)).MakeExpanded(400.0f)); + var node = Surface.Context.SpawnNode(16, 6, surfaceBounds.BottomLeft + new Vector2(0, 50), null, OnBeforeSpawnedNewHandler); + Surface.Select(node); + + // Bind this function + SetValue(2, node.ID); + } + } + + private void OnBeforeSpawnedNewHandler(SurfaceNode node) + { + // Initialize signature to match the event + var functionNode = (VisualScriptFunctionNode)node; + functionNode._signature = new VisualScriptFunctionNode.Signature + { + Name = "On" + (string)Values[1], + IsStatic = false, + IsVirtual = false, + Node = functionNode, + ReturnType = ScriptType.Void, + Parameters = new VisualScriptFunctionNode.Parameter[_signature.Length], + }; + for (int i = 0; i < _signature.Length; i++) + functionNode._signature.Parameters[i] = new VisualScriptFunctionNode.Parameter(ref _signature[i]); + } + + private void OnSelectedChanged(ComboBox cb) + { + if (_isUpdateLocked) + return; + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + var selectedID = cb.SelectedIndex != -1 ? _functionNodesIds[cb.SelectedIndex] : 0u; + if (selectedID != handlerFunctionNodeId) + { + SetValue(2, selectedID); + UpdateUI(); + } + } + + public void OnFunctionCreated(SurfaceNode node) + { + UpdateUI(); + } + + public void OnFunctionEdited(SurfaceNode node) + { + UpdateUI(); + } + + public void OnFunctionDeleted(SurfaceNode node) + { + // Deselect if that function was selected + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + if (node.ID == handlerFunctionNodeId) + _combobox.SelectedIndex = -1; + + UpdateUI(); + } + + public override void OnSurfaceLoaded() + { + base.OnSurfaceLoaded(); + + // Find reflection information about event + _signature = null; + var isStatic = false; + var eventName = (string)Values[1]; + var eventType = TypeUtils.GetType((string)Values[0]); + var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) + { + isStatic = member.IsStatic; + _signature = member.GetParameters(); + TooltipText = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + } + + // Setup instance box (static events don't need it) + var instanceBox = GetBox(1); + instanceBox.Visible = !isStatic; + if (isStatic) + instanceBox.RemoveConnections(); + else + instanceBox.CurrentType = eventType; + + _isUpdateLocked = false; + UpdateUI(); + } + + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + UpdateUI(); + } + + /// + public override void OnDestroy() + { + _combobox = null; + _helperButton = null; + _tooltips.Clear(); + _tooltips = null; + _functionNodesIds.Clear(); + _functionNodesIds = null; + _signature = null; + + base.OnDestroy(); + } + } + + private sealed class BindEventNode : EventBaseNode + { + public BindEventNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(true, id, context, nodeArch, groupArch) + { + } + + public override void OnSurfaceLoaded() + { + Title = "Bind " + (string)Values[1]; + + base.OnSurfaceLoaded(); + } + } + + private sealed class UnbindEventNode : EventBaseNode + { + public UnbindEventNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(false, id, context, nodeArch, groupArch) + { + } + + public override void OnSurfaceLoaded() + { + Title = "Unbind " + (string)Values[1]; + + base.OnSurfaceLoaded(); + } + } + /// /// The nodes for that group. /// @@ -2031,6 +2290,50 @@ namespace FlaxEditor.Surface.Archetypes null, // Default value }, }, + new NodeArchetype + { + TypeID = 9, + Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch), + Title = string.Empty, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + Size = new Vector2(260, 60), + DefaultValues = new object[] + { + string.Empty, // Event type + string.Empty, // Event name + (uint)0, // Handler function nodeId + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(2, "Instance", true, typeof(object), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + NodeElementArchetype.Factory.Text(2, 20, "Handler function:"), + NodeElementArchetype.Factory.ComboBox(100, 20, 140), + } + }, + new NodeArchetype + { + TypeID = 10, + Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch), + Title = string.Empty, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + Size = new Vector2(260, 60), + DefaultValues = new object[] + { + string.Empty, // Event type + string.Empty, // Event name + (uint)0, // Handler function nodeId + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(2, "Instance", true, typeof(object), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + NodeElementArchetype.Factory.Text(2, 20, "Handler function:"), + NodeElementArchetype.Factory.ComboBox(100, 20, 140), + } + }, }; } } diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 8726359e9..ae094c2be 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -187,7 +187,10 @@ namespace FlaxEditor.Surface var titleLabelFont = Style.Current.FontLarge; for (int i = 0; i < Children.Count; i++) { - if (Children[i] is InputBox inputBox) + var child = Children[i]; + if (!child.Visible) + continue; + if (child is InputBox inputBox) { var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) @@ -195,12 +198,12 @@ namespace FlaxEditor.Surface leftWidth = Mathf.Max(leftWidth, boxWidth); leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Children[i] is OutputBox outputBox) + else if (child is OutputBox outputBox) { rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Children[i] is Control control) + else if (child is Control control) { if (control.AnchorPreset == AnchorPresets.TopLeft) { diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 18f2e9693..74f1d6130 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -406,6 +406,11 @@ namespace FlaxEditor.Surface return member.IsField && IsValidVisualScriptType(member.ValueType); } + internal static bool IsValidVisualScriptEvent(ScriptMemberInfo member) + { + return member.IsEvent && member.HasAttribute(typeof(UnmanagedAttribute)); + } + internal static bool IsValidVisualScriptType(ScriptType scriptType) { if (scriptType.IsGenericType || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true)) diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index d436cd91e..81b72bf4e 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -331,12 +331,13 @@ namespace FlaxEditor.Surface /// The node archetype ID. /// The location. /// The custom values array. Must match node archetype size. Pass null to use default values. + /// The custom callback action to call after node creation but just before invoking spawn event. Can be used to initialize custom node data. /// Created node. - public SurfaceNode SpawnNode(ushort groupID, ushort typeID, Vector2 location, object[] customValues = null) + public SurfaceNode SpawnNode(ushort groupID, ushort typeID, Vector2 location, object[] customValues = null, Action beforeSpawned = null) { if (NodeFactory.GetArchetype(_surface.NodeArchetypes, groupID, typeID, out var groupArchetype, out var nodeArchetype)) { - return SpawnNode(groupArchetype, nodeArchetype, location, customValues); + return SpawnNode(groupArchetype, nodeArchetype, location, customValues, beforeSpawned); } return null; } @@ -348,8 +349,9 @@ namespace FlaxEditor.Surface /// The node archetype. /// The location. /// The custom values array. Must match node archetype size. Pass null to use default values. + /// The custom callback action to call after node creation but just before invoking spawn event. Can be used to initialize custom node data. /// Created node. - public SurfaceNode SpawnNode(GroupArchetype groupArchetype, NodeArchetype nodeArchetype, Vector2 location, object[] customValues = null) + public SurfaceNode SpawnNode(GroupArchetype groupArchetype, NodeArchetype nodeArchetype, Vector2 location, object[] customValues = null, Action beforeSpawned = null) { if (groupArchetype == null || nodeArchetype == null) throw new ArgumentNullException(); @@ -387,6 +389,7 @@ namespace FlaxEditor.Surface } node.Location = location; OnControlLoaded(node); + beforeSpawned?.Invoke(node); node.OnSurfaceLoaded(); OnControlSpawned(node); diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index ccabc8f2f..335860c52 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -2,6 +2,11 @@ //#define DEBUG_INVOKE_METHODS_SEARCHING //#define DEBUG_FIELDS_SEARCHING +//#define DEBUG_EVENTS_SEARCHING + +#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING || DEBUG_EVENTS_SEARCHING +#define DEBUG_SEARCH_TIME +#endif using System; using System.Collections.Generic; @@ -103,7 +108,7 @@ namespace FlaxEditor.Surface private static void OnActiveContextMenuShowAsync() { Profiler.BeginEvent("Setup Visual Script Context Menu (async)"); -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME var searchStartTime = DateTime.Now; var searchHitsCount = 0; #endif @@ -338,13 +343,65 @@ namespace FlaxEditor.Surface } } } + else if (member.IsEvent) + { + var name = member.Name; + + // Skip if searching by name doesn't return a match + var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (!members.Contains(member)) + continue; + + // Check if field is valid for Visual Script usage + if (SurfaceUtils.IsValidVisualScriptEvent(member)) + { + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!_cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = _version, + Archetypes = new List(), + }; + _cache.Add(groupKey, group); + } + + // Add Bind event node + var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone(); + bindNode.DefaultValues[0] = scriptTypeTypeName; + bindNode.DefaultValues[1] = name; + bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + bindNode.Title = "Bind " + name; + bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName); + ((IList)group.Archetypes).Add(bindNode); + + // Add Unbind event node + var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone(); + unbindNode.DefaultValues[0] = scriptTypeTypeName; + unbindNode.DefaultValues[1] = name; + unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + unbindNode.Title = "Unbind " + name; + unbindNode.Description = bindNode.Description; + unbindNode.SubTitle = bindNode.SubTitle; + ((IList)group.Archetypes).Add(unbindNode); + +#if DEBUG_EVENTS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); + searchHitsCount++; +#endif + } + } } } // Add group to context menu (on a main thread) FlaxEngine.Scripting.InvokeOnUpdate(() => { -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME var addStartTime = DateTime.Now; #endif lock (_locker) @@ -352,12 +409,12 @@ namespace FlaxEditor.Surface _taskContextMenu.AddGroups(_cache.Values); _taskContextMenu = null; } -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms"); #endif }); -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME Editor.LogError($"Collected {searchHitsCount} items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms"); #endif Profiler.EndEvent(); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 729ee0a5e..da1b97687 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Scripting/MException.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/Events.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MField.h" @@ -347,7 +348,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v const auto instanceParams = stack.Stack->Script->_instances.Find(stack.Stack->Instance->GetID()); if (param && instanceParams) { - value = instanceParams->Value[paramIndex]; + value = instanceParams->Value.Params[paramIndex]; } else { @@ -371,7 +372,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v const auto instanceParams = stack.Stack->Script->_instances.Find(stack.Stack->Instance->GetID()); if (param && instanceParams) { - instanceParams->Value[paramIndex] = tryGetValue(node->GetBox(1), 1, Value::Zero); + instanceParams->Value.Params[paramIndex] = tryGetValue(node->GetBox(1), 1, Value::Zero); } else { @@ -1004,6 +1005,125 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& if (returnedImpulse && returnedImpulse->HasConnection()) eatBox(node, returnedImpulse->FirstConnection()); break; + } + // Bind/Unbind + case 9: + case 10: + { + const bool bind = node->TypeID == 9; + auto& stack = ThreadStacks.Get(); + if (!stack.Stack->Instance) + { + // TODO: add support for binding to events in static Visual Script + LOG(Error, "Cannot bind to event in static Visual Script."); + PrintStack(LogType::Error); + break; + } + const auto object = stack.Stack->Instance; + + // Find method to bind + VisualScriptGraphNode* methodNode = nullptr; + const auto graph = stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr; + if (graph) + methodNode = graph->GetNode((uint32)node->Values[2]); + if (!methodNode) + { + LOG(Error, "Missing function handler to bind to the event."); + PrintStack(LogType::Error); + break; + } + VisualScript::Method* method = nullptr; + for (auto& m : stack.Stack->Script->_methods) + { + if (m.Node == methodNode) + { + method = &m; + break; + } + } + if (!method) + { + LOG(Error, "Missing method to bind to the event."); + PrintStack(LogType::Error); + break; + } + + // Find event + const StringView eventTypeName(node->Values[0]); + const StringView eventName(node->Values[1]); + const StringAsANSI<100> eventTypeNameAnsi(eventTypeName.Get(), eventTypeName.Length()); + const ScriptingTypeHandle eventType = Scripting::FindScriptingType(StringAnsiView(eventTypeNameAnsi.Get(), eventTypeName.Length())); + + // Find event binding callback + auto eventBinder = ScriptingEvents::EventsTable.TryGet(Pair(eventType, eventName)); + if (!eventBinder) + { + LOG(Error, "Cannot bind to missing event {0} from type {1}.", eventName, eventTypeName); + PrintStack(LogType::Error); + break; + } + + // Evaluate object instance + const auto box = node->GetBox(1); + Variant instance; + if (box->HasConnection()) + instance = eatBox(node, box->FirstConnection()); + else + instance.SetObject(object); + if (!instance.AsObject) + { + LOG(Error, "Cannot bind event to null object."); + PrintStack(LogType::Error); + break; + } + // TODO: check if instance is of event type (including inheritance) + + // Add Visual Script method to the event bindings table + const auto& type = object->GetType(); + Guid id; + if (Guid::Parse(type.Fullname, id)) + break; + if (const auto visualScript = (VisualScript*)Content::GetAsset(id)) + { + if (auto i = visualScript->GetScriptInstance(object)) + { + VisualScript::EventBinding* eventBinding = nullptr; + for (auto& b : i->EventBindings) + { + if (b.Type == eventType && b.Name == eventName) + { + eventBinding = &b; + break; + } + } + if (bind) + { + // Bind to the event + if (!eventBinding) + { + eventBinding = &i->EventBindings.AddOne(); + eventBinding->Type = eventType; + eventBinding->Name = eventName; + } + eventBinding->BindedMethods.Add(method); + if (eventBinding->BindedMethods.Count() == 1) + (*eventBinder)(instance.AsObject, object, true); + } + else if (eventBinding) + { + // Unbind from the event + if (eventBinding->BindedMethods.Count() == 1) + (*eventBinder)(instance.AsObject, object, false); + eventBinding->BindedMethods.Remove(method); + } + } + } + + // Call graph further + const auto returnedImpulse = &node->Boxes[2]; + if (returnedImpulse && returnedImpulse->HasConnection()) + eatBox(node, returnedImpulse->FirstConnection()); + break; } default: break; @@ -1304,7 +1424,7 @@ Asset::LoadResult VisualScript::load() // Update instanced data from previous format to the current graph parameters scheme for (auto& e : _instances) { - auto& instanceParams = e.Value; + auto& instanceParams = e.Value.Params; Array valuesCache(MoveTemp(instanceParams)); instanceParams.Resize(count); for (int32 i = 0; i < count; i++) @@ -1319,7 +1439,7 @@ Asset::LoadResult VisualScript::load() // Reset instances values to defaults for (auto& e : _instances) { - auto& instanceParams = e.Value; + auto& instanceParams = e.Value.Params; instanceParams.Resize(count); for (int32 i = 0; i < count; i++) instanceParams[i] = Graph.Parameters[i].Value; @@ -1421,13 +1541,17 @@ void VisualScript::CacheScriptingType() _scriptingTypeHandle = ScriptingTypeHandle(&binaryModule, typeIndex); binaryModule.Scripts.Add(this); -#if USE_EDITOR - // When first Visual Script gets loaded register for other modules unload to clear runtime execution cache + // Special initialization when the first Visual Script gets loaded if (typeIndex == 0) { +#if USE_EDITOR + // Register for other modules unload to clear runtime execution cache Scripting::ScriptsReloading.Bind(&binaryModule); - } #endif + + // Register for scripting events + ScriptingEvents::Event.Bind(VisualScriptingBinaryModule::OnEvent); + } } auto& type = _scriptingTypeHandle.Module->Types[_scriptingTypeHandle.TypeIndex]; type.ManagedClass = baseType.GetType().ManagedClass; @@ -1550,7 +1674,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri VisualScript* visualScript = VisualScriptingModule.Scripts[params.Type.TypeIndex]; // Initialize instance data - auto& instanceParams = visualScript->_instances[object->GetID()]; + auto& instanceParams = visualScript->_instances[object->GetID()].Params; instanceParams.Resize(visualScript->Graph.Parameters.Count()); for (int32 i = 0; i < instanceParams.Count(); i++) instanceParams[i] = visualScript->Graph.Parameters[i].Value; @@ -1608,6 +1732,56 @@ void VisualScriptingBinaryModule::OnScriptsReloading() #endif +void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span& parameters, const ScriptingTypeHandle& eventType, const StringView& eventName) +{ + if (object) + { + // Object event + const auto& type = object->GetType(); + Guid id; + if (Guid::Parse(type.Fullname, id)) + return; + if (const auto visualScript = (VisualScript*)Content::GetAsset(id)) + { + if (auto instance = visualScript->GetScriptInstance(object)) + { + for (auto& b : instance->EventBindings) + { + if (b.Type != eventType || b.Name != eventName) + continue; + for (auto& m : b.BindedMethods) + { + VisualScripting::Invoke(m, object, parameters); + } + } + } + } + } + else + { + // Static event + for (auto& asset : Content::GetAssetsRaw()) + { + if (const auto visualScript = ScriptingObject::Cast(asset.Value)) + { + for (auto& e : visualScript->_instances) + { + auto instance = &e.Value; + for (auto& b : instance->EventBindings) + { + if (b.Type != eventType || b.Name != eventName) + continue; + for (auto& m : b.BindedMethods) + { + VisualScripting::Invoke(m, object, parameters); + } + } + } + } + } + } +} + const StringAnsi& VisualScriptingBinaryModule::GetName() const { return _name; @@ -1702,7 +1876,7 @@ bool VisualScriptingBinaryModule::GetFieldValue(void* field, const Variant& inst LOG(Error, "Missing parameters for the object instance."); return true; } - result = instanceParams->Value[vsFiled->Index]; + result = instanceParams->Value.Params[vsFiled->Index]; return false; } @@ -1721,7 +1895,7 @@ bool VisualScriptingBinaryModule::SetFieldValue(void* field, const Variant& inst LOG(Error, "Missing parameters for the object instance."); return true; } - instanceParams->Value[vsFiled->Index] = value; + instanceParams->Value.Params[vsFiled->Index] = value; return false; } @@ -1735,7 +1909,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO const auto instanceParams = asset->_instances.Find(object->GetID()); if (instanceParams) { - auto& params = instanceParams->Value; + auto& params = instanceParams->Value.Params; if (otherObj) { // Serialize parameters diff @@ -1746,7 +1920,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO { auto& param = asset->Graph.Parameters[paramIndex]; auto& value = params[paramIndex]; - auto& otherValue = otherParams->Value[paramIndex]; + auto& otherValue = otherParams->Value.Params[paramIndex]; if (value != otherValue) { param.Identifier.ToString(idName, Guid::FormatType::N); @@ -1798,7 +1972,7 @@ void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeSt if (instanceParams) { // Deserialize all parameters - auto& params = instanceParams->Value; + auto& params = instanceParams->Value.Params; for (auto i = stream.MemberBegin(); i != stream.MemberEnd(); ++i) { StringAnsiView idNameAnsi(i->name.GetString(), i->name.GetStringLength()); @@ -1865,6 +2039,11 @@ ScriptingObject* VisualScript::CreateInstance() return scriptingTypeHandle ? scriptingTypeHandle.GetType().Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; } +VisualScript::Instance* VisualScript::GetScriptInstance(ScriptingObject* instance) const +{ + return instance ? _instances.TryGet(instance->GetID()) : nullptr; +} + Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const { CHECK_RETURN(instance, Variant()); @@ -1874,7 +2053,7 @@ Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, Sc { const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) - return instanceParams->Value[paramIndex]; + return instanceParams->Value.Params[paramIndex]; LOG(Error, "Failed to access Visual Script parameter {1} for {0}.", instance->ToString(), name); return Graph.Parameters[paramIndex].Value; } @@ -1893,7 +2072,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) { - instanceParams->Value[paramIndex] = value; + instanceParams->Value.Params[paramIndex] = value; return; } LOG(Error, "Failed to access Visual Script parameter {1} for {0}.", instance->ToString(), name); @@ -1913,7 +2092,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) { - instanceParams->Value[paramIndex] = MoveTemp(value); + instanceParams->Value.Params[paramIndex] = MoveTemp(value); return; } } diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 0a7f34b1c..cf8dccce3 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -125,9 +125,22 @@ public: StringAnsi Name; }; + struct EventBinding + { + ScriptingTypeHandle Type; + String Name; + Array> BindedMethods; + }; + + struct Instance + { + Array Params; + Array EventBindings; + }; + private: - Dictionary> _instances; + Dictionary _instances; ScriptingTypeHandle _scriptingTypeHandle; ScriptingTypeHandle _scriptingTypeHandleCached; StringAnsiView _typename; @@ -179,6 +192,13 @@ public: /// The created instance or null if failed. API_FUNCTION() ScriptingObject* CreateInstance(); + /// + /// Gets the Visual Script instance data. + /// + /// The object instance. + /// The data or invalid instance (not VS or missing). + Instance* GetScriptInstance(ScriptingObject* instance) const; + /// /// Gets the value of the Visual Script parameter of the given instance. /// @@ -307,6 +327,7 @@ private: #if USE_EDITOR void OnScriptsReloading(); #endif + static void OnEvent(ScriptingObject* object, Span& parameters, const ScriptingTypeHandle& eventType, const StringView& eventName); public: diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index c802caff2..689b552f8 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -14,6 +14,10 @@ #include "MException.h" #include "Scripting.h" #include "StdTypesContainer.h" +#include "Events.h" + +Dictionary, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable; +Delegate&, const ScriptingTypeHandle&, const StringView&> ScriptingEvents::Event; ManagedBinaryModule* GetBinaryModuleCorlib() { diff --git a/Source/Engine/Scripting/Events.h b/Source/Engine/Scripting/Events.h new file mode 100644 index 000000000..11f1643ee --- /dev/null +++ b/Source/Engine/Scripting/Events.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ScriptingType.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Types/Span.h" +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Dictionary.h" + +/// +/// The helper utility for binding and invoking scripting events (eg. used by Visual Scripting). +/// +class FLAXENGINE_API ScriptingEvents +{ +public: + + /// + /// Global table for registered even binder methods (key is pair of type and event name, value is method that takes instance with event, object to bind and flag to bind or unbind). + /// + /// + /// Key: pair of event type name (full), event name. + /// Value: event binder function with parameters: event caller instance (null for static events), object to bind, true to bind/false to unbind. + /// + static Dictionary, void(*)(ScriptingObject*, void*, bool)> EventsTable; + + /// + /// The action called when any scripting event occurs. Can be used to invoke scripting code that binded to this particular event. + /// + /// + /// Delegate parameters: event caller instance (null for static events), event invocation parameters list, event type name (full), event name. + /// + static Delegate&, const ScriptingTypeHandle&, const StringView&> Event; +}; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 6cdc5462b..11fe6a414 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1091,19 +1091,19 @@ namespace Flax.Build.Bindings { if (!useScripting) continue; - CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); + var paramsCount = eventInfo.Type.GenericArgs.Count; // C# event invoking wrapper (calls C# event from C++ delegate) + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]); - contents.Append(" arg" + i); + contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); @@ -1112,11 +1112,11 @@ namespace Flax.Build.Bindings contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, eventInfo.Type.GenericArgs.Count).AppendLine(); contents.Append(" CHECK(mmethod);").AppendLine(); contents.Append(" MonoObject* exception = nullptr;").AppendLine(); - if (eventInfo.Type.GenericArgs.Count == 0) + if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); else - contents.AppendLine($" void* params[{eventInfo.Type.GenericArgs.Count}];"); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + contents.AppendLine($" void* params[{paramsCount}];"); + for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; @@ -1139,7 +1139,7 @@ namespace Flax.Build.Bindings contents.Append("bool bind)").AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" Function(params, {paramsCount}), {classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}));"); + contents.Append(" }").AppendLine().AppendLine(); + + // Scripting event wrapper binding method (binds/unbinds generic wrapper to C++ delegate) + contents.AppendFormat(" static void {0}_Bind(", eventInfo.Name); + contents.AppendFormat("{0}* obj, void* instance, bool bind)", classTypeNameNative).AppendLine(); + contents.Append(" {").AppendLine(); + contents.Append(" Function f;").AppendLine(); + if (eventInfo.IsStatic) + contents.AppendFormat(" f.Bind<{0}_Wrapper>();", eventInfo.Name).AppendLine(); + else + contents.AppendFormat(" f.Bind<{1}Internal, &{1}Internal::{0}_Wrapper>(({1}Internal*)instance);", eventInfo.Name, classTypeNameInternal).AppendLine(); + contents.Append(" if (bind)").AppendLine(); + contents.AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine(); + contents.Append(" else").AppendLine(); + contents.AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine(); + contents.Append(" }").AppendLine().AppendLine(); } // Fields @@ -1299,6 +1349,9 @@ namespace Flax.Build.Bindings foreach (var eventInfo in classInfo.Events) { contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); + + // Register scripting event binder + contents.AppendLine($" ScriptingEvents::EventsTable[Pair({classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}))] = (void(*)(ScriptingObject*, void*, bool)){classTypeNameInternal}Internal::{eventInfo.Name}_Bind;"); } foreach (var fieldInfo in classInfo.Fields) { From d76c167ad93f5672898096cd2925b086616d6f6b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 15:34:40 +0100 Subject: [PATCH 066/222] Add NetworkBase. --- Source/Engine/Platform/Base/NetworkBase.cpp | 63 +++++++++++++++++++++ Source/Engine/Platform/Base/NetworkBase.h | 63 +++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 Source/Engine/Platform/Base/NetworkBase.cpp create mode 100644 Source/Engine/Platform/Base/NetworkBase.h diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp new file mode 100644 index 000000000..79e122f08 --- /dev/null +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NetworkBase.h" + +bool NetworkBase::Init() +{ + return false; +} + +void NetworkBase::Exit() +{ +} + +bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings) +{ + return false; +} + +bool NetworkBase::DestroySocket(NetworkSocket& socket) +{ + return false; +} + +bool NetworkBase::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + return false; +} + +bool NetworkBase::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + return false; +} + +bool NetworkBase::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + return false; +} + +bool NetworkBase::IsReadable(NetworkSocket& socket, uint64* size) +{ + return false; +} + +uint32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + return 0; +} + +uint32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + return 0; +} + +bool NetworkBase::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint) +{ + return false; +} + +NetworkEndPoint NetworkBase::RemapEndPointToIPv6(NetworkEndPoint& endPoint) +{ + return NetworkEndPoint(); +} + diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h new file mode 100644 index 000000000..60231e66c --- /dev/null +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Core/Types/String.h" +API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); + +enum class FLAXENGINE_API NetworkProtocolType +{ + Undefined, + Udp, + Tcp +}; + +enum class FLAXENGINE_API NetworkIPVersion +{ + Undefined, + IPv4, + IPv6 +}; + +struct FLAXENGINE_API NetworkSocketCreateSettings +{ + NetworkProtocolType Protocol = NetworkProtocolType::Udp; + NetworkIPVersion IPVersion = NetworkIPVersion::IPv4; + bool DualStacking = true; + bool ReuseAddress = true; + bool Broadcast = false; +}; + +struct FLAXENGINE_API NetworkSocket +{ + NetworkProtocolType Protocol = NetworkProtocolType::Undefined; + NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + byte Data[8] = {}; // sizeof SOCKET = unsigned __int64 +}; + +struct FLAXENGINE_API NetworkEndPoint +{ + NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + String Address; + String Port; + byte Data[28] = {}; // sizeof sockaddr_in6 , biggest sockaddr that we will use +}; + +class FLAXENGINE_API NetworkBase +{ + public: + static bool Init(); + static void Exit(); + static bool CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings); + static bool DestroySocket(NetworkSocket& socket); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket, uint64* size); + static uint32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static uint32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); +}; + From aa972961232a1d9faa275e38f16fe34ccacf8c6d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 15:35:22 +0100 Subject: [PATCH 067/222] Add windows implementation. --- Source/Engine/Platform/Win32/Win32Network.cpp | 370 ++++++++++++++++++ Source/Engine/Platform/Win32/Win32Network.h | 29 ++ .../Engine/Platform/Win32/Win32Platform.cpp | 9 + Source/Engine/Platform/Win32/Win32Platform.h | 1 + .../Platform/Windows/WindowsPlatform.cpp | 2 + 5 files changed, 411 insertions(+) create mode 100644 Source/Engine/Platform/Win32/Win32Network.cpp create mode 100644 Source/Engine/Platform/Win32/Win32Network.h diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp new file mode 100644 index 000000000..6013f92e2 --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -0,0 +1,370 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Win32Network.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include +#include +#include + +static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); +static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +static const IN6_ADDR v4MappedPrefix = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}; +static WSAData _wsaData; + +/* + * TODO: Create func to retrieve WSA error string + * Known issues : + * Sometimes getaddrinfo fails without reason, re-trying right after works + * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation + */ + +static int GetAddrSize(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; +} + +static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) +{ + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; +} + +static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; +} + +//TODO: can be simplified by casting addr to the right struct and ntohl the port, so we can get rid of getnameinfo +// getnameinfo return a name ( like JimiPC ), not the ip ! +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint16 size = GetAddrSize(*addr); + char name[100]; + char service[20]; + if (getnameinfo(addr, size, name, sizeof name, service, sizeof service, 0) != 0) + { + LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", WSAGetLastError()); + return true; + } + void* paddr; + if (addr->sa_family == AF_INET6) + paddr = &((sockaddr_in6*)addr)->sin6_addr; + else if (addr->sa_family == AF_INET) + paddr = &((sockaddr_in*)addr)->sin_addr; + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", WSAGetLastError()); + return true; + } + endPoint.Address = String(ip); + endPoint.Port = String(service); + endPoint.IPVersion = GetIPVersionFromAddr(*addr); + memcpy(endPoint.Data, addr, size); + return false; +} + +static void PrintAddrFromInfo(addrinfo& info) +{ + addrinfo* curr; + for (curr = &info; curr != nullptr; curr = curr->ai_next) + { + void* addr; + if (curr->ai_family == AF_INET) { + sockaddr_in *ipv4 = (struct sockaddr_in *)curr->ai_addr; + addr = &(ipv4->sin_addr); + } else { + sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)curr->ai_addr; + addr = &(ipv6->sin6_addr); + } + + char str[INET6_ADDRSTRLEN]; + inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); + LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString().Get()); + } +} + +bool Win32Network::Init() +{ + if (WSAStartup(MAKEWORD(2,0), &_wsaData) != 0) + return true; + return false; +} + +void Win32Network::Exit() +{ + WSACleanup(); +} + +bool Win32Network::CreateSocket(NetworkSocket& netsock, NetworkSocketCreateSettings& settings) +{ + netsock.Protocol = settings.Protocol; + netsock.IPVersion = settings.IPVersion; + const uint8 family = settings.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const uint8 stype = settings.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const uint8 proto = settings.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + SOCKET sock; + + if ((sock = socket(family, stype, proto)) == INVALID_SOCKET) + { + LOG(Error, "Can't create native socket ! Error : {0}", WSAGetLastError()); + return true; + } + memcpy(netsock.Data, &sock, sizeof sock); + DWORD dw = 0; + if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) + { + LOG(Warning, "System does not support dual stacking socket ! Error : {0}", WSAGetLastError()); + } + unsigned long value = 1; + if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) + { + LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", WSAGetLastError()); + } + + if (settings.Broadcast && settings.Protocol == NetworkProtocolType::Udp) + { + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof value) == SOCKET_ERROR) + { + LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", WSAGetLastError()); + } + } + else if (settings.Broadcast) + LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", WSAGetLastError()); + + if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) + { + LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", WSAGetLastError()); + return true; // Support using blocking socket , need to test it + } + //DEBUG + LOG(Info, "New socket created : {0}", sock); //TODO: DEBUG + return false; +} + +bool Win32Network::DestroySocket(NetworkSocket& socket) +{ + const SOCKET sock = *(SOCKET*)socket.Data; + if (sock != INVALID_SOCKET) + { + closesocket(sock); + //DEBUG + LOG(Info, "Deleting socket : {0}", sock); //TODO: DEBUG + return false; + } + LOG(Warning, "Unable to delete socket INVALID_SOCKET ! Socket : {0}", sock); + return true; +} + + +bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const uint16 size = GetAddrSizeFromEP(endPoint); + if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + LOG(Error, "Unable to connect socket to address ! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + return true; + } + return false; +} + +bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion ! Socket : {0}", *(SOCKET*)socket.Data); + return true; + } + + const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + const sockaddr* addr = (sockaddr*)endPoint.Data; + LOG(Info, "BIND : EndPoint family : {0}", addr->sa_family); + if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), WSAGetLastError()); + return true; + } + //DEBUG + LOG(Info, "Binding socket ! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + return false; +} + +bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + if (serverSock.Protocol != NetworkProtocolType::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket ! Socket : {0}", *(SOCKET*)serverSock.Data); + return true; + } + SOCKET sock; + sockaddr addr; + if ((sock = accept(*(SOCKET*)serverSock.Data, &addr, nullptr)) == INVALID_SOCKET) + { + LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, WSAGetLastError()); + return true; + } + memcpy(newSock.Data, &sock, sizeof sock); + memcpy(newEndPoint.Data, &addr, GetAddrSize(addr)); + + newSock.Protocol = serverSock.Protocol; + newSock.IPVersion = serverSock.IPVersion; + if (CreateEndPointFromAddr(&addr, newEndPoint)) + return true; + return false; +} + +bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) +{ + unsigned long value; + if (ioctlsocket(*(SOCKET*)socket.Data, FIONREAD, &value) != 0) + { + LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, WSAGetLastError()); + return true; + } + *size = value; + return false; +} + +uint32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + if (socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion ! Socket : {0}", *(SOCKET*)socket.Data); + return 0; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocolType::Tcp) + { + if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocolType::Udp) + { + if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length); + return -1; + } + } + else + { + //TODO: better explanation + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + return -1; + } + return size; +} + +/* + * TODO: handle size == 0 when there is a shutdown + */ +uint32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + uint32 size; + if (endPoint == nullptr) // TCP + { + if ((size = recv(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0)) == SOCKET_ERROR) + { + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1}", *(SOCKET*)socket.Data, bufferSize); + return -1; + } + } + else // UDP + { + int32 addrsize; + sockaddr_in6 addr; + if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) + { + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1}", *(SOCKET*)socket.Data, bufferSize); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +// if address is null, it's ADDR_ANY +bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint) +{ + int status; + addrinfo hints; + addrinfo *info; + //TODO: Refactor this crappy thing + char* paddr= address != nullptr ? address->ToStringAnsi().Get() : nullptr; + char* pport = port != nullptr ? port->ToStringAnsi().Get() : nullptr; + //DEBUG + LOG(Info, "Searching available adresses for {0} : {1}", address == nullptr ? String("nullptr").Get() : address->Get(), + port == nullptr ? String("nullptr").Get() : port->Get()); + memset(&hints, 0, sizeof hints); + hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; + hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_V4MAPPED; + if (paddr == nullptr) + { + hints.ai_flags = AI_PASSIVE; + LOG(Info, "PASSIVE MODE"); + } + + // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) + // can *paddr works ? + // paddr = nullptr don't work with this func + if ((status = getaddrinfo(paddr == nullptr ? nullptr : paddr, pport, &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0} Error : {1} !", address->Get(), gai_strerror(status)); //TODO: address can be NULL + return true; + } + + if (info == nullptr) + { + LOG(Error, "Unable to resolve address ! Address : {0}", address->Get());//TODO: address can be NULL + return true; + } + + //DEBUG + PrintAddrFromInfo(*info); + + // We are taking the first addr in the linked list + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + freeaddrinfo(info); + return true; + } + freeaddrinfo(info); + + //DEBUG + LOG(Info, "Address found : {0} : {1}", endPoint.Address, endPoint.Port); + return false; +} + +NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) +{ + if (endPoint.IPVersion == NetworkIPVersion::IPv6) + { + LOG(Warning, "Unable to remap EndPoint, already in IPv6 format !"); + return endPoint; + } + + NetworkEndPoint pv6; + sockaddr_in* addr4 = (sockaddr_in*)endPoint.Data; + sockaddr_in6* addr6 = (sockaddr_in6*)pv6.Data; + const SCOPE_ID scope = SCOPEID_UNSPECIFIED_INIT; + + // Can be replaced by windows built-in macro IN6ADDR_SETV4MAPPED() + memset(addr6, 0, sizeof sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_scope_struct = scope; + addr6->sin6_addr = v4MappedPrefix; + addr6->sin6_port = addr4->sin_port; + memcpy(&addr6->sin6_addr.u.Byte[12], &addr4->sin_addr, 4); // :::::FFFF:XXXX:XXXX X=IPv4 + pv6.IPVersion = NetworkIPVersion::IPv6; + pv6.Port = endPoint.Port; + + return pv6; +} diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h new file mode 100644 index 000000000..708462320 --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WIN32 + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API Win32Network : public NetworkBase +{ +public: + // [NetworkBase] + static bool Init(); + static void Exit(); + static bool CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings); + static bool DestroySocket(NetworkSocket& socket); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket, uint64* size); + static uint32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static uint32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); + friend NetworkEndPoint; + friend NetworkSocket; +}; + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 129ff4249..db6151a17 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -11,6 +11,7 @@ #include "Engine/Core/Math/Math.h" #include "IncludeWindowsHeaders.h" #include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Platform/Network.h" #include #include @@ -212,9 +213,17 @@ bool Win32Platform::Init() DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize; } + //TODO: log error if true + Win32Network::Init(); + return false; } +void Win32Platform::Exit() +{ + Network::Exit(); +} + void Win32Platform::MemoryBarrier() { // NOTE: _ReadWriteBarrier and friends only prevent the diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index b6a4cdd62..cde506278 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -16,6 +16,7 @@ public: // [PlatformBase] static bool Init(); + static void Exit(); static void MemoryBarrier(); static int64 InterlockedExchange(int64 volatile* dst, int64 exchange); static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 0b1f70989..13c52d9a8 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -624,6 +624,8 @@ void WindowsPlatform::Exit() // Unregister app class UnregisterClassW(ApplicationWindowClass, nullptr); + + Win32Platform::Exit(); } #if !BUILD_RELEASE From 125b4347daf9010329cbabc7fa0b0cd9831c4440 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 15:35:41 +0100 Subject: [PATCH 068/222] Add windows typedef. --- Source/Engine/Platform/Types.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index c8e955c1b..82b44ace9 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -22,6 +22,8 @@ class Win32Thread; typedef Win32Thread Thread; class WindowsWindow; typedef WindowsWindow Window; +class Win32Network; +typedef Win32Network Network; #elif PLATFORM_UWP From c53fe681f573c1f30e0abd903f8af9dbbfbeb802 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 15:36:02 +0100 Subject: [PATCH 069/222] Add network header. --- Source/Engine/Platform/Network.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Source/Engine/Platform/Network.h diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h new file mode 100644 index 000000000..916555ef4 --- /dev/null +++ b/Source/Engine/Platform/Network.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WINDOWS +#include "Win32/Win32Network.h" +#elif PLATFORM_UWP +#include "Win32/Win32Network.h" +#elif PLATFORM_LINUX + +#elif PLATFORM_PS4 + +#elif PLATFORM_XBOX_SCARLETT +#include "Win32/Win32Network.h" // it's probably isn't working +#elif PLATFORM_ANDROID + +#else +#error Missing Network implementation! +#endif + +#include "Types.h" From a60e1ab8bbff273aa69fbbbc4c7118b4b1b6b422 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 17:37:43 +0100 Subject: [PATCH 070/222] Place holder for other platforms. --- Source/Engine/Platform/Network.h | 8 ++++---- Source/Engine/Platform/Types.h | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h index 916555ef4..a361eb6bf 100644 --- a/Source/Engine/Platform/Network.h +++ b/Source/Engine/Platform/Network.h @@ -5,15 +5,15 @@ #if PLATFORM_WINDOWS #include "Win32/Win32Network.h" #elif PLATFORM_UWP -#include "Win32/Win32Network.h" +#include "Win32/Win32Network.h" // place holder #elif PLATFORM_LINUX - +#include "Base/NetworkBase.h" // place holder #elif PLATFORM_PS4 - +#include "Base/NetworkBase.h" // place holder #elif PLATFORM_XBOX_SCARLETT #include "Win32/Win32Network.h" // it's probably isn't working #elif PLATFORM_ANDROID - +#include "Base/NetworkBase.h" // place holder #else #error Missing Network implementation! #endif diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 82b44ace9..cc1bcafcf 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -45,6 +45,8 @@ class Win32Thread; typedef Win32Thread Thread; class UWPWindow; typedef UWPWindow Window; +class NetworkBase; // place holder +typedef NetworkBase Network; // place holder #elif PLATFORM_LINUX @@ -66,6 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; +class NetworkBase; // place holder +typedef NetworkBase Network; // place holder #elif PLATFORM_PS4 @@ -87,6 +91,8 @@ class PS4Thread; typedef PS4Thread Thread; class PS4Window; typedef PS4Window Window; +class NetworkBase; // place holder +typedef NetworkBase Network; // place holder #elif PLATFORM_XBOX_SCARLETT @@ -108,6 +114,8 @@ class Win32Thread; typedef Win32Thread Thread; class XboxScarlettWindow; typedef XboxScarlettWindow Window; +class NetworkBase; // place holder +typedef NetworkBase Network; // place holder #elif PLATFORM_ANDROID @@ -129,6 +137,8 @@ class AndroidThread; typedef AndroidThread Thread; class AndroidWindow; typedef AndroidWindow Window; +class NetworkBase; // place holder +typedef NetworkBase Network; // place holder #else From 3c1a69eb6f04dd0b3c1a8ba46bdf71c28d06db0b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 20:15:13 +0100 Subject: [PATCH 071/222] Replace old getaddrinfo by unicode one. --- Source/Engine/Platform/Win32/Win32Network.cpp | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 6013f92e2..5a05573a6 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -67,9 +67,9 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) return false; } -static void PrintAddrFromInfo(addrinfo& info) +static void PrintAddrFromInfo(addrinfoW& info) { - addrinfo* curr; + addrinfoW* curr; for (curr = &info; curr != nullptr; curr = curr->ai_next) { void* addr; @@ -294,11 +294,8 @@ uint32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffe bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint) { int status; - addrinfo hints; - addrinfo *info; - //TODO: Refactor this crappy thing - char* paddr= address != nullptr ? address->ToStringAnsi().Get() : nullptr; - char* pport = port != nullptr ? port->ToStringAnsi().Get() : nullptr; + addrinfoW hints; + addrinfoW *info; //DEBUG LOG(Info, "Searching available adresses for {0} : {1}", address == nullptr ? String("nullptr").Get() : address->Get(), port == nullptr ? String("nullptr").Get() : port->Get()); @@ -309,21 +306,19 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio if (paddr == nullptr) { hints.ai_flags = AI_PASSIVE; - LOG(Info, "PASSIVE MODE"); - } // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) // can *paddr works ? // paddr = nullptr don't work with this func - if ((status = getaddrinfo(paddr == nullptr ? nullptr : paddr, pport, &hints, &info)) != 0) + if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) { - LOG(Error, "Unable to query info for address : {0} Error : {1} !", address->Get(), gai_strerror(status)); //TODO: address can be NULL + LOG(Error, "Unable to query info for address : {0} Error : {1} !", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); //TODO: address can be NULL return true; } if (info == nullptr) { - LOG(Error, "Unable to resolve address ! Address : {0}", address->Get());//TODO: address can be NULL + LOG(Error, "Unable to resolve address ! Address : {0}", address != nullptr ? address->Get() : String("ANY").Get());//TODO: address can be NULL return true; } @@ -333,10 +328,10 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio // We are taking the first addr in the linked list if (CreateEndPointFromAddr(info->ai_addr, endPoint)) { - freeaddrinfo(info); + FreeAddrInfoW(info); return true; } - freeaddrinfo(info); + FreeAddrInfoW(info); //DEBUG LOG(Info, "Address found : {0} : {1}", endPoint.Address, endPoint.Port); From 54da2c8de026f6865e60bad287d76fdd56e4a0f6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 20:19:21 +0100 Subject: [PATCH 072/222] Fix type. --- Source/Engine/Platform/Base/NetworkBase.cpp | 4 ++-- Source/Engine/Platform/Base/NetworkBase.h | 4 ++-- Source/Engine/Platform/Win32/Win32Network.cpp | 4 ++-- Source/Engine/Platform/Win32/Win32Network.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 79e122f08..6c8c1b822 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -41,12 +41,12 @@ bool NetworkBase::IsReadable(NetworkSocket& socket, uint64* size) return false; } -uint32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { return 0; } -uint32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +int32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) { return 0; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 60231e66c..084f64027 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -55,8 +55,8 @@ class FLAXENGINE_API NetworkBase static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket, uint64* size); - static uint32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); - static uint32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); }; diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 5a05573a6..1b05e12a1 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -228,7 +228,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) return false; } -uint32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { if (socket.IPVersion != endPoint->IPVersion) { @@ -264,7 +264,7 @@ uint32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length /* * TODO: handle size == 0 when there is a shutdown */ -uint32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) { uint32 size; if (endPoint == nullptr) // TCP diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 708462320..abb215842 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -18,8 +18,8 @@ public: static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket, uint64* size); - static uint32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); - static uint32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); friend NetworkEndPoint; From c9655eee8f851aee921c4ed4a6808695c608d7aa Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 20:21:06 +0100 Subject: [PATCH 073/222] Add bindable argument. --- Source/Engine/Platform/Base/NetworkBase.cpp | 2 +- Source/Engine/Platform/Base/NetworkBase.h | 2 +- Source/Engine/Platform/Win32/Win32Network.cpp | 5 ++--- Source/Engine/Platform/Win32/Win32Network.h | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 6c8c1b822..fb1fb7ade 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -51,7 +51,7 @@ int32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferS return 0; } -bool NetworkBase::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint) +bool NetworkBase::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) { return false; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 084f64027..749c5df3c 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -57,7 +57,7 @@ class FLAXENGINE_API NetworkBase static bool IsReadable(NetworkSocket& socket, uint64* size); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); - static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); }; diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 1b05e12a1..3e45e2117 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -291,7 +291,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer } // if address is null, it's ADDR_ANY -bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint) +bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) { int status; addrinfoW hints; @@ -303,8 +303,7 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; hints.ai_flags |= AI_ADDRCONFIG; hints.ai_flags |= AI_V4MAPPED; - if (paddr == nullptr) - { + if (bindable) hints.ai_flags = AI_PASSIVE; // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index abb215842..3388e09f2 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -20,7 +20,7 @@ public: static bool IsReadable(NetworkSocket& socket, uint64* size); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); - static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); friend NetworkEndPoint; friend NetworkSocket; From 3250ca9fad61161a2abb6ca0c7ccdaf1e0772447 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 20:21:44 +0100 Subject: [PATCH 074/222] Add error code to logs. --- Source/Engine/Platform/Win32/Win32Network.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 3e45e2117..f2999f2cf 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -240,7 +240,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, WSAGetLastError()); return -1; } } @@ -248,7 +248,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length); + LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, WSAGetLastError()); return -1; } } @@ -271,7 +271,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer { if ((size = recv(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1}", *(SOCKET*)socket.Data, bufferSize); + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, WSAGetLastError()); return -1; } } @@ -281,7 +281,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1}", *(SOCKET*)socket.Data, bufferSize); + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, WSAGetLastError()); return -1; } if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) From 4a71fe6727c49266a3deff8fe72e58b029001b9b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 20:21:58 +0100 Subject: [PATCH 075/222] Fix crash. --- Source/Engine/Platform/Win32/Win32Network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index f2999f2cf..a75eeaed5 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -277,7 +277,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer } else // UDP { - int32 addrsize; + int32 addrsize = sizeof sockaddr_in6; sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { From c9bab2c521ef60272d1b8d960c78fcc37a765398 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 21:07:19 +0100 Subject: [PATCH 076/222] Remove issue comment, resolved by commit #c9655ee. --- Source/Engine/Platform/Win32/Win32Network.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index a75eeaed5..2047be372 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -17,7 +17,6 @@ static WSAData _wsaData; /* * TODO: Create func to retrieve WSA error string * Known issues : - * Sometimes getaddrinfo fails without reason, re-trying right after works * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation */ From b3388d6fc7167fd40052646b774560236c6767c2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 21:17:14 +0100 Subject: [PATCH 077/222] Replace error code by error strings. --- Source/Engine/Platform/Win32/Win32Network.cpp | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 2047be372..1e9b3fe3c 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -20,6 +20,16 @@ static WSAData _wsaData; * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation */ +static Char* GetLastErrorMessage() +{ + wchar_t *s = NULL; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&s, 0, NULL); + return s; +} + static int GetAddrSize(const sockaddr& addr) { return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; @@ -44,7 +54,7 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) char service[20]; if (getnameinfo(addr, size, name, sizeof name, service, sizeof service, 0) != 0) { - LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", WSAGetLastError()); + LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", String(GetLastErrorMessage()).Get()); return true; } void* paddr; @@ -56,7 +66,7 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) char ip[INET6_ADDRSTRLEN]; if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) { - LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", WSAGetLastError()); + LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", String(GetLastErrorMessage()).Get()); return true; } endPoint.Address = String(ip); @@ -109,34 +119,34 @@ bool Win32Network::CreateSocket(NetworkSocket& netsock, NetworkSocketCreateSetti if ((sock = socket(family, stype, proto)) == INVALID_SOCKET) { - LOG(Error, "Can't create native socket ! Error : {0}", WSAGetLastError()); + LOG(Error, "Can't create native socket ! Error : {0}", String(GetLastErrorMessage()).Get()); return true; } memcpy(netsock.Data, &sock, sizeof sock); DWORD dw = 0; if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) { - LOG(Warning, "System does not support dual stacking socket ! Error : {0}", WSAGetLastError()); + LOG(Warning, "System does not support dual stacking socket ! Error : {0}", String(GetLastErrorMessage()).Get()); } unsigned long value = 1; if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", WSAGetLastError()); + LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", String(GetLastErrorMessage()).Get()); } if (settings.Broadcast && settings.Protocol == NetworkProtocolType::Udp) { if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", WSAGetLastError()); + LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", String(GetLastErrorMessage()).Get()); } } else if (settings.Broadcast) - LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", WSAGetLastError()); + LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", String(GetLastErrorMessage()).Get()); if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { - LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", WSAGetLastError()); + LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", String(GetLastErrorMessage()).Get()); return true; // Support using blocking socket , need to test it } //DEBUG @@ -183,7 +193,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) LOG(Info, "BIND : EndPoint family : {0}", addr->sa_family); if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), WSAGetLastError()); + LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), String(GetLastErrorMessage()).Get()); return true; } //DEBUG @@ -202,7 +212,7 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net sockaddr addr; if ((sock = accept(*(SOCKET*)serverSock.Data, &addr, nullptr)) == INVALID_SOCKET) { - LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, WSAGetLastError()); + LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, String(GetLastErrorMessage()).Get()); return true; } memcpy(newSock.Data, &sock, sizeof sock); @@ -220,7 +230,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) unsigned long value; if (ioctlsocket(*(SOCKET*)socket.Data, FIONREAD, &value) != 0) { - LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, WSAGetLastError()); + LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, String(GetLastErrorMessage()).Get()); return true; } *size = value; @@ -239,7 +249,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, WSAGetLastError()); + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, String(GetLastErrorMessage()).Get()); return -1; } } @@ -247,7 +257,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, WSAGetLastError()); + LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, String(GetLastErrorMessage()).Get()); return -1; } } @@ -270,7 +280,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer { if ((size = recv(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, WSAGetLastError()); + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, String(GetLastErrorMessage()).Get()); return -1; } } @@ -280,7 +290,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, WSAGetLastError()); + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, String(GetLastErrorMessage()).Get()); return -1; } if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) From 261aeb92456b4eba6bf9ca04a04170b693cff0cc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 21:22:38 +0100 Subject: [PATCH 078/222] Fix leak. --- Source/Engine/Platform/Win32/Win32Network.cpp | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 1e9b3fe3c..e41779dac 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -20,14 +20,16 @@ static WSAData _wsaData; * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation */ -static Char* GetLastErrorMessage() +static String GetLastErrorMessage() { wchar_t *s = NULL; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&s, 0, NULL); - return s; + String str(s); + LocalFree(s); + return str; } static int GetAddrSize(const sockaddr& addr) @@ -54,7 +56,7 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) char service[20]; if (getnameinfo(addr, size, name, sizeof name, service, sizeof service, 0) != 0) { - LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", GetLastErrorMessage().Get()); return true; } void* paddr; @@ -66,7 +68,7 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) char ip[INET6_ADDRSTRLEN]; if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) { - LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", GetLastErrorMessage().Get()); return true; } endPoint.Address = String(ip); @@ -119,34 +121,34 @@ bool Win32Network::CreateSocket(NetworkSocket& netsock, NetworkSocketCreateSetti if ((sock = socket(family, stype, proto)) == INVALID_SOCKET) { - LOG(Error, "Can't create native socket ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Error, "Can't create native socket ! Error : {0}", GetLastErrorMessage().Get()); return true; } memcpy(netsock.Data, &sock, sizeof sock); DWORD dw = 0; if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) { - LOG(Warning, "System does not support dual stacking socket ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Warning, "System does not support dual stacking socket ! Error : {0}", GetLastErrorMessage().Get()); } unsigned long value = 1; if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", GetLastErrorMessage().Get()); } if (settings.Broadcast && settings.Protocol == NetworkProtocolType::Udp) { if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", GetLastErrorMessage().Get()); } } else if (settings.Broadcast) - LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", GetLastErrorMessage().Get()); if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { - LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", String(GetLastErrorMessage()).Get()); + LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", GetLastErrorMessage().Get()); return true; // Support using blocking socket , need to test it } //DEBUG @@ -193,7 +195,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) LOG(Info, "BIND : EndPoint family : {0}", addr->sa_family); if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); return true; } //DEBUG @@ -212,7 +214,7 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net sockaddr addr; if ((sock = accept(*(SOCKET*)serverSock.Data, &addr, nullptr)) == INVALID_SOCKET) { - LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, String(GetLastErrorMessage()).Get()); + LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetLastErrorMessage().Get()); return true; } memcpy(newSock.Data, &sock, sizeof sock); @@ -230,7 +232,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) unsigned long value; if (ioctlsocket(*(SOCKET*)socket.Data, FIONREAD, &value) != 0) { - LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); return true; } *size = value; @@ -249,7 +251,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage().Get()); return -1; } } @@ -257,7 +259,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage().Get()); return -1; } } @@ -280,7 +282,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer { if ((size = recv(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); return -1; } } @@ -290,7 +292,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, String(GetLastErrorMessage()).Get()); + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); return -1; } if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) From 86b5fa93213801d93579a5c6b5de867f298ef7ac Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sat, 23 Jan 2021 21:32:07 +0100 Subject: [PATCH 079/222] Remove old comment. --- Source/Engine/Platform/Win32/Win32Network.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index e41779dac..bb2b4e51f 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -15,7 +15,6 @@ static const IN6_ADDR v4MappedPrefix = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x static WSAData _wsaData; /* - * TODO: Create func to retrieve WSA error string * Known issues : * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation */ From 9b300fe074b4ad4c3897d14ad8f8990448d62930 Mon Sep 17 00:00:00 2001 From: Damian Korczowski Date: Sat, 23 Jan 2021 21:33:01 +0100 Subject: [PATCH 080/222] Cleanup --- Source/Engine/Platform/Win32/Win32Network.cpp | 107 +++++++++--------- Source/Engine/Platform/Win32/Win32Network.h | 9 +- 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index bb2b4e51f..29f5ef495 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -5,13 +5,13 @@ #include "Engine/Core/Collections/Array.h" #include #include -#include +#include static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); -static const IN6_ADDR v4MappedPrefix = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}; +static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; static WSAData _wsaData; /* @@ -21,11 +21,11 @@ static WSAData _wsaData; static String GetLastErrorMessage() { - wchar_t *s = NULL; - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, WSAGetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&s, 0, NULL); + wchar_t* s = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); String str(s); LocalFree(s); return str; @@ -47,7 +47,7 @@ static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) } //TODO: can be simplified by casting addr to the right struct and ntohl the port, so we can get rid of getnameinfo -// getnameinfo return a name ( like JimiPC ), not the ip ! +// getnameinfo return a name ( like JimiPC ), not the ip! static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) { uint16 size = GetAddrSize(*addr); @@ -55,7 +55,7 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) char service[20]; if (getnameinfo(addr, size, name, sizeof name, service, sizeof service, 0) != 0) { - LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Unable to extract info from sockaddr! Error : {0}", GetLastErrorMessage().Get()); return true; } void* paddr; @@ -67,7 +67,7 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) char ip[INET6_ADDRSTRLEN]; if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) { - LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Unable to extract address from sockaddr! Error : {0}", GetLastErrorMessage().Get()); return true; } endPoint.Address = String(ip); @@ -83,23 +83,24 @@ static void PrintAddrFromInfo(addrinfoW& info) for (curr = &info; curr != nullptr; curr = curr->ai_next) { void* addr; - if (curr->ai_family == AF_INET) { - sockaddr_in *ipv4 = (struct sockaddr_in *)curr->ai_addr; + if (curr->ai_family == AF_INET) { + sockaddr_in* ipv4 = (struct sockaddr_in*)curr->ai_addr; addr = &(ipv4->sin_addr); - } else { - sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)curr->ai_addr; + } + else { + sockaddr_in6* ipv6 = (struct sockaddr_in6*)curr->ai_addr; addr = &(ipv6->sin6_addr); } - + char str[INET6_ADDRSTRLEN]; inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); - LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString().Get()); + LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString().Get()); } } bool Win32Network::Init() { - if (WSAStartup(MAKEWORD(2,0), &_wsaData) != 0) + if (WSAStartup(MAKEWORD(2, 0), &_wsaData) != 0) return true; return false; } @@ -109,45 +110,45 @@ void Win32Network::Exit() WSACleanup(); } -bool Win32Network::CreateSocket(NetworkSocket& netsock, NetworkSocketCreateSettings& settings) +bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings) { - netsock.Protocol = settings.Protocol; - netsock.IPVersion = settings.IPVersion; + socket.Protocol = settings.Protocol; + socket.IPVersion = settings.IPVersion; const uint8 family = settings.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; const uint8 stype = settings.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; const uint8 proto = settings.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; SOCKET sock; - - if ((sock = socket(family, stype, proto)) == INVALID_SOCKET) + + if ((sock = ::socket(family, stype, proto)) == INVALID_SOCKET) { - LOG(Error, "Can't create native socket ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage().Get()); return true; } - memcpy(netsock.Data, &sock, sizeof sock); + memcpy(socket.Data, &sock, sizeof sock); DWORD dw = 0; if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) { - LOG(Warning, "System does not support dual stacking socket ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Warning, "System does not support dual stacking socket! Error : {0}", GetLastErrorMessage().Get()); } unsigned long value = 1; if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Warning, "Can't set socket option to SO_REUSEADDR! Error : {0}", GetLastErrorMessage().Get()); } if (settings.Broadcast && settings.Protocol == NetworkProtocolType::Udp) { if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Warning, "Can't set socket option to SO_BROADCAST! Error : {0}", GetLastErrorMessage().Get()); } } else if (settings.Broadcast) - LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", GetLastErrorMessage().Get()); - + LOG(Warning, "Can't set socket option to SO_BROADCAST! The socket must use UDP protocol. Error : {0}", GetLastErrorMessage().Get()); + if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { - LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage().Get()); return true; // Support using blocking socket , need to test it } //DEBUG @@ -165,7 +166,7 @@ bool Win32Network::DestroySocket(NetworkSocket& socket) LOG(Info, "Deleting socket : {0}", sock); //TODO: DEBUG return false; } - LOG(Warning, "Unable to delete socket INVALID_SOCKET ! Socket : {0}", sock); + LOG(Warning, "Unable to delete socket INVALID_SOCKET! Socket : {0}", sock); return true; } @@ -175,7 +176,7 @@ bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoin const uint16 size = GetAddrSizeFromEP(endPoint); if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - LOG(Error, "Unable to connect socket to address ! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); return true; } return false; @@ -185,7 +186,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) { if (socket.IPVersion != endPoint.IPVersion) { - LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion ! Socket : {0}", *(SOCKET*)socket.Data); + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); return true; } @@ -194,11 +195,11 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) LOG(Info, "BIND : EndPoint family : {0}", addr->sa_family); if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); + LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); return true; } //DEBUG - LOG(Info, "Binding socket ! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + LOG(Info, "Binding socket! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); return false; } @@ -206,14 +207,14 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net { if (serverSock.Protocol != NetworkProtocolType::Tcp) { - LOG(Warning, "Can't accept connection on UDP socket ! Socket : {0}", *(SOCKET*)serverSock.Data); + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSock.Data); return true; } SOCKET sock; sockaddr addr; if ((sock = accept(*(SOCKET*)serverSock.Data, &addr, nullptr)) == INVALID_SOCKET) { - LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetLastErrorMessage().Get()); + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetLastErrorMessage().Get()); return true; } memcpy(newSock.Data, &sock, sizeof sock); @@ -231,7 +232,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) unsigned long value; if (ioctlsocket(*(SOCKET*)socket.Data, FIONREAD, &value) != 0) { - LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + LOG(Error, "Unable to query socket for readability! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); return true; } *size = value; @@ -242,7 +243,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if (socket.IPVersion != endPoint->IPVersion) { - LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion ! Socket : {0}", *(SOCKET*)socket.Data); + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); return 0; } uint32 size; @@ -250,7 +251,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage().Get()); + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage().Get()); return -1; } } @@ -258,14 +259,14 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) { - LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage().Get()); + LOG(Error, "Unable to send data! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage().Get()); return -1; } } else { //TODO: better explanation - LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); return -1; } return size; @@ -279,9 +280,9 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer uint32 size; if (endPoint == nullptr) // TCP { - if ((size = recv(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0)) == SOCKET_ERROR) + if ((size = recv(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); return -1; } } @@ -289,9 +290,9 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer { int32 addrsize = sizeof sockaddr_in6; sockaddr_in6 addr; - if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) + if ((size = recvfrom(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); return -1; } if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) @@ -305,7 +306,7 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio { int status; addrinfoW hints; - addrinfoW *info; + addrinfoW* info; //DEBUG LOG(Info, "Searching available adresses for {0} : {1}", address == nullptr ? String("nullptr").Get() : address->Get(), port == nullptr ? String("nullptr").Get() : port->Get()); @@ -321,19 +322,19 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio // paddr = nullptr don't work with this func if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) { - LOG(Error, "Unable to query info for address : {0} Error : {1} !", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); //TODO: address can be NULL + LOG(Error, "Unable to query info for address : {0} Error : {1}", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); //TODO: address can be NULL return true; } if (info == nullptr) { - LOG(Error, "Unable to resolve address ! Address : {0}", address != nullptr ? address->Get() : String("ANY").Get());//TODO: address can be NULL + LOG(Error, "Unable to resolve address! Address : {0}", address != nullptr ? address->Get() : String("ANY").Get());//TODO: address can be NULL return true; } //DEBUG PrintAddrFromInfo(*info); - + // We are taking the first addr in the linked list if (CreateEndPointFromAddr(info->ai_addr, endPoint)) { @@ -351,7 +352,7 @@ NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) { if (endPoint.IPVersion == NetworkIPVersion::IPv6) { - LOG(Warning, "Unable to remap EndPoint, already in IPv6 format !"); + LOG(Warning, "Unable to remap EndPoint, already in IPv6 format!"); return endPoint; } @@ -369,6 +370,6 @@ NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) memcpy(&addr6->sin6_addr.u.Byte[12], &addr4->sin_addr, 4); // :::::FFFF:XXXX:XXXX X=IPv4 pv6.IPVersion = NetworkIPVersion::IPv6; pv6.Port = endPoint.Port; - + return pv6; } diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 3388e09f2..fc1ca093b 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #pragma once @@ -7,7 +7,10 @@ #include "Engine/Platform/Base/NetworkBase.h" class FLAXENGINE_API Win32Network : public NetworkBase -{ +{ + friend NetworkEndPoint; + friend NetworkSocket; + public: // [NetworkBase] static bool Init(); @@ -22,8 +25,6 @@ public: static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); - friend NetworkEndPoint; - friend NetworkSocket; }; #endif From 3ab54bd7e12c0cf70e69193a854de2271d65f4b0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sun, 24 Jan 2021 00:02:24 +0100 Subject: [PATCH 081/222] Add listen implementation. --- Source/Engine/Platform/Base/NetworkBase.cpp | 5 +++++ Source/Engine/Platform/Base/NetworkBase.h | 1 + Source/Engine/Platform/Win32/Win32Network.cpp | 10 ++++++++++ Source/Engine/Platform/Win32/Win32Network.h | 1 + 4 files changed, 17 insertions(+) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index fb1fb7ade..389f58335 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -31,6 +31,11 @@ bool NetworkBase::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) return false; } +bool NetworkBase::Listen(NetworkSocket& socket, int32 queueSize) +{ + return false; +} + bool NetworkBase::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) { return false; diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 749c5df3c..6b33d7b1e 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -53,6 +53,7 @@ class FLAXENGINE_API NetworkBase static bool DestroySocket(NetworkSocket& socket); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, int32 queueSize); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket, uint64* size); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 29f5ef495..cec785225 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -203,6 +203,16 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) return false; } +bool Win32Network::Listen(NetworkSocket& socket, int32 queueSize) +{ + if (listen(*(SOCKET*)socket.Data, (int32)queueSize) == SOCKET_ERROR) + { + LOG(Error, "Unable to listen ! Socket : {0} Error : {1}", GetLastErrorMessage().Get()); + return true; + } + return false; +} + bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) { if (serverSock.Protocol != NetworkProtocolType::Tcp) diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index fc1ca093b..8f4d375d0 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -19,6 +19,7 @@ public: static bool DestroySocket(NetworkSocket& socket); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, int32 queueSize); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket, uint64* size); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); From 9b2c940896a24ebac6764dd47fc086e7c2a72950 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sun, 24 Jan 2021 11:39:59 +0100 Subject: [PATCH 082/222] Switch type. --- Source/Engine/Platform/Base/NetworkBase.cpp | 2 +- Source/Engine/Platform/Base/NetworkBase.h | 2 +- Source/Engine/Platform/Win32/Win32Network.cpp | 2 +- Source/Engine/Platform/Win32/Win32Network.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 389f58335..8c28f26bd 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -31,7 +31,7 @@ bool NetworkBase::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) return false; } -bool NetworkBase::Listen(NetworkSocket& socket, int32 queueSize) +bool NetworkBase::Listen(NetworkSocket& socket, uint16 queueSize) { return false; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 6b33d7b1e..142e7ead1 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -53,7 +53,7 @@ class FLAXENGINE_API NetworkBase static bool DestroySocket(NetworkSocket& socket); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); - static bool Listen(NetworkSocket& socket, int32 queueSize); + static bool Listen(NetworkSocket& socket, uint16 queueSize); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket, uint64* size); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index cec785225..6d26b9ba5 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -203,7 +203,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) return false; } -bool Win32Network::Listen(NetworkSocket& socket, int32 queueSize) +bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) { if (listen(*(SOCKET*)socket.Data, (int32)queueSize) == SOCKET_ERROR) { diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 8f4d375d0..69f37c8c6 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -19,7 +19,7 @@ public: static bool DestroySocket(NetworkSocket& socket); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); - static bool Listen(NetworkSocket& socket, int32 queueSize); + static bool Listen(NetworkSocket& socket, uint16 queueSize); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket, uint64* size); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); From b4f791e0cac697b85f7f89e90b79b09b3d8fd839 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sun, 24 Jan 2021 11:42:11 +0100 Subject: [PATCH 083/222] Remove comment. --- Source/Engine/Platform/Win32/Win32Network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 6d26b9ba5..02d7be637 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -332,13 +332,13 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio // paddr = nullptr don't work with this func if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) { - LOG(Error, "Unable to query info for address : {0} Error : {1}", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); //TODO: address can be NULL + LOG(Error, "Unable to query info for address : {0} Error : {1}", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); return true; } if (info == nullptr) { - LOG(Error, "Unable to resolve address! Address : {0}", address != nullptr ? address->Get() : String("ANY").Get());//TODO: address can be NULL + LOG(Error, "Unable to resolve address! Address : {0}", address != nullptr ? address->Get() : String("ANY").Get()); return true; } From 6e193d4d0d741e3b9134063f1cda199352a204cc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sun, 24 Jan 2021 22:54:44 +0100 Subject: [PATCH 084/222] Add Set/Get sockoption, refactor CreateSocket. --- Source/Engine/Platform/Base/NetworkBase.cpp | 22 ++++- Source/Engine/Platform/Base/NetworkBase.h | 34 ++++--- Source/Engine/Platform/Win32/Win32Network.cpp | 90 +++++++++++++++++-- Source/Engine/Platform/Win32/Win32Network.h | 6 +- 4 files changed, 133 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 8c28f26bd..d2b48206e 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -11,7 +11,7 @@ void NetworkBase::Exit() { } -bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings) +bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) { return false; } @@ -21,6 +21,26 @@ bool NetworkBase::DestroySocket(NetworkSocket& socket) return false; } +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value) +{ + return false; +} + +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value) +{ + return false; +} + +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value) +{ + return false; +} + +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value) +{ + return false; +} + bool NetworkBase::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) { return false; diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 142e7ead1..3731bbda6 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -20,15 +20,6 @@ enum class FLAXENGINE_API NetworkIPVersion IPv6 }; -struct FLAXENGINE_API NetworkSocketCreateSettings -{ - NetworkProtocolType Protocol = NetworkProtocolType::Udp; - NetworkIPVersion IPVersion = NetworkIPVersion::IPv4; - bool DualStacking = true; - bool ReuseAddress = true; - bool Broadcast = false; -}; - struct FLAXENGINE_API NetworkSocket { NetworkProtocolType Protocol = NetworkProtocolType::Undefined; @@ -44,13 +35,36 @@ struct FLAXENGINE_API NetworkEndPoint byte Data[28] = {}; // sizeof sockaddr_in6 , biggest sockaddr that we will use }; +enum FLAXENGINE_API NetworkSocketOption +{ + Debug, + ReuseAddr, + KeepAlive, + DontRoute, + Broadcast, + UseLoopback, + Linger, + OOBInline, + SendBuffer, + RecvBuffer, + SendTimeout, + RecvTimeout, + Error, + NoDelay, + IPv6Only +}; + class FLAXENGINE_API NetworkBase { public: static bool Init(); static void Exit(); - static bool CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings); + static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 02d7be637..f8325d6ff 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -7,6 +7,8 @@ #include #include +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) if (option == OPTENUM) { optlvl = OPTLEVEL; optnme = OPTNAME;} + static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); @@ -110,13 +112,13 @@ void Win32Network::Exit() WSACleanup(); } -bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings) +bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) { - socket.Protocol = settings.Protocol; - socket.IPVersion = settings.IPVersion; - const uint8 family = settings.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; - const uint8 stype = settings.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; - const uint8 proto = settings.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + socket.Protocol = proto; + socket.IPVersion = ipv; + const uint8 family = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const uint8 stype = socket.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const uint8 proto = socket.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; SOCKET sock; if ((sock = ::socket(family, stype, proto)) == INVALID_SOCKET) @@ -125,11 +127,13 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettin return true; } memcpy(socket.Data, &sock, sizeof sock); + /* DWORD dw = 0; if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) { LOG(Warning, "System does not support dual stacking socket! Error : {0}", GetLastErrorMessage().Get()); } + unsigned long value = 1; if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) { @@ -145,7 +149,8 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettin } else if (settings.Broadcast) LOG(Warning, "Can't set socket option to SO_BROADCAST! The socket must use UDP protocol. Error : {0}", GetLastErrorMessage().Get()); - +*/ + unsigned long value = 1; if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage().Get()); @@ -170,6 +175,77 @@ bool Win32Network::DestroySocket(NetworkSocket& socket) return true; } +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value) +{ + int32 v = value; + return SetSocketOption(socket, option, v); +} + +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value) +{ + int32 optlvl = 0; + int32 optnme = 0; + + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, sizeof int32) == SOCKET_ERROR) + { + LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + return true; + } + return false; +} + +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value) +{ + int32 v; + bool status = GetSocketOption(socket, option, &v); + *value = v; + return status; +} + +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value) +{ + int32 optlvl = 0; + int32 optnme = 0; + + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + + int32 size; + if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) + { + LOG(Warning, "Unable to get socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + return true; + } + return false; +} bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) { diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 69f37c8c6..805f68c0a 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -15,8 +15,12 @@ public: // [NetworkBase] static bool Init(); static void Exit(); - static bool CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings); + static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); From ad621b824a851113f99d0bacfdacc9d6fa0765ef Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Sun, 24 Jan 2021 22:59:57 +0100 Subject: [PATCH 085/222] Add some const. --- Source/Engine/Platform/Win32/Win32Network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index f8325d6ff..09ae94727 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -177,7 +177,7 @@ bool Win32Network::DestroySocket(NetworkSocket& socket) bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value) { - int32 v = value; + const int32 v = value; return SetSocketOption(socket, option, v); } @@ -213,7 +213,7 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& o bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value) { int32 v; - bool status = GetSocketOption(socket, option, &v); + const bool status = GetSocketOption(socket, option, &v); *value = v; return status; } From fe78fa757585b5e1315f77069eaed064cf7952fe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 25 Jan 2021 10:41:53 +0100 Subject: [PATCH 086/222] Add Spline --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 19 + Source/Editor/SceneGraph/SceneGraphFactory.cs | 1 + .../Editor/Windows/SceneTreeWindow.Actors.cs | 1 + Source/Editor/Windows/ToolboxWindow.cs | 1 + Source/Engine/Animations/AnimationUtils.h | 25 ++ Source/Engine/Animations/Curve.cs | 3 + Source/Engine/Animations/Curve.h | 44 +++ Source/Engine/Animations/CurveSerialization.h | 171 +++++++++ Source/Engine/Core/Math/Color.h | 8 + Source/Engine/Core/Math/Color32.h | 8 + Source/Engine/Core/Math/Transform.h | 8 + Source/Engine/Core/Math/Vector4.h | 2 +- .../Engine/Level/Actors/ModelInstanceActor.h | 2 +- Source/Engine/Level/Actors/Spline.cpp | 335 ++++++++++++++++++ Source/Engine/Level/Actors/Spline.h | 247 +++++++++++++ Source/Engine/Level/Spline.cs | 41 +++ 16 files changed, 914 insertions(+), 2 deletions(-) create mode 100644 Source/Editor/SceneGraph/Actors/SplineNode.cs create mode 100644 Source/Engine/Animations/CurveSerialization.h create mode 100644 Source/Engine/Level/Actors/Spline.cpp create mode 100644 Source/Engine/Level/Actors/Spline.h create mode 100644 Source/Engine/Level/Spline.cs diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs new file mode 100644 index 000000000..ea826c29e --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Scene tree node for actor type. + /// + [HideInEditor] + public sealed class SplineNode : ActorNode + { + /// + public SplineNode(Actor actor) + : base(actor) + { + } + } +} diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 0076742b3..5f3dd8b0f 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -65,6 +65,7 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(NavModifierVolume), typeof(NavModifierVolumeNode)); CustomNodesTypes.Add(typeof(ParticleEffect), typeof(ParticleEffectNode)); CustomNodesTypes.Add(typeof(SceneAnimationPlayer), typeof(SceneAnimationPlayerNode)); + CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode)); } /// diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs index 6fafe444f..8970b6a48 100644 --- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs +++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs @@ -99,6 +99,7 @@ namespace FlaxEditor.Windows new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)), new KeyValuePair("Nav Link", typeof(NavLink)), new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)), + new KeyValuePair("Spline", typeof(Spline)), } }, new ActorsGroup diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 8c30621b0..63e1f1f05 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -167,6 +167,7 @@ namespace FlaxEditor.Windows groupOther.AddChild(CreateActorItem("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume))); groupOther.AddChild(CreateActorItem("Nav Link", typeof(NavLink))); groupOther.AddChild(CreateActorItem("Nav Modifier Volume", typeof(NavModifierVolume))); + groupOther.AddChild(CreateActorItem("Spline", typeof(Spline))); var groupGui = CreateGroupWithList(actorGroups, "GUI"); groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl))); diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index 248efea0c..a7f85f124 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -7,6 +7,7 @@ #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/Transform.h" namespace AnimationUtils { @@ -40,6 +41,12 @@ namespace AnimationUtils return Quaternion::Identity; } + template<> + FORCE_INLINE Transform GetZero() + { + return Transform::Identity; + } + template<> FORCE_INLINE Color GetZero() { @@ -66,6 +73,16 @@ namespace AnimationUtils Quaternion::Slerp(a, b, oneThird, result); } + template<> + FORCE_INLINE void GetTangent(const Transform& a, const Transform& b, float length, Transform& result) + { + const float oneThird = 1.0f / 3.0f; + const float oneThirdLength = length * oneThird; + result.Translation = a.Translation + b.Translation * oneThirdLength; + Quaternion::Slerp(a.Orientation, b.Orientation, oneThird, result.Orientation); + result.Scale = a.Scale + b.Scale * oneThirdLength; + } + template FORCE_INLINE static void Interpolate(const T& a, const T& b, float alpha, T& result) { @@ -203,4 +220,12 @@ namespace AnimationUtils Quaternion::Slerp(p12, p23, alpha, p123); Quaternion::Slerp(p012, p123, alpha, result); } + + template<> + void Bezier(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float alpha, Transform& result) + { + Bezier(p0.Translation, p1.Translation, p2.Translation, p3.Translation, alpha, result.Translation); + Bezier(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, alpha, result.Orientation); + Bezier(p0.Scale, p1.Scale, p2.Scale, p3.Scale, alpha, result.Scale); + } } diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs index d9762d05f..3a8ddc100 100644 --- a/Source/Engine/Animations/Curve.cs +++ b/Source/Engine/Animations/Curve.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace FlaxEngine { @@ -338,6 +339,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into linear curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// @@ -586,6 +588,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into Bezier curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 8261dea5b..618e0ee0c 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -52,6 +52,11 @@ public: { result = a; } + + bool operator==(const StepCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -97,6 +102,11 @@ public: result.Time = a.Time + (b.Time - a.Time) * alpha; AnimationUtils::Interpolate(a.Value, b.Value, alpha, result.Value); } + + bool operator==(const LinearCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -164,6 +174,11 @@ public: result.TangentIn /= length; result.TangentOut = result.TangentIn; } + + bool operator==(const HermiteCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); /// @@ -240,6 +255,11 @@ public: result.TangentIn = a.TangentOut; result.TangentOut = b.TangentIn; } + + bool operator==(const BezierCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); // @formatter:on @@ -708,6 +728,30 @@ public: return false; } + +public: + + FORCE_INLINE KeyFrame& operator[](int32 index) + { + return _keyframes[index]; + } + + FORCE_INLINE const KeyFrame& operator[](int32 index) const + { + return _keyframes[index]; + } + + bool operator==(const Curve& other) const + { + if (_keyframes.Count() != other._keyframes.Count()) + return false; + for (int32 i = 0; i < _keyframes.Count(); i++) + { + if (!(_keyframes[i] == other._keyframes[i])) + return false; + } + return true; + } }; /// diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h new file mode 100644 index 000000000..06f7788ea --- /dev/null +++ b/Source/Engine/Animations/CurveSerialization.h @@ -0,0 +1,171 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Curve.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Serialization/Serialization.h" + +// @formatter:off + +namespace Serialization +{ + // StepCurveKeyframe + + template + inline bool ShouldSerialize(const StepCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const StepCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const StepCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, StepCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // LinearCurveKeyframe + + template + inline bool ShouldSerialize(const LinearCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const LinearCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const LinearCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, LinearCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // HermiteCurveKeyframe + + template + inline bool ShouldSerialize(const HermiteCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const HermiteCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const HermiteCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, HermiteCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // BezierCurveKeyframe + + template + inline bool ShouldSerialize(const BezierCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const BezierCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const BezierCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, BezierCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // Curve + + template + inline bool ShouldSerialize(const Curve& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const Curve*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const Curve& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Keyframes"); + stream.StartArray(); + for (auto& k : v.GetKeyframes()) + Serialize(stream, k, nullptr); + stream.EndArray(); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, Curve& v, ISerializeModifier* modifier) + { + if (!stream.IsObject()) + return; + const auto mKeyframes = SERIALIZE_FIND_MEMBER(stream, "Keyframes"); + if (mKeyframes != stream.MemberEnd()) + { + const auto& keyframesArray = mKeyframes->value.GetArray(); + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(keyframesArray.Size()); + for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++) + Deserialize(keyframesArray[i], keyframes[i], modifier); + } + } +} + +// @formatter:on diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index 3eee6d9f8..5a7986517 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -506,6 +506,14 @@ inline Color operator*(float a, const Color& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color& a, const Color& b) + { + return Color::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Color32.h b/Source/Engine/Core/Math/Color32.h index 89f64cc35..f848f6f73 100644 --- a/Source/Engine/Core/Math/Color32.h +++ b/Source/Engine/Core/Math/Color32.h @@ -231,6 +231,14 @@ inline Color32 operator*(float a, const Color32& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color32& a, const Color32& b) + { + return a == b; + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index ed19efeae..c5e67c986 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -293,6 +293,14 @@ public: static void Lerp(const Transform& t1, const Transform& t2, float amount, Transform& result); }; +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Transform& a, const Transform& b) + { + return Transform::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h index adca955fc..c7771aa67 100644 --- a/Source/Engine/Core/Math/Vector4.h +++ b/Source/Engine/Core/Math/Vector4.h @@ -379,7 +379,7 @@ public: static bool NearEqual(const Vector4& a, const Vector4& b) { - return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) & Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); + return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) && Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); } static bool NearEqual(const Vector4& a, const Vector4& b, float epsilon) diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 63c6c4807..c431429fd 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -42,7 +42,7 @@ public: /// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot. /// /// The material slot entry index. - /// The material to set.. + /// The material to set. API_FUNCTION() void SetMaterial(int32 entryIndex, MaterialBase* material); /// diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp new file mode 100644 index 000000000..509c2f389 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -0,0 +1,335 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Animations/CurveSerialization.h" +#include + +Spline::Spline(const SpawnParams& params) + : Actor(params) +{ +} + +void Spline::SetIsLoop(bool value) +{ + if (_loop != value) + { + _loop = value; + UpdateSpline(); + } +} + +Vector3 Spline::GetSplinePoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Translation; +} + +Quaternion Spline::GetSplineOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + Quaternion::Multiply(_transform.Orientation, t.Orientation, t.Orientation); + t.Orientation.Normalize(); + return t.Orientation; +} + +Quaternion Spline::GetSplineLocalOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Orientation; +} + +Vector3 Spline::GetSplineScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.Scale * t.Scale; +} + +Vector3 Spline::GetSplineLocalScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Scale; +} + +Transform Spline::GetSplineTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t); +} + +Transform Spline::GetSplineLocalTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t; +} + +Vector3 Spline::GetSplinePoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return _transform.LocalToWorld(Curve[index].Value.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return Curve[index].Value.Translation; +} + +Transform Spline::GetSplineTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return _transform.LocalToWorld(Curve[index].Value); +} + +Transform Spline::GetSplineLocalTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return Curve[index].Value; +} + +int32 Spline::GetSplinePointsCount() const +{ + return Curve.GetKeyframes().Count(); +} + +float Spline::GetSplineDuration() const +{ + return Curve.GetLength(); +} + +float Spline::GetSplineLength() const +{ + float sum = 0.0f; + for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++) + sum += Vector3::DistanceSquared(Curve[i - 1].Value.Translation * _transform.Scale, Curve[i].Value.Translation * _transform.Scale); + return Math::Sqrt(sum); +} + +namespace +{ + void FindTimeClosestToPoint(const Vector3& point, const Spline::Keyframe& start, const Spline::Keyframe& end, float& bestDistanceSquared, float& bestTime) + { + // TODO: implement sth more analytical than brute-force solution + const int32 slices = 100; + const float step = 1.0f / (float)slices; + const float length = Math::Abs(end.Time - start.Time); + for (int32 i = 0; i <= slices; i++) + { + const float t = (float)i * step; + Transform result; + Spline::Keyframe::Interpolate(start, end, t, length, result); + const float distanceSquared = Vector3::DistanceSquared(point, result.Translation); + if (distanceSquared < bestDistanceSquared) + { + bestDistanceSquared = distanceSquared; + bestTime = start.Time + t * length; + } + } + } +} + +float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const +{ + const int32 pointsCount = Curve.GetKeyframes().Count(); + if (pointsCount == 0) + return 0.0f; + if (pointsCount == 1) + return Curve[0].Time; + const Vector3 localPoint = _transform.WorldToLocal(point); + float bestDistanceSquared = MAX_float; + float bestTime = 0.0f; + for (int32 i = 1; i < pointsCount; i++) + FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime); + if (_loop) + FindTimeClosestToPoint(localPoint, Curve[pointsCount - 1], Curve[0], bestDistanceSquared, bestTime); + return bestTime; +} + +Vector3 Spline::GetSplinePointClosestToPoint(const Vector3& point) const +{ + return GetSplinePoint(GetSplineTimeClosestToPoint(point)); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value.Translation)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value.Translation); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value); +} + +void Spline::ClearSpline() +{ + if (Curve.IsEmpty()) + return; + Curve.Clear(); + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, point); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::UpdateSpline() +{ +} + +void Spline::GetKeyframes(MonoArray* data) +{ + Platform::MemoryCopy(mono_array_addr_with_size(data, sizeof(Keyframe), 0), Curve.GetKeyframes().Get(), sizeof(Keyframe) * Curve.GetKeyframes().Count()); +} + +void Spline::SetKeyframes(MonoArray* data) +{ + const auto count = (int32)mono_array_length(data); + Curve.GetKeyframes().Resize(count, false); + Platform::MemoryCopy(Curve.GetKeyframes().Get(), mono_array_addr_with_size(data, sizeof(Keyframe), 0), sizeof(Keyframe) * count); + UpdateSpline(); +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +namespace +{ + void DrawSegment(Spline* spline, int32 start, int32 end, const Color& color, const Transform& transform, bool depthTest) + { + const auto& startKey = spline->Curve[start]; + const auto& endKey = spline->Curve[end]; + const Vector3 startPos = transform.LocalToWorld(startKey.Value.Translation); + const Vector3 startTangent = transform.LocalToWorld(startKey.TangentOut.Translation); + const Vector3 endPos = transform.LocalToWorld(endKey.Value.Translation); + const Vector3 endTangent = transform.LocalToWorld(endKey.TangentIn.Translation); + const float d = (endKey.Time - startKey.Time) / 3.0f; + DEBUG_DRAW_BEZIER(startPos, startPos + startTangent * d, endPos + endTangent * d, endPos, color, 0.0f, depthTest); + } + + void DrawSpline(Spline* spline, const Color& color, const Transform& transform, bool depthTest) + { + const int32 count = spline->Curve.GetKeyframes().Count(); + for (int32 i = 0; i < count; i++) + { + Vector3 p = transform.LocalToWorld(spline->Curve[i].Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(p, 5.0f), color, 0.0f, true); + if (i != 0) + DrawSegment(spline, i - 1, i, color, transform, depthTest); + } + if (spline->GetIsLoop() && count > 1) + DrawSegment(spline, count - 1, 0, color, transform, depthTest); + } +} + +void Spline::OnDebugDraw() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true); + + // Base + Actor::OnDebugDraw(); +} + +void Spline::OnDebugDrawSelected() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false); + DrawSpline(this, color, _transform, true); + + // Base + Actor::OnDebugDrawSelected(); +} + +#endif + +void Spline::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(Spline); + + SERIALIZE_MEMBER(IsLoop, _loop); + SERIALIZE(Curve); +} + +void Spline::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(IsLoop, _loop); + DESERIALIZE(Curve); + + // Initialize spline when loading data during gameplay + if (IsDuringPlay()) + { + UpdateSpline(); + } +} + +void Spline::OnEnable() +{ + // Base + Actor::OnEnable(); + + // Initialize spline + UpdateSpline(); +} diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h new file mode 100644 index 000000000..a94804ea7 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.h @@ -0,0 +1,247 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../Actor.h" +#include "Engine/Animations/Curve.h" + +/// +/// Spline shape actor that defines spatial curve with utility functions for general purpose usage. +/// +API_CLASS() class FLAXENGINE_API Spline : public Actor +{ +DECLARE_SCENE_OBJECT(Spline); + typedef BezierCurveKeyframe Keyframe; +private: + + bool _loop = false; + +public: + + /// + /// The spline bezier curve points represented as series of transformations in 3D space (with tangents). Points are stored in local-space of the actor. + /// + /// Ensure to call UpdateSpline() after editing curve to reflect the changes. + BezierCurve Curve; + + /// + /// Whether to use spline as closed loop. + /// + API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")") + FORCE_INLINE bool GetIsLoop() const + { + return _loop; + } + + /// + /// Whether to use spline as closed loop. + /// + API_PROPERTY() void SetIsLoop(bool value); + +public: + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (world-space). + API_FUNCTION() Quaternion GetSplineOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (local-space). + API_FUNCTION() Quaternion GetSplineLocalOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (world-space). + API_FUNCTION() Vector3 GetSplineScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (local-space). + API_FUNCTION() Vector3 GetSplineLocalScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(float time) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(int32 index) const; + + /// + /// Gets the amount of points in the spline. + /// + API_PROPERTY() int32 GetSplinePointsCount() const; + + /// + /// Gets the total duration of the spline curve (time of the last point). + /// + API_PROPERTY() float GetSplineDuration() const; + + /// + /// Gets the total length of the spline curve (distance between all the points). + /// + API_PROPERTY() float GetSplineLength() const; + + /// + /// Calculates the closest point to the given location and returns the spline time at that point. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline time. + API_FUNCTION() float GetSplineTimeClosestToPoint(const Vector3& point) const; + + /// + /// Calculates the closest point to the given location. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline position. + API_FUNCTION() Vector3 GetSplinePointClosestToPoint(const Vector3& point) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + +public: + + /// + /// Clears the spline to be empty. + /// + API_FUNCTION() void ClearSpline(); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Transform& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true); + +public: + + /// + /// Updates the spline after it was modified. Recreates the collision and/or any cached state that depends on the spline type. + /// + API_FUNCTION() virtual void UpdateSpline(); + +protected: + +#if USE_EDITOR + virtual Color GetSplineColor() + { + return Color::White; + } +#endif + +private: + + // Internal bindings + API_FUNCTION(NoProxy) void GetKeyframes(MonoArray* data); + API_FUNCTION(NoProxy) void SetKeyframes(MonoArray* data); + +public: + + // [Actor] +#if USE_EDITOR + void OnDebugDraw() override; + void OnDebugDrawSelected() override; +#endif + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnEnable() override; +}; diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs new file mode 100644 index 000000000..46a05cd5d --- /dev/null +++ b/Source/Engine/Level/Spline.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Runtime.InteropServices; + +namespace FlaxEngine +{ + partial class Spline + { + private BezierCurve.Keyframe[] _keyframes; + + /// + /// Gets or sets the spline keyframes collection. + /// + [Unmanaged] + [Tooltip("Spline keyframes collection.")] + [EditorOrder(10), EditorDisplay("Spline"), Collection(CanReorderItems = false)] + public BezierCurve.Keyframe[] SplineKeyframes + { + get + { + var count = SplinePointsCount; + if (_keyframes == null || _keyframes.Length != count) + _keyframes = new BezierCurve.Keyframe[count]; +#if !BUILD_RELEASE + if (Marshal.SizeOf(typeof(BezierCurve.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float)) + throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve.Keyframe)) + " bytes."); +#endif + Internal_GetKeyframes(__unmanagedPtr, _keyframes); + return _keyframes; + } + set + { + if (value == null) + value = Utils.GetEmptyArray.Keyframe>(); + _keyframes = null; + Internal_SetKeyframes(__unmanagedPtr, value); + } + } + } +} From 075b049bb1d6edefa8a15387505e37b174965d03 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 12:25:20 +0100 Subject: [PATCH 087/222] Fixes. --- Source/Engine/Platform/Base/NetworkBase.cpp | 8 ++-- Source/Engine/Platform/Base/NetworkBase.h | 8 ++-- Source/Engine/Platform/Win32/Win32Network.cpp | 39 ++++--------------- Source/Engine/Platform/Win32/Win32Network.h | 8 ++-- 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index d2b48206e..73b649281 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -21,22 +21,22 @@ bool NetworkBase::DestroySocket(NetworkSocket& socket) return false; } -bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value) +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) { return false; } -bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value) +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) { return false; } -bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value) +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) { return false; } -bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value) +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) { return false; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 3731bbda6..a568fd215 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -61,10 +61,10 @@ class FLAXENGINE_API NetworkBase static void Exit(); static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value); - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value); - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value); - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 09ae94727..38018f6ff 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -118,38 +118,15 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto socket.IPVersion = ipv; const uint8 family = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; const uint8 stype = socket.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; - const uint8 proto = socket.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + const uint8 prot = socket.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; SOCKET sock; - if ((sock = ::socket(family, stype, proto)) == INVALID_SOCKET) + if ((sock = ::socket(family, stype, prot)) == INVALID_SOCKET) { LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage().Get()); return true; } memcpy(socket.Data, &sock, sizeof sock); - /* - DWORD dw = 0; - if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) - { - LOG(Warning, "System does not support dual stacking socket! Error : {0}", GetLastErrorMessage().Get()); - } - - unsigned long value = 1; - if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) - { - LOG(Warning, "Can't set socket option to SO_REUSEADDR! Error : {0}", GetLastErrorMessage().Get()); - } - - if (settings.Broadcast && settings.Protocol == NetworkProtocolType::Udp) - { - if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof value) == SOCKET_ERROR) - { - LOG(Warning, "Can't set socket option to SO_BROADCAST! Error : {0}", GetLastErrorMessage().Get()); - } - } - else if (settings.Broadcast) - LOG(Warning, "Can't set socket option to SO_BROADCAST! The socket must use UDP protocol. Error : {0}", GetLastErrorMessage().Get()); -*/ unsigned long value = 1; if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { @@ -175,13 +152,13 @@ bool Win32Network::DestroySocket(NetworkSocket& socket) return true; } -bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value) +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) { const int32 v = value; return SetSocketOption(socket, option, v); } -bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value) +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) { int32 optlvl = 0; int32 optnme = 0; @@ -202,7 +179,7 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& o SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) - if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, sizeof int32) == SOCKET_ERROR) + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) { LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); return true; @@ -210,15 +187,15 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption& o return false; } -bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value) +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) { int32 v; const bool status = GetSocketOption(socket, option, &v); - *value = v; + *value = v == 1 ? true : false; return status; } -bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value) +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) { int32 optlvl = 0; int32 optnme = 0; diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 805f68c0a..8cbcf57ec 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -17,10 +17,10 @@ public: static void Exit(); static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool value); - static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32 value); - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, bool* value); - static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption& option, int32* value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); From dae366f04ca942112a3d2fa305a65bcacc800976 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 12:40:08 +0100 Subject: [PATCH 088/222] Cleanup. --- Source/Engine/Platform/Win32/Win32Network.cpp | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 38018f6ff..68a973547 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -133,8 +133,6 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage().Get()); return true; // Support using blocking socket , need to test it } - //DEBUG - LOG(Info, "New socket created : {0}", sock); //TODO: DEBUG return false; } @@ -144,8 +142,6 @@ bool Win32Network::DestroySocket(NetworkSocket& socket) if (sock != INVALID_SOCKET) { closesocket(sock); - //DEBUG - LOG(Info, "Deleting socket : {0}", sock); //TODO: DEBUG return false; } LOG(Warning, "Unable to delete socket INVALID_SOCKET! Socket : {0}", sock); @@ -251,8 +247,6 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); return true; } - //DEBUG - LOG(Info, "Binding socket! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); return false; } @@ -282,7 +276,6 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net } memcpy(newSock.Data, &sock, sizeof sock); memcpy(newEndPoint.Data, &addr, GetAddrSize(addr)); - newSock.Protocol = serverSock.Protocol; newSock.IPVersion = serverSock.IPVersion; if (CreateEndPointFromAddr(&addr, newEndPoint)) @@ -335,9 +328,6 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, return size; } -/* - * TODO: handle size == 0 when there is a shutdown - */ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) { uint32 size; @@ -364,15 +354,11 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer return size; } -// if address is null, it's ADDR_ANY bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) { int status; addrinfoW hints; addrinfoW* info; - //DEBUG - LOG(Info, "Searching available adresses for {0} : {1}", address == nullptr ? String("nullptr").Get() : address->Get(), - port == nullptr ? String("nullptr").Get() : port->Get()); memset(&hints, 0, sizeof hints); hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; hints.ai_flags |= AI_ADDRCONFIG; @@ -381,8 +367,6 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio hints.ai_flags = AI_PASSIVE; // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) - // can *paddr works ? - // paddr = nullptr don't work with this func if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) { LOG(Error, "Unable to query info for address : {0} Error : {1}", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); @@ -395,10 +379,6 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio return true; } - //DEBUG - PrintAddrFromInfo(*info); - - // We are taking the first addr in the linked list if (CreateEndPointFromAddr(info->ai_addr, endPoint)) { FreeAddrInfoW(info); @@ -406,8 +386,6 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio } FreeAddrInfoW(info); - //DEBUG - LOG(Info, "Address found : {0} : {1}", endPoint.Address, endPoint.Port); return false; } From baa273f589f5f144be3fe8334e361c8314794efe Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 13:07:02 +0100 Subject: [PATCH 089/222] Remove old comment. --- Source/Engine/Platform/Win32/Win32Network.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 68a973547..ac43f29fe 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -241,7 +241,6 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; const sockaddr* addr = (sockaddr*)endPoint.Data; - LOG(Info, "BIND : EndPoint family : {0}", addr->sa_family); if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); From 5bc1773dc5755db2b05d284cd0f9d441ce2f906b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 13:13:57 +0100 Subject: [PATCH 090/222] Refactor sock option translation. --- Source/Engine/Platform/Win32/Win32Network.cpp | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index ac43f29fe..18db01917 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -7,7 +7,7 @@ #include #include -#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) if (option == OPTENUM) { optlvl = OPTLEVEL; optnme = OPTNAME;} +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: optlvl = OPTLEVEL; optnme = OPTNAME; break; static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); @@ -159,21 +159,24 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption op int32 optlvl = 0; int32 optnme = 0; - SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) - SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) - SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) - SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) - SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) - SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) - SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) - SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) - SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) - SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) - SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) - SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) - SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) - SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) - SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + switch (option) + { + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + } if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) { @@ -195,22 +198,24 @@ bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption op { int32 optlvl = 0; int32 optnme = 0; - - SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) - SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) - SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) - SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) - SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) - SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) - SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) - SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) - SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) - SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) - SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) - SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) - SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) - SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) - + + switch (option) + { + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + } int32 size; if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) { From 206461e80ec492912cf9d8023260d016959be8ab Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 13:17:04 +0100 Subject: [PATCH 091/222] Refactor duplicate code. --- Source/Engine/Platform/Win32/Win32Network.cpp | 62 ++++++++----------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 18db01917..a7b26a0ca 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -7,7 +7,7 @@ #include #include -#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: optlvl = OPTLEVEL; optnme = OPTNAME; break; +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); @@ -100,6 +100,28 @@ static void PrintAddrFromInfo(addrinfoW& info) } } +static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) +{ + switch (option) + { + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + } +} + bool Win32Network::Init() { if (WSAStartup(MAKEWORD(2, 0), &_wsaData) != 0) @@ -159,24 +181,7 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption op int32 optlvl = 0; int32 optnme = 0; - switch (option) - { - SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) - SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) - SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) - SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) - SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) - SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) - SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) - SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) - SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) - SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) - SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) - SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) - SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) - SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) - SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) - } + TranslateSockOptToNative(option, &optlvl, &optnme); if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) { @@ -199,23 +204,8 @@ bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption op int32 optlvl = 0; int32 optnme = 0; - switch (option) - { - SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) - SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) - SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) - SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) - SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) - SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) - SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) - SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) - SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) - SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) - SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) - SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) - SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) - SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) - } + TranslateSockOptToNative(option, &optlvl, &optnme); + int32 size; if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) { From 6dd699cbab64d9d3bd50eec76d7a652b330fbdef Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 13:19:50 +0100 Subject: [PATCH 092/222] Remove dead code. --- Source/Engine/Platform/Win32/Win32Network.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index a7b26a0ca..c594aa80d 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -235,7 +235,6 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) } const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; - const sockaddr* addr = (sockaddr*)endPoint.Data; if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); From 27a62f4411919d7fab1c43fc31016f3cbb799825 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 13:32:17 +0100 Subject: [PATCH 093/222] Add error. --- Source/Engine/Platform/Win32/Win32Network.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index c594aa80d..f50b042e0 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -65,7 +65,12 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) paddr = &((sockaddr_in6*)addr)->sin6_addr; else if (addr->sa_family == AF_INET) paddr = &((sockaddr_in*)addr)->sin_addr; - + else + { + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6!"); + return true; + } + char ip[INET6_ADDRSTRLEN]; if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) { From c2af9164d6d9cd92676b4bc4d92e51ba60a48d16 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 13:34:18 +0100 Subject: [PATCH 094/222] Typo. --- Source/Engine/Platform/Win32/Win32Network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index f50b042e0..d33df3ace 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -4,7 +4,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" #include -#include +#include #include #define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; From a32658beea2fc9f1f4fe939faa5d32f4b443dbd6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 14:02:55 +0100 Subject: [PATCH 095/222] Add GetErrorMessage(). --- Source/Engine/Platform/Win32/Win32Network.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index d33df3ace..feaa3f6a0 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -21,11 +21,11 @@ static WSAData _wsaData; * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation */ -static String GetLastErrorMessage() +static String GetErrorMessage(int error) { wchar_t* s = nullptr; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, WSAGetLastError(), + nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&s), 0, nullptr); String str(s); @@ -33,6 +33,11 @@ static String GetLastErrorMessage() return str; } +static String GetLastErrorMessage() +{ + return GetErrorMessage(WSAGetLastError()); +} + static int GetAddrSize(const sockaddr& addr) { return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; From 8e2a8c045c17de45e33b82249b75e6ee331a7eed Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 17:51:52 +0100 Subject: [PATCH 096/222] Get rid off getnameinfo. --- Source/Engine/Platform/Win32/Win32Network.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index feaa3f6a0..48942f29b 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -67,12 +67,18 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) } void* paddr; if (addr->sa_family == AF_INET6) + { paddr = &((sockaddr_in6*)addr)->sin6_addr; + port = ntohs(((sockaddr_in6*)addr)->sin6_port); + } else if (addr->sa_family == AF_INET) + { paddr = &((sockaddr_in*)addr)->sin_addr; + port = ntohs(((sockaddr_in*)addr)->sin_port); + } else { - LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6!"); + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); return true; } @@ -83,7 +89,9 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) return true; } endPoint.Address = String(ip); - endPoint.Port = String(service); + char strPort[6]; + _itoa(port, strPort, 10); + endPoint.Port = String(strPort); endPoint.IPVersion = GetIPVersionFromAddr(*addr); memcpy(endPoint.Data, addr, size); return false; From 330a588260634b7ef7a6d97245d06568a267fdcf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 17:52:11 +0100 Subject: [PATCH 097/222] Get rid off getnameinfo. --- Source/Engine/Platform/Win32/Win32Network.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 48942f29b..77c0baf4d 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -57,14 +57,8 @@ static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) // getnameinfo return a name ( like JimiPC ), not the ip! static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) { - uint16 size = GetAddrSize(*addr); - char name[100]; - char service[20]; - if (getnameinfo(addr, size, name, sizeof name, service, sizeof service, 0) != 0) - { - LOG(Error, "Unable to extract info from sockaddr! Error : {0}", GetLastErrorMessage().Get()); - return true; - } + uint32 size = GetAddrSize(*addr); + uint16 port; void* paddr; if (addr->sa_family == AF_INET6) { From 67a824607797706f2058aa70fb629f0589a90103 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 17:54:16 +0100 Subject: [PATCH 098/222] Revamp IsReadable & IsWriteable. --- Source/Engine/Platform/Base/NetworkBase.cpp | 7 +++- Source/Engine/Platform/Base/NetworkBase.h | 3 +- Source/Engine/Platform/Win32/Win32Network.cpp | 42 ++++++++++++++++--- Source/Engine/Platform/Win32/Win32Network.h | 3 +- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 73b649281..e7b700911 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -61,11 +61,16 @@ bool NetworkBase::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Netw return false; } -bool NetworkBase::IsReadable(NetworkSocket& socket, uint64* size) +bool NetworkBase::IsReadable(NetworkSocket& socket) { return false; } +bool NetworkBase::IsWriteable(NetworkSocket& socket) +{ + return true; +} + int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { return 0; diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index a568fd215..295100ffe 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -69,7 +69,8 @@ class FLAXENGINE_API NetworkBase static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); - static bool IsReadable(NetworkSocket& socket, uint64* size); + static bool IsReadable(NetworkSocket& socket); + static bool IsWriteable(NetworkSocket& socket); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 77c0baf4d..e0f82de6b 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -288,15 +288,45 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net return false; } -bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) +bool Win32Network::IsReadable(NetworkSocket& socket) { - unsigned long value; - if (ioctlsocket(*(SOCKET*)socket.Data, FIONREAD, &value) != 0) + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(*(SOCKET*)socket.Data, &readfds); + timeval t; + t.tv_sec = 0; + t.tv_usec = 0; + if (select(0, &readfds, nullptr, nullptr, &t) == SOCKET_ERROR) { - LOG(Error, "Unable to query socket for readability! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); - return true; + int error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to check readability of socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); + return false; } - *size = value; + if (FD_ISSET(*(SOCKET*)socket.Data, &readfds)) + return true; + return false; +} + +bool Win32Network::IsWriteable(NetworkSocket& socket) +{ + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(*(SOCKET*)socket.Data, &writefds); + timeval t; + t.tv_sec = 0; + t.tv_usec = 0; + if (select(0, nullptr, &writefds, nullptr, &t) == SOCKET_ERROR) + { + int error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to check writeability of socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); + return false; + } + if (FD_ISSET(*(SOCKET*)socket.Data, &writefds)) + return true; return false; } diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 8cbcf57ec..bbf15a4c4 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -25,7 +25,8 @@ public: static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); static bool Listen(NetworkSocket& socket, uint16 queueSize); static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); - static bool IsReadable(NetworkSocket& socket, uint64* size); + static bool IsReadable(NetworkSocket& socket); + static bool IsWriteable(NetworkSocket& socket); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); From bbabd099835f8b32e204a1f550605361bb82bf76 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 17:54:52 +0100 Subject: [PATCH 099/222] Non-Blocking handling. --- Source/Engine/Platform/Win32/Win32Network.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index e0f82de6b..cff163f2a 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -232,7 +232,10 @@ bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoin const uint16 size = GetAddrSizeFromEP(endPoint); if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + int error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetErrorMessage(error).Get()); return true; } return false; @@ -370,7 +373,10 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer { if ((size = recv(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); + const int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return 0; + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetErrorMessage(error).Get()); return -1; } } From 029d9acaa2f66702dc87b7a9dade2581bf996124 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 25 Jan 2021 17:55:22 +0100 Subject: [PATCH 100/222] Fixes. --- Source/Engine/Platform/Win32/Win32Network.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index cff163f2a..d2e7e0944 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -276,17 +276,18 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net return true; } SOCKET sock; - sockaddr addr; - if ((sock = accept(*(SOCKET*)serverSock.Data, &addr, nullptr)) == INVALID_SOCKET) + sockaddr_in6 addr; + int32 size = sizeof sockaddr_in6; + if ((sock = accept(*(SOCKET*)serverSock.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) { LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetLastErrorMessage().Get()); return true; } memcpy(newSock.Data, &sock, sizeof sock); - memcpy(newEndPoint.Data, &addr, GetAddrSize(addr)); + memcpy(newEndPoint.Data, &addr, size); newSock.Protocol = serverSock.Protocol; newSock.IPVersion = serverSock.IPVersion; - if (CreateEndPointFromAddr(&addr, newEndPoint)) + if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) return true; return false; } @@ -335,7 +336,7 @@ bool Win32Network::IsWriteable(NetworkSocket& socket) int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { - if (socket.IPVersion != endPoint->IPVersion) + if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) { LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); return 0; From 023cdced0a178dd72d61dba9cdb66e0f8d36a65a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 09:32:41 +0100 Subject: [PATCH 101/222] Add utility to make spline curve linear or smooth --- .../CustomEditors/Dedicated/SplineEditor.cs | 64 ++++++++++++++++ .../Editor/Undo/Actions/EditSplineAction.cs | 73 +++++++++++++++++++ Source/Engine/Debug/DebugDraw.cpp | 14 +--- Source/Engine/Level/Actors/Spline.cpp | 61 ++++++++++++++-- Source/Engine/Level/Actors/Spline.h | 14 +++- 5 files changed, 206 insertions(+), 20 deletions(-) create mode 100644 Source/Editor/CustomEditors/Dedicated/SplineEditor.cs create mode 100644 Source/Editor/Undo/Actions/EditSplineAction.cs diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs new file mode 100644 index 000000000..9eedd0ed3 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Actions; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(Spline)), DefaultEditor] + public class SplineEditor : ActorEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + if (Values.HasDifferentTypes == false) + { + layout.Space(10); + var grid = layout.CustomContainer(); + grid.CustomControl.SlotsHorizontally = 2; + grid.CustomControl.SlotsVertically = 1; + grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear; + grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth; + } + } + + private void OnSetTangentsLinear() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsLinear(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + + private void OnSetTangentsSmooth() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsSmooth(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + } +} diff --git a/Source/Editor/Undo/Actions/EditSplineAction.cs b/Source/Editor/Undo/Actions/EditSplineAction.cs new file mode 100644 index 000000000..3bb8dac5c --- /dev/null +++ b/Source/Editor/Undo/Actions/EditSplineAction.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Modules; +using FlaxEngine; + +namespace FlaxEditor.Actions +{ + /// + /// Change keyframes undo action. + /// + /// + /// + [Serializable] + public class EditSplineAction : IUndoAction, ISceneEditAction + { + [Serialize] + private Guid _splineId; + + [Serialize] + private BezierCurve.Keyframe[] _before; + + [Serialize] + private BezierCurve.Keyframe[] _after; + + /// + /// Initializes a new instance of the class. + /// + /// The spline. + /// The spline keyframes state before editing it. + public EditSplineAction(Spline spline, BezierCurve.Keyframe[] before) + { + _splineId = spline.ID; + _before = before; + _after = (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone(); + } + + /// + public string ActionString => "Edit spline keyframes"; + + /// + public void Do() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _after; + } + + /// + public void Undo() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _before; + } + + /// + public void Dispose() + { + _before = _after = null; + } + + /// + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline != null) + sceneModule.MarkSceneEdited(spline.Scene); + } + } +} diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 457c90411..596956dea 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -650,16 +650,6 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, c } } -static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) -{ - const float oneMinusT = 1.0f - t; - return - oneMinusT * oneMinusT * oneMinusT * p0 + - 3.0f * oneMinusT * oneMinusT * t * p1 + - 3.0f * oneMinusT * t * t * p2 + - t * t * t * p3; -} - void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color, float duration, bool depthTest) { // Create draw call entry @@ -676,13 +666,13 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& const Vector3 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float segmentCountInv = 1.0f / segmentCount; + const float segmentCountInv = 1.0f / (float)segmentCount; list->EnsureCapacity(list->Count() + segmentCount + 2); // Draw segmented curve for (int32 i = 0; i <= segmentCount; i++) { - const float t = i * segmentCountInv; + const float t = (float)i * segmentCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, l.End); list->Add(l); l.Start = l.End; diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 509c2f389..5e9e7fe94 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -154,8 +154,6 @@ float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const float bestTime = 0.0f; for (int32 i = 1; i < pointsCount; i++) FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime); - if (_loop) - FindTimeClosestToPoint(localPoint, Curve[pointsCount - 1], Curve[0], bestDistanceSquared, bestTime); return bestTime; } @@ -228,8 +226,61 @@ void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline) UpdateSpline(); } +void Spline::SetTangentsLinear() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i < count; i++) + { + auto& k = Curve[i]; + k.TangentIn = k.TangentOut = Transform::Identity; + } + + UpdateSpline(); +} + +void Spline::SetTangentsSmooth() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + auto& keys = Curve.GetKeyframes(); + const int32 last = count - 2; + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i <= last; i++) + { + auto& key = keys[i]; + const auto& prevKey = keys[i == 0 ? (_loop ? last : 0) : i - 1]; + const auto& nextKey = keys[i == last ? (_loop ? 0 : last) : i + 1]; + const float prevTime = _loop && i == 0 ? key.Time : prevKey.Time; + const float nextTime = _loop && i == last ? key.Time : nextKey.Time; + const Vector3 slope = key.Value.Translation - prevKey.Value.Translation + nextKey.Value.Translation - key.Value.Translation; + const Vector3 tangent = slope / Math::Max(nextTime - prevTime, ZeroTolerance); + key.TangentIn.Translation = -tangent; + key.TangentOut.Translation = tangent; + } + + UpdateSpline(); +} + void Spline::UpdateSpline() { + // Always keep last point in the loop + const int32 count = Curve.GetKeyframes().Count(); + if (_loop && count > 1) + { + auto& first = Curve[0]; + auto& last = Curve[count - 1]; + last.Value = first.Value; + last.TangentIn = first.TangentIn; + last.TangentOut = first.TangentOut; + } } void Spline::GetKeyframes(MonoArray* data) @@ -256,9 +307,9 @@ namespace const auto& startKey = spline->Curve[start]; const auto& endKey = spline->Curve[end]; const Vector3 startPos = transform.LocalToWorld(startKey.Value.Translation); - const Vector3 startTangent = transform.LocalToWorld(startKey.TangentOut.Translation); + const Vector3& startTangent = startKey.TangentOut.Translation; const Vector3 endPos = transform.LocalToWorld(endKey.Value.Translation); - const Vector3 endTangent = transform.LocalToWorld(endKey.TangentIn.Translation); + const Vector3& endTangent = endKey.TangentIn.Translation; const float d = (endKey.Time - startKey.Time) / 3.0f; DEBUG_DRAW_BEZIER(startPos, startPos + startTangent * d, endPos + endTangent * d, endPos, color, 0.0f, depthTest); } @@ -273,8 +324,6 @@ namespace if (i != 0) DrawSegment(spline, i - 1, i, color, transform, depthTest); } - if (spline->GetIsLoop() && count > 1) - DrawSegment(spline, count - 1, 0, color, transform, depthTest); } } diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index a94804ea7..fd489b823 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -25,7 +25,7 @@ public: BezierCurve Curve; /// - /// Whether to use spline as closed loop. + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. /// API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")") FORCE_INLINE bool GetIsLoop() const @@ -34,7 +34,7 @@ public: } /// - /// Whether to use spline as closed loop. + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. /// API_PROPERTY() void SetIsLoop(bool value); @@ -212,6 +212,16 @@ public: /// True if update spline after adding the point, otherwise false. API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true); + /// + /// Updates the curve tangent points to make curve linear. + /// + API_FUNCTION() void SetTangentsLinear(); + + /// + /// Updates the curve tangent points to make curve smooth. + /// + API_FUNCTION() void SetTangentsSmooth(); + public: /// From dda5f26d8993da9bbc47204537d383238afaed46 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 12:11:41 +0100 Subject: [PATCH 102/222] Add `Transform.LocalToWorldVector` and `Transform.WorldToLocalVector` --- Source/Engine/Core/Math/Transform.cpp | 25 +++++++++++++++++++ Source/Engine/Core/Math/Transform.cs | 35 +++++++++++++++++++++++++++ Source/Engine/Core/Math/Transform.h | 14 +++++++++++ 3 files changed, 74 insertions(+) diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp index 5ac71779b..728fc6a90 100644 --- a/Source/Engine/Core/Math/Transform.cpp +++ b/Source/Engine/Core/Math/Transform.cpp @@ -94,6 +94,13 @@ Vector3 Transform::LocalToWorld(const Vector3& point) const return result + Translation; } +Vector3 Transform::LocalToWorldVector(const Vector3& vector) const +{ + Vector3 result = vector * Scale; + Vector3::Transform(result, Orientation, result); + return result; +} + void Transform::LocalToWorld(const Vector3& point, Vector3& result) const { Vector3 tmp = point * Scale; @@ -171,6 +178,24 @@ Vector3 Transform::WorldToLocal(const Vector3& point) const return result * invScale; } +Vector3 Transform::WorldToLocalVector(const Vector3& vector) const +{ + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + const Quaternion invRotation = Orientation.Conjugated(); + + Vector3 result; + Vector3::Transform(vector, invRotation, result); + + return result * invScale; +} + void Transform::WorldToLocal(const Vector3* points, int32 pointsCount, Vector3* result) const { Vector3 invScale = Scale; diff --git a/Source/Engine/Core/Math/Transform.cs b/Source/Engine/Core/Math/Transform.cs index 21ed705ff..7065309e3 100644 --- a/Source/Engine/Core/Math/Transform.cs +++ b/Source/Engine/Core/Math/Transform.cs @@ -197,6 +197,18 @@ namespace FlaxEngine return point + Translation; } + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + public Vector3 LocalToWorldVector(Vector3 vector) + { + vector *= Scale; + Vector3.Transform(ref vector, ref Orientation, out vector); + return vector; + } + /// /// Perform transformation of the given points in local space /// @@ -259,6 +271,29 @@ namespace FlaxEngine return result * invScale; } + /// + /// Perform transformation of the given vector in world space + /// + /// World space vector + /// Local space vector + public Vector3 WorldToLocalVector(Vector3 vector) + { + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + Quaternion invRotation = Orientation; + invRotation.Invert(); + + Vector3.Transform(ref vector, ref invRotation, out var result); + + return result * invScale; + } + /// /// Perform transformation of the given points in world space /// diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index c5e67c986..5a39c2d86 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -184,6 +184,13 @@ public: /// The world space point. Vector3 LocalToWorld(const Vector3& point) const; + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + Vector3 LocalToWorldVector(const Vector3& vector) const; + /// /// Performs transformation of the given point in local space to the world space of this transform. /// @@ -220,6 +227,13 @@ public: /// The local space point. Vector3 WorldToLocal(const Vector3& point) const; + /// + /// Performs transformation of the given vector in world space to the local space of this transform. + /// + /// The world space vector. + /// The local space vector. + Vector3 WorldToLocalVector(const Vector3& vector) const; + /// /// Performs transformation of the given points in world space to the local space of this transform. /// From b674aae970b97bd8fcf42fde9fb592cb8ffa0c2d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 12:12:16 +0100 Subject: [PATCH 103/222] Fix spline length calculation to include bezier shape --- Source/Engine/Level/Actors/Spline.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 5e9e7fe94..4c2680e79 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -114,8 +114,30 @@ float Spline::GetSplineDuration() const float Spline::GetSplineLength() const { float sum = 0.0f; + const int32 slices = 20; + const float step = 1.0f / (float)slices; + Vector3 prevPoint = Vector3::Zero; for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++) - sum += Vector3::DistanceSquared(Curve[i - 1].Value.Translation * _transform.Scale, Curve[i].Value.Translation * _transform.Scale); + { + const auto& a = Curve[i = 1]; + const auto& b = Curve[i]; + + const float length = Math::Abs(b.Time - a.Time); + Vector3 leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); + AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); + + // TODO: implement sth more analytical than brute-force solution + for (int32 slice = 0; slice < slices; slice++) + { + const float t = (float)slice * step; + Vector3 pos; + AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos); + pos *= _transform.Scale; + sum += Vector3::DistanceSquared(pos, prevPoint); + prevPoint = pos; + } + } return Math::Sqrt(sum); } From 937248794ae209172ae15ddf86f44434a606c06e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 12:32:16 +0100 Subject: [PATCH 104/222] Add first derivative calculation utilities for Curve --- Source/Engine/Animations/AnimationUtils.h | 135 ++++++++++++---------- Source/Engine/Animations/Curve.h | 91 +++++++++++++-- 2 files changed, 155 insertions(+), 71 deletions(-) diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index a7f85f124..c90afcea3 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -84,21 +84,21 @@ namespace AnimationUtils } template - FORCE_INLINE static void Interpolate(const T& a, const T& b, float alpha, T& result) + FORCE_INLINE static void Interpolate(const T& a, const T& b, float t, T& result) { - result = (T)(a + alpha * (b - a)); + result = (T)(a + t * (b - a)); } template<> - FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float alpha, Vector3& result) + FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float t, Vector3& result) { - Vector3::Lerp(a, b, alpha, result); + Vector3::Lerp(a, b, t, result); } template<> - FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float alpha, Quaternion& result) + FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float t, Quaternion& result) { - Quaternion::Slerp(a, b, alpha, result); + Quaternion::Slerp(a, b, t, result); } static void WrapTime(float& time, float start, float end, bool loop) @@ -134,98 +134,111 @@ namespace AnimationUtils /// Evaluates a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t = 1). /// The evaluated value. template - static void CubicHermite(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermite(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - const float t3 = t2 * t; - - float a = 2 * t3 - 3 * t2 + 1; - float b = t3 - 2 * t2 + t; - float c = -2 * t3 + 3 * t2; - float d = t3 - t2; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + const float ttt = tt * t; + result = (2 * ttt - 3 * tt + 1) * p0 + (ttt - 2 * tt + t) * t0 + (-2 * ttt + 3 * tt) * p1 + (ttt - tt) * t1; } /// /// Evaluates the first derivative of a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t=1). /// The evaluated value. template - static void CubicHermiteD1(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermiteFirstDerivative(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - - float a = 6 * t2 - 6 * t; - float b = 3 * t2 - 4 * t + 1; - float c = -6 * t2 + 6 * t; - float d = 3 * t2 - 2 * t; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + result = (6 * tt - 6 * t) * p0 + (3 * tt - 4 * t + 1) * t0 + (-6 * tt + 6 * t) * p1 + (3 * tt - 2 * t) * t1; } template - static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float alpha, T& result) + static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) { T p01, p12, p23, p012, p123; - Interpolate(p0, p1, alpha, p01); - Interpolate(p1, p2, alpha, p12); - Interpolate(p2, p3, alpha, p23); - Interpolate(p01, p12, alpha, p012); - Interpolate(p12, p23, alpha, p123); - Interpolate(p012, p123, alpha, result); + Interpolate(p0, p1, t, p01); + Interpolate(p1, p2, t, p12); + Interpolate(p2, p3, t, p23); + Interpolate(p01, p12, t, p012); + Interpolate(p12, p23, t, p123); + Interpolate(p012, p123, t, result); } template<> - void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float alpha, Vector2& result) + void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float t, Vector2& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float alpha, Vector3& result) + void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float t, Vector3& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float alpha, Quaternion& result) + void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) { Quaternion p01, p12, p23, p012, p123; - Quaternion::Slerp(p0, p1, alpha, p01); - Quaternion::Slerp(p1, p2, alpha, p12); - Quaternion::Slerp(p2, p3, alpha, p23); - Quaternion::Slerp(p01, p12, alpha, p012); - Quaternion::Slerp(p12, p23, alpha, p123); - Quaternion::Slerp(p012, p123, alpha, result); + Quaternion::Slerp(p0, p1, t, p01); + Quaternion::Slerp(p1, p2, t, p12); + Quaternion::Slerp(p2, p3, t, p23); + Quaternion::Slerp(p01, p12, t, p012); + Quaternion::Slerp(p12, p23, t, p123); + Quaternion::Slerp(p012, p123, t, result); } template<> - void Bezier(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float alpha, Transform& result) + void Bezier(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) { - Bezier(p0.Translation, p1.Translation, p2.Translation, p3.Translation, alpha, result.Translation); - Bezier(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, alpha, result.Orientation); - Bezier(p0.Scale, p1.Scale, p2.Scale, p3.Scale, alpha, result.Scale); + Bezier(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + Bezier(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + Bezier(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); + } + + template + static void BezierFirstDerivative(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) + { + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + result = 3.0f * uu * (p1 - p0) + 6.0f * u * t * (p2 - p1) + 3.0f * tt * (p3 - p2); + } + + template<> + static void BezierFirstDerivative(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) + { + Vector3 euler; + BezierFirstDerivative(p0.GetEuler(), p1.GetEuler(), p2.GetEuler(), p3.GetEuler(), t, euler); + result = Quaternion::Euler(euler); + } + + template<> + static void BezierFirstDerivative(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + BezierFirstDerivative(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + BezierFirstDerivative(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + BezierFirstDerivative(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); } } diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 618e0ee0c..24dcd87a8 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -48,6 +48,11 @@ public: result = a.Value; } + FORCE_INLINE static void InterpolateFirstDerivative(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, T& result) + { + result = AnimationUtils::GetZero(); + } + FORCE_INLINE static void InterpolateKey(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, StepCurveKeyframe& result) { result = a; @@ -97,6 +102,11 @@ public: AnimationUtils::Interpolate(a.Value, b.Value, alpha, result); } + FORCE_INLINE static void InterpolateFirstDerivative(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, T& result) + { + result = b.Value - a.Value; + } + FORCE_INLINE static void InterpolateKey(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, LinearCurveKeyframe& result) { result.Time = a.Time + (b.Time - a.Time) * alpha; @@ -157,20 +167,23 @@ public: { T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermite( a.Value, b.Value, leftTangent, rightTangent, alpha, result); + } - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result); + static void InterpolateFirstDerivative(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent = a.Value + a.TangentOut * length; + T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermiteFirstDerivative( a.Value, b.Value, leftTangent, rightTangent, alpha, result); } static void InterpolateKey(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, HermiteCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; - - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result.Value); - AnimationUtils::CubicHermiteD1(alpha, a.Value, b.Value, leftTangent, rightTangent, result.TangentIn); - + AnimationUtils::CubicHermite(a.Value, b.Value, leftTangent, rightTangent, alpha, result.Value); + AnimationUtils::CubicHermiteFirstDerivative(a.Value, b.Value, leftTangent, rightTangent, alpha, result.TangentIn); result.TangentIn /= length; result.TangentOut = result.TangentIn; } @@ -238,20 +251,24 @@ public: T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result); } + static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); + AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result); + } + static void InterpolateKey(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, BezierCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value); - result.TangentIn = a.TangentOut; result.TangentOut = b.TangentIn; } @@ -366,6 +383,48 @@ public: KeyFrame::Interpolate(leftKey, rightKey, t, length, result); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The keyframes data container. + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(const KeyFrameData& data, T& result, float time, bool loop = true) const + { + const int32 count = data.Length(); + if (count == 0) + { + result = _default; + return; + } + + const float start = 0; + const float end = data[count - 1].Time; + AnimationUtils::WrapTime(time, start, end, loop); + + int32 leftKeyIdx; + int32 rightKeyIdx; + FindKeys(data, time, leftKeyIdx, rightKeyIdx); + + const KeyFrame& leftKey = data[leftKeyIdx]; + const KeyFrame& rightKey = data[rightKeyIdx]; + + if (leftKeyIdx == rightKeyIdx) + { + result = leftKey.Value; + return; + } + + const float length = rightKey.Time - leftKey.Time; + + // Scale from arbitrary range to [0, 1] + float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; + + // Evaluate the derivative at the curve + KeyFrame::InterpolateFirstDerivative(leftKey, rightKey, t, length, result); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -586,6 +645,18 @@ public: Base::Evaluate(data, result, time, loop); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(T& result, float time, bool loop = true) const + { + typename Base::KeyFrameData data(_keyframes); + Base::EvaluateFirstDerivative(data, result, time, loop); + } + /// /// Evaluates the animation curve key at the specified time. /// From aa2eeb82437363680415e83562a83bbf64d291d3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 12:32:35 +0100 Subject: [PATCH 105/222] Add GetSplineDirection util to Spline --- Source/Engine/Level/Actors/Spline.cpp | 15 +++++++++++++++ Source/Engine/Level/Actors/Spline.h | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 4c2680e79..9f47aa06e 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -77,6 +77,21 @@ Transform Spline::GetSplineLocalTransform(float time) const return t; } +Vector3 Spline::GetSplineDirection(float time) const +{ + return _transform.LocalToWorldVector(GetSplineLocalDirection(time)); +} + +Vector3 Spline::GetSplineLocalDirection(float time) const +{ + if (Curve.GetKeyframes().Count() == 0) + return Vector3::Forward; + Transform t; + Curve.EvaluateFirstDerivative(t, time, _loop); + t.Translation.Normalize(); + return t.Translation; +} + Vector3 Spline::GetSplinePoint(int32 index) const { CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index fd489b823..cc4226414 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -96,6 +96,20 @@ public: /// The calculated curve point transformation (local-space). API_FUNCTION() Transform GetSplineLocalTransform(float time) const; + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (world-space). + API_FUNCTION() Vector3 GetSplineDirection(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (local-space). + API_FUNCTION() Vector3 GetSplineLocalDirection(float time) const; + /// /// Evaluates the spline curve at the given index (world-space). /// From 8180877269887e94e264f99ec95d3026db560d81 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 12:52:03 +0100 Subject: [PATCH 106/222] Add support for copy/paste/rever values for groups in Custom Editor (eg. array editor) --- .../CustomEditors/LayoutElementsContainer.cs | 20 ++++++++++ Source/Engine/UI/GUI/Panels/DropPanel.cs | 40 +++++++++++++------ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 9df2de8be..59058056d 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; @@ -553,6 +554,8 @@ namespace FlaxEditor.CustomEditors var group = Group(name, true); group.Panel.Close(false); group.Panel.TooltipText = tooltip; + group.Panel.Tag = editor; + group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; return group.Object(values, editor); } @@ -560,6 +563,23 @@ namespace FlaxEditor.CustomEditors return property.Object(values, editor); } + private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location) + { + var linkedEditor = (CustomEditor)groupPanel.Tag; + var menu = new ContextMenu(); + + var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue); + revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue; + var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue); + resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue; + menu.AddSeparator(); + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + + menu.Show(groupPanel, location); + } + /// /// Adds object property editor. Selects proper based on overrides. /// diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index fa5ff7f59..04493c7ef 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -31,9 +31,14 @@ namespace FlaxEngine.GUI protected bool _mouseOverHeader; /// - /// The 'mouse down' flag (over header). + /// The 'mouse down' flag (over header) for the left mouse button. /// - protected bool _mouseDown; + protected bool _mouseButtonLeftDown; + + /// + /// The 'mouse down' flag (over header) for the right mouse button. + /// + protected bool _mouseButtonRightDown; /// /// The animation progress (normalized). @@ -126,6 +131,11 @@ namespace FlaxEngine.GUI [EditorDisplay("Style"), EditorOrder(2000)] public bool EnableDropDownIcon { get; set; } + /// + /// Occurs when mouse right-clicks over the header. + /// + public event Action MouseButtonRightClicked; + /// /// Occurs when drop panel is opened or closed. /// @@ -430,10 +440,14 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - if (button == MouseButton.Left && _mouseOverHeader) { - _mouseDown = true; + _mouseButtonLeftDown = true; + return true; + } + if (button == MouseButton.Right && _mouseOverHeader) + { + _mouseButtonRightDown = true; return true; } @@ -455,16 +469,17 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - - if (button == MouseButton.Left && _mouseDown) + if (button == MouseButton.Left && _mouseButtonLeftDown) { - _mouseDown = false; - + _mouseButtonLeftDown = false; if (_mouseOverHeader) - { Toggle(); - } - + return true; + } + if (button == MouseButton.Right && _mouseButtonRightDown) + { + _mouseButtonRightDown = false; + MouseButtonRightClicked?.Invoke(this, location); return true; } @@ -474,7 +489,8 @@ namespace FlaxEngine.GUI /// public override void OnMouseLeave() { - _mouseDown = false; + _mouseButtonLeftDown = false; + _mouseButtonRightDown = false; _mouseOverHeader = false; base.OnMouseLeave(); From 51a2c1e868fbaff710af17a0eedddeefefa55a77 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 13:41:23 +0100 Subject: [PATCH 107/222] Add NetworkSocketGroup. --- Source/Engine/Platform/Base/NetworkBase.cpp | 20 ++++++++++++++++++++ Source/Engine/Platform/Base/NetworkBase.h | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index e7b700911..b4f1110ab 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -71,6 +71,26 @@ bool NetworkBase::IsWriteable(NetworkSocket& socket) return true; } +int32 NetworkBase::Poll(NetworkSocketGroup& group) +{ + return 0; +} + +bool NetworkBase::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + return false; +} + +int32 NetworkBase::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return 0; +} + +void NetworkBase::ClearGroup(NetworkSocketGroup& group) +{ + group.Count = 0; +} + int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { return 0; diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 295100ffe..0f0f89cb7 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -5,6 +5,8 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Core/Types/String.h" API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); +#define SOCKGROUP_MAXCOUNT 64 +#define SOCKGROUP_ITEMSIZE 16 enum class FLAXENGINE_API NetworkProtocolType { @@ -54,6 +56,21 @@ enum FLAXENGINE_API NetworkSocketOption IPv6Only }; +struct FLAXENGINE_API NetworkSocketState +{ + bool Error = false; + bool Invalid = false; + bool Disconnected = false; + bool Readable = false; + bool Writeable = false; +}; + +struct FLAXENGINE_API NetworkSocketGroup +{ + uint32 Count; + byte Data[SOCKGROUP_MAXCOUNT * 16] = {}; +}; + class FLAXENGINE_API NetworkBase { public: @@ -71,6 +88,10 @@ class FLAXENGINE_API NetworkBase static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket); static bool IsWriteable(NetworkSocket& socket); + static int32 Poll(NetworkSocketGroup& group); + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static void ClearGroup(NetworkSocketGroup& group); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); From 45ba15487775410d79f3b8b35f703424e274a8c0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 13:41:44 +0100 Subject: [PATCH 108/222] Add NetworkSocketGroup window impl. --- Source/Engine/Platform/Win32/Win32Network.cpp | 45 +++++++++++++++++++ Source/Engine/Platform/Win32/Win32Network.h | 4 ++ 2 files changed, 49 insertions(+) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index d2e7e0944..a9fa388dc 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -334,6 +334,51 @@ bool Win32Network::IsWriteable(NetworkSocket& socket) return false; } +static thread_local int32 pollret; +int32 Win32Network::Poll(NetworkSocketGroup& group) +{ + pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); + if (pollret == SOCKET_ERROR) + LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage().Get()); + return pollret; +} + +static thread_local pollfd* pollptr; +bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + if (index >= SOCKGROUP_MAXCOUNT) + return true; + pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; + if (pollptr->revents & POLLERR) + state.Error = true; + if (pollptr->revents & POLLHUP) + state.Disconnected = true; + if (pollptr->revents & POLLNVAL) + state.Invalid = true; + if (pollptr->revents & POLLRDNORM) + state.Readable = true; + if (pollptr->revents & POLLWRNORM) + state.Writeable = true; + return false; +} + +static thread_local pollfd pollinfo; +int32 Win32Network::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + if (group.Count >= SOCKGROUP_MAXCOUNT) + return -1; + pollinfo.fd = *(SOCKET*)socket.Data; + pollinfo.events = POLLRDNORM | POLLWRNORM; + *(pollfd*)&group.Data[group.Count * SOCKGROUP_ITEMSIZE] = pollinfo; + group.Count++; + return group.Count-1; +} + +void Win32Network::ClearGroup(NetworkSocketGroup& group) +{ + group.Count = 0; +} + int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index bbf15a4c4..8700d9f19 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -27,6 +27,10 @@ public: static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket); static bool IsWriteable(NetworkSocket& socket); + static int32 Poll(NetworkSocketGroup& group); + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static void ClearGroup(NetworkSocketGroup& group); static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); From ecb20afaa45a2c760cff9e80a8f975351c1f9b1c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 13:52:38 +0100 Subject: [PATCH 109/222] Reimpl. SiReadable & IsWriteable. --- Source/Engine/Platform/Win32/Win32Network.cpp | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index a9fa388dc..a2038def5 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -294,42 +294,28 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net bool Win32Network::IsReadable(NetworkSocket& socket) { - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(*(SOCKET*)socket.Data, &readfds); - timeval t; - t.tv_sec = 0; - t.tv_usec = 0; - if (select(0, &readfds, nullptr, nullptr, &t) == SOCKET_ERROR) + pollfd entry; + entry.events = POLLRDNORM; + if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - int error = WSAGetLastError(); - if (error == WSAEWOULDBLOCK) - return false; - LOG(Error, "Unable to check readability of socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); return false; } - if (FD_ISSET(*(SOCKET*)socket.Data, &readfds)) + if (entry.revents & POLLRDNORM) return true; return false; } bool Win32Network::IsWriteable(NetworkSocket& socket) { - fd_set writefds; - FD_ZERO(&writefds); - FD_SET(*(SOCKET*)socket.Data, &writefds); - timeval t; - t.tv_sec = 0; - t.tv_usec = 0; - if (select(0, nullptr, &writefds, nullptr, &t) == SOCKET_ERROR) + pollfd entry; + entry.events = POLLWRNORM; + if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - int error = WSAGetLastError(); - if (error == WSAEWOULDBLOCK) - return false; - LOG(Error, "Unable to check writeability of socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); return false; } - if (FD_ISSET(*(SOCKET*)socket.Data, &writefds)) + if (entry.revents & POLLWRNORM) return true; return false; } From 1b2dd0aff50d225311b0ce2cadd99f689ed5116a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 14:14:27 +0100 Subject: [PATCH 110/222] Fixes. --- Source/Engine/Platform/Win32/Win32Network.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index a2038def5..5d981ea2c 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -292,13 +292,18 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net return false; } +static thread_local int32 error; bool Win32Network::IsReadable(NetworkSocket& socket) { pollfd entry; + entry.fd = *(SOCKET*)socket.Data; entry.events = POLLRDNORM; if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); return false; } if (entry.revents & POLLRDNORM) @@ -309,10 +314,14 @@ bool Win32Network::IsReadable(NetworkSocket& socket) bool Win32Network::IsWriteable(NetworkSocket& socket) { pollfd entry; + entry.fd = *(SOCKET*)socket.Data; entry.events = POLLWRNORM; if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); return false; } if (entry.revents & POLLWRNORM) From a51d47a4bb97bdf12349270d013e824a88844939 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 14:29:53 +0100 Subject: [PATCH 111/222] Add partial docs. --- Source/Engine/Platform/Base/NetworkBase.h | 85 ++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 0f0f89cb7..99a93ebf5 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #pragma once @@ -74,17 +74,100 @@ struct FLAXENGINE_API NetworkSocketGroup class FLAXENGINE_API NetworkBase { public: + /// + /// Initialize the network module. + /// + /// Return true on error. Otherwise false. static bool Init(); + + /// + /// Deinitialize the network module. + /// static void Exit(); + + /// + /// Create a new native socket. + /// + /// The socket struct to fill in. + /// The protocol. + /// The ip version. + /// Return true on error. Otherwise false. static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); + + /// + /// Close native socket. + /// + /// The socket. + /// Return true on error. Otherwise false. static bool DestroySocket(NetworkSocket& socket); + + /// + /// Set the specified socket option. + /// + /// The socket. + /// The option. + /// The value. + /// Return true on error. Otherwise false. static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + + /// + /// Set the specified socket option. + /// + /// The socket. + /// The option. + /// The value. + /// Return true on error. Otherwise false. static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + + /// + /// Get the specified socket option. + /// + /// The socket. + /// The option. + /// The returned value. + /// Return true on error. Otherwise false. static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + + /// + /// Get the specified socket option. + /// + /// The socket. + /// The option. + /// The returned value. + /// Return true on error. Otherwise false. static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + + /// + /// Connect a socket to the specified end point. + /// + /// The socket. + /// The end point. + /// Return true on error. Otherwise false. static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + + /// + /// Bind a socket to the specified end point. + /// + /// The socket. + /// The end point. + /// Return true on error. Otherwise false. static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + + /// + /// Listen for incoming connection. + /// + /// The socket. + /// Pending connection queue size. + /// Return true on error. Otherwise false. static bool Listen(NetworkSocket& socket, uint16 queueSize); + + /// + /// Accept a pending connection. + /// + /// The socket. + /// The newly connected socket. + /// The end point of the new socket. + /// Return true on error. Otherwise false. static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); static bool IsReadable(NetworkSocket& socket); static bool IsWriteable(NetworkSocket& socket); From 1df47bd8c5dd6c33655e8148abf7cc8aaefad856 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 14:50:45 +0100 Subject: [PATCH 112/222] Add partial docs. --- Source/Engine/Platform/Base/NetworkBase.h | 72 +++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 99a93ebf5..3dff55a56 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -169,15 +169,87 @@ class FLAXENGINE_API NetworkBase /// The end point of the new socket. /// Return true on error. Otherwise false. static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + + /// + /// Check for socket readability. + /// + /// The socket. + /// Return true when data is available. Otherwise false. static bool IsReadable(NetworkSocket& socket); + + /// + /// Check for socket writeability. + /// + /// The socket. + /// Return true when data can be written. Otherwise false. static bool IsWriteable(NetworkSocket& socket); + + /// + /// Update sockets states. + /// + /// The sockets group. + /// Return -1 on error, The number of elements where states are nonzero, otherwise 0. static int32 Poll(NetworkSocketGroup& group); + + /// + /// Retrieve socket state. + /// + /// The group. + /// The socket index in group. + /// The returned state. + /// Return true on error. Otherwise false. static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + + /// + /// Add a socket to a group. + /// + /// The group. + /// The socket. + /// Return the socket index in group or -1 on error. static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + + /// + /// Clear the socket group. + /// + /// The group. static void ClearGroup(NetworkSocketGroup& group); + + /// + /// Write data to the socket. + /// + /// The socket. + /// The data to write. + /// The length of data. + /// If protocol is UDP , the destination end point. Otherwise nullptr. + /// Return -1 on error, otherwise bytes written. static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + + /// + /// Read data on the socket. + /// + /// The socket. + /// The buffer. + /// Size of the buffer. + /// If UDP, the end point from where data is coming. Otherwise nullptr. + /// Return -1 on error, otherwise bytes read. static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + + /// + /// Create an end point. + /// + /// The address (hostname, IPv4 or IPv6). + /// The port. + /// The ip version. + /// The created end point. + /// True if the end point will be connected or binded. + /// Return true on error. Otherwise false. static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); + + /// + /// Remap an ipv4 end point to an ipv6 one. + /// + /// The ipv4 end point. + /// The ipv6 end point. static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); }; From 21190752406ff33a5f9be89e7378fdfa9468923c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 14:51:01 +0100 Subject: [PATCH 113/222] Fix. --- Source/Engine/Platform/Win32/Win32Network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 5d981ea2c..ea2e7c46d 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -379,7 +379,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) { LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); - return 0; + return -1; } uint32 size; if (endPoint == nullptr && socket.Protocol == NetworkProtocolType::Tcp) From 01a3a8010323323990ef6c093734893e14de8388 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jan 2021 15:34:57 +0100 Subject: [PATCH 114/222] Fix compilation --- Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index 0f650da2c..fa47c5ee5 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -36,6 +36,7 @@ ArchitectureType LinuxPlatformTools::GetArchitecture() const bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) { + const auto gameSettings = GameSettings::Get(); const auto platformSettings = LinuxPlatformSettings::Get(); const auto outputPath = data.OutputPath; @@ -64,7 +65,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) // Apply game executable file name #if !BUILD_DEBUG const String outputExePath = outputPath / TEXT("FlaxGame"); - const String gameExePath = outputPath / GameSettings::ProductName; + const String gameExePath = outputPath / gameSettings->ProductName; if (FileSystem::FileExists(outputExePath) && gameExePath.Compare(outputExePath, StringSearchCase::IgnoreCase) == 0) { if (FileSystem::MoveFile(gameExePath, outputExePath, true)) From 2bcbf23bca7061db9611ed29adec517d81fc3333 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:37:33 +0100 Subject: [PATCH 115/222] All base functions return true. --- Source/Engine/Platform/Base/NetworkBase.cpp | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index b4f1110ab..ab05a4c06 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -4,7 +4,7 @@ bool NetworkBase::Init() { - return false; + return true; } void NetworkBase::Exit() @@ -13,57 +13,57 @@ void NetworkBase::Exit() bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) { - return false; + return true; } bool NetworkBase::DestroySocket(NetworkSocket& socket) { - return false; + return true; } bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) { - return false; + return true; } bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) { - return false; + return true; } bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) { - return false; + return true; } bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) { - return false; + return true; } bool NetworkBase::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) { - return false; + return true; } bool NetworkBase::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) { - return false; + return true; } bool NetworkBase::Listen(NetworkSocket& socket, uint16 queueSize) { - return false; + return true; } bool NetworkBase::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) { - return false; + return true; } bool NetworkBase::IsReadable(NetworkSocket& socket) { - return false; + return true; } bool NetworkBase::IsWriteable(NetworkSocket& socket) @@ -73,17 +73,17 @@ bool NetworkBase::IsWriteable(NetworkSocket& socket) int32 NetworkBase::Poll(NetworkSocketGroup& group) { - return 0; + return -1; } bool NetworkBase::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) { - return false; + return true; } int32 NetworkBase::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) { - return 0; + return -1; } void NetworkBase::ClearGroup(NetworkSocketGroup& group) @@ -93,17 +93,17 @@ void NetworkBase::ClearGroup(NetworkSocketGroup& group) int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) { - return 0; + return -1; } int32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) { - return 0; + return -1; } bool NetworkBase::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) { - return false; + return true; } NetworkEndPoint NetworkBase::RemapEndPointToIPv6(NetworkEndPoint& endPoint) From 8ac0e7491aa8e223cf5d5859073eb1d3da0f24d7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:41:39 +0100 Subject: [PATCH 116/222] Typo Initialize -> Initializes, and so on ... --- Source/Engine/Platform/Base/NetworkBase.h | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 3dff55a56..4b9c10196 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -75,18 +75,18 @@ class FLAXENGINE_API NetworkBase { public: /// - /// Initialize the network module. + /// Initializes the network module. /// /// Return true on error. Otherwise false. static bool Init(); /// - /// Deinitialize the network module. + /// Deinitializes the network module. /// static void Exit(); /// - /// Create a new native socket. + /// Creates a new native socket. /// /// The socket struct to fill in. /// The protocol. @@ -95,14 +95,14 @@ class FLAXENGINE_API NetworkBase static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); /// - /// Close native socket. + /// Closes native socket. /// /// The socket. /// Return true on error. Otherwise false. static bool DestroySocket(NetworkSocket& socket); /// - /// Set the specified socket option. + /// Sets the specified socket option. /// /// The socket. /// The option. @@ -111,7 +111,7 @@ class FLAXENGINE_API NetworkBase static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); /// - /// Set the specified socket option. + /// Sets the specified socket option. /// /// The socket. /// The option. @@ -120,7 +120,7 @@ class FLAXENGINE_API NetworkBase static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); /// - /// Get the specified socket option. + /// Gets the specified socket option. /// /// The socket. /// The option. @@ -129,7 +129,7 @@ class FLAXENGINE_API NetworkBase static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); /// - /// Get the specified socket option. + /// Gets the specified socket option. /// /// The socket. /// The option. @@ -138,7 +138,7 @@ class FLAXENGINE_API NetworkBase static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); /// - /// Connect a socket to the specified end point. + /// Connects a socket to the specified end point. /// /// The socket. /// The end point. @@ -146,7 +146,7 @@ class FLAXENGINE_API NetworkBase static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); /// - /// Bind a socket to the specified end point. + /// Binds a socket to the specified end point. /// /// The socket. /// The end point. @@ -154,7 +154,7 @@ class FLAXENGINE_API NetworkBase static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); /// - /// Listen for incoming connection. + /// Listens for incoming connection. /// /// The socket. /// Pending connection queue size. @@ -162,7 +162,7 @@ class FLAXENGINE_API NetworkBase static bool Listen(NetworkSocket& socket, uint16 queueSize); /// - /// Accept a pending connection. + /// Accepts a pending connection. /// /// The socket. /// The newly connected socket. @@ -171,28 +171,28 @@ class FLAXENGINE_API NetworkBase static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); /// - /// Check for socket readability. + /// Checks for socket readability. /// /// The socket. /// Return true when data is available. Otherwise false. static bool IsReadable(NetworkSocket& socket); /// - /// Check for socket writeability. + /// Checks for socket writeability. /// /// The socket. /// Return true when data can be written. Otherwise false. static bool IsWriteable(NetworkSocket& socket); /// - /// Update sockets states. + /// Updates sockets states. /// /// The sockets group. /// Return -1 on error, The number of elements where states are nonzero, otherwise 0. static int32 Poll(NetworkSocketGroup& group); /// - /// Retrieve socket state. + /// Retrieves socket state. /// /// The group. /// The socket index in group. @@ -201,7 +201,7 @@ class FLAXENGINE_API NetworkBase static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); /// - /// Add a socket to a group. + /// Adds a socket to a group. /// /// The group. /// The socket. @@ -209,13 +209,13 @@ class FLAXENGINE_API NetworkBase static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); /// - /// Clear the socket group. + /// Clears the socket group. /// /// The group. static void ClearGroup(NetworkSocketGroup& group); /// - /// Write data to the socket. + /// Writes data to the socket. /// /// The socket. /// The data to write. @@ -225,7 +225,7 @@ class FLAXENGINE_API NetworkBase static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); /// - /// Read data on the socket. + /// Reads data on the socket. /// /// The socket. /// The buffer. @@ -235,7 +235,7 @@ class FLAXENGINE_API NetworkBase static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); /// - /// Create an end point. + /// Creates an end point. /// /// The address (hostname, IPv4 or IPv6). /// The port. @@ -246,7 +246,7 @@ class FLAXENGINE_API NetworkBase static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); /// - /// Remap an ipv4 end point to an ipv6 one. + /// Remaps an ipv4 end point to an ipv6 one. /// /// The ipv4 end point. /// The ipv6 end point. From 15d24308e87fe13c351edff7d88b6d6242e6d748 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:44:08 +0100 Subject: [PATCH 117/222] Fix "Returns true on error, otherwise false." & Return"s". --- Source/Engine/Platform/Base/NetworkBase.h | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 4b9c10196..805ca246e 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -91,14 +91,14 @@ class FLAXENGINE_API NetworkBase /// The socket struct to fill in. /// The protocol. /// The ip version. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); /// /// Closes native socket. /// /// The socket. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool DestroySocket(NetworkSocket& socket); /// @@ -107,7 +107,7 @@ class FLAXENGINE_API NetworkBase /// The socket. /// The option. /// The value. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); /// @@ -116,7 +116,7 @@ class FLAXENGINE_API NetworkBase /// The socket. /// The option. /// The value. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); /// @@ -125,7 +125,7 @@ class FLAXENGINE_API NetworkBase /// The socket. /// The option. /// The returned value. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); /// @@ -134,7 +134,7 @@ class FLAXENGINE_API NetworkBase /// The socket. /// The option. /// The returned value. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); /// @@ -142,7 +142,7 @@ class FLAXENGINE_API NetworkBase /// /// The socket. /// The end point. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); /// @@ -150,7 +150,7 @@ class FLAXENGINE_API NetworkBase /// /// The socket. /// The end point. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); /// @@ -158,7 +158,7 @@ class FLAXENGINE_API NetworkBase /// /// The socket. /// Pending connection queue size. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool Listen(NetworkSocket& socket, uint16 queueSize); /// @@ -167,28 +167,28 @@ class FLAXENGINE_API NetworkBase /// The socket. /// The newly connected socket. /// The end point of the new socket. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); /// /// Checks for socket readability. /// /// The socket. - /// Return true when data is available. Otherwise false. + /// Returns true when data is available. Otherwise false. static bool IsReadable(NetworkSocket& socket); /// /// Checks for socket writeability. /// /// The socket. - /// Return true when data can be written. Otherwise false. + /// Returns true when data can be written. Otherwise false. static bool IsWriteable(NetworkSocket& socket); /// /// Updates sockets states. /// /// The sockets group. - /// Return -1 on error, The number of elements where states are nonzero, otherwise 0. + /// Returns -1 on error, The number of elements where states are nonzero, otherwise 0. static int32 Poll(NetworkSocketGroup& group); /// @@ -197,7 +197,7 @@ class FLAXENGINE_API NetworkBase /// The group. /// The socket index in group. /// The returned state. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); /// @@ -205,7 +205,7 @@ class FLAXENGINE_API NetworkBase /// /// The group. /// The socket. - /// Return the socket index in group or -1 on error. + /// Returns the socket index in group or -1 on error. static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); /// @@ -221,7 +221,7 @@ class FLAXENGINE_API NetworkBase /// The data to write. /// The length of data. /// If protocol is UDP , the destination end point. Otherwise nullptr. - /// Return -1 on error, otherwise bytes written. + /// Returns -1 on error, otherwise bytes written. static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); /// @@ -231,7 +231,7 @@ class FLAXENGINE_API NetworkBase /// The buffer. /// Size of the buffer. /// If UDP, the end point from where data is coming. Otherwise nullptr. - /// Return -1 on error, otherwise bytes read. + /// Returns -1 on error, otherwise bytes read. static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); /// @@ -242,7 +242,7 @@ class FLAXENGINE_API NetworkBase /// The ip version. /// The created end point. /// True if the end point will be connected or binded. - /// Return true on error. Otherwise false. + /// Returns true on error, otherwise false. static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); /// From af1dfc0d08d187c7f9fdb97157c9b69b76385d7f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:45:35 +0100 Subject: [PATCH 118/222] Remove "Place holder" comments. --- Source/Engine/Platform/Network.h | 10 +++++----- Source/Engine/Platform/Types.h | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h index a361eb6bf..b8c198770 100644 --- a/Source/Engine/Platform/Network.h +++ b/Source/Engine/Platform/Network.h @@ -5,15 +5,15 @@ #if PLATFORM_WINDOWS #include "Win32/Win32Network.h" #elif PLATFORM_UWP -#include "Win32/Win32Network.h" // place holder +#include "Win32/Win32Network.h" #elif PLATFORM_LINUX -#include "Base/NetworkBase.h" // place holder +#include "Base/NetworkBase.h" #elif PLATFORM_PS4 -#include "Base/NetworkBase.h" // place holder +#include "Base/NetworkBase.h" #elif PLATFORM_XBOX_SCARLETT -#include "Win32/Win32Network.h" // it's probably isn't working +#include "Win32/Win32Network.h" #elif PLATFORM_ANDROID -#include "Base/NetworkBase.h" // place holder +#include "Base/NetworkBase.h" #else #error Missing Network implementation! #endif diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index cc1bcafcf..ad67e0b01 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -45,8 +45,8 @@ class Win32Thread; typedef Win32Thread Thread; class UWPWindow; typedef UWPWindow Window; -class NetworkBase; // place holder -typedef NetworkBase Network; // place holder +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_LINUX @@ -68,8 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; -class NetworkBase; // place holder -typedef NetworkBase Network; // place holder +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_PS4 @@ -91,8 +91,8 @@ class PS4Thread; typedef PS4Thread Thread; class PS4Window; typedef PS4Window Window; -class NetworkBase; // place holder -typedef NetworkBase Network; // place holder +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_XBOX_SCARLETT @@ -114,8 +114,8 @@ class Win32Thread; typedef Win32Thread Thread; class XboxScarlettWindow; typedef XboxScarlettWindow Window; -class NetworkBase; // place holder -typedef NetworkBase Network; // place holder +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_ANDROID @@ -137,8 +137,8 @@ class AndroidThread; typedef AndroidThread Thread; class AndroidWindow; typedef AndroidWindow Window; -class NetworkBase; // place holder -typedef NetworkBase Network; // place holder +class NetworkBase; +typedef NetworkBase Network; #else From aaac0aec963e4efebfc79b8916a67781819b2c5b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:46:27 +0100 Subject: [PATCH 119/222] Brace to the next line. --- Source/Engine/Platform/Win32/Win32Network.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index ea2e7c46d..508374365 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -97,11 +97,13 @@ static void PrintAddrFromInfo(addrinfoW& info) for (curr = &info; curr != nullptr; curr = curr->ai_next) { void* addr; - if (curr->ai_family == AF_INET) { + if (curr->ai_family == AF_INET) + { sockaddr_in* ipv4 = (struct sockaddr_in*)curr->ai_addr; addr = &(ipv4->sin_addr); } - else { + else + { sockaddr_in6* ipv6 = (struct sockaddr_in6*)curr->ai_addr; addr = &(ipv6->sin6_addr); } From 54b9eaa9692184b535c182fa4bb6be4dbdd9bf7f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:48:48 +0100 Subject: [PATCH 120/222] Move static var to local. --- Source/Engine/Platform/Win32/Win32Network.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 508374365..a25a8653a 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -294,7 +294,6 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net return false; } -static thread_local int32 error; bool Win32Network::IsReadable(NetworkSocket& socket) { pollfd entry; @@ -302,7 +301,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket) entry.events = POLLRDNORM; if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - error = WSAGetLastError(); + int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); @@ -320,7 +319,7 @@ bool Win32Network::IsWriteable(NetworkSocket& socket) entry.events = POLLWRNORM; if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) { - error = WSAGetLastError(); + int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); @@ -331,21 +330,19 @@ bool Win32Network::IsWriteable(NetworkSocket& socket) return false; } -static thread_local int32 pollret; int32 Win32Network::Poll(NetworkSocketGroup& group) { - pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); + int32 pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); if (pollret == SOCKET_ERROR) LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage().Get()); return pollret; } -static thread_local pollfd* pollptr; bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) { if (index >= SOCKGROUP_MAXCOUNT) return true; - pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; + pollfd* pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; if (pollptr->revents & POLLERR) state.Error = true; if (pollptr->revents & POLLHUP) @@ -359,11 +356,11 @@ bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, Netwo return false; } -static thread_local pollfd pollinfo; int32 Win32Network::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) { if (group.Count >= SOCKGROUP_MAXCOUNT) return -1; + pollfd pollinfo; pollinfo.fd = *(SOCKET*)socket.Data; pollinfo.events = POLLRDNORM | POLLWRNORM; *(pollfd*)&group.Data[group.Count * SOCKGROUP_ITEMSIZE] = pollinfo; From c20f0fa430c325a9f38b82e690bc1ea006910d5c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 15:55:51 +0100 Subject: [PATCH 121/222] Remove Init & Exit, logic is now in Win32Platform.cpp. --- Source/Engine/Platform/Base/NetworkBase.cpp | 9 -------- Source/Engine/Platform/Base/NetworkBase.h | 11 ---------- Source/Engine/Platform/Win32/Win32Network.cpp | 13 ----------- Source/Engine/Platform/Win32/Win32Network.h | 2 -- .../Engine/Platform/Win32/Win32Platform.cpp | 22 +++++++++++++++---- 5 files changed, 18 insertions(+), 39 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index ab05a4c06..6a83cd9f6 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -2,15 +2,6 @@ #include "NetworkBase.h" -bool NetworkBase::Init() -{ - return true; -} - -void NetworkBase::Exit() -{ -} - bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) { return true; diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 805ca246e..e5c816b77 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -74,17 +74,6 @@ struct FLAXENGINE_API NetworkSocketGroup class FLAXENGINE_API NetworkBase { public: - /// - /// Initializes the network module. - /// - /// Return true on error. Otherwise false. - static bool Init(); - - /// - /// Deinitializes the network module. - /// - static void Exit(); - /// /// Creates a new native socket. /// diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index a25a8653a..3783cd879 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -14,7 +14,6 @@ static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPo static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; -static WSAData _wsaData; /* * Known issues : @@ -136,18 +135,6 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i } } -bool Win32Network::Init() -{ - if (WSAStartup(MAKEWORD(2, 0), &_wsaData) != 0) - return true; - return false; -} - -void Win32Network::Exit() -{ - WSACleanup(); -} - bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) { socket.Protocol = proto; diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index 8700d9f19..b0e313056 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -13,8 +13,6 @@ class FLAXENGINE_API Win32Network : public NetworkBase public: // [NetworkBase] - static bool Init(); - static void Exit(); static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index db6151a17..f74485748 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -12,6 +12,7 @@ #include "IncludeWindowsHeaders.h" #include "Engine/Core/Collections/HashFunctions.h" #include "Engine/Platform/Network.h" +#include "Engine/Core/Log.h" #include #include @@ -28,6 +29,8 @@ namespace double CyclesToSeconds; } +static WSAData _wsaData; + // Helper function to count set bits in the processor mask DWORD CountSetBits(ULONG_PTR bitMask) { @@ -45,6 +48,18 @@ DWORD CountSetBits(ULONG_PTR bitMask) return bitSetCount; } +static String GetLastErrorMessage() +{ + wchar_t* s = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); + String str(s); + LocalFree(s); + return str; +} + bool Win32Platform::Init() { if (PlatformBase::Init()) @@ -213,15 +228,14 @@ bool Win32Platform::Init() DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize; } - //TODO: log error if true - Win32Network::Init(); - + if (WSAStartup(MAKEWORD(2, 0), &_wsaData) != 0) + LOG(Error, "Unable to initializes native network! Error : {0}", GetLastErrorMessage().Get()); return false; } void Win32Platform::Exit() { - Network::Exit(); + WSACleanup(); } void Win32Platform::MemoryBarrier() From 254bdacc73a038de74bd67458b16a889dc03c3fc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 17:07:37 +0100 Subject: [PATCH 122/222] Cleanup. --- Source/Engine/Platform/Win32/Win32Network.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 3783cd879..fe1ef9d48 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -52,8 +52,6 @@ static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; } -//TODO: can be simplified by casting addr to the right struct and ntohl the port, so we can get rid of getnameinfo -// getnameinfo return a name ( like JimiPC ), not the ip! static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) { uint32 size = GetAddrSize(*addr); From 674d1fbcc5eb149be868783294eb332f2b9fa229 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 20:06:47 +0100 Subject: [PATCH 123/222] Handle error. --- Source/Engine/Platform/Win32/Win32Network.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index fe1ef9d48..fbbe19b58 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -267,7 +267,10 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net int32 size = sizeof sockaddr_in6; if ((sock = accept(*(SOCKET*)serverSock.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) { - LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetLastErrorMessage().Get()); + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error).Get()); return true; } memcpy(newSock.Data, &sock, sizeof sock); From 09049990bd71a0f52749dbfde41575e5e974772d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 20:07:09 +0100 Subject: [PATCH 124/222] Default value. --- Source/Engine/Platform/Base/NetworkBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index e5c816b77..7e039e2a6 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -67,7 +67,7 @@ struct FLAXENGINE_API NetworkSocketState struct FLAXENGINE_API NetworkSocketGroup { - uint32 Count; + uint32 Count = 0; byte Data[SOCKGROUP_MAXCOUNT * 16] = {}; }; From 5f85e9ae96753251c8b4cf7034d1f4e01d33e99e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Tue, 26 Jan 2021 20:07:35 +0100 Subject: [PATCH 125/222] Reset state. --- Source/Engine/Platform/Win32/Win32Network.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index fbbe19b58..edadd2267 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -331,6 +331,7 @@ bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, Netwo if (index >= SOCKGROUP_MAXCOUNT) return true; pollfd* pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; + memset(&state, 0, sizeof state); if (pollptr->revents & POLLERR) state.Error = true; if (pollptr->revents & POLLHUP) From cd2ae08da05f97c2f318dbc7edcbb393a91a5a60 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 10:37:15 +0100 Subject: [PATCH 126/222] Fixes and tweaks to the networking impl --- Source/Engine/Platform/Base/NetworkBase.cpp | 3 +- Source/Engine/Platform/Base/NetworkBase.h | 35 ++++++-- Source/Engine/Platform/UWP/UWPPlatform.cpp | 1 + Source/Engine/Platform/Win32/Win32Network.cpp | 90 ++++++++++--------- .../Engine/Platform/Win32/Win32Platform.cpp | 46 +++------- 5 files changed, 87 insertions(+), 88 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 6a83cd9f6..06f297cc1 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NetworkBase.h" @@ -101,4 +101,3 @@ NetworkEndPoint NetworkBase::RemapEndPointToIPv6(NetworkEndPoint& endPoint) { return NetworkEndPoint(); } - diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 7e039e2a6..6e6821ab4 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -5,20 +5,27 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Core/Types/String.h" API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); + #define SOCKGROUP_MAXCOUNT 64 #define SOCKGROUP_ITEMSIZE 16 enum class FLAXENGINE_API NetworkProtocolType { + /// Not specified. Undefined, + /// User Datagram Protocol. Udp, + /// Transmission Control Protocol. Tcp }; enum class FLAXENGINE_API NetworkIPVersion { + /// Not specified. Undefined, + /// Internet Protocol version 4. IPv4, + /// Internet Protocol version 6. IPv6 }; @@ -26,7 +33,7 @@ struct FLAXENGINE_API NetworkSocket { NetworkProtocolType Protocol = NetworkProtocolType::Undefined; NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; - byte Data[8] = {}; // sizeof SOCKET = unsigned __int64 + byte Data[8] = {}; }; struct FLAXENGINE_API NetworkEndPoint @@ -34,26 +41,41 @@ struct FLAXENGINE_API NetworkEndPoint NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; String Address; String Port; - byte Data[28] = {}; // sizeof sockaddr_in6 , biggest sockaddr that we will use + byte Data[28] = {}; }; -enum FLAXENGINE_API NetworkSocketOption +enum class FLAXENGINE_API NetworkSocketOption { + /// Enables debugging info recording. Debug, + /// Allows local address reusing. ReuseAddr, + /// Keeps connections alive. KeepAlive, + /// Indicates that outgoing data should be sent on whatever interface the socket is bound to and not a routed on some other interface. DontRoute, + /// Allows for sending broadcast data. Broadcast, + /// Uses the local loopback address when sending data from this socket. UseLoopback, + /// Lingers on close if data present. Linger, + /// Allows out-of-bound data to be returned in-line with regular data. OOBInline, + /// Socket send data buffer size. SendBuffer, + /// Socket receive data buffer size. RecvBuffer, + /// The timeout in milliseconds for blocking send calls. SendTimeout, + /// The timeout in milliseconds for blocking receive calls. RecvTimeout, + /// The last socket error code. Error, + /// Enables the Nagle algorithm for TCP sockets. NoDelay, - IPv6Only + /// Enables IPv6 communication only for TCP socket. + IPv6Only, }; struct FLAXENGINE_API NetworkSocketState @@ -68,12 +90,12 @@ struct FLAXENGINE_API NetworkSocketState struct FLAXENGINE_API NetworkSocketGroup { uint32 Count = 0; - byte Data[SOCKGROUP_MAXCOUNT * 16] = {}; + byte Data[SOCKGROUP_MAXCOUNT * SOCKGROUP_ITEMSIZE] = {}; }; class FLAXENGINE_API NetworkBase { - public: +public: /// /// Creates a new native socket. /// @@ -241,4 +263,3 @@ class FLAXENGINE_API NetworkBase /// The ipv6 end point. static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); }; - diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp index 3bd654f10..ed01948aa 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.cpp +++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp @@ -111,6 +111,7 @@ void UWPPlatform::BeforeExit() void UWPPlatform::Exit() { + Win32Platform::Exit(); } int32 UWPPlatform::GetDpi() diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index edadd2267..2da68957b 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Win32Network.h" #include "Engine/Core/Log.h" @@ -12,8 +12,10 @@ static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); +// @formatter:off static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; +// @formatter:on /* * Known issues : @@ -24,9 +26,9 @@ static String GetErrorMessage(int error) { wchar_t* s = nullptr; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&s), 0, nullptr); + nullptr, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); String str(s); LocalFree(s); return str; @@ -72,11 +74,11 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); return true; } - + char ip[INET6_ADDRSTRLEN]; if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) { - LOG(Error, "Unable to extract address from sockaddr! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Unable to extract address from sockaddr! Error : {0}", GetLastErrorMessage()); return true; } endPoint.Address = String(ip); @@ -107,7 +109,7 @@ static void PrintAddrFromInfo(addrinfoW& info) char str[INET6_ADDRSTRLEN]; inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); - LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString().Get()); + LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString()); } } @@ -115,21 +117,21 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i { switch (option) { - SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) - SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) - SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) - SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) - SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) - SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) - SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) - SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) - SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) - SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) - SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) - SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) - SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) - SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) - SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) } } @@ -144,14 +146,14 @@ bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto if ((sock = ::socket(family, stype, prot)) == INVALID_SOCKET) { - LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage()); return true; } memcpy(socket.Data, &sock, sizeof sock); unsigned long value = 1; if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) { - LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage()); return true; // Support using blocking socket , need to test it } return false; @@ -181,10 +183,10 @@ bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption op int32 optnme = 0; TranslateSockOptToNative(option, &optlvl, &optnme); - + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) { - LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); return true; } return false; @@ -202,13 +204,13 @@ bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption op { int32 optlvl = 0; int32 optnme = 0; - + TranslateSockOptToNative(option, &optlvl, &optnme); - + int32 size; if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) { - LOG(Warning, "Unable to get socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage().Get()); + LOG(Warning, "Unable to get socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); return true; } return false; @@ -222,7 +224,7 @@ bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoin int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; - LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetErrorMessage(error).Get()); + LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address, endPoint.Port, GetErrorMessage(error)); return true; } return false; @@ -239,7 +241,7 @@ bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) { - LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), GetLastErrorMessage().Get()); + LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address, endPoint.Port, GetLastErrorMessage()); return true; } return false; @@ -249,7 +251,7 @@ bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) { if (listen(*(SOCKET*)socket.Data, (int32)queueSize) == SOCKET_ERROR) { - LOG(Error, "Unable to listen ! Socket : {0} Error : {1}", GetLastErrorMessage().Get()); + LOG(Error, "Unable to listen ! Socket : {0} Error : {1}", GetLastErrorMessage()); return true; } return false; @@ -270,7 +272,7 @@ bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, Net int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; - LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error).Get()); + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error)); return true; } memcpy(newSock.Data, &sock, sizeof sock); @@ -292,7 +294,7 @@ bool Win32Network::IsReadable(NetworkSocket& socket) int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; - LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); return false; } if (entry.revents & POLLRDNORM) @@ -310,7 +312,7 @@ bool Win32Network::IsWriteable(NetworkSocket& socket) int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return false; - LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error).Get()); + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); return false; } if (entry.revents & POLLWRNORM) @@ -322,7 +324,7 @@ int32 Win32Network::Poll(NetworkSocketGroup& group) { int32 pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); if (pollret == SOCKET_ERROR) - LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage().Get()); + LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage()); return pollret; } @@ -354,7 +356,7 @@ int32 Win32Network::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& s pollinfo.events = POLLRDNORM | POLLWRNORM; *(pollfd*)&group.Data[group.Count * SOCKGROUP_ITEMSIZE] = pollinfo; group.Count++; - return group.Count-1; + return group.Count - 1; } void Win32Network::ClearGroup(NetworkSocketGroup& group) @@ -374,7 +376,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) { - LOG(Error, "Unable to send data! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage().Get()); + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage()); return -1; } } @@ -382,13 +384,13 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, { if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) { - LOG(Error, "Unable to send data! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage().Get()); + LOG(Error, "Unable to send data! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage()); return -1; } } else { - //TODO: better explanation + // TODO: better explanation LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); return -1; } @@ -405,7 +407,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer const int32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) return 0; - LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetErrorMessage(error).Get()); + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetErrorMessage(error)); return -1; } } @@ -415,7 +417,7 @@ int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 buffer sockaddr_in6 addr; if ((size = recvfrom(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) { - LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage().Get()); + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage()); return -1; } if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) @@ -439,13 +441,13 @@ bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersio // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) { - LOG(Error, "Unable to query info for address : {0} Error : {1}", address != nullptr ? address->Get() : String("ANY").Get(), gai_strerror(status)); + LOG(Error, "Unable to query info for address : {0} Error : {1}", address ? address->Get() : String("ANY"), gai_strerror(status)); return true; } if (info == nullptr) { - LOG(Error, "Unable to resolve address! Address : {0}", address != nullptr ? address->Get() : String("ANY").Get()); + LOG(Error, "Unable to resolve address! Address : {0}", address ? address->Get() : String("ANY")); return true; } diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index f74485748..b01b96feb 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -9,10 +9,9 @@ #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Math/Math.h" -#include "IncludeWindowsHeaders.h" #include "Engine/Core/Collections/HashFunctions.h" -#include "Engine/Platform/Network.h" #include "Engine/Core/Log.h" +#include "IncludeWindowsHeaders.h" #include #include @@ -27,10 +26,9 @@ namespace CPUInfo CpuInfo; uint64 ClockFrequency; double CyclesToSeconds; + WSAData WsaData; } -static WSAData _wsaData; - // Helper function to count set bits in the processor mask DWORD CountSetBits(ULONG_PTR bitMask) { @@ -52,9 +50,9 @@ static String GetLastErrorMessage() { wchar_t* s = nullptr; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, WSAGetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&s), 0, nullptr); + nullptr, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); String str(s); LocalFree(s); return str; @@ -72,6 +70,7 @@ bool Win32Platform::Init() ClockFrequency = frequency.QuadPart; CyclesToSeconds = 1.0 / static_cast(frequency.QuadPart); + // Count CPUs BOOL done = FALSE; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr; @@ -84,12 +83,10 @@ bool Win32Platform::Init() DWORD processorPackageCount = 0; DWORD byteOffset = 0; PCACHE_DESCRIPTOR cache; - while (!done) { DWORD rc = GetLogicalProcessorInformation(buffer, &returnLength); - - if (FALSE == rc) + if (rc == FALSE) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { @@ -97,9 +94,7 @@ bool Win32Platform::Init() { free(buffer); } - buffer = static_cast(malloc(returnLength)); - if (buffer == nullptr) { return true; @@ -115,23 +110,16 @@ bool Win32Platform::Init() done = TRUE; } } - ptr = buffer; - while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { switch (ptr->Relationship) { case RelationProcessorCore: - processorCoreCount++; - - // A hyper threaded core supplies more than one logical processor logicalProcessorCount += CountSetBits(ptr->ProcessorMask); break; - case RelationCache: - // Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache. cache = &ptr->Cache; if (cache->Level == 1) { @@ -146,9 +134,7 @@ bool Win32Platform::Init() processorL3CacheSize += cache->Size; } break; - case RelationProcessorPackage: - // Logical processors share a physical package processorPackageCount++; break; } @@ -228,8 +214,10 @@ bool Win32Platform::Init() DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize; } - if (WSAStartup(MAKEWORD(2, 0), &_wsaData) != 0) - LOG(Error, "Unable to initializes native network! Error : {0}", GetLastErrorMessage().Get()); + // Init networking + if (WSAStartup(MAKEWORD(2, 0), &WsaData) != 0) + LOG(Error, "Unable to initializes native network! Error : {0}", GetLastErrorMessage()); + return false; } @@ -240,19 +228,8 @@ void Win32Platform::Exit() void Win32Platform::MemoryBarrier() { - // NOTE: _ReadWriteBarrier and friends only prevent the - // compiler from reordering loads and stores. To prevent - // the CPU from doing the same, we have to use the - // MemoryBarrier macro which expands to e.g. a serializing - // XCHG instruction on x86. Also note that the MemoryBarrier - // macro does *not* imply _ReadWriteBarrier, so that call - // cannot be eliminated. - _ReadWriteBarrier(); - - // MemoryBarrier macro (we use undef to hide some symbols from Windows headers) #if PLATFORM_64BITS - #ifdef _AMD64_ __faststorefence(); #elif defined(_IA64_) @@ -260,7 +237,6 @@ void Win32Platform::MemoryBarrier() #else #error "Invalid platform." #endif - #else LONG barrier; __asm { From 53601d8a85c34de27ea34456e379934ab67a6740 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 12:11:59 +0100 Subject: [PATCH 127/222] Optimize and improve ActorChildNodes handling --- Source/Editor/Gizmo/TransformGizmo.cs | 2 +- Source/Editor/SceneGraph/ActorChildNode.cs | 17 +++++++++++++++++ Source/Editor/SceneGraph/ActorNode.cs | 19 ++++++++++++++----- .../Editor/SceneGraph/Actors/BoxVolumeNode.cs | 4 ++-- .../Editor/SceneGraph/Actors/NavLinkNode.cs | 4 ++-- .../Editor/Viewport/PrefabWindowViewport.cs | 2 +- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index eca5475ed..f7aed5597 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Gizmo if (hit != null) { // For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor) - if (hit is ActorChildNode actorChildNode) + if (hit is ActorChildNode actorChildNode && !actorChildNode.CanBeSelectedDirectly) { var parentNode = actorChildNode.ParentNode; bool canChildBeSelected = sceneEditing.Selection.Contains(parentNode); diff --git a/Source/Editor/SceneGraph/ActorChildNode.cs b/Source/Editor/SceneGraph/ActorChildNode.cs index 0711aa20a..328ca277a 100644 --- a/Source/Editor/SceneGraph/ActorChildNode.cs +++ b/Source/Editor/SceneGraph/ActorChildNode.cs @@ -19,6 +19,11 @@ namespace FlaxEditor.SceneGraph /// public readonly int Index; + /// + /// Gets a value indicating whether this node can be selected directly without selecting parent actor node first. + /// + public virtual bool CanBeSelectedDirectly => false; + /// /// Initializes a new instance of the class. /// @@ -66,6 +71,18 @@ namespace FlaxEditor.SceneGraph /// public override object UndoRecordObject => ParentNode.UndoRecordObject; + + /// + public override void Dispose() + { + // Unlink from the parent + if (parentNode is ActorNode parentActorNode && parentActorNode.ActorChildNodes != null) + { + parentActorNode.ActorChildNodes.Remove(this); + } + + base.Dispose(); + } } /// diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 753440378..5cc5f5337 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.SceneGraph /// /// The actor child nodes used to represent special parts of the actor (meshes, links, surfaces). /// - public readonly List ActorChildNodes = new List(); + public List ActorChildNodes; /// /// Initializes a new instance of the class. @@ -108,6 +108,8 @@ namespace FlaxEditor.SceneGraph /// The node public ActorChildNode AddChildNode(ActorChildNode node) { + if (ActorChildNodes == null) + ActorChildNodes = new List(); ActorChildNodes.Add(node); node.ParentNode = this; return node; @@ -125,9 +127,12 @@ namespace FlaxEditor.SceneGraph root.OnActorChildNodesDispose(this); } - for (int i = 0; i < ActorChildNodes.Count; i++) - ActorChildNodes[i].Dispose(); - ActorChildNodes.Clear(); + if (ActorChildNodes != null) + { + for (int i = 0; i < ActorChildNodes.Count; i++) + ActorChildNodes[i].Dispose(); + ActorChildNodes.Clear(); + } } /// @@ -275,8 +280,12 @@ namespace FlaxEditor.SceneGraph /// public override void Dispose() { - // Cleanup UI _treeNode.Dispose(); + if (ActorChildNodes != null) + { + ActorChildNodes.Clear(); + ActorChildNodes = null; + } base.Dispose(); } diff --git a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs index 92b252138..24b035bc8 100644 --- a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs +++ b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs @@ -78,14 +78,14 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (BoxVolume)((BoxVolumeNode)ParentNode).Actor; + var actor = (BoxVolume)_actor.Actor; var localOffset = _offset * actor.Size; Transform localTrans = new Transform(localOffset); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (BoxVolume)((BoxVolumeNode)ParentNode).Actor; + var actor = (BoxVolume)_actor.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); var prevLocalOffset = _offset * actor.Size; var localOffset = Vector3.Abs(_offset) * 2.0f * localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs index 8eb5fbc8b..941e39267 100644 --- a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs +++ b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs @@ -37,13 +37,13 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (NavLink)((NavLinkNode)ParentNode).Actor; + var actor = (NavLink)_actor.Actor; Transform localTrans = new Transform(_isStart ? actor.Start : actor.End); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (NavLink)((NavLinkNode)ParentNode).Actor; + var actor = (NavLink)_actor.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); if (_isStart) actor.Start = localTrans.Translation; diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 94b994ac8..b5a19b1ec 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -513,7 +513,7 @@ namespace FlaxEditor.Viewport if (hit != null) { // For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor) - if (hit is ActorChildNode actorChildNode) + if (hit is ActorChildNode actorChildNode && !actorChildNode.CanBeSelectedDirectly) { var parentNode = actorChildNode.ParentNode; bool canChildBeSelected = _window.Selection.Contains(parentNode); From d0c0259ee23cdc1f6495b52ecb7e4ec5f34e3202 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 12:12:40 +0100 Subject: [PATCH 128/222] Add setter methods for spline points --- Source/Engine/Level/Actors/Spline.cpp | 36 +++++++++++++++++++++++++-- Source/Engine/Level/Actors/Spline.h | 32 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 9f47aa06e..3bb08b65c 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -231,9 +231,41 @@ void Spline::ClearSpline() UpdateSpline(); } +void Spline::SetSplinePoint(int32 index, const Vector3& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value.Translation = _transform.WorldToLocal(point); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value.Translation = point; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineTransform(int32 index, const Transform& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value = _transform.WorldToLocal(point); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value = point; + if (updateSpline) + UpdateSpline(); +} + void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) { - const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point)); + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); Curve.GetKeyframes().Add(k); if (updateSpline) UpdateSpline(); @@ -241,7 +273,7 @@ void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) void Spline::AddSplineLocalPoint(const Vector3& point, bool updateSpline) { - const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point)); Curve.GetKeyframes().Add(k); if (updateSpline) UpdateSpline(); diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index cc4226414..76f1eddab 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -198,6 +198,38 @@ public: /// API_FUNCTION() void ClearSpline(); + /// + /// Sets the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplinePoint(int32 index, const Vector3& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplineTransform(int32 index, const Transform& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline = true); + /// /// Adds the point to the spline curve (at the end). /// From fd96957819de53d2112b3aecf0f9ce73ca4ac205 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 12:13:00 +0100 Subject: [PATCH 129/222] Add gizmo handles for spline points --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index ea826c29e..03d4c5736 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using FlaxEngine; +using FlaxEngine.Json; namespace FlaxEditor.SceneGraph.Actors { @@ -10,10 +12,78 @@ namespace FlaxEditor.SceneGraph.Actors [HideInEditor] public sealed class SplineNode : ActorNode { + private sealed class SplinePointNode : ActorChildNode + { + public SplinePointNode(SplineNode actor, Guid id, int index) + : base(actor, id, index) + { + } + + public override bool CanBeSelectedDirectly => true; + + public override Transform Transform + { + get + { + var actor = (Spline)_actor.Actor; + return actor.GetSplineTransform(Index); + } + set + { + var actor = (Spline)_actor.Actor; + actor.SetSplineTransform(Index, value); + } + } + + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + var actor = (Spline)_actor.Actor; + var pos = actor.GetSplinePoint(Index); + normal = -ray.Ray.Direction; + return new BoundingSphere(pos, 5.0f).Intersects(ref ray.Ray, out distance); + } + } + /// public SplineNode(Actor actor) : base(actor) { + OnUpdate(); + FlaxEngine.Scripting.Update += OnUpdate; + } + + private unsafe void OnUpdate() + { + // Sync spline points with gizmo handles + var actor = (Spline)Actor; + var dstCount = actor.SplinePointsCount; + if (dstCount > 1 && actor.IsLoop) + dstCount--; // The last point is the same as the first one for loop mode + var srcCount = ActorChildNodes?.Count ?? 0; + if (dstCount != srcCount) + { + // Remove unused points + while (srcCount > dstCount) + ActorChildNodes[srcCount-- - 1].Dispose(); + + // Add new points + var id = ID; + var g = (JsonSerializer.GuidInterop*)&id; + g->D += (uint)srcCount; + while (srcCount < dstCount) + { + g->D += 1; + AddChildNode(new SplinePointNode(this, id, srcCount++)); + } + } + } + + /// + public override void OnDispose() + { + FlaxEngine.Scripting.Update -= OnUpdate; + + base.OnDispose(); } } } From 37e3a85080844b689af26f6e10d9f260017c8acd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 12:33:32 +0100 Subject: [PATCH 130/222] Optimize splines debug rendering performance --- Source/Engine/Debug/DebugDraw.cpp | 1 + Source/Engine/Level/Actors/Spline.cpp | 31 +++++++++++---------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 596956dea..320a991a3 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -593,6 +593,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount) { + PROFILE_CPU(); if (selectedActors) { for (int32 i = 0; i < selectedActorsCount; i++) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 3bb08b65c..aaccd13fe 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -371,27 +371,23 @@ void Spline::SetKeyframes(MonoArray* data) namespace { - void DrawSegment(Spline* spline, int32 start, int32 end, const Color& color, const Transform& transform, bool depthTest) - { - const auto& startKey = spline->Curve[start]; - const auto& endKey = spline->Curve[end]; - const Vector3 startPos = transform.LocalToWorld(startKey.Value.Translation); - const Vector3& startTangent = startKey.TangentOut.Translation; - const Vector3 endPos = transform.LocalToWorld(endKey.Value.Translation); - const Vector3& endTangent = endKey.TangentIn.Translation; - const float d = (endKey.Time - startKey.Time) / 3.0f; - DEBUG_DRAW_BEZIER(startPos, startPos + startTangent * d, endPos + endTangent * d, endPos, color, 0.0f, depthTest); - } - void DrawSpline(Spline* spline, const Color& color, const Transform& transform, bool depthTest) { const int32 count = spline->Curve.GetKeyframes().Count(); - for (int32 i = 0; i < count; i++) + if (count == 0) + return; + Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get(); + Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(prevPos, 5.0f), color, 0.0f, depthTest); + for (int32 i = 1; i < count; i++) { - Vector3 p = transform.LocalToWorld(spline->Curve[i].Value.Translation); - DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(p, 5.0f), color, 0.0f, true); - if (i != 0) - DrawSegment(spline, i - 1, i, color, transform, depthTest); + Spline::Keyframe* next = prev + 1; + Vector3 nextPos = transform.LocalToWorld(next->Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, 5.0f), color, 0.0f, depthTest); + const float d = (next->Time - prev->Time) / 3.0f; + DEBUG_DRAW_BEZIER(prevPos, prevPos + prev->TangentOut.Translation * d, nextPos + next->TangentIn.Translation * d, nextPos, color, 0.0f, depthTest); + prev = next; + prevPos = nextPos; } } } @@ -409,7 +405,6 @@ void Spline::OnDebugDrawSelected() { const Color color = GetSplineColor(); DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false); - DrawSpline(this, color, _transform, true); // Base Actor::OnDebugDrawSelected(); From 590fcebaec80240518eb0cece5f888a5bb3e09af Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 12:33:46 +0100 Subject: [PATCH 131/222] Fix drawing spline when spline point gizmo is selected --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 03d4c5736..09d50e706 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -42,6 +42,11 @@ namespace FlaxEditor.SceneGraph.Actors normal = -ray.Ray.Direction; return new BoundingSphere(pos, 5.0f).Intersects(ref ray.Ray, out distance); } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + ParentNode.OnDebugDraw(data); + } } /// From 3dc6afef624a509880f207fac17f301a4ea5c339 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 12:38:03 +0100 Subject: [PATCH 132/222] Improve selecting spline gizmos and add highlight sphere --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 09d50e706..aa7fff53a 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -40,12 +40,16 @@ namespace FlaxEditor.SceneGraph.Actors var actor = (Spline)_actor.Actor; var pos = actor.GetSplinePoint(Index); normal = -ray.Ray.Direction; - return new BoundingSphere(pos, 5.0f).Intersects(ref ray.Ray, out distance); + return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); } public override void OnDebugDraw(ViewportDebugDrawData data) { ParentNode.OnDebugDraw(data); + + var actor = (Spline)_actor.Actor; + var pos = actor.GetSplinePoint(Index); + DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false); } } From 5a402df3ff72c91654f62c324aa045e39fb64666 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:07:02 +0100 Subject: [PATCH 133/222] Add Add/Subtract methods to Transform --- Source/Engine/Core/Math/Quaternion.cs | 8 +++ Source/Engine/Core/Math/Transform.cpp | 15 +++-- Source/Engine/Core/Math/Transform.cs | 59 +++++++++++++++++-- Source/Engine/Core/Math/Transform.h | 12 ++++ .../Tools/FlaxEngine.Tests/TestTransform.cs | 24 +++++++- 5 files changed, 108 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index bc86fa2c9..0e809f271 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -324,6 +324,14 @@ namespace FlaxEngine Z = -Z; } + /// + /// Gets the conjugated quaternion. + /// + public Quaternion Conjugated() + { + return new Quaternion(-X, -Y, -Z, W); + } + /// /// Conjugates and renormalizes the quaternion. /// diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp index 728fc6a90..35f616236 100644 --- a/Source/Engine/Core/Math/Transform.cpp +++ b/Source/Engine/Core/Math/Transform.cpp @@ -43,37 +43,42 @@ void Transform::GetWorld(Matrix& result) const Transform Transform::Add(const Vector3& translation) const { Transform result; - result.Orientation = Orientation; result.Scale = Scale; Vector3::Add(Translation, translation, result.Translation); - return result; } Transform Transform::Add(const Transform& other) const { Transform result; - Quaternion::Multiply(Orientation, other.Orientation, result.Orientation); result.Orientation.Normalize(); Vector3::Multiply(Scale, other.Scale, result.Scale); Vector3::Add(Translation, other.Translation, result.Translation); + return result; +} +Transform Transform::Subtract(const Transform& other) const +{ + Transform result; + Vector3::Subtract(Translation, other.Translation, result.Translation); + const Quaternion invRotation = other.Orientation.Conjugated(); + Quaternion::Multiply(Orientation, invRotation, result.Orientation); + result.Orientation.Normalize(); + Vector3::Divide(Scale, other.Scale, result.Scale); return result; } Transform Transform::LocalToWorld(const Transform& other) const { Transform result; - Quaternion::Multiply(Orientation, other.Orientation, result.Orientation); result.Orientation.Normalize(); Vector3::Multiply(Scale, other.Scale, result.Scale); Vector3 tmp = other.Translation * Scale; Vector3::Transform(tmp, Orientation, tmp); Vector3::Add(tmp, Translation, result.Translation); - return result; } diff --git a/Source/Engine/Core/Math/Transform.cs b/Source/Engine/Core/Math/Transform.cs index 7065309e3..129ddf92a 100644 --- a/Source/Engine/Core/Math/Transform.cs +++ b/Source/Engine/Core/Math/Transform.cs @@ -169,6 +169,37 @@ namespace FlaxEngine Matrix.Transformation(ref Scale, ref Orientation, ref Translation, out result); } + /// + /// Adds two transforms. + /// + /// The first transform to add. + /// The second transform to add. + /// The sum of the two transforms. + public static Transform Add(Transform left, Transform right) + { + Transform result; + Quaternion.Multiply(ref left.Orientation, ref right.Orientation, out result.Orientation); + Vector3.Multiply(ref left.Scale, ref right.Scale, out result.Scale); + Vector3.Add(ref left.Translation, ref right.Translation, out result.Translation); + return result; + } + + /// + /// Subtracts two transforms. + /// + /// The first transform to subtract from. + /// The second transform to subtract. + /// The difference of the two transforms. + public static Transform Subtract(Transform left, Transform right) + { + Transform result; + Vector3.Subtract(ref left.Translation, ref right.Translation, out result.Translation); + Quaternion invRotation = right.Orientation.Conjugated(); + Quaternion.Multiply(ref left.Orientation, ref invRotation, out result.Orientation); + Vector3.Divide(ref left.Scale, ref right.Scale, out result.Scale); + return result; + } + /// /// Perform transformation of the given transform in local space /// @@ -176,12 +207,10 @@ namespace FlaxEngine /// World space transform public Transform LocalToWorld(Transform other) { - Transform result = new Transform(Vector3.Zero); - + Transform result; Quaternion.Multiply(ref Orientation, ref other.Orientation, out result.Orientation); Vector3.Multiply(ref Scale, ref other.Scale, out result.Scale); result.Translation = LocalToWorld(other.Translation); - return result; } @@ -229,7 +258,6 @@ namespace FlaxEngine /// Local space transform public Transform WorldToLocal(Transform other) { - Transform result = new Transform(Vector3.Zero); Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; @@ -238,6 +266,7 @@ namespace FlaxEngine if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; + Transform result; result.Orientation = Orientation; result.Orientation.Invert(); Quaternion.Multiply(ref result.Orientation, ref other.Orientation, out result.Orientation); @@ -404,6 +433,28 @@ namespace FlaxEngine return !left.Equals(ref right); } + /// + /// Adds two transformations. + /// + /// The first transform to add. + /// The second transform to add. + /// The sum of the two transformations. + public static Transform operator +(Transform left, Transform right) + { + return Add(left, right); + } + + /// + /// Subtracts two transformations. + /// + /// The first transform to subtract from. + /// The second transform to subtract. + /// The difference of the two transformations. + public static Transform operator -(Transform left, Transform right) + { + return Subtract(left, right); + } + /// /// Returns a that represents this instance. /// diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index 5a39c2d86..069a49992 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -163,6 +163,13 @@ public: /// The sum of two transformations. Transform Add(const Transform& other) const; + /// + /// Subtracts transformation from this transform. + /// + /// The other transformation. + /// The different of two transformations. + Transform Subtract(const Transform& other) const; + /// /// Performs transformation of the given transform in local space to the world space of this transform. /// @@ -254,6 +261,11 @@ public: return Add(other); } + FORCE_INLINE Transform operator-(const Transform& other) const + { + return Subtract(other); + } + FORCE_INLINE Transform operator+(const Vector3& other) const { return Add(other); diff --git a/Source/Tools/FlaxEngine.Tests/TestTransform.cs b/Source/Tools/FlaxEngine.Tests/TestTransform.cs index d947c295e..38f71e217 100644 --- a/Source/Tools/FlaxEngine.Tests/TestTransform.cs +++ b/Source/Tools/FlaxEngine.Tests/TestTransform.cs @@ -143,7 +143,29 @@ namespace FlaxEngine.Tests Transform ab = a.LocalToWorld(b); Transform ba = a.WorldToLocal(ab); - Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f)); + Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f), $"Got: {b} but expected {ba}"); + } + } + + /// + /// Test conversions between transform local/world space + /// + [Test] + public void TestAddSubtract() + { + var rand = new Random(10); + for (int i = 0; i < 10; i++) + { + Transform a = new Transform(rand.NextVector3(), Quaternion.Euler(i * 10, 0, i), rand.NextVector3() * 10.0f); + Transform b = new Transform(rand.NextVector3(), Quaternion.Euler(i, 1, 22), rand.NextVector3() * 0.3f); + + Transform ab = a + b; + Transform newA = ab - b; + Assert.IsTrue(Transform.NearEqual(ref a, ref newA, 0.00001f), $"Got: {newA} but expected {a}"); + + Transform ba = b + a; + Transform newB = ba - a; + Assert.IsTrue(Transform.NearEqual(ref b, ref newB, 0.00001f), $"Got: {newB} but expected {b}"); } } } From c0e8b735ad18adcbf9e932733346b1d8ab3305f9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:12:02 +0100 Subject: [PATCH 134/222] Fix Get Node Transform ndoe in anim graph --- Source/Editor/Surface/Archetypes/Animation.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index e067dceb4..15096e696 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -882,8 +882,7 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 160, 0), NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 1, "Node:"), - NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0), - NodeElementArchetype.Factory.Output(1, "Transform", typeof(Transform), 1), + NodeElementArchetype.Factory.Output(0, "Transform", typeof(Transform), 1), } }, new NodeArchetype @@ -924,8 +923,7 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 120, 0), NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 1, "Node:"), - NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0), - NodeElementArchetype.Factory.Output(1, "Transform", typeof(Transform), 1), + NodeElementArchetype.Factory.Output(0, "Transform", typeof(Transform), 1), } }, new NodeArchetype From 560b6cf5daeb7390f93e7d79c4560db3d8aa68c4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:12:29 +0100 Subject: [PATCH 135/222] Fix error when animated model skeleton has duplicated node name --- Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs b/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs index 0d8d676c7..d73590ac7 100644 --- a/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs +++ b/Source/Editor/Surface/Elements/SkeletonNodeNameSelectElement.cs @@ -92,7 +92,7 @@ namespace FlaxEditor.Surface.Elements { sb.Clear(); var node = nodes[nodeIndex]; - _nodeNameToIndex.Add(node.Name, nodeIndex); + _nodeNameToIndex[node.Name] = nodeIndex; int parent = node.ParentIndex; while (parent != -1) { From f0c7fbb0cf04735af77059450d3ebefaf6feca0b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:28:26 +0100 Subject: [PATCH 136/222] Add more utilities for Spline --- Source/Engine/Level/Actors/Spline.cpp | 40 +++++++++++++++++++-- Source/Engine/Level/Actors/Spline.h | 50 +++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index aaccd13fe..a3757253f 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -106,16 +106,29 @@ Vector3 Spline::GetSplineLocalPoint(int32 index) const Transform Spline::GetSplineTransform(int32 index) const { - CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) return _transform.LocalToWorld(Curve[index].Value); } Transform Spline::GetSplineLocalTransform(int32 index) const { - CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) return Curve[index].Value; } +Transform Spline::GetSplineTangent(int32 index, bool isIn) +{ + return _transform.LocalToWorld(GetSplineLocalTangent(index, isIn)); +} + +Transform Spline::GetSplineLocalTangent(int32 index, bool isIn) +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + const auto& k = Curve[index]; + const auto& tangent = isIn ? k.TangentIn : k.TangentOut; + return tangent + k.Value; +} + int32 Spline::GetSplinePointsCount() const { return Curve.GetKeyframes().Count(); @@ -263,6 +276,29 @@ void Spline::SetSplineLocalTransform(int32 index, const Transform& point, bool u UpdateSpline(); } +void Spline::SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline) +{ + SetSplineLocalTangent(index, _transform.WorldToLocal(point), isIn, updateSpline); +} + +void Spline::SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + auto& k = Curve[index]; + auto& tangent = isIn ? k.TangentIn : k.TangentOut; + tangent = point - k.Value; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplinePointTime(int32 index, float time, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Time = time; + if (updateSpline) + UpdateSpline(); +} + void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) { const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index 76f1eddab..772aac821 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -138,6 +138,24 @@ public: /// The curve point transformation (local-space). API_FUNCTION() Transform GetSplineLocalTransform(int32 index) const; + /// + /// Gets the spline curve point tangent at the given index (world-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in world-space. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if get arrive tangent, otherwise gets leave tangent (in or out). + /// The transformation of the tangent to set (world-space). + API_FUNCTION() Transform GetSplineTangent(int32 index, bool isIn); + + /// + /// Gets the spline curve point tangent at the given index (local-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in local-space of the actor. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if get arrive tangent, otherwise gets leave tangent (in or out). + /// The transformation of the tangent to set (world-space). + API_FUNCTION() Transform GetSplineLocalTangent(int32 index, bool isIn); + /// /// Gets the amount of points in the spline. /// @@ -218,7 +236,7 @@ public: /// Sets the spline curve at the given index (world-space). /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). - /// The location of the point to set (world-space). + /// The transformation of the point to set (world-space). /// True if update spline after adding the point, otherwise false. API_FUNCTION() void SetSplineTransform(int32 index, const Transform& point, bool updateSpline = true); @@ -226,10 +244,38 @@ public: /// Sets the spline curve at the given index (local-space). /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). - /// The location of the point to set (local-space). + /// The transformation of the point to set (local-space). /// True if update spline after adding the point, otherwise false. API_FUNCTION() void SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline = true); + /// + /// Sets the spline curve point tangent at the given index (world-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in world-space. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the tangent to set (world-space). + /// True if set arrive tangent, otherwise sets leave tangent (in or out). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); + + /// + /// Sets the spline curve point tangent at the given index (local-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in local-space of the actor. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the tangent to set (local-space). + /// True if set arrive tangent, otherwise sets leave tangent (in or out). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); + + /// + /// Sets the spline curve point time at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time to set. + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void SetSplinePointTime(int32 index, float time, bool updateSpline = true); + /// /// Adds the point to the spline curve (at the end). /// From bd93e46624b478fac32b7249b89cb749172afdb6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:29:14 +0100 Subject: [PATCH 137/222] Tweaks for ActorChildNode --- Source/Editor/SceneGraph/ActorChildNode.cs | 18 +++++++++++++----- .../Editor/SceneGraph/Actors/BoxVolumeNode.cs | 4 ++-- Source/Editor/SceneGraph/Actors/NavLinkNode.cs | 4 ++-- Source/Editor/SceneGraph/Actors/SplineNode.cs | 17 ++++++++++------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Source/Editor/SceneGraph/ActorChildNode.cs b/Source/Editor/SceneGraph/ActorChildNode.cs index 328ca277a..436984bc4 100644 --- a/Source/Editor/SceneGraph/ActorChildNode.cs +++ b/Source/Editor/SceneGraph/ActorChildNode.cs @@ -94,20 +94,28 @@ namespace FlaxEditor.SceneGraph public abstract class ActorChildNode : ActorChildNode where T : ActorNode { /// - /// The actor. + /// The actor node. /// - protected readonly T _actor; + protected T _node; /// /// Initializes a new instance of the class. /// - /// The parent actor. + /// The parent actor node. /// The child id. /// The child index. - protected ActorChildNode(T actor, Guid id, int index) + protected ActorChildNode(T node, Guid id, int index) : base(id, index) { - _actor = actor; + _node = node; + } + + /// + public override void OnDispose() + { + _node = null; + + base.OnDispose(); } } } diff --git a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs index 24b035bc8..ec62ebd47 100644 --- a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs +++ b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs @@ -78,14 +78,14 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (BoxVolume)_actor.Actor; + var actor = (BoxVolume)_node.Actor; var localOffset = _offset * actor.Size; Transform localTrans = new Transform(localOffset); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (BoxVolume)_actor.Actor; + var actor = (BoxVolume)_node.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); var prevLocalOffset = _offset * actor.Size; var localOffset = Vector3.Abs(_offset) * 2.0f * localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs index 941e39267..62bc0a2e7 100644 --- a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs +++ b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs @@ -37,13 +37,13 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (NavLink)_actor.Actor; + var actor = (NavLink)_node.Actor; Transform localTrans = new Transform(_isStart ? actor.Start : actor.End); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (NavLink)_actor.Actor; + var actor = (NavLink)_node.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); if (_isStart) actor.Start = localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index aa7fff53a..879e7e241 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -14,8 +14,8 @@ namespace FlaxEditor.SceneGraph.Actors { private sealed class SplinePointNode : ActorChildNode { - public SplinePointNode(SplineNode actor, Guid id, int index) - : base(actor, id, index) + public unsafe SplinePointNode(SplineNode node, Guid id, int index) + : base(node, id, index) { } @@ -25,19 +25,19 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (Spline)_actor.Actor; + var actor = (Spline)_node.Actor; return actor.GetSplineTransform(Index); } set { - var actor = (Spline)_actor.Actor; + var actor = (Spline)_node.Actor; actor.SetSplineTransform(Index, value); } } public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) { - var actor = (Spline)_actor.Actor; + var actor = (Spline)_node.Actor; var pos = actor.GetSplinePoint(Index); normal = -ray.Ray.Direction; return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); @@ -45,10 +45,13 @@ namespace FlaxEditor.SceneGraph.Actors public override void OnDebugDraw(ViewportDebugDrawData data) { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplinePoint(Index); + + // Draw spline path ParentNode.OnDebugDraw(data); - var actor = (Spline)_actor.Actor; - var pos = actor.GetSplinePoint(Index); + // Draw selected point highlight DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false); } } From 9cdbbf590aad96427f87603f5b551b5fcf64a032 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:29:33 +0100 Subject: [PATCH 138/222] Add spline points tangents gizmos and debug drawing --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 879e7e241..6589fa848 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -17,6 +17,11 @@ namespace FlaxEditor.SceneGraph.Actors public unsafe SplinePointNode(SplineNode node, Guid id, int index) : base(node, id, index) { + var g = (JsonSerializer.GuidInterop*)&id; + g->D++; + AddChild(new SplinePointTangentNode(node, id, index, true)); + g->D++; + AddChild(new SplinePointTangentNode(node, id, index, false)); } public override bool CanBeSelectedDirectly => true; @@ -47,12 +52,81 @@ namespace FlaxEditor.SceneGraph.Actors { var actor = (Spline)_node.Actor; var pos = actor.GetSplinePoint(Index); + var tangentIn = actor.GetSplineTangent(Index, true).Translation; + var tangentOut = actor.GetSplineTangent(Index, false).Translation; // Draw spline path ParentNode.OnDebugDraw(data); // Draw selected point highlight DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false); + + // Draw tangent points + if (tangentIn != pos) + { + DebugDraw.DrawLine(pos, tangentIn, Color.White.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.White, 0, false); + } + if (tangentIn != pos) + { + DebugDraw.DrawLine(pos, tangentOut, Color.White.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.White, 0, false); + } + } + } + + private sealed class SplinePointTangentNode : ActorChildNode + { + private SplineNode _node; + private int _index; + private bool _isIn; + + public SplinePointTangentNode(SplineNode node, Guid id, int index, bool isIn) + : base(id, isIn ? 0 : 1) + { + _node = node; + _index = index; + _isIn = isIn; + } + + public override Transform Transform + { + get + { + var actor = (Spline)_node.Actor; + return actor.GetSplineTangent(_index, _isIn); + } + set + { + var actor = (Spline)_node.Actor; + actor.SetSplineTangent(_index, value, _isIn); + } + } + + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplineTangent(_index, _isIn).Translation; + normal = -ray.Ray.Direction; + return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + // Draw spline and spline point + ParentNode.OnDebugDraw(data); + + // Draw selected tangent highlight + var actor = (Spline)_node.Actor; + var pos = actor.GetSplineTangent(_index, _isIn).Translation; + DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false); + } + + public override void OnDispose() + { + _node = null; + + base.OnDispose(); } } @@ -81,10 +155,10 @@ namespace FlaxEditor.SceneGraph.Actors // Add new points var id = ID; var g = (JsonSerializer.GuidInterop*)&id; - g->D += (uint)srcCount; + g->D += (uint)srcCount * 3; while (srcCount < dstCount) { - g->D += 1; + g->D += 3; AddChildNode(new SplinePointNode(this, id, srcCount++)); } } From 7c85ac61b80d90e4c107475f5a0da2247a347ebb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Jan 2021 15:39:38 +0100 Subject: [PATCH 139/222] Add new Spline actor initialization when spawning in editor --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 6589fa848..48030dbd4 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -164,6 +164,17 @@ namespace FlaxEditor.SceneGraph.Actors } } + /// + public override void PostSpawn() + { + base.PostSpawn(); + + // Setup for an initial spline + var spline = (Spline)Actor; + spline.AddSplineLocalPoint(Vector3.Zero, false); + spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); + } + /// public override void OnDispose() { From bf80827bfdad603fbee11db93910b00b7eb73c5a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 13:50:56 +0100 Subject: [PATCH 140/222] Add more Spline API for curve editing --- Source/Engine/Level/Actors/Spline.cpp | 24 ++++++++++++++++ Source/Engine/Level/Actors/Spline.h | 41 +++++++++++++++++++++------ Source/Engine/Level/Spline.cs | 22 ++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index a3757253f..f522a8770 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -244,6 +244,14 @@ void Spline::ClearSpline() UpdateSpline(); } +void Spline::RemoveSplinePoint(int32 index, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve.GetKeyframes().RemoveAtKeepOrder(index); + if (updateSpline) + UpdateSpline(); +} + void Spline::SetSplinePoint(int32 index, const Vector3& point, bool updateSpline) { CHECK(index >= 0 && index < GetSplinePointsCount()); @@ -331,6 +339,22 @@ void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline) UpdateSpline(); } +void Spline::InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline) +{ + const Keyframe k(time, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Insert(index, k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline) +{ + const Keyframe k(time, point); + Curve.GetKeyframes().Insert(index, k); + if (updateSpline) + UpdateSpline(); +} + void Spline::SetTangentsLinear() { const int32 count = Curve.GetKeyframes().Count(); diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index 772aac821..7f8076eac 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -216,12 +216,19 @@ public: /// API_FUNCTION() void ClearSpline(); + /// + /// Removes the spline curve point at the given index. + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void RemoveSplinePoint(int32 index, bool updateSpline = true); + /// /// Sets the spline curve at the given index (world-space). /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The location of the point to set (world-space). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplinePoint(int32 index, const Vector3& point, bool updateSpline = true); /// @@ -229,7 +236,7 @@ public: /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The location of the point to set (local-space). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline = true); /// @@ -237,7 +244,7 @@ public: /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The transformation of the point to set (world-space). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplineTransform(int32 index, const Transform& point, bool updateSpline = true); /// @@ -245,7 +252,7 @@ public: /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The transformation of the point to set (local-space). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline = true); /// @@ -255,7 +262,7 @@ public: /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The transformation of the tangent to set (world-space). /// True if set arrive tangent, otherwise sets leave tangent (in or out). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); /// @@ -265,7 +272,7 @@ public: /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The transformation of the tangent to set (local-space). /// True if set arrive tangent, otherwise sets leave tangent (in or out). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); /// @@ -273,14 +280,14 @@ public: /// /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). /// The time to set. - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void SetSplinePointTime(int32 index, float time, bool updateSpline = true); /// /// Adds the point to the spline curve (at the end). /// /// The location of the point to add to the curve (world-space). - /// True if update spline after adding the point, otherwise false. + /// True if update spline after editing the point, otherwise false. API_FUNCTION() void AddSplinePoint(const Vector3& point, bool updateSpline = true); /// @@ -304,6 +311,24 @@ public: /// True if update spline after adding the point, otherwise false. API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true); + /// + /// Inserts the spline curve point at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time value. + /// The location of the point to add to the curve (world-space). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline = true); + + /// + /// Inserts the spline curve point at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time value. + /// The location of the point to add to the curve (local-space). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline = true); + /// /// Updates the curve tangent points to make curve linear. /// diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs index 46a05cd5d..29c65763a 100644 --- a/Source/Engine/Level/Spline.cs +++ b/Source/Engine/Level/Spline.cs @@ -37,5 +37,27 @@ namespace FlaxEngine Internal_SetKeyframes(__unmanagedPtr, value); } } + + /// + /// Gets the spline keyframe. + /// + /// The spline point index. + /// The keyframe. + public BezierCurve.Keyframe GetSplineKeyframe(int index) + { + return SplineKeyframes[index]; + } + + /// + /// Sets the spline keyframe. + /// + /// The spline point index. + /// The keyframe. + public void SetSplineKeyframe(int index, BezierCurve.Keyframe keyframe) + { + SetSplineLocalTransform(index, keyframe.Value, false); + SetSplineLocalTangent(index, keyframe.Value + keyframe.TangentIn, true, false); + SetSplineLocalTangent(index, keyframe.Value + keyframe.TangentOut, false); + } } } From bab236c081d76ad96bc3762e3f97422fdf8b78e5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 13:51:32 +0100 Subject: [PATCH 141/222] Add support for custom SceneGraphNode to handle delete with undo --- Source/Editor/SceneGraph/SceneGraphNode.cs | 40 ++++++ .../Editor/Undo/Actions/DeleteActorsAction.cs | 119 ++++++++++++++---- .../Windows/Assets/PrefabWindow.Actions.cs | 7 +- 3 files changed, 136 insertions(+), 30 deletions(-) diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 755aea139..c06c0d1c4 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -312,6 +312,46 @@ namespace FlaxEditor.SceneGraph { } + /// + /// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node). + /// + public struct StateData + { + /// + /// The name of the scene graph node type (full). + /// + public string TypeName; + + /// + /// The name of the method (in ) that takes this state as a parameter and returns the created scene graph node. Used by the undo actions to restore deleted objects. + /// + public string CreateMethodName; + + /// + /// The custom state data (as string). + /// + public string State; + + /// + /// The custom state data (as raw bytes). + /// + public byte[] StateRaw; + } + + /// + /// Gets a value indicating whether this node can use property for editor undo operations. + /// + public virtual bool CanUseState => false; + + /// + /// Gets or sets the node state. + /// + public virtual StateData State + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + /// /// Deletes object represented by this node eg. actor. /// diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index fa319e6f3..41dac3ccc 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using FlaxEditor.SceneGraph; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.Actions @@ -15,7 +16,13 @@ namespace FlaxEditor.Actions class DeleteActorsAction : IUndoAction { [Serialize] - private byte[] _data; + private byte[] _actorsData; + + [Serialize] + private List _nodesData; + + [Serialize] + private Guid[] _nodeParentsIDs; [Serialize] private Guid[] _prefabIds; @@ -30,31 +37,51 @@ namespace FlaxEditor.Actions /// The node parents. /// [Serialize] - protected List _nodeParents; + protected List _nodeParents; /// /// Initializes a new instance of the class. /// - /// The objects. + /// The objects. /// If set to true action will be inverted - instead of delete it will be create actors. - internal DeleteActorsAction(List objects, bool isInverted = false) + internal DeleteActorsAction(List nodes, bool isInverted = false) { _isInverted = isInverted; - _nodeParents = new List(objects.Count); - var actorNodes = new List(objects.Count); - var actors = new List(objects.Count); - for (int i = 0; i < objects.Count; i++) + + // Collect nodes to delete + var deleteNodes = new List(nodes.Count); + var actors = new List(nodes.Count); + for (int i = 0; i < nodes.Count; i++) { - if (objects[i] is ActorNode node) + var node = nodes[i]; + if (node is ActorNode actorNode) { - actorNodes.Add(node); - actors.Add(node.Actor); + deleteNodes.Add(actorNode); + actors.Add(actorNode.Actor); + } + else + { + deleteNodes.Add(node); + if (node.CanUseState) + { + if (_nodesData == null) + _nodesData = new List(); + _nodesData.Add(node.State); + } } } - actorNodes.BuildNodesParents(_nodeParents); - _data = Actor.ToBytes(actors.ToArray()); + // Collect parent nodes to delete + _nodeParents = new List(nodes.Count); + deleteNodes.BuildNodesParents(_nodeParents); + _nodeParentsIDs = new Guid[_nodeParents.Count]; + for (int i = 0; i < _nodeParentsIDs.Length; i++) + _nodeParentsIDs[i] = _nodeParents[i].ID; + // Serialize actors + _actorsData = Actor.ToBytes(actors.ToArray()); + + // Cache actors linkage to prefab objects _prefabIds = new Guid[actors.Count]; _prefabObjectIds = new Guid[actors.Count]; for (int i = 0; i < actors.Count; i++) @@ -88,9 +115,11 @@ namespace FlaxEditor.Actions /// public void Dispose() { - _data = null; + _actorsData = null; + _nodeParentsIDs = null; _prefabIds = null; _prefabObjectIds = null; + _nodeParents.Clear(); } /// @@ -123,28 +152,64 @@ namespace FlaxEditor.Actions /// protected virtual void Create() { - // Restore objects - var actors = Actor.FromBytes(_data); - if (actors == null) - return; - for (int i = 0; i < actors.Length; i++) + var nodes = new List(); + + // Restore actors + var actors = Actor.FromBytes(_actorsData); + if (actors != null) { - Guid prefabId = _prefabIds[i]; - if (prefabId != Guid.Empty) + nodes.Capacity = Math.Max(nodes.Capacity, actors.Length); + + // Preserve prefab objects linkage + for (int i = 0; i < actors.Length; i++) { - Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); + Guid prefabId = _prefabIds[i]; + if (prefabId != Guid.Empty) + { + Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); + } } } - var actorNodes = new List(actors.Length); - for (int i = 0; i < actors.Length; i++) + + // Restore nodes state + if (_nodesData != null) { - var foundNode = GetNode(actors[i].ID); + for (int i = 0; i < _nodesData.Count; i++) + { + var state = _nodesData[i]; + var type = TypeUtils.GetManagedType(state.TypeName); + if (type == null) + { + Editor.LogError($"Missing type {state.TypeName} for scene graph node undo state restore."); + continue; + } + var method = type.GetMethod(state.CreateMethodName); + if (method == null) + { + Editor.LogError($"Missing method {state.CreateMethodName} from type {state.TypeName} for scene graph node undo state restore."); + continue; + } + var node = method.Invoke(null, new object[] { state }); + if (node == null) + { + Editor.LogError($"Failed to restore scene graph node state via method {state.CreateMethodName} from type {state.TypeName}."); + continue; + } + } + } + + // Cache parent nodes ids + for (int i = 0; i < _nodeParentsIDs.Length; i++) + { + var foundNode = GetNode(_nodeParentsIDs[i]); if (foundNode is ActorNode node) { - actorNodes.Add(node); + nodes.Add(node); } } - actorNodes.BuildNodesParents(_nodeParents); + nodes.BuildNodesParents(_nodeParents); + + // Mark scenes as modified for (int i = 0; i < _nodeParents.Count; i++) { Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 00a96910f..fca4909f0 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -194,8 +194,8 @@ namespace FlaxEditor.Windows.Assets private class CustomDeleteActorsAction : DeleteActorsAction { - public CustomDeleteActorsAction(List objects, bool isInverted = false) - : base(objects, isInverted) + public CustomDeleteActorsAction(List nodes, bool isInverted = false) + : base(nodes, isInverted) { } @@ -207,7 +207,8 @@ namespace FlaxEditor.Windows.Assets // Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events) for (int i = 0; i < nodes.Length; i++) { - nodes[i].Actor.Parent = null; + if (nodes[i] is ActorNode actorNode) + actorNode.Actor.Parent = null; } base.Delete(); From 69790f3576e2676fdc3cc6e8442f104d78525510 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 13:51:37 +0100 Subject: [PATCH 142/222] Add support for deleting selected spline points (with undo) --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 48030dbd4..7b7e98239 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -3,6 +3,7 @@ using System; using FlaxEngine; using FlaxEngine.Json; +using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.Actors { @@ -26,6 +27,10 @@ namespace FlaxEditor.SceneGraph.Actors public override bool CanBeSelectedDirectly => true; + public override bool CanDelete => true; + + public override bool CanUseState => true; + public override Transform Transform { get @@ -40,6 +45,39 @@ namespace FlaxEditor.SceneGraph.Actors } } + private struct Data + { + public Guid Spline; + public int Index; + public BezierCurve.Keyframe Keyframe; + } + + public override StateData State + { + get + { + var actor = (Spline)_node.Actor; + return new StateData + { + TypeName = typeof(SplinePointNode).FullName, + CreateMethodName = nameof(Create), + State = JsonSerializer.Serialize(new Data + { + Spline = actor.ID, + Index = Index, + Keyframe = actor.GetSplineKeyframe(Index), + }), + }; + } + set => throw new NotImplementedException(); + } + + public override void Delete() + { + var actor = (Spline)_node.Actor; + actor.RemoveSplinePoint(Index); + } + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) { var actor = (Spline)_node.Actor; @@ -73,6 +111,19 @@ namespace FlaxEditor.SceneGraph.Actors DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.White, 0, false); } } + + public static SceneGraphNode Create(StateData state) + { + var data = JsonSerializer.Deserialize(state.State); + var spline = Object.Find(ref data.Spline); + spline.InsertSplineLocalPoint(data.Index, data.Keyframe.Time, data.Keyframe.Value, false); + spline.SetSplineKeyframe(data.Index, data.Keyframe); + var splineNode = (SplineNode)SceneGraphFactory.FindNode(data.Spline); + if (splineNode == null) + return null; + splineNode.OnUpdate(); + return splineNode.ActorChildNodes[data.Index]; + } } private sealed class SplinePointTangentNode : ActorChildNode From d69b00579253a5bba8c2bd7d96ecf147e9ee3d3f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 14:16:32 +0100 Subject: [PATCH 143/222] Move Curve data serialization to binary format into CurveSerialization.h --- Source/Engine/Animations/Curve.h | 65 ++----------------- Source/Engine/Animations/CurveSerialization.h | 49 +++++++++++++- Source/Engine/Content/Assets/Animation.cpp | 13 ++-- Source/Engine/Graphics/Models/ModelData.cpp | 9 +-- 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 24dcd87a8..be5c268ef 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -4,9 +4,7 @@ #include "AnimationUtils.h" #include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Types/DataContainer.h" -#include "Engine/Serialization/ReadStream.h" -#include "Engine/Serialization/WriteStream.h" +#include "Engine/Core/Types/Span.h" // @formatter:off @@ -289,7 +287,7 @@ class CurveBase { public: - typedef DataContainer KeyFrameData; + typedef Span KeyFrameData; protected: @@ -641,7 +639,7 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void Evaluate(T& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::Evaluate(data, result, time, loop); } @@ -653,7 +651,7 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateFirstDerivative(T& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::EvaluateFirstDerivative(data, result, time, loop); } @@ -665,7 +663,7 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateKey(KeyFrame& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::EvaluateKey(data, result, time, loop); } @@ -686,7 +684,7 @@ public: return; } - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); KeyFrame startValue, endValue; Base::EvaluateKey(data, startValue, start, false); Base::EvaluateKey(data, endValue, end, false); @@ -749,57 +747,6 @@ public: _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset;; } -public: - - /// - /// Serializes the curve data to the stream. - /// - /// The output stream. - void Serialize(WriteStream& stream) const - { - // Version - if (_keyframes.IsEmpty()) - { - stream.WriteInt32(0); - return; - } - stream.WriteInt32(1); - - // TODO: support compression (serialize compression mode) - - // Raw keyframes data - stream.WriteInt32(_keyframes.Count()); - stream.WriteBytes(_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame)); - } - - /// - /// Deserializes the curve data from the stream. - /// - /// The input stream. - bool Deserialize(ReadStream& stream) - { - // Cleanup - _keyframes.Resize(0); - - // Version - int32 version; - stream.ReadInt32(&version); - if (version == 0) - return false; - if (version != 1) - { - return true; - } - - // Raw keyframes data - int32 keyframesCount; - stream.ReadInt32(&keyframesCount); - _keyframes.Resize(keyframesCount, false); - stream.ReadBytes(_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame)); - - return false; - } - public: FORCE_INLINE KeyFrame& operator[](int32 index) diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h index 06f7788ea..4af89ceca 100644 --- a/Source/Engine/Animations/CurveSerialization.h +++ b/Source/Engine/Animations/CurveSerialization.h @@ -3,7 +3,9 @@ #pragma once #include "Curve.h" -#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Serialization/ReadStream.h" +#include "Engine/Serialization/WriteStream.h" #include "Engine/Serialization/Serialization.h" // @formatter:off @@ -166,6 +168,51 @@ namespace Serialization Deserialize(keyframesArray[i], keyframes[i], modifier); } } + + template + inline void Serialize(WriteStream& stream, const Curve& v) + { + auto& keyframes = v.GetKeyframes(); + + // Version + if (keyframes.IsEmpty()) + { + stream.WriteInt32(0); + return; + } + stream.WriteInt32(1); + + // TODO: support compression (serialize compression mode) + + // Raw keyframes data + stream.WriteInt32(keyframes.Count()); + stream.WriteBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); + } + + template + inline bool Deserialize(ReadStream& stream, Curve& v) + { + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(0); + + // Version + int32 version; + stream.ReadInt32(&version); + if (version == 0) + return false; + if (version != 1) + { + return true; + } + + // Raw keyframes data + int32 keyframesCount; + stream.ReadInt32(&keyframesCount); + keyframes.Resize(keyframesCount, false); + stream.ReadBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); + + return false; + } } // @formatter:on diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index c6927530e..963ddaceb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/MemoryReadStream.h" #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" @@ -358,9 +359,9 @@ bool Animation::Save(const StringView& path) { auto& anim = Data.Channels[i]; stream.WriteString(anim.NodeName, 172); - anim.Position.Serialize(stream); - anim.Rotation.Serialize(stream); - anim.Scale.Serialize(stream); + Serialization::Serialize(stream, anim.Position); + Serialization::Serialize(stream, anim.Rotation); + Serialization::Serialize(stream, anim.Scale); } // Set data to the chunk asset @@ -442,9 +443,9 @@ Asset::LoadResult Animation::load() auto& anim = Data.Channels[i]; stream.ReadString(&anim.NodeName, 172); - bool failed = anim.Position.Deserialize(stream); - failed |= anim.Rotation.Deserialize(stream); - failed |= anim.Scale.Deserialize(stream); + bool failed = Serialization::Deserialize(stream, anim.Position); + failed |= Serialization::Deserialize(stream, anim.Rotation); + failed |= Serialization::Deserialize(stream, anim.Scale); if (failed) { diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 56981e65d..71b511b4f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -2,6 +2,7 @@ #include "ModelData.h" #include "Engine/Core/Log.h" +#include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/WriteStream.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" @@ -753,7 +754,7 @@ bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const stream->Write(&sphere); // TODO: calculate Sphere and Box at once - make it faster using SSE - + // Blend Shapes const int32 blendShapes = mesh.BlendShapes.Count(); stream->WriteUint16(blendShapes); @@ -830,9 +831,9 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream) const auto& anim = Animation.Channels[i]; stream->WriteString(anim.NodeName, 172); - anim.Position.Serialize(*stream); - anim.Rotation.Serialize(*stream); - anim.Scale.Serialize(*stream); + Serialization::Serialize(*stream, anim.Position); + Serialization::Serialize(*stream, anim.Rotation); + Serialization::Serialize(*stream, anim.Scale); } return false; From b22805f8662d6a2918701fa7fe3b1d642d459891 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 16:03:33 +0100 Subject: [PATCH 144/222] Add support for custom duplicate of SceneGraphNode --- Source/Editor/Modules/SceneEditingModule.cs | 91 +++++++++++++++---- Source/Editor/SceneGraph/ActorChildNode.cs | 3 + Source/Editor/SceneGraph/ActorNode.cs | 3 + Source/Editor/SceneGraph/Actors/SceneNode.cs | 3 + Source/Editor/SceneGraph/RootNode.cs | 3 + Source/Editor/SceneGraph/SceneGraphNode.cs | 16 ++++ .../Windows/Assets/PrefabWindow.Actions.cs | 2 +- 7 files changed, 103 insertions(+), 18 deletions(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 78ffd26b0..96aefe804 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -358,7 +358,15 @@ namespace FlaxEditor.Modules var pasteAction = PasteActorsAction.Paste(data, pasteTargetActor?.ID ?? Guid.Empty); if (pasteAction != null) { - OnPasteAction(pasteAction); + pasteAction.Do(out _, out var nodeParents); + + // Select spawned objects (parents only) + var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast().ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build single compound undo action that pastes the actors and selects the created objects (parents only) + Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); + OnSelectionChanged(); } } @@ -377,12 +385,57 @@ namespace FlaxEditor.Modules public void Duplicate() { // Peek things that can be copied (copy all actors) - var objects = Selection.Where(x => x.CanCopyPaste).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList(); - if (objects.Count == 0) + var nodes = Selection.Where(x => x.CanDuplicate).ToList().BuildAllNodes(); + if (nodes.Count == 0) return; + var actors = new List(); + var newSelection = new List(); + List customUndoActions = null; + foreach (var node in nodes) + { + if (node.CanDuplicate) + { + if (node is ActorNode actorNode) + { + actors.Add(actorNode.Actor); + } + else + { + var customDuplicatedObject = node.Duplicate(out var customUndoAction); + if (customDuplicatedObject != null) + newSelection.Add(customDuplicatedObject); + if (customUndoAction != null) + { + if (customUndoActions == null) + customUndoActions = new List(); + customUndoActions.Add(customUndoAction); + } + } + } + } + if (actors.Count == 0) + { + // Duplicate custom scene graph nodes only without actors + if (newSelection.Count != 0) + { + // Select spawned objects (parents only) + var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build a single compound undo action that pastes the actors, pastes custom stuff (scene graph extension) and selects the created objects (parents only) + var customUndoActionsCount = customUndoActions?.Count ?? 0; + var undoActions = new IUndoAction[1 + customUndoActionsCount]; + for (int i = 0; i < customUndoActionsCount; i++) + undoActions[i] = customUndoActions[i]; + undoActions[undoActions.Length - 1] = selectAction; + + Undo.AddAction(new MultiUndoAction(undoActions)); + OnSelectionChanged(); + } + return; + } // Serialize actors - var actors = objects.ConvertAll(x => ((ActorNode)x).Actor); var data = Actor.ToBytes(actors.ToArray()); if (data == null) { @@ -394,22 +447,26 @@ namespace FlaxEditor.Modules var pasteAction = PasteActorsAction.Duplicate(data, Guid.Empty); if (pasteAction != null) { - OnPasteAction(pasteAction); + pasteAction.Do(out _, out var nodeParents); + + // Select spawned objects (parents only) + newSelection.AddRange(nodeParents); + var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build a single compound undo action that pastes the actors, pastes custom stuff (scene graph extension) and selects the created objects (parents only) + var customUndoActionsCount = customUndoActions?.Count ?? 0; + var undoActions = new IUndoAction[2 + customUndoActionsCount]; + undoActions[0] = pasteAction; + for (int i = 0; i < customUndoActionsCount; i++) + undoActions[i + 1] = customUndoActions[i]; + undoActions[undoActions.Length - 1] = selectAction; + + Undo.AddAction(new MultiUndoAction(undoActions)); + OnSelectionChanged(); } } - private void OnPasteAction(PasteActorsAction pasteAction) - { - pasteAction.Do(out _, out var nodeParents); - - // Select spawned objects - var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast().ToArray(), OnSelectionUndo); - selectAction.Do(); - - Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); - OnSelectionChanged(); - } - /// /// Called when selection gets changed. Invokes the other events and updates editor. Call it when you manually modify selected objects collection. /// diff --git a/Source/Editor/SceneGraph/ActorChildNode.cs b/Source/Editor/SceneGraph/ActorChildNode.cs index 436984bc4..d514534ca 100644 --- a/Source/Editor/SceneGraph/ActorChildNode.cs +++ b/Source/Editor/SceneGraph/ActorChildNode.cs @@ -63,6 +63,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDrag => false; diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 5cc5f5337..07ff6a3b5 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -182,6 +182,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; + /// + public override bool CanDuplicate => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; + /// public override bool IsActive => _actor.IsActive; diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 359d3d207..35c71bf02 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -57,6 +57,9 @@ namespace FlaxEditor.SceneGraph.Actors /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDelete => false; diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs index fec8f68ad..10af88489 100644 --- a/Source/Editor/SceneGraph/RootNode.cs +++ b/Source/Editor/SceneGraph/RootNode.cs @@ -56,6 +56,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDelete => false; diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index c06c0d1c4..7ddf2d876 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -65,6 +65,11 @@ namespace FlaxEditor.SceneGraph /// public virtual bool CanCopyPaste => true; + /// + /// Gets a value indicating whether this instance can be duplicated by the user. + /// + public virtual bool CanDuplicate => true; + /// /// Gets a value indicating whether this node can be deleted by the user. /// @@ -359,6 +364,17 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Duplicates this object. Valid only if returns true. + /// + /// The undo action that duplicated the object (already performed), null if skip it. + /// The duplicated object node. + public virtual SceneGraphNode Duplicate(out IUndoAction undoAction) + { + undoAction = null; + return null; + } + /// /// Releases the node and the child tree. Disposed all GUI parts and used resources. /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index fca4909f0..af568cd3f 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -159,7 +159,7 @@ namespace FlaxEditor.Windows.Assets public void Duplicate() { // Peek things that can be copied (copy all actors) - var objects = Selection.Where(x => x.CanCopyPaste && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList(); + var objects = Selection.Where(x => x.CanDuplicate && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanDuplicate && x is ActorNode).ToList(); if (objects.Count == 0) return; From c3d9c52f1f1489de6d616ac2f8befaa8e47fd4bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 16:04:20 +0100 Subject: [PATCH 145/222] Add support for spline point duplicate via Transform gizmo --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 68 ++++++++++++++++++- Source/Engine/Level/Actors/Spline.cpp | 6 ++ Source/Engine/Level/Actors/Spline.h | 7 ++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 7b7e98239..179f3267e 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.Json; using Object = FlaxEngine.Object; @@ -27,6 +28,8 @@ namespace FlaxEditor.SceneGraph.Actors public override bool CanBeSelectedDirectly => true; + public override bool CanDuplicate => true; + public override bool CanDelete => true; public override bool CanUseState => true; @@ -45,7 +48,7 @@ namespace FlaxEditor.SceneGraph.Actors } } - private struct Data + struct Data { public Guid Spline; public int Index; @@ -78,6 +81,69 @@ namespace FlaxEditor.SceneGraph.Actors actor.RemoveSplinePoint(Index); } + class DuplicateUndoAction : IUndoAction, ISceneEditAction + { + public Guid SplineId; + public int Index; + public float Time; + public Transform Value; + + public string ActionString => "Duplicate spline point"; + + public void Dispose() + { + } + + public void Do() + { + var spline = Object.Find(ref SplineId); + spline.InsertSplineLocalPoint(Index, Time, Value); + } + + public void Undo() + { + var spline = Object.Find(ref SplineId); + spline.RemoveSplinePoint(Index); + } + + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = Object.Find(ref SplineId); + sceneModule.MarkSceneEdited(spline.Scene); + } + } + + public override SceneGraphNode Duplicate(out IUndoAction undoAction) + { + var actor = (Spline)_node.Actor; + int newIndex; + float newTime; + if (Index == actor.SplinePointsCount - 1) + { + // Append to the end + newIndex = actor.SplinePointsCount; + newTime = actor.GetSplineTime(newIndex - 1) + 1.0f; + } + else + { + // Insert between this and next point + newIndex = Index + 1; + newTime = (actor.GetSplineTime(Index) + actor.GetSplineTime(Index + 1)) * 0.5f; + } + var action = new DuplicateUndoAction + { + SplineId = actor.ID, + Index = newIndex, + Time = newTime, + Value = actor.GetSplineLocalTransform(Index), + }; + actor.InsertSplineLocalPoint(newIndex, newTime, action.Value); + undoAction = action; + var splineNode = (SplineNode)SceneGraphFactory.FindNode(action.SplineId); + splineNode.OnUpdate(); + return splineNode.ActorChildNodes[newIndex]; + } + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) { var actor = (Spline)_node.Actor; diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index f522a8770..e16ff915e 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -169,6 +169,12 @@ float Spline::GetSplineLength() const return Math::Sqrt(sum); } +float Spline::GetSplineTime(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f) + return Curve[index].Time; +} + namespace { void FindTimeClosestToPoint(const Vector3& point, const Spline::Keyframe& start, const Spline::Keyframe& end, float& bestDistanceSquared, float& bestTime) diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index 7f8076eac..c712695d3 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -171,6 +171,13 @@ public: /// API_PROPERTY() float GetSplineLength() const; + /// + /// Gets the time of the spline keyframe. + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The spline time. + API_FUNCTION() float GetSplineTime(int32 index) const; + /// /// Calculates the closest point to the given location and returns the spline time at that point. /// From 1ae898918c31a65e9761f2d154b1afa159c300a8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 16:21:05 +0100 Subject: [PATCH 146/222] Add auto-select for spawned actors in the level --- Source/Editor/Modules/SceneEditingModule.cs | 13 +++++++++++-- Source/Editor/Tools/Foliage/FoliageTab.cs | 5 +---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 96aefe804..8c0914d58 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -200,7 +200,8 @@ namespace FlaxEditor.Modules /// /// The actor. /// The parent actor. Set null as default. - public void Spawn(Actor actor, Actor parent = null) + /// True if automatically select the spawned actor, otherwise false. + public void Spawn(Actor actor, Actor parent = null, bool autoSelect = true) { bool isPlayMode = Editor.StateMachine.IsPlayMode; @@ -225,7 +226,15 @@ namespace FlaxEditor.Modules actorNode.PostSpawn(); // Create undo action - var action = new DeleteActorsAction(new List(1) { actorNode }, true); + IUndoAction action = new DeleteActorsAction(new List(1) { actorNode }, true); + if (autoSelect) + { + var before = Selection.ToArray(); + Selection.Clear(); + Selection.Add(actorNode); + OnSelectionChanged(); + action = new MultiUndoAction(action, new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo)); + } Undo.AddAction(action); // Mark scene as dirty diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index fe4120035..faf71bf1f 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -183,11 +183,8 @@ namespace FlaxEditor.Tools.Foliage actor.StaticFlags = StaticFlags.FullyStatic; actor.Name = "Foliage"; - // Spawn + // Spawn and select Editor.SceneEditing.Spawn(actor); - - // Select - Editor.SceneEditing.Select(actor); } private void OnSelectionChanged() From efea84248687b7eca9f3774e5151a452606a6d80 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 16:23:57 +0100 Subject: [PATCH 147/222] Fix warning --- Source/Engine/Scripting/BinaryModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 689b552f8..d5df8947e 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -606,6 +606,8 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) for (int32 typeIndex = 0; typeIndex < Types.Count(); typeIndex++) { ScriptingType& type = Types[typeIndex]; + if (type.Type == ScriptingTypes::Interface) + continue; // TODO: generate C# class for interfaces in API ASSERT(type.ManagedClass == nullptr); // Cache class From e2cefc7407cb1f74c49748c4c3726eab3fb9765a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Feb 2021 16:58:01 +0100 Subject: [PATCH 148/222] Fix Quaternion comparison epsilon to reduce error rate --- Source/Engine/Core/Math/Quaternion.cs | 8 ++++---- Source/Engine/Core/Math/Quaternion.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index 0e809f271..938bf48b1 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -635,7 +635,7 @@ namespace FlaxEngine public static float AngleBetween(Quaternion a, Quaternion b) { float num = Dot(a, b); - return num > 0.99999999f ? 0 : Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2f * 57.29578f; + return num > 0.9999999f ? 0 : Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2f * 57.29578f; } /// @@ -1478,7 +1478,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) > 0.99999999f; + return Dot(ref left, ref right) > 0.9999999f; } /// @@ -1493,7 +1493,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) <= 0.99999999f; + return Dot(ref left, ref right) <= 0.9999999f; } /// @@ -1605,7 +1605,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Quaternion other) { - //return Dot(ref this, ref other) > 0.99999999f; + //return Dot(ref this, ref other) > 0.9999999f; return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); } diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 54ea49540..d3eeb4f32 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -391,7 +391,7 @@ public: /// true if the specified is equal to this instance; otherwise, false. FORCE_INLINE bool operator==(const Quaternion& other) const { - return Dot(*this, other) > 0.99999999f; + return Dot(*this, other) > 0.9999999f; } /// @@ -414,7 +414,7 @@ public: /// true if the specified structures are equal; otherwise, false. static bool NearEqual(const Quaternion& a, const Quaternion& b) { - return Dot(a, b) > 0.99999999f; + return Dot(a, b) > 0.9999999f; } /// @@ -474,7 +474,7 @@ public: static float AngleBetween(const Quaternion& a, const Quaternion& b) { const float dot = Dot(a, b); - return dot > 0.99999999f ? 0 : Math::Acos(Math::Min(Math::Abs(dot), 1.0f)) * 2.0f * 57.29578f; + return dot > 0.9999999f ? 0 : Math::Acos(Math::Min(Math::Abs(dot), 1.0f)) * 2.0f * 57.29578f; } // Adds two quaternions From c00b32364ab97a6c8434d7fe02d17c0691e8eb7c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 2 Feb 2021 12:51:36 +0100 Subject: [PATCH 149/222] Add `SceneGraphNode.OnContextMenu` for customizations --- Source/Editor/SceneGraph/SceneGraphNode.cs | 7 +++++++ .../Windows/SceneTreeWindow.ContextMenu.cs | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 7ddf2d876..cc47c20f2 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -317,6 +317,13 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Called when scene tree window wants to show the context menu. Allows to add custom options. + /// + public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu) + { + } + /// /// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node). /// diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index b6756b438..f407017df 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEngine; @@ -113,6 +114,24 @@ namespace FlaxEditor.Windows // Custom options + bool showCustomNodeOptions = Editor.SceneEditing.Selection.Count == 1; + if (!showCustomNodeOptions && Editor.SceneEditing.Selection.Count != 0) + { + showCustomNodeOptions = true; + for (int i = 1; i < Editor.SceneEditing.Selection.Count; i++) + { + if (Editor.SceneEditing.Selection[0].GetType() != Editor.SceneEditing.Selection[i].GetType()) + { + showCustomNodeOptions = false; + break; + } + } + } + if (showCustomNodeOptions) + { + Editor.SceneEditing.Selection[0].OnContextMenu(contextMenu); + } + ContextMenuShow?.Invoke(contextMenu); return contextMenu; From d2dba5680bf3bd8e83aa62c74da8e5f36cee3ee6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 2 Feb 2021 12:52:21 +0100 Subject: [PATCH 150/222] Fix calling OnParentChanged in Actor load --- Source/Engine/Level/Actor.cpp | 22 +++++++++++++++++----- Source/Engine/Level/Actor.h | 4 +--- Source/Engine/Level/Actors/BoneSocket.cpp | 3 +++ Source/Engine/Level/Actors/BoxBrush.cpp | 3 +++ Source/Engine/Physics/Joints/Joint.cpp | 3 +++ Source/Engine/UI/UIControl.cpp | 3 +++ 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 30ca06daf..ce3bcdb65 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -948,11 +948,19 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) const auto parent = Scripting::FindObject(parentId); if (_parent != parent) { - if (_parent) - _parent->Children.RemoveKeepOrder(this); - _parent = parent; - if (_parent) - _parent->Children.Add(this); + if (IsDuringPlay()) + { + SetParent(parent, false, false); + } + else + { + if (_parent) + _parent->Children.RemoveKeepOrder(this); + _parent = parent; + if (_parent) + _parent->Children.Add(this); + OnParentChanged(); + } } else if (!parent && parentId.IsValid()) { @@ -1044,6 +1052,10 @@ void Actor::OnDisable() } } +void Actor::OnParentChanged() +{ +} + void Actor::OnTransformChanged() { ASSERT_LOW_LAYER(!_localTransform.IsNanOrInfinity()); diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index da08bc498..6416fd33a 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -918,9 +918,7 @@ public: /// /// Called when actor parent gets changed. /// - virtual void OnParentChanged() - { - } + virtual void OnParentChanged(); /// /// Called when actor transform gets changed. diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp index 7673781ff..028b1c507 100644 --- a/Source/Engine/Level/Actors/BoneSocket.cpp +++ b/Source/Engine/Level/Actors/BoneSocket.cpp @@ -104,6 +104,9 @@ void BoneSocket::OnParentChanged() { // Base Actor::OnParentChanged(); + + if (!IsDuringPlay()) + return; _index = -1; UpdateTransformation(); diff --git a/Source/Engine/Level/Actors/BoxBrush.cpp b/Source/Engine/Level/Actors/BoxBrush.cpp index ca38c103d..f02dd5bd3 100644 --- a/Source/Engine/Level/Actors/BoxBrush.cpp +++ b/Source/Engine/Level/Actors/BoxBrush.cpp @@ -272,6 +272,9 @@ void BoxBrush::OnParentChanged() { // Base Actor::OnParentChanged(); + + if (!IsDuringPlay()) + return; // Fire event OnBrushModified(); diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index cda249d71..56c5c1b32 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -295,6 +295,9 @@ void Joint::OnParentChanged() // Base Actor::OnParentChanged(); + if (!IsDuringPlay()) + return; + // Check reparenting Joint case const auto parent = dynamic_cast(GetParent()); if (parent == nullptr) diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 131c04298..33fe8a93b 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -156,6 +156,9 @@ void UIControl::OnParentChanged() // Base Actor::OnParentChanged(); + if (!IsDuringPlay()) + return; + UICONTROL_INVOKE(ParentChanged); } From 85a6f485f12804bac866fb15103feb754603db10 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 2 Feb 2021 12:52:37 +0100 Subject: [PATCH 151/222] Fixes and tweaks --- Source/Engine/Level/Actors/AnimatedModel.cpp | 16 ++++++++-------- Source/Engine/Level/Actors/AnimatedModel.h | 3 ++- Source/Engine/Level/Actors/ModelInstanceActor.h | 15 ++++++++------- Source/Engine/Level/Actors/StaticModel.h | 2 +- Source/Engine/Renderer/DrawCall.h | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 3d6450716..85ee6d333 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -327,14 +327,6 @@ void AnimatedModel::SyncParameters() } } -void AnimatedModel::UpdateBounds() -{ - UpdateLocalBounds(); - - BoundingBox::Transform(_boxLocal, _world, _box); - BoundingSphere::FromBox(_box, _sphere); -} - void AnimatedModel::BeginPlay(SceneBeginData* data) { if (SkinnedModel && SkinnedModel->IsLoaded()) @@ -401,6 +393,14 @@ void AnimatedModel::UpdateLocalBounds() _boxLocal = box; } +void AnimatedModel::UpdateBounds() +{ + UpdateLocalBounds(); + + BoundingBox::Transform(_boxLocal, _world, _box); + BoundingSphere::FromBox(_box, _sphere); +} + void AnimatedModel::OnAnimUpdate() { UpdateBounds(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 641247273..d5d9b4424 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -290,6 +290,8 @@ private: /// void UpdateLocalBounds(); + void UpdateBounds(); + /// /// Called after animation graph update. /// @@ -317,7 +319,6 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override; - void UpdateBounds() final override; protected: diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index c431429fd..7b2a08fd9 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -19,11 +19,6 @@ public: /// ModelInstanceEntries Entries; - /// - /// Updates the bounds of the actor. - /// - virtual void UpdateBounds() = 0; - /// /// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.). /// @@ -63,7 +58,10 @@ public: /// 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 if the actor is intersected by the ray, otherwise false. - API_FUNCTION() virtual bool IntersectsEntry(int32 entryIndex, API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal) = 0; + API_FUNCTION() virtual bool IntersectsEntry(int32 entryIndex, API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal) + { + return false; + } /// /// Determines if there is an intersection between the model actor mesh entry and a ray. @@ -76,7 +74,10 @@ public: /// When the method completes, contains the intersection surface normal vector (if any valid). /// When the method completes, contains the intersection entry index (if any valid). /// True if the actor is intersected by the ray, otherwise false. - API_FUNCTION() virtual bool IntersectsEntry(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& entryIndex) = 0; + API_FUNCTION() virtual bool IntersectsEntry(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& entryIndex) + { + return false; + } protected: diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 5ca6e05e4..a255ba4ab 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -181,6 +181,7 @@ private: void OnModelChanged(); void OnModelLoaded(); + void UpdateBounds(); public: @@ -193,7 +194,6 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override; - void UpdateBounds() override; protected: diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 7c4f4fe6d..cc35b2627 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -333,6 +333,6 @@ struct TIsPODType #define GEOMETRY_DRAW_STATE_EVENT_END(drawState, worldMatrix) \ if (drawState.PrevFrame != frame) \ { \ - drawState.PrevWorld = _world; \ + drawState.PrevWorld = worldMatrix; \ drawState.PrevFrame = frame; \ } From 00fab5cfd6ef06b66270163f8b44025ae7c51a8d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 2 Feb 2021 12:53:18 +0100 Subject: [PATCH 152/222] Add event to spline for updating custom data --- Source/Engine/Level/Actors/Spline.cpp | 2 ++ Source/Engine/Level/Actors/Spline.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index e16ff915e..08a06d433 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -416,6 +416,8 @@ void Spline::UpdateSpline() last.TangentIn = first.TangentIn; last.TangentOut = first.TangentOut; } + + SplineUpdated(); } void Spline::GetKeyframes(MonoArray* data) diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index c712695d3..7d7e8dcb1 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -348,6 +348,11 @@ public: public: + /// + /// Called when spline gets updated (eg. after curve modification). + /// + API_EVENT() Action SplineUpdated; + /// /// Updates the spline after it was modified. Recreates the collision and/or any cached state that depends on the spline type. /// From 81cc8cf69c36a611fa1d844379e60c92de4b4315 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 2 Feb 2021 15:44:14 +0100 Subject: [PATCH 153/222] Add Matrix3x4 --- Source/Engine/Core/Math/Matrix3x4.h | 55 +++++++++++++++++ .../Graphics/Models/SkinnedMeshDrawData.cpp | 61 +++---------------- 2 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 Source/Engine/Core/Math/Matrix3x4.h diff --git a/Source/Engine/Core/Math/Matrix3x4.h b/Source/Engine/Core/Math/Matrix3x4.h new file mode 100644 index 000000000..49f6cc1ff --- /dev/null +++ b/Source/Engine/Core/Math/Matrix3x4.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Matrix.h" + +/// +/// Helper matrix for optimized float3x4 package of transformation matrices. +/// +struct FLAXENGINE_API Matrix3x4 +{ + float M[3][4]; + + void SetMatrix(const Matrix& m) + { + const float* src = m.Raw; + float* dst = &M[0][0]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; + dst[8] = src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + } + + void SetMatrixTranspose(const Matrix& m) + { + const float* src = m.Raw; + float* dst = &M[0][0]; + dst[0] = src[0]; + dst[1] = src[4]; + dst[2] = src[8]; + dst[3] = src[12]; + dst[4] = src[1]; + dst[5] = src[5]; + dst[6] = src[9]; + dst[7] = src[13]; + dst[8] = src[2]; + dst[9] = src[6]; + dst[10] = src[10]; + dst[11] = src[14]; + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp index 2094be3b2..25c29b11a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp @@ -4,53 +4,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Animations/Config.h" #include "Engine/Core/Math/Matrix.h" - -struct SkinMatrix3x4 -{ - float M[3][4]; - - FORCE_INLINE void SetMatrix(const Matrix& mat) - { - const float* src = mat.Raw; - float* dest = &(M[0][0]); - - dest[0] = src[0]; // [0][0] - dest[1] = src[1]; // [0][1] - dest[2] = src[2]; // [0][2] - dest[3] = src[3]; // [0][3] - - dest[4] = src[4]; // [1][0] - dest[5] = src[5]; // [1][1] - dest[6] = src[6]; // [1][2] - dest[7] = src[7]; // [1][3] - - dest[8] = src[8]; // [2][0] - dest[9] = src[9]; // [2][1] - dest[10] = src[10]; // [2][2] - dest[11] = src[11]; // [2][3] - } - - FORCE_INLINE void SetMatrixTranspose(const Matrix& mat) - { - const float* src = mat.Raw; - float* dest = &(M[0][0]); - - dest[0] = src[0]; // [0][0] - dest[1] = src[4]; // [1][0] - dest[2] = src[8]; // [2][0] - dest[3] = src[12]; // [3][0] - - dest[4] = src[1]; // [0][1] - dest[5] = src[5]; // [1][1] - dest[6] = src[9]; // [2][1] - dest[7] = src[13]; // [3][1] - - dest[8] = src[2]; // [0][2] - dest[9] = src[6]; // [1][2] - dest[10] = src[10]; // [2][2] - dest[11] = src[14]; // [3][2] - } -}; +#include "Engine/Core/Math/Matrix3x4.h" SkinnedMeshDrawData::~SkinnedMeshDrawData() { @@ -99,7 +53,6 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) LOG(Fatal, "Failed to initialize the skinned mesh bones buffer"); } } - Swap(PrevBoneMatrices, BoneMatrices); } else @@ -109,15 +62,15 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) // Copy bones to the buffer const int32 count = BonesCount; - const int32 PreFetchStride = 2; + const int32 preFetchStride = 2; const Matrix* input = bones; - const auto output = (SkinMatrix3x4*)Data.Get(); - ASSERT(Data.Count() == count * sizeof(SkinMatrix3x4)); + const auto output = (Matrix3x4*)Data.Get(); + ASSERT(Data.Count() == count * sizeof(Matrix3x4)); for (int32 i = 0; i < count; i++) { - SkinMatrix3x4* bone = output + i; - Platform::Prefetch(bone + PreFetchStride); - Platform::Prefetch((byte*)(bone + PreFetchStride) + PLATFORM_CACHE_LINE_SIZE); + Matrix3x4* bone = output + i; + Platform::Prefetch(bone + preFetchStride); + Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE); bone->SetMatrixTranspose(input[i]); } From a560b19cbceb5a015099d52662b8970885ddc5da Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Feb 2021 09:33:48 +0100 Subject: [PATCH 154/222] Refactor draw calls and instancing logic to be more modular --- Source/Engine/Content/Assets/Material.cpp | 4 +- Source/Engine/Content/Assets/Material.h | 2 +- .../Content/Assets/MaterialInstance.cpp | 4 +- .../Engine/Content/Assets/MaterialInstance.h | 2 +- .../Materials/DeferredMaterialShader.cpp | 28 ++--- .../Materials/DeferredMaterialShader.h | 2 +- .../Materials/ForwardMaterialShader.cpp | 18 +-- .../Materials/ForwardMaterialShader.h | 2 +- Source/Engine/Graphics/Materials/IMaterial.h | 32 ++---- .../Materials/ParticleMaterialShader.cpp | 54 ++++----- .../Materials/TerrainMaterialShader.cpp | 26 ++--- Source/Engine/Graphics/Models/Mesh.cpp | 24 ++-- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 12 +- Source/Engine/Level/Actors/Skybox.cpp | 2 +- Source/Engine/Particles/ParticleManager.cpp | 11 +- Source/Engine/Renderer/DrawCall.h | 108 ++++++------------ .../Renderer/Editor/LightmapUVsDensity.cpp | 2 +- Source/Engine/Renderer/GBufferPass.cpp | 8 +- Source/Engine/Renderer/RenderList.cpp | 74 ++++++++---- Source/Engine/Renderer/RenderList.h | 33 ++++-- Source/Engine/Renderer/Renderer.cpp | 3 - Source/Engine/Terrain/TerrainChunk.cpp | 60 +++++----- Source/Engine/UI/TextRender.cpp | 12 +- 23 files changed, 255 insertions(+), 268 deletions(-) diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index b40e1e9c0..571e46243 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -61,9 +61,9 @@ bool Material::CanUseLightmap() const return _materialShader && _materialShader->CanUseLightmap(); } -bool Material::CanUseInstancing() const +bool Material::CanUseInstancing(InstancingHandler& handler) const { - return _materialShader && _materialShader->CanUseInstancing(); + return _materialShader && _materialShader->CanUseInstancing(handler); } void Material::Bind(BindParameters& params) diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index bdaba7abb..0a9ad1dbc 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -48,7 +48,7 @@ public: bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; // [ShaderAssetBase] diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index ace2fcee9..0678f9992 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -167,9 +167,9 @@ bool MaterialInstance::CanUseLightmap() const return _baseMaterial && _baseMaterial->CanUseLightmap(); } -bool MaterialInstance::CanUseInstancing() const +bool MaterialInstance::CanUseInstancing(InstancingHandler& handler) const { - return _baseMaterial && _baseMaterial->CanUseInstancing(); + return _baseMaterial && _baseMaterial->CanUseInstancing(handler); } void MaterialInstance::Bind(BindParameters& params) diff --git a/Source/Engine/Content/Assets/MaterialInstance.h b/Source/Engine/Content/Assets/MaterialInstance.h index 9fe92e39c..3cf74db03 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.h +++ b/Source/Engine/Content/Assets/MaterialInstance.h @@ -64,7 +64,7 @@ public: bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; protected: diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 049484459..5a460079d 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -5,6 +5,7 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" #include "Engine/Level/Scene/Lightmap.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" @@ -48,8 +49,9 @@ bool DeferredMaterialShader::CanUseLightmap() const return true; } -bool DeferredMaterialShader::CanUseInstancing() const +bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const { + handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, }; return true; } @@ -79,7 +81,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - Matrix::Transpose(drawCall.PrevWorld, materialData->PrevWorldMatrix); + Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); materialData->ViewPos = view.Position; @@ -100,40 +102,40 @@ void DeferredMaterialShader::Bind(BindParameters& params) materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; - materialData->LODDitherFactor = drawCall.LODDitherFactor; + materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; materialData->TemporalAAJitter = view.TemporalAAJitter; - materialData->GeometrySize = drawCall.GeometrySize; + materialData->GeometrySize = drawCall.Surface.GeometrySize; } const bool useLightmap = view.Flags & ViewFlags::GI #if USE_EDITOR && EnableLightmapsUsage #endif - && drawCall.Lightmap != nullptr; + && drawCall.Surface.Lightmap != nullptr; if (useLightmap) { // Bind lightmap textures GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); + drawCall.Surface.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); context->BindSR(0, lightmap0); context->BindSR(1, lightmap1); context->BindSR(2, lightmap2); // Set lightmap data - materialData->LightmapArea = drawCall.LightmapUVsArea; + materialData->LightmapArea = drawCall.Surface.LightmapUVsArea; } // Check if is using mesh skinning - const bool useSkinning = drawCall.Skinning != nullptr; + const bool useSkinning = drawCall.Surface.Skinning != nullptr; bool perBoneMotionBlur = false; if (useSkinning) { // Bind skinning buffer - ASSERT(drawCall.Skinning->IsReady()); - context->BindSR(0, drawCall.Skinning->BoneMatrices->View()); - if (drawCall.Skinning->PrevBoneMatrices && drawCall.Skinning->PrevBoneMatrices->IsAllocated()) + ASSERT(drawCall.Surface.Skinning->IsReady()); + context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); + if (drawCall.Surface.Skinning->PrevBoneMatrices && drawCall.Surface.Skinning->PrevBoneMatrices->IsAllocated()) { - context->BindSR(1, drawCall.Skinning->PrevBoneMatrices->View()); + context->BindSR(1, drawCall.Surface.Skinning->PrevBoneMatrices->View()); perBoneMotionBlur = true; } } @@ -152,7 +154,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h index f8df968c9..537547c6a 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h @@ -66,7 +66,7 @@ public: // [MaterialShader] DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; void Unload() override; diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 863af34e1..39e39a7d4 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -10,6 +10,7 @@ #include "Engine/Renderer/DepthOfFieldPass.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Renderer/ShadowsPass.h" +#include "Engine/Renderer/RenderList.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif @@ -54,8 +55,9 @@ DrawPass ForwardMaterialShader::GetDrawModes() const return _drawModes; } -bool ForwardMaterialShader::CanUseInstancing() const +bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const { + handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, }; return true; } @@ -82,12 +84,12 @@ void ForwardMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Check if is using mesh skinning - const bool useSkinning = drawCall.Skinning != nullptr; + const bool useSkinning = drawCall.Surface.Skinning != nullptr; if (useSkinning) { // Bind skinning buffer - ASSERT(drawCall.Skinning->IsReady()); - context->BindSR(0, drawCall.Skinning->BoneMatrices->View()); + ASSERT(drawCall.Surface.Skinning->IsReady()); + context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); } // Setup material constants data @@ -97,7 +99,7 @@ void ForwardMaterialShader::Bind(BindParameters& params) Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - Matrix::Transpose(drawCall.PrevWorld, materialData->PrevWorldMatrix); + Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); materialData->ViewPos = view.Position; @@ -118,9 +120,9 @@ void ForwardMaterialShader::Bind(BindParameters& params) materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; - materialData->LODDitherFactor = drawCall.LODDitherFactor; + materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; - materialData->GeometrySize = drawCall.GeometrySize; + materialData->GeometrySize = drawCall.Surface.GeometrySize; } // Setup lighting constants data @@ -249,7 +251,7 @@ void ForwardMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h index d39fcea54..bcba48259 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h @@ -67,7 +67,7 @@ public: // [MaterialShader] DrawPass GetDrawModes() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; void Unload() override; diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index 09796c04b..f1e7856af 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Threading/Task.h" #include "MaterialInfo.h" struct MaterialParamsLink; @@ -81,24 +80,6 @@ public: return GetInfo().Domain == MaterialDomain::Particle; } - /// - /// Checks if material needs vertex color vertex buffer data for rendering. - /// - /// True if mesh renderer have to provide vertex color buffer to render that material - FORCE_INLINE bool RequireVertexColor() const - { - return (GetInfo().UsageFlags & MaterialUsageFlags::UseVertexColor) != 0; - } - - /// - /// Checks if material supports dithered LOD transitions. - /// - /// True if material supports dithered LOD transitions, otherwise false. - FORCE_INLINE bool IsDitheredLODTransition() const - { - return (GetInfo().FeaturesFlags & MaterialFeaturesFlags::DitheredLODTransition) != 0; - } - /// /// Returns true if material is ready for rendering. /// @@ -123,11 +104,22 @@ public: return false; } + /// + /// The instancing handling used to hash, batch and write draw calls. + /// + struct InstancingHandler + { + void (*GetHash)(const DrawCall& drawCall, int32& batchKey); + bool (*CanBatch)(const DrawCall& a, const DrawCall& b); + void (*WriteDrawCall)(struct InstanceData* instanceData, const DrawCall& drawCall); + }; + /// /// Returns true if material can use draw calls instancing. /// + /// The output data for the instancing handling used to hash, batch and write draw calls. Valid only when function returns true. /// True if can use instancing, otherwise false. - virtual bool CanUseInstancing() const + virtual bool CanUseInstancing(InstancingHandler& handler) const { return false; } diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 8c1c065ab..210c81e7d 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -76,7 +76,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) const bool hasCb0 = cb0->GetSize() != 0; const auto cb1 = _shader->GetCB(1); const bool hasCb1 = cb1->GetSize() != 0; - const uint32 sortedIndicesOffset = drawCall.Module->SortedIndicesOffset; + const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; // Setup parameters MaterialParameter::BindMeta bindMeta; @@ -90,9 +90,9 @@ void ParticleMaterialShader::Bind(BindParameters& params) // Setup particles data and attributes binding info { - context->BindSR(0, drawCall.Particles->GPU.Buffer->View()); - if (drawCall.Particles->GPU.SortedIndices) - context->BindSR(1, drawCall.Particles->GPU.SortedIndices->View()); + context->BindSR(0, drawCall.Particle.Particles->GPU.Buffer->View()); + if (drawCall.Particle.Particles->GPU.SortedIndices) + context->BindSR(1, drawCall.Particle.Particles->GPU.SortedIndices->View()); if (hasCb0) { @@ -104,7 +104,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) { auto name = StringView(param.GetName().Get() + 9); - const int32 offset = drawCall.Particles->Layout->FindAttributeOffset(name); + const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); *((int32*)(bindMeta.Buffer0 + param.GetBindOffset())) = offset; } } @@ -115,7 +115,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode; PipelineStateCache* psCache = nullptr; - switch (drawCall.Module->TypeID) + switch (drawCall.Particle.Module->TypeID) { // Sprite Rendering case 400: @@ -142,20 +142,20 @@ void ParticleMaterialShader::Bind(BindParameters& params) { const auto materialData = reinterpret_cast(_cb0Data.Get()); - materialData->RibbonWidthOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonTwistOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonFacingVectorOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->RibbonUVTilingDistance = drawCall.Ribbon.UVTilingDistance; - materialData->RibbonUVScale.X = drawCall.Ribbon.UVScaleX; - materialData->RibbonUVScale.Y = drawCall.Ribbon.UVScaleY; - materialData->RibbonUVOffset.X = drawCall.Ribbon.UVOffsetX; - materialData->RibbonUVOffset.Y = drawCall.Ribbon.UVOffsetY; - materialData->RibbonSegmentCount = drawCall.Ribbon.SegmentCount; + materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance; + materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX; + materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY; + materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX; + materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY; + materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount; } - if (drawCall.Ribbon.SegmentDistances) - context->BindSR(1, drawCall.Ribbon.SegmentDistances->View()); + if (drawCall.Particle.Ribbon.SegmentDistances) + context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View()); break; } @@ -186,17 +186,17 @@ void ParticleMaterialShader::Bind(BindParameters& params) materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - materialData->SortedIndicesOffset = drawCall.Particles->GPU.SortedIndices && params.RenderContext.View.Pass != DrawPass::Depth ? sortedIndicesOffset : 0xFFFFFFFF; + materialData->SortedIndicesOffset = drawCall.Particle.Particles->GPU.SortedIndices && params.RenderContext.View.Pass != DrawPass::Depth ? sortedIndicesOffset : 0xFFFFFFFF; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; - materialData->ParticleStride = drawCall.Particles->Stride; - materialData->PositionOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticlePosition, ParticleAttribute::ValueTypes::Vector3); - materialData->SpriteSizeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteSize, ParticleAttribute::ValueTypes::Vector2); - materialData->SpriteFacingModeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingMode, ParticleAttribute::ValueTypes::Int, -1); - materialData->SpriteFacingVectorOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingVector, ParticleAttribute::ValueTypes::Vector3); - materialData->VelocityOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleVelocityOffset, ParticleAttribute::ValueTypes::Vector3); - materialData->RotationOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRotationOffset, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->ScaleOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleScaleOffset, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->ModelFacingModeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleModelFacingModeOffset, ParticleAttribute::ValueTypes::Int, -1); + materialData->ParticleStride = drawCall.Particle.Particles->Stride; + materialData->PositionOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticlePosition, ParticleAttribute::ValueTypes::Vector3); + materialData->SpriteSizeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteSize, ParticleAttribute::ValueTypes::Vector2); + materialData->SpriteFacingModeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingMode, ParticleAttribute::ValueTypes::Int, -1); + materialData->SpriteFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingVector, ParticleAttribute::ValueTypes::Vector3); + materialData->VelocityOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleVelocityOffset, ParticleAttribute::ValueTypes::Vector3); + materialData->RotationOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRotationOffset, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->ScaleOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleScaleOffset, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->ModelFacingModeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleModelFacingModeOffset, ParticleAttribute::ValueTypes::Int, -1); Matrix::Invert(drawCall.World, materialData->WorldMatrixInverseTransposed); } diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 212e27d87..37f932a5a 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -93,36 +93,36 @@ void TerrainMaterialShader::Bind(BindParameters& params) data->WorldDeterminantSign = drawCall.WorldDeterminantSign; data->PerInstanceRandom = drawCall.PerInstanceRandom; - data->CurrentLOD = drawCall.TerrainData.CurrentLOD; - data->ChunkSizeNextLOD = drawCall.TerrainData.ChunkSizeNextLOD; - data->TerrainChunkSizeLOD0 = drawCall.TerrainData.TerrainChunkSizeLOD0; - data->HeightmapUVScaleBias = drawCall.TerrainData.HeightmapUVScaleBias; - data->NeighborLOD = drawCall.TerrainData.NeighborLOD; - data->OffsetUV = drawCall.TerrainData.OffsetUV; + data->CurrentLOD = drawCall.Terrain.CurrentLOD; + data->ChunkSizeNextLOD = drawCall.Terrain.ChunkSizeNextLOD; + data->TerrainChunkSizeLOD0 = drawCall.Terrain.TerrainChunkSizeLOD0; + data->HeightmapUVScaleBias = drawCall.Terrain.HeightmapUVScaleBias; + data->NeighborLOD = drawCall.Terrain.NeighborLOD; + data->OffsetUV = drawCall.Terrain.OffsetUV; } const bool useLightmap = view.Flags & ViewFlags::GI #if USE_EDITOR && EnableLightmapsUsage #endif && view.Pass == DrawPass::GBuffer - && drawCall.Lightmap != nullptr; + && drawCall.Terrain.Lightmap != nullptr; if (useLightmap) { // Bind lightmap textures GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); + drawCall.Terrain.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); context->BindSR(0, lightmap0); context->BindSR(1, lightmap1); context->BindSR(2, lightmap2); // Set lightmap data - data->LightmapArea = drawCall.LightmapUVsArea; + data->LightmapArea = drawCall.Terrain.LightmapUVsArea; } // Bind terrain textures - const auto heightmap = drawCall.TerrainData.Patch->Heightmap.Get()->GetTexture(); - const auto splatmap0 = drawCall.TerrainData.Patch->Splatmap[0] ? drawCall.TerrainData.Patch->Splatmap[0]->GetTexture() : nullptr; - const auto splatmap1 = drawCall.TerrainData.Patch->Splatmap[1] ? drawCall.TerrainData.Patch->Splatmap[1]->GetTexture() : nullptr; + const auto heightmap = drawCall.Terrain.Patch->Heightmap.Get()->GetTexture(); + const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr; + const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr; context->BindSR(3, heightmap); context->BindSR(4, splatmap0); context->BindSR(5, splatmap1); @@ -141,7 +141,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 444d60540..99262cd51 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -393,15 +393,15 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = world; - drawCall.PrevWorld = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = world; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = 0.0f; - drawCall.LODDitherFactor = 0.0f; renderContext.List->AddDrawCall(DrawPass::Default, flags, drawCall, receiveDecals); } @@ -456,15 +456,15 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; - drawCall.PrevWorld = info.DrawState->PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr; - drawCall.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; + drawCall.Surface.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr; + drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - drawCall.LODDitherFactor = lodDitherFactor; renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals); } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 90aa03d86..e9e7f8438 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -193,15 +193,15 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; - drawCall.PrevWorld = info.DrawState->PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = info.Skinning; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = info.Skinning; + drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - drawCall.LODDitherFactor = lodDitherFactor; renderContext.List->AddDrawCall(drawModes, StaticFlags::None, drawCall, entry.ReceiveDecals); } diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 48c19191f..e8ea08c89 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -93,7 +93,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M Platform::MemoryClear(&drawCall, sizeof(DrawCall)); drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); + drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); MaterialBase::BindParameters bindParams(context, renderContext, drawCall); diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp index 40cebd892..c83aacbbf 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/ParticleManager.cpp @@ -423,7 +423,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { const int32 moduleIndex = renderModulesIndices[index]; auto module = emitter->Graph.RenderModules[moduleIndex]; - drawCall.Module = module; + drawCall.Particle.Module = module; switch (module->TypeID) { @@ -486,7 +486,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa int32 count = buffer->CPU.Count; // Setup ribbon data - auto& ribbon = drawCall.Ribbon; + auto& ribbon = drawCall.Particle.Ribbon; ribbon.UVTilingDistance = uvTilingDistance; ribbon.SegmentCount = ribbonModulesSegmentCount[ribbonModuleIndex]; ribbon.UVScaleX = uvScale.X; @@ -788,7 +788,7 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { int32 moduleIndex = renderModulesIndices[index]; auto module = emitter->Graph.RenderModules[moduleIndex]; - drawCall.Module = module; + drawCall.Particle.Module = module; switch (module->TypeID) { @@ -875,9 +875,7 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect // Setup a draw call common data DrawCall drawCall; - drawCall.LightmapUVsArea = Rectangle::Empty; drawCall.PerInstanceRandom = effect->GetPerInstanceRandom(); - drawCall.LODDitherFactor = 1.0f; drawCall.ObjectPosition = world.GetTranslation(); // Draw all emitters @@ -890,9 +888,8 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect auto emitter = buffer->Emitter; drawCall.World = emitter->SimulationSpace == ParticlesSimulationSpace::World ? Matrix::Identity : world; - drawCall.PrevWorld = drawCall.World; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); - drawCall.Particles = buffer; + drawCall.Particle.Particles = buffer; // Check if need to render any module RenderModulesIndices renderModulesIndices; diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index cc35b2627..31656145b 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -113,6 +113,11 @@ public: /// struct DrawCall { + /// + /// The material to use for rendering. + /// + IMaterial* Material; + struct { /// @@ -156,53 +161,18 @@ struct DrawCall /// GPUBuffer* IndirectArgsBuffer; - /// - /// The target material to use. - /// - IMaterial* Material; - - // Particles don't use skinning nor lightmaps so pack those stuff together + // Per-material shader data packed into union union { struct { - /// - /// Pointer to lightmap for static object with prebaked lighting. - /// const Lightmap* Lightmap; - - /// - /// The skinning data. If set then material should use GPU skinning during rendering. - /// SkinnedMeshDrawData* Skinning; - }; - - struct - { - /// - /// The particles data. Used only by the particles shaders. - /// - class ParticleBuffer* Particles; - - /// - /// The particle module to draw. - /// - class ParticleEmitterGraphCPUNode* Module; - }; - }; - - /// - /// Object world transformation matrix. - /// - Matrix World; - - // Terrain and particles don't use previous world matrix so pack those stuff together - union - { - /// - /// Object world transformation matrix using during previous frame. - /// - Matrix PrevWorld; + Rectangle LightmapUVsArea; + Vector3 GeometrySize; // Object geometry size in the world (unscaled). + float LODDitherFactor; // The model LOD transition dither progress. + Matrix PrevWorld; + } Surface; struct { @@ -212,26 +182,39 @@ struct DrawCall float CurrentLOD; float ChunkSizeNextLOD; float TerrainChunkSizeLOD0; + Rectangle LightmapUVsArea; const class TerrainPatch* Patch; - } TerrainData; + const Lightmap* Lightmap; + } Terrain; struct { - int32 RibbonOrderOffset; - float UVTilingDistance; - float UVScaleX; - float UVScaleY; - float UVOffsetX; - float UVOffsetY; - uint32 SegmentCount; - GPUBuffer* SegmentDistances; - } Ribbon; + class ParticleBuffer* Particles; + class ParticleEmitterGraphCPUNode* Module; + + struct + { + int32 OrderOffset; + float UVTilingDistance; + float UVScaleX; + float UVScaleY; + float UVOffsetX; + float UVOffsetY; + uint32 SegmentCount; + GPUBuffer* SegmentDistances; + } Ribbon; + } Particle; + + struct + { + byte Raw[96]; + } Custom; }; /// - /// Lightmap UVs area that entry occupies. + /// Object world transformation matrix. /// - Rectangle LightmapUVsArea; + Matrix World; /// /// Object location in the world used for draw calls sorting. @@ -243,36 +226,17 @@ struct DrawCall /// float WorldDeterminantSign; - /// - /// Object geometry size in the world (unscaled). - /// - Vector3 GeometrySize; - /// /// The random per-instance value (normalized to range 0-1). /// float PerInstanceRandom; - /// - /// The model LOD transition dither factor. - /// - float LODDitherFactor; - /// /// Does nothing. /// DrawCall() { } - - /// - /// Determines whether world transform matrix is performing negative scale (then model culling should be inverted). - /// - /// true if world matrix contains negative scale; otherwise, false. - FORCE_INLINE bool IsNegativeScale() const - { - return WorldDeterminantSign < 0; - } }; template<> diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 152b43d50..9ba9f1a0d 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -178,7 +178,7 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params) scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); data.LightmapTexelsPerWorldUnit = ShadowsOfMordor::LightmapTexelsPerWorldUnit; data.LightmapSize = 1024.0f; - data.LightmapArea = drawCall.LightmapUVsArea; + data.LightmapArea = drawCall.Surface.LightmapUVsArea; if (drawCallModel) { // Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 5c6f76905..b9e91ee01 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -145,7 +145,8 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer drawCall.Material = _lightmapUVsDensityMaterialShader; } } - if (!_lightmapUVsDensityMaterialShader->CanUseInstancing()) + IMaterial::InstancingHandler handler; + if (!_lightmapUVsDensityMaterialShader->CanUseInstancing(handler)) { drawCallsList.CanUseInstancing = false; } @@ -166,7 +167,8 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer drawCall.Material = _vertexColorsMaterialShader; } } - if (!_vertexColorsMaterialShader->CanUseInstancing()) + IMaterial::InstancingHandler handler; + if (!_vertexColorsMaterialShader->CanUseInstancing(handler)) { drawCallsList.CanUseInstancing = false; } @@ -311,8 +313,6 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light DrawCall drawCall; MaterialBase::BindParameters bindParams(gpuContext, renderContext, drawCall); drawCall.Material = nullptr; - drawCall.Lightmap = nullptr; - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = 1.0f; // Draw all decals diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index c78931416..dd711a5e0 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -19,6 +19,11 @@ #define BATCH_KEY_BITS 32 #define BATCH_KEY_MASK ((1 << BATCH_KEY_BITS) - 1) +static_assert(sizeof(DrawCall) <= 280, "Too big draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Terrain), "Wrong draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Particle), "Wrong draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Custom), "Wrong draw call data size."); + namespace { // Cached data for the draw calls sorting @@ -495,24 +500,24 @@ end: } } -/// -/// Checks if this draw call be batched together with the other one. -/// -/// The first draw call. -/// The second draw call. -/// True if can merge them, otherwise false. -FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b) +namespace { - return Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && - a.Material == b.Material && - a.Lightmap == b.Lightmap && - // TODO: add batch.CanBatch flag computed in AddDrawCall to remove those checks here for Skinning and IndirectDrawArgs - a.Skinning == nullptr && - b.Skinning == nullptr && - a.IndirectArgsBuffer == nullptr && - b.IndirectArgsBuffer == nullptr && - a.WorldDeterminantSign == b.WorldDeterminantSign && - a.Material->CanUseInstancing(); + /// + /// Checks if this draw call be batched together with the other one. + /// + /// The first draw call. + /// The second draw call. + /// True if can merge them, otherwise false. + FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b) + { + IMaterial::InstancingHandler handler; + return a.Material == b.Material && + a.Material->CanUseInstancing(handler) && + Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && + a.IndirectArgsBuffer == nullptr && + b.IndirectArgsBuffer == nullptr && + a.WorldDeterminantSign == b.WorldDeterminantSign; + } } void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list) @@ -540,7 +545,9 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[1]); batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[2]); batchKey = (batchKey * 397) ^ GetHash(drawCall.Material); - batchKey = (batchKey * 397) ^ GetHash(drawCall.Lightmap); + IMaterial::InstancingHandler handler; + if (drawCall.Material->CanUseInstancing(handler)) + handler.GetHash(drawCall, batchKey); batchKey += (int32)(471 * drawCall.WorldDeterminantSign); #if USE_BATCH_KEY_MASK const uint32 batchHashKey = (uint32)batchKey & BATCH_KEY_MASK; @@ -635,14 +642,12 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL auto& batch = list.Batches[i]; if (batch.BatchSize > 1) { + IMaterial::InstancingHandler handler; + DrawCalls[list.Indices[batch.StartIndex]].Material->CanUseInstancing(handler); for (int32 j = 0; j < batch.BatchSize; j++) { auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]]; - instanceData->InstanceOrigin = Vector4(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43, drawCall.PerInstanceRandom); - instanceData->InstanceTransform1 = Vector4(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13, drawCall.LODDitherFactor); - instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23); - instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33); - instanceData->InstanceLightmapArea = Half4(drawCall.LightmapUVsArea); + handler.WriteDrawCall(instanceData, drawCall); instanceData++; } } @@ -742,3 +747,26 @@ DRAW: } } } + +void SurfaceDrawCallHandler::GetHash(const DrawCall& drawCall, int32& batchKey) +{ + batchKey = (batchKey * 397) ^ ::GetHash(drawCall.Surface.Lightmap); +} + +bool SurfaceDrawCallHandler::CanBatch(const DrawCall& a, const DrawCall& b) +{ + return a.Surface.Lightmap == b.Surface.Lightmap && + a.Surface.Skinning == nullptr && + b.Surface.Skinning == nullptr; +} + +void SurfaceDrawCallHandler::WriteDrawCall(InstanceData* instanceData, const DrawCall& drawCall) +{ + instanceData->InstanceOrigin = Vector3(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43); + instanceData->PerInstanceRandom = drawCall.PerInstanceRandom; + instanceData->InstanceTransform1 = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13); + instanceData->LODDitherFactor = drawCall.Surface.LODDitherFactor; + instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23); + instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33); + instanceData->InstanceLightmapArea = Half4(drawCall.Surface.LightmapUVsArea); +} diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index dffbbd2d1..69ef0c33d 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -357,18 +357,6 @@ public: private: - /// - /// Represents data per instance element used for instanced rendering. - /// - struct InstanceData - { - Vector4 InstanceOrigin; // .w contains PerInstanceRandom - Vector4 InstanceTransform1; // .w contains LODDitherFactor - Vector3 InstanceTransform2; - Vector3 InstanceTransform3; - Half4 InstanceLightmapArea; - }; - DynamicVertexBuffer _instanceBuffer; public: @@ -475,3 +463,24 @@ public: /// The collected draw calls list. void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list); }; + +/// +/// Represents data per instance element used for instanced rendering. +/// +struct FLAXENGINE_API InstanceData +{ + Vector3 InstanceOrigin; + float PerInstanceRandom; + Vector3 InstanceTransform1; + float LODDitherFactor; + Vector3 InstanceTransform2; + Vector3 InstanceTransform3; + Half4 InstanceLightmapArea; +}; + +struct SurfaceDrawCallHandler +{ + static void GetHash(const DrawCall& drawCall, int32& batchKey); + static bool CanBatch(const DrawCall& a, const DrawCall& b); + static void WriteDrawCall(InstanceData* instanceData, const DrawCall& drawCall); +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 4d3241375..89efef74c 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -31,9 +31,6 @@ #include "Editor/Editor.h" #endif -// It must use less or the same amount of memory -static_assert(sizeof(DrawCall::TerrainData) <= sizeof(DrawCall::PrevWorld), "Invalid size of the terrain data in the draw call."); - #if USE_EDITOR // Additional options used in editor for lightmaps baking bool IsRunningRadiancePass = false; diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 1241935e5..6cff541f3 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -88,33 +88,31 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const drawCall.Material = _cachedDrawMaterial; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.TerrainData.Patch = _patch; - drawCall.TerrainData.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.TerrainData.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); - drawCall.TerrainData.CurrentLOD = (float)lod; - drawCall.TerrainData.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); - drawCall.TerrainData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; + drawCall.Terrain.Patch = _patch; + drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.CurrentLOD = (float)lod; + drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); + drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; // TODO: try using SIMD clamping for 4 chunks at once - drawCall.TerrainData.NeighborLOD.X = (float)Math::Clamp(_neighbors[0]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.Y = (float)Math::Clamp(_neighbors[1]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.Z = (float)Math::Clamp(_neighbors[2]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.W = (float)Math::Clamp(_neighbors[3]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.X = (float)Math::Clamp(_neighbors[0]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.Y = (float)Math::Clamp(_neighbors[1]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.Z = (float)Math::Clamp(_neighbors[2]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.W = (float)Math::Clamp(_neighbors[3]->_cachedDrawLOD, lod, minLod); const auto scene = _patch->_terrain->GetScene(); const auto flags = _patch->_terrain->_staticFlags; if (flags & StaticFlags::Lightmap && scene) { - drawCall.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - drawCall.LightmapUVsArea = Lightmap.UVsArea; + drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea; } else { - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; + drawCall.Terrain.Lightmap = nullptr; + drawCall.Terrain.LightmapUVsArea = Rectangle::Empty; } - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = _perInstanceRandom; - drawCall.LODDitherFactor = 0.0f; // Add half-texel offset for heightmap sampling in vertex shader //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); @@ -147,32 +145,30 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi drawCall.Material = material; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.TerrainData.Patch = _patch; - drawCall.TerrainData.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.TerrainData.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); - drawCall.TerrainData.CurrentLOD = (float)lod; - drawCall.TerrainData.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); - drawCall.TerrainData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; - drawCall.TerrainData.NeighborLOD.X = (float)lod; - drawCall.TerrainData.NeighborLOD.Y = (float)lod; - drawCall.TerrainData.NeighborLOD.Z = (float)lod; - drawCall.TerrainData.NeighborLOD.W = (float)lod; + drawCall.Terrain.Patch = _patch; + drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.CurrentLOD = (float)lod; + drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); + drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; + drawCall.Terrain.NeighborLOD.X = (float)lod; + drawCall.Terrain.NeighborLOD.Y = (float)lod; + drawCall.Terrain.NeighborLOD.Z = (float)lod; + drawCall.Terrain.NeighborLOD.W = (float)lod; const auto scene = _patch->_terrain->GetScene(); const auto flags = _patch->_terrain->_staticFlags; if (flags & StaticFlags::Lightmap && scene) { - drawCall.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - drawCall.LightmapUVsArea = Lightmap.UVsArea; + drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea; } else { - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; + drawCall.Terrain.Lightmap = nullptr; + drawCall.Terrain.LightmapUVsArea = Rectangle::Empty; } - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = _perInstanceRandom; - drawCall.LODDitherFactor = 0.0f; // Add half-texel offset for heightmap sampling in vertex shader //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 958aced8b..5707b4461 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -319,15 +319,15 @@ void TextRender::Draw(RenderContext& renderContext) // Setup draw call DrawCall drawCall; drawCall.World = _world; - drawCall.PrevWorld = _drawState.PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _localBox.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _localBox.GetSize(); + drawCall.Surface.PrevWorld = _drawState.PrevWorld; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(_world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); - drawCall.LODDitherFactor = 0.0f; drawCall.Geometry.IndexBuffer = _ib.GetBuffer(); drawCall.Geometry.VertexBuffers[0] = _vb0.GetBuffer(); drawCall.Geometry.VertexBuffers[1] = _vb1.GetBuffer(); From 2a3b6edf50978d282632055420e03115018dd825 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 4 Feb 2021 10:43:04 +0100 Subject: [PATCH 155/222] Refactor material shaders generator to use modular features as extensions --- Content/Editor/MaterialTemplates/Decal.shader | 4 + .../MaterialTemplates/Features/Lightmap.hlsl | 53 +++ .../Features/Tessellation.hlsl | 294 ++++++++++++++++ Content/Editor/MaterialTemplates/GUI.shader | 4 + .../Editor/MaterialTemplates/Particle.shader | 4 + .../MaterialTemplates/PostProcess.shader | 4 + .../MaterialTemplates/SurfaceDeferred.shader | 330 +----------------- .../MaterialTemplates/SurfaceForward.shader | 258 +------------- .../Editor/MaterialTemplates/Terrain.shader | 22 +- Source/Engine/Content/Assets/Material.cpp | 1 - .../Materials/DecalMaterialShader.cpp | 2 +- .../Materials/DeferredMaterialShader.cpp | 32 +- .../Materials/ForwardMaterialShader.cpp | 14 +- .../Graphics/Materials/GUIMaterialShader.cpp | 2 +- Source/Engine/Graphics/Materials/IMaterial.h | 6 - .../Graphics/Materials/MaterialParams.cpp | 43 +-- .../Graphics/Materials/MaterialParams.h | 4 +- .../Materials/MaterialShaderFeatures.cpp | 172 +++++++++ .../Materials/MaterialShaderFeatures.h | 70 ++++ .../Materials/ParticleMaterialShader.cpp | 4 +- .../Materials/PostFxMaterialShader.cpp | 2 +- .../Materials/TerrainMaterialShader.cpp | 2 +- .../Particles/Graph/GPU/GPUParticles.cpp | 2 +- .../MaterialGenerator.Texture.cpp | 23 -- .../MaterialGenerator/MaterialGenerator.cpp | 190 +++++++++- .../MaterialGenerator/MaterialGenerator.h | 2 - Source/Engine/Utilities/TextWriter.h | 20 ++ .../Engine/Visject/ShaderGraphUtilities.cpp | 4 - Source/Shaders/MaterialCommon.hlsl | 20 -- 29 files changed, 877 insertions(+), 711 deletions(-) create mode 100644 Content/Editor/MaterialTemplates/Features/Lightmap.hlsl create mode 100644 Content/Editor/MaterialTemplates/Features/Tessellation.hlsl create mode 100644 Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp create mode 100644 Source/Engine/Graphics/Materials/MaterialShaderFeatures.h diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index e0253efd6..35446b7e2 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -108,6 +108,8 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for pixel shader) Material GetMaterialPS(MaterialInput input) { @@ -211,3 +213,5 @@ void PS_Decal( #error "Invalid decal blending mode" #endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl new file mode 100644 index 000000000..6f6a44d8b --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl @@ -0,0 +1,53 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Lightmap: Defines +@1// Lightmap: Includes +@2// Lightmap: Constants +float4 LightmapArea; +@3// Lightmap: Resources +#if USE_LIGHTMAP +// Irradiance and directionality prebaked lightmaps +Texture2D Lightmap0 : register(t__SRV__); +Texture2D Lightmap1 : register(t__SRV__); +Texture2D Lightmap2 : register(t__SRV__); +#endif +@4// Lightmap: Utilities +#if USE_LIGHTMAP + +// Evaluates the H-Basis coefficients in the tangent space normal direction +float3 GetHBasisIrradiance(float3 n, float3 h0, float3 h1, float3 h2, float3 h3) +{ + // Band 0 + float3 color = h0 * (1.0f / sqrt(2.0f * PI)); + + // Band 1 + color += h1 * -sqrt(1.5f / PI) * n.y; + color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); + color += h3 * -sqrt(1.5f / PI) * n.x; + + return color; +} + +float3 SampleLightmap(Material material, MaterialInput materialInput) +{ + // Sample lightmaps + float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); + float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); + float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); + + // Unpack H-basis + float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); + float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); + float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); + float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); + + // Sample baked diffuse irradiance from the H-basis coefficients + float3 normal = material.TangentNormal; +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + normal *= material.TangentNormal; +#endif + return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; +} + +#endif +@5// Lightmap: Shaders \ No newline at end of file diff --git a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl new file mode 100644 index 000000000..c5ebbba92 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl @@ -0,0 +1,294 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Tessellation: Defines +@1// Tessellation: Includes +@2// Tessellation: Constants +@3// Tessellation: Resources +@4// Tessellation: Utilities +@5// Tessellation: Shaders +#if USE_TESSELLATION + +// Interpolants passed from the hull shader to the domain shader +struct TessalationHSToDS +{ + float4 Position : SV_Position; + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD3; + float4 WorldTangent : TEXCOORD4; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + float3 InstanceOrigin : TEXCOORD6; + float2 InstanceParams : TEXCOORD7; +#if IS_MOTION_VECTORS_PASS + float3 PrevWorldPosition : TEXCOORD8; +#endif + float TessellationMultiplier : TESS; +}; + +// Interpolants passed from the domain shader and to the pixel shader +struct TessalationDSToPS +{ + float4 Position : SV_Position; + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD3; + float4 WorldTangent : TEXCOORD4; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + float3 InstanceOrigin : TEXCOORD6; + float2 InstanceParams : TEXCOORD7; +#if IS_MOTION_VECTORS_PASS + float3 PrevWorldPosition : TEXCOORD8; +#endif +}; + +MaterialInput GetMaterialInput(TessalationDSToPS input) +{ + MaterialInput result = (MaterialInput)0; + result.WorldPosition = input.WorldPosition; + result.TexCoord = input.TexCoord; +#if USE_LIGHTMAP + result.LightmapUV = input.LightmapUV; +#endif +#if USE_VERTEX_COLOR + result.VertexColor = input.VertexColor; +#endif + result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); + result.TwoSidedSign = WorldDeterminantSign; + result.InstanceOrigin = input.InstanceOrigin; + result.InstanceParams = input.InstanceParams; + result.SvPosition = input.Position; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + result.CustomVSToPS = input.CustomVSToPS; +#endif + return result; +} + +struct TessalationPatch +{ + float EdgeTessFactor[3] : SV_TessFactor; + float InsideTessFactor : SV_InsideTessFactor; +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + float3 B210 : POSITION4; + float3 B120 : POSITION5; + float3 B021 : POSITION6; + float3 B012 : POSITION7; + float3 B102 : POSITION8; + float3 B201 : POSITION9; + float3 B111 : CENTER; +#endif +}; + +TessalationPatch HS_PatchConstant(InputPatch input) +{ + TessalationPatch output; + + // Average tess factors along edges, and pick an edge tess factor for the interior tessellation + float4 tessellationMultipliers; + tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); + tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); + tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); + tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); + tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); + + output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge + output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge + output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge + output.InsideTessFactor = tessellationMultipliers.w; + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + // Calculate PN Triangle control points + // Reference: [Vlachos 2001] + float3 p1 = input[0].WorldPosition; + float3 p2 = input[1].WorldPosition; + float3 p3 = input[2].WorldPosition; + float3 n1 = input[0].WorldNormal; + float3 n2 = input[1].WorldNormal; + float3 n3 = input[2].WorldNormal; + output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; + output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; + output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; + output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; + output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; + output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; + float3 e = (output.B210 + output.B120 + output.B021 + + output.B012 + output.B102 + output.B201) / 6.0f; + float3 v = (p1 + p2 + p3) / 3.0f; + output.B111 = e + ((e - v) / 2.0f); +#endif + + return output; +} + +META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) +META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) +META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) +[domain("tri")] +[partitioning("fractional_odd")] +[outputtopology("triangle_cw")] +[maxtessfactor(MAX_TESSELLATION_FACTOR)] +[outputcontrolpoints(3)] +[patchconstantfunc("HS_PatchConstant")] +TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) +{ + TessalationHSToDS output; + + // Pass through shader +#define COPY(thing) output.thing = input[ControlPointID].thing; + COPY(Position); + COPY(WorldPosition); + COPY(TexCoord); + COPY(LightmapUV); +#if USE_VERTEX_COLOR + COPY(VertexColor); +#endif + COPY(WorldNormal); + COPY(WorldTangent); + COPY(InstanceOrigin); + COPY(InstanceParams); +#if IS_MOTION_VECTORS_PASS + COPY(PrevWorldPosition); +#endif + COPY(TessellationMultiplier); +#if USE_CUSTOM_VERTEX_INTERPOLATORS + COPY(CustomVSToPS); +#endif +#undef COPY + + return output; +} + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG + +// Orthogonal projection on to plane +float3 ProjectOntoPlane(float3 planeNormal, float3 planePosition, float3 pointToProject) +{ + return pointToProject - dot(pointToProject - planePosition, planeNormal) * planeNormal; +} + +#endif + +META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) +META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) +[domain("tri")] +TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) +{ + TessalationDSToPS output; + + // Get the barycentric coords + float U = barycentricCoords.x; + float V = barycentricCoords.y; + float W = barycentricCoords.z; + + // Interpolate patch attributes to generated vertices +#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing +#define COPY(thing) output.thing = input[0].thing + INTERPOLATE(Position); +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + float UU = U * U; + float VV = V * V; + float WW = W * W; + float UU3 = UU * 3.0f; + float VV3 = VV * 3.0f; + float WW3 = WW * 3.0f; + + // Interpolate using barycentric coordinates and PN Triangle control points + output.WorldPosition = + input[0].WorldPosition * UU * U + + input[1].WorldPosition * VV * V + + input[2].WorldPosition * WW * W + + constantData.B210 * UU3 * V + + constantData.B120 * VV3 * U + + constantData.B021 * VV3 * W + + constantData.B012 * WW3 * V + + constantData.B102 * WW3 * U + + constantData.B201 * UU3 * W + + constantData.B111 * 6.0f * W * U * V; +#if IS_MOTION_VECTORS_PASS + output.PrevWorldPosition = + input[0].PrevWorldPosition * UU * U + + input[1].PrevWorldPosition * VV * V + + input[2].PrevWorldPosition * WW * W + + constantData.B210 * UU3 * V + + constantData.B120 * VV3 * U + + constantData.B021 * VV3 * W + + constantData.B012 * WW3 * V + + constantData.B102 * WW3 * U + + constantData.B201 * UU3 * W + + constantData.B111 * 6.0f * W * U * V; +#endif +#else + INTERPOLATE(WorldPosition); +#if IS_MOTION_VECTORS_PASS + INTERPOLATE(PrevWorldPosition); +#endif +#endif + INTERPOLATE(TexCoord); + INTERPOLATE(LightmapUV); +#if USE_VERTEX_COLOR + INTERPOLATE(VertexColor); +#endif + INTERPOLATE(WorldNormal); + INTERPOLATE(WorldTangent); + COPY(InstanceOrigin); + COPY(InstanceParams); +#if USE_CUSTOM_VERTEX_INTERPOLATORS + UNROLL + for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) + { + INTERPOLATE(CustomVSToPS[i]); + } +#endif +#undef INTERPOLATE +#undef COPY + + // Interpolating tangents can unnormalize it, so normalize it + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG + // Orthogonal projection in the tangent planes + float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); + float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); + float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); + + // Interpolate the projected points + output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; +#if IS_MOTION_VECTORS_PASS + posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].PrevWorldPosition, output.PrevWorldPosition); + posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].PrevWorldPosition, output.PrevWorldPosition); + posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].PrevWorldPosition, output.PrevWorldPosition); + output.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; +#endif +#endif + + // Perform displacement mapping +#if USE_DISPLACEMENT + MaterialInput materialInput = GetMaterialInput(output); + Material material = GetMaterialDS(materialInput); + output.WorldPosition += material.WorldDisplacement; +#if IS_MOTION_VECTORS_PASS + output.PrevWorldPosition += material.WorldDisplacement; +#endif +#endif + + // Recalculate the clip space position + output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); + + return output; +} + +#endif diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index 1457a0bc3..08fddde79 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -185,6 +185,8 @@ float4 GetVertexColor(MaterialInput input) #endif } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -257,3 +259,5 @@ float4 PS_GUI(PixelInput input) : SV_Target0 return float4(material.Emissive, material.Opacity); } + +@9 diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 5fd161645..e8d00d1db 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -312,6 +312,8 @@ float3 TransformParticleVector(float3 input) return mul(float4(input, 0.0f), WorldMatrixInverseTransposed).xyz; } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -865,3 +867,5 @@ void PS_Depth(PixelInput input OutColor = 0; #endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader index dad9795af..a3e5e42c7 100644 --- a/Content/Editor/MaterialTemplates/PostProcess.shader +++ b/Content/Editor/MaterialTemplates/PostProcess.shader @@ -128,6 +128,8 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for pixel shader) Material GetMaterialPS(MaterialInput input) { @@ -147,3 +149,5 @@ float4 PS_PostFx(PixelInput input) : SV_Target0 return float4(material.Emissive, material.Opacity); } + +@9 diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader index 6f393464b..303cb2fee 100644 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" @@ -21,7 +20,6 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float4 LightmapArea; float3 WorldInvScale; float WorldDeterminantSign; float2 Dummy0; @@ -32,18 +30,8 @@ float3 GeometrySize; float Dummy1; @1META_CB_END -#if CAN_USE_LIGHTMAP - -// Irradiance and directionality prebaked lightmaps -Texture2D Lightmap0 : register(t0); -Texture2D Lightmap1 : register(t1); -Texture2D Lightmap2 : register(t2); - -#endif - // Material shader resources @2 - // Interpolants passed from the vertex shader struct VertexOutput { @@ -322,6 +310,8 @@ float4 GetVertexColor(MaterialInput input) #endif } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -613,320 +603,6 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) #endif -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 tessellationMultipliers; - tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = tessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(WorldNormal); - COPY(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if IS_MOTION_VECTORS_PASS - COPY(PrevWorldPosition); -#endif - COPY(TessellationMultiplier); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = - input[0].PrevWorldPosition * UU * U + - input[1].PrevWorldPosition * VV * V + - input[2].PrevWorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#endif -#else - INTERPOLATE(WorldPosition); -#if IS_MOTION_VECTORS_PASS - INTERPOLATE(PrevWorldPosition); -#endif -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(WorldNormal); - INTERPOLATE(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating tangents can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#if IS_MOTION_VECTORS_PASS - posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].PrevWorldPosition, output.PrevWorldPosition); - posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].PrevWorldPosition, output.PrevWorldPosition); - posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].PrevWorldPosition, output.PrevWorldPosition); - output.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition += material.WorldDisplacement; -#endif -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_LIGHTMAP - -float3 SampleLightmap(Material material, MaterialInput materialInput) -{ - // Sample lightmaps - float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); - - // Unpack H-basis - float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); - float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); - float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); - float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); - - // Sample baked diffuse irradiance from the H-basis coefficients - float3 normal = material.TangentNormal; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - normal *= material.TangentNormal; -#endif - return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; -} - -#endif - #if USE_DITHERED_LOD_TRANSITION void ClipLODTransition(PixelInput input) @@ -1081,3 +757,5 @@ float4 PS_MotionVectors(PixelInput input) : SV_Target0 return float4(0, 0, 0, 1); #endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader index 7a97e5e5a..9ce0a5f5a 100644 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ b/Content/Editor/MaterialTemplates/SurfaceForward.shader @@ -29,7 +29,6 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float4 LightmapArea; float3 WorldInvScale; float WorldDeterminantSign; float2 Dummy0; @@ -319,6 +318,8 @@ float4 GetVertexColor(MaterialInput input) #endif } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -387,12 +388,11 @@ VertexOutput VS(ModelInput input) #if USE_VERTEX_COLOR output.VertexColor = input.Color; #endif + output.LightmapUV = input.LightmapUV; #if USE_INSTANCING - output.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; output.InstanceOrigin = world[3].xyz; output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); #else - output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; output.InstanceOrigin = WorldMatrix[3].xyz; output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); #endif @@ -576,256 +576,6 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) #endif -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = input.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 tessellationMultipliers; - tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = tessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].TBN[2]; - float3 n2 = input[1].TBN[2]; - float3 n3 = input[2].TBN[2]; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(TBN); - COPY(InstanceOrigin); - COPY(InstanceParams); - COPY(TessellationMultiplier); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#else - INTERPOLATE(WorldPosition); -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(TBN[0]); - INTERPOLATE(TBN[1]); - INTERPOLATE(TBN[2]); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating normal can unnormalize it, so normalize it - output.TBN[0] = normalize(output.TBN[0]); - output.TBN[1] = normalize(output.TBN[1]); - output.TBN[2] = normalize(output.TBN[2]); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].TBN[2], input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].TBN[2], input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].TBN[2], input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - #if USE_DITHERED_LOD_TRANSITION void ClipLODTransition(PixelInput input) @@ -1009,3 +759,5 @@ void PS_Depth(PixelInput input OutColor = 0; #endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index 03a2081bf..0a6cb8028 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -39,15 +39,11 @@ float2 OffsetUV; float2 Dummy0; @1META_CB_END -#if CAN_USE_LIGHTMAP - // Irradiance and directionality prebaked lightmaps Texture2D Lightmap0 : register(t0); Texture2D Lightmap1 : register(t1); Texture2D Lightmap2 : register(t2); -#endif - // Terrain data Texture2D Heightmap : register(t3); Texture2D Splatmap0 : register(t4); @@ -216,6 +212,22 @@ float4 GetVertexColor(MaterialInput input) return 1; } +// Evaluates the H-Basis coefficients in the tangent space normal direction +float3 GetHBasisIrradiance(float3 n, float3 h0, float3 h1, float3 h2, float3 h3) +{ + // Band 0 + float3 color = h0 * (1.0f / sqrt(2.0f * PI)); + + // Band 1 + color += h1 * -sqrt(1.5f / PI) * n.y; + color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); + color += h3 * -sqrt(1.5f / PI) * n.x; + + return color; +} + +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -781,3 +793,5 @@ void PS_Depth(PixelInput input OutColor = 0; #endif } + +@9 diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 571e46243..966253aba 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -458,7 +458,6 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrain && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); - options.Macros.Add({ "CAN_USE_LIGHTMAP", Numbers[isSurfaceOrTerrain ? 1 : 0] }); #endif } diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index c5166a088..27e9c7b38 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -44,7 +44,7 @@ void DecalMaterialShader::Bind(BindParameters& params) // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(DecalMaterialShaderData) : nullptr; + bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(DecalMaterialShaderData) : nullptr; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = true; diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 5a460079d..322726a3e 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "DeferredMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderView.h" @@ -28,7 +29,6 @@ PACK_STRUCT(struct DeferredMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; Vector2 Dummy0; @@ -63,11 +63,21 @@ void DeferredMaterialShader::Bind(BindParameters& params) auto& drawCall = *params.FirstDrawCall; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0 && cb0->GetSize() != 0; + ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing + byte* cb = _cb0Data.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DeferredMaterialShaderData); + int32 srv = 0; + + // Setup features + if (_info.TessellationMode != TessellationMethod::None) + TessellationFeature::Bind(params, cb, srv); + const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(DeferredMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; @@ -75,7 +85,6 @@ void DeferredMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Setup material constants data - auto materialData = reinterpret_cast(_cb0Data.Get()); if (hasCb0) { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); @@ -107,23 +116,6 @@ void DeferredMaterialShader::Bind(BindParameters& params) materialData->TemporalAAJitter = view.TemporalAAJitter; materialData->GeometrySize = drawCall.Surface.GeometrySize; } - const bool useLightmap = view.Flags & ViewFlags::GI -#if USE_EDITOR - && EnableLightmapsUsage -#endif - && drawCall.Surface.Lightmap != nullptr; - if (useLightmap) - { - // Bind lightmap textures - GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Surface.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); - - // Set lightmap data - materialData->LightmapArea = drawCall.Surface.LightmapUVsArea; - } // Check if is using mesh skinning const bool useSkinning = drawCall.Surface.Skinning != nullptr; diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 39e39a7d4..7dc68a85a 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ForwardMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" #include "Engine/Graphics/GPULimits.h" @@ -29,7 +30,6 @@ PACK_STRUCT(struct ForwardMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; Vector2 Dummy0; @@ -70,13 +70,22 @@ void ForwardMaterialShader::Bind(BindParameters& params) auto& drawCall = *params.FirstDrawCall; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0 && cb0->GetSize() != 0; + ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing const auto cb1 = _shader->GetCB(1); const bool hasCb1 = cb1 && cb1->GetSize() != 0; + byte* cb = _cb0Data.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(ForwardMaterialShaderData); + int32 srv = 0; + + // Setup features + if (_info.TessellationMode != TessellationMethod::None) + TessellationFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(ForwardMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; // forward pass materials cannot sample scene color for now bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; @@ -93,7 +102,6 @@ void ForwardMaterialShader::Bind(BindParameters& params) } // Setup material constants data - const auto materialData = reinterpret_cast(_cb0Data.Get()); if (hasCb0) { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 4b079dd54..23b327a1b 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -36,7 +36,7 @@ void GUIMaterialShader::Bind(BindParameters& params) // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(GUIMaterialShaderData) : nullptr; + bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(GUIMaterialShaderData) : nullptr; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index f1e7856af..e85328133 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -29,7 +29,6 @@ public: /// /// Determines whether material is a surface shader. /// - /// true if material is surface shader; otherwise, false. FORCE_INLINE bool IsSurface() const { return GetInfo().Domain == MaterialDomain::Surface; @@ -38,7 +37,6 @@ public: /// /// Determines whether material is a post fx. /// - /// true if material is post fx; otherwise, false. FORCE_INLINE bool IsPostFx() const { return GetInfo().Domain == MaterialDomain::PostProcess; @@ -47,7 +45,6 @@ public: /// /// Determines whether material is a decal. /// - /// true if material is decal; otherwise, false. FORCE_INLINE bool IsDecal() const { return GetInfo().Domain == MaterialDomain::Decal; @@ -56,7 +53,6 @@ public: /// /// Determines whether material is a GUI shader. /// - /// true if material is GUI shader; otherwise, false. FORCE_INLINE bool IsGUI() const { return GetInfo().Domain == MaterialDomain::GUI; @@ -65,7 +61,6 @@ public: /// /// Determines whether material is a terrain shader. /// - /// true if material is terrain shader; otherwise, false. FORCE_INLINE bool IsTerrain() const { return GetInfo().Domain == MaterialDomain::Terrain; @@ -74,7 +69,6 @@ public: /// /// Determines whether material is a particle shader. /// - /// true if material is particle shader; otherwise, false. FORCE_INLINE bool IsParticle() const { return GetInfo().Domain == MaterialDomain::Particle; diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 1f1d1bac6..ea70d4587 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -230,36 +230,28 @@ void MaterialParameter::Bind(BindMeta& meta) const switch (_type) { case MaterialParameterType::Bool: - if (meta.Buffer0) - *((int32*)(meta.Buffer0 + _offset)) = _asBool; + *((int32*)(meta.Constants + _offset)) = _asBool; break; case MaterialParameterType::Integer: - if (meta.Buffer0) - *((int32*)(meta.Buffer0 + _offset)) = _asInteger; + *((int32*)(meta.Constants + _offset)) = _asInteger; break; case MaterialParameterType::Float: - if (meta.Buffer0) - *((float*)(meta.Buffer0 + _offset)) = _asFloat; + *((float*)(meta.Constants + _offset)) = _asFloat; break; case MaterialParameterType::Vector2: - if (meta.Buffer0) - *((Vector2*)(meta.Buffer0 + _offset)) = _asVector2; + *((Vector2*)(meta.Constants + _offset)) = _asVector2; break; case MaterialParameterType::Vector3: - if (meta.Buffer0) - *((Vector3*)(meta.Buffer0 + _offset)) = _asVector3; + *((Vector3*)(meta.Constants + _offset)) = _asVector3; break; case MaterialParameterType::Vector4: - if (meta.Buffer0) - *((Vector4*)(meta.Buffer0 + _offset)) = _asVector4; + *((Vector4*)(meta.Constants + _offset)) = _asVector4; break; case MaterialParameterType::Color: - if (meta.Buffer0) - *((Color*)(meta.Buffer0 + _offset)) = _asColor; + *((Color*)(meta.Constants + _offset)) = _asColor; break; case MaterialParameterType::Matrix: - if (meta.Buffer0) - Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Buffer0 + _offset)); + Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Constants + _offset)); break; case MaterialParameterType::NormalMap: { @@ -336,11 +328,10 @@ void MaterialParameter::Bind(BindMeta& meta) const break; } case MaterialParameterType::ChannelMask: - if (meta.Buffer0) - *((Vector4*)(meta.Buffer0 + _offset)) = Vector4(_asInteger == 0, _asInteger == 1, _asInteger == 2, _asInteger == 3); + *((Vector4*)(meta.Constants + _offset)) = Vector4(_asInteger == 0, _asInteger == 1, _asInteger == 2, _asInteger == 3); break; case MaterialParameterType::GameplayGlobal: - if (meta.Buffer0 && _asAsset) + if (_asAsset) { const auto e = _asAsset.As()->Variables.TryGet(_name); if (e) @@ -348,26 +339,26 @@ void MaterialParameter::Bind(BindMeta& meta) const switch (e->Value.Type.Type) { case VariantType::Bool: - *((bool*)(meta.Buffer0 + _offset)) = e->Value.AsBool; + *((bool*)(meta.Constants + _offset)) = e->Value.AsBool; break; case VariantType::Int: - *((int32*)(meta.Buffer0 + _offset)) = e->Value.AsInt; + *((int32*)(meta.Constants + _offset)) = e->Value.AsInt; break; case VariantType::Uint: - *((uint32*)(meta.Buffer0 + _offset)) = e->Value.AsUint; + *((uint32*)(meta.Constants + _offset)) = e->Value.AsUint; break; case VariantType::Float: - *((float*)(meta.Buffer0 + _offset)) = e->Value.AsFloat; + *((float*)(meta.Constants + _offset)) = e->Value.AsFloat; break; case VariantType::Vector2: - *((Vector2*)(meta.Buffer0 + _offset)) = e->Value.AsVector2(); + *((Vector2*)(meta.Constants + _offset)) = e->Value.AsVector2(); break; case VariantType::Vector3: - *((Vector3*)(meta.Buffer0 + _offset)) = e->Value.AsVector3(); + *((Vector3*)(meta.Constants + _offset)) = e->Value.AsVector3(); break; case VariantType::Vector4: case VariantType::Color: - *((Vector4*)(meta.Buffer0 + _offset)) = e->Value.AsVector4(); + *((Vector4*)(meta.Constants + _offset)) = e->Value.AsVector4(); break; default: ; } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index 997b18d34..ca48dc143 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -309,9 +309,9 @@ public: GPUContext* Context; /// - /// The pointer to the first constants buffer in memory. + /// The pointer to the constants buffer in the memory. /// - byte* Buffer0; + byte* Constants; /// /// The input scene color. It's optional and used in forward/postFx rendering. diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp new file mode 100644 index 000000000..a07e740f8 --- /dev/null +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -0,0 +1,172 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "MaterialShaderFeatures.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Renderer/ShadowsPass.h" +#if USE_EDITOR +#include "Engine/Renderer/Lightmaps.h" +#endif +#include "Engine/Level/Scene/Lightmap.h" +#include "Engine/Level/Actors/EnvironmentProbe.h" + +void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) +{ + auto context = params.GPUContext; + auto cache = params.RenderContext.List; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + auto& data = *(Data*)cb; + const int32 envProbeShaderRegisterIndex = srv + 0; + const int32 skyLightShaderRegisterIndex = srv + 1; + const int32 dirLightShaderRegisterIndex = srv + 2; + + // Set fog input + if (cache->Fog) + { + cache->Fog->GetExponentialHeightFogData(view, data.ExponentialHeightFog); + } + else + { + data.ExponentialHeightFog.FogMinOpacity = 1.0f; + data.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; + } + + // Set directional light input + if (cache->DirectionalLights.HasItems()) + { + const auto& dirLight = cache->DirectionalLights.First(); + const auto shadowPass = ShadowsPass::Instance(); + const bool useShadow = shadowPass->LastDirLightIndex == 0; + if (useShadow) + { + data.DirectionalLightShadow = shadowPass->LastDirLight; + context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); + } + else + { + context->UnBindSR(dirLightShaderRegisterIndex); + } + dirLight.SetupLightData(&data.DirectionalLight, view, useShadow); + } + else + { + data.DirectionalLight.Color = Vector3::Zero; + data.DirectionalLight.CastShadows = 0.0f; + context->UnBindSR(dirLightShaderRegisterIndex); + } + + // Set sky light + if (cache->SkyLights.HasItems()) + { + auto& skyLight = cache->SkyLights.First(); + skyLight.SetupLightData(&data.SkyLight, view, false); + const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; + context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); + } + else + { + Platform::MemoryClear(&data.SkyLight, sizeof(data.SkyLight)); + context->UnBindSR(skyLightShaderRegisterIndex); + } + + // Set reflection probe data + EnvironmentProbe* probe = nullptr; + // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it + for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) + { + const auto p = cache->EnvironmentProbes[i]; + if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + probe = p; + break; + } + } + if (probe && probe->GetProbe()) + { + probe->SetupProbeData(&data.EnvironmentProbe); + const auto texture = probe->GetProbe()->GetTexture(); + context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); + } + else + { + data.EnvironmentProbe.Data1 = Vector4::Zero; + context->UnBindSR(envProbeShaderRegisterIndex); + } + + // Set local lights + data.LocalLightsCount = 0; + for (int32 i = 0; i < cache->PointLights.Count(); i++) + { + const auto& light = cache->PointLights[i]; + if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + data.LocalLightsCount++; + if (data.LocalLightsCount == MaxLocalLights) + break; + } + } + for (int32 i = 0; i < cache->SpotLights.Count(); i++) + { + const auto& light = cache->SpotLights[i]; + if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + data.LocalLightsCount++; + if (data.LocalLightsCount == MaxLocalLights) + break; + } + } + + cb += sizeof(Data); + srv += SRVs; +} + +bool LightmapFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) +{ + auto context = params.GPUContext; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + auto& data = *(Data*)cb; + + const bool useLightmap = view.Flags & ViewFlags::GI +#if USE_EDITOR + && EnableLightmapsUsage +#endif + && drawCall.Surface.Lightmap != nullptr; + if (useLightmap) + { + // Bind lightmap textures + GPUTexture *lightmap0, *lightmap1, *lightmap2; + drawCall.Surface.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); + context->BindSR(0, lightmap0); + context->BindSR(1, lightmap1); + context->BindSR(2, lightmap2); + + // Set lightmap data + data.LightmapArea = drawCall.Surface.LightmapUVsArea; + } + + srv += SRVs; + cb += sizeof(Data); + return useLightmap; +} + +#if USE_EDITOR + +void TessellationFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Tessellation.hlsl"); + data.ConstantsSize = 0; + data.ResourcesCount = SRVs; +} + +void LightmapFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Lightmap.hlsl"); + data.ConstantsSize = sizeof(Data); + data.ResourcesCount = SRVs; +} + +#endif diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h new file mode 100644 index 000000000..0a2349dfe --- /dev/null +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "MaterialShader.h" +#include "Engine/Core/Math/Rectangle.h" + +// Material shader features are plugin-based functionalities that are reusable between different material domains. +struct MaterialShaderFeature +{ +#if USE_EDITOR + struct GeneratorData + { + const Char* Template; + int32 ConstantsSize; + int32 ResourcesCount; + }; +#endif; +}; + +// Material shader feature that add support for Forward shading inside the material shader. +struct ForwardShadingFeature : MaterialShaderFeature +{ + enum { MaxLocalLights = 4 }; + + enum { SRVs = 3 }; + + PACK_STRUCT(struct Data + { + LightData DirectionalLight; + LightShadowData DirectionalLightShadow; + LightData SkyLight; + ProbeData EnvironmentProbe; + ExponentialHeightFogData ExponentialHeightFog; + Vector3 Dummy2; + uint32 LocalLightsCount; + LightData LocalLights[MaxLocalLights]; + }); + + static void Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +}; + +// Material shader feature that adds geometry hardware tessellation (using Hull and Domain shaders). +struct TessellationFeature : MaterialShaderFeature +{ + enum { SRVs = 0 }; + + static void Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) + { + } +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds lightmap sampling feature. +struct LightmapFeature : MaterialShaderFeature +{ + enum { SRVs = 3 }; + + PACK_STRUCT(struct Data + { + Rectangle LightmapArea; + }); + + static bool Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 210c81e7d..5cfad0126 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -81,7 +81,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(ParticleMaterialShaderData) : nullptr; + bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(ParticleMaterialShaderData) : nullptr; bindMeta.Input = nullptr; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; @@ -105,7 +105,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) auto name = StringView(param.GetName().Get() + 9); const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); - *((int32*)(bindMeta.Buffer0 + param.GetBindOffset())) = offset; + *((int32*)(bindMeta.Constants + param.GetBindOffset())) = offset; } } } diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 5c2fb1df0..2d9840103 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -33,7 +33,7 @@ void PostFxMaterialShader::Bind(BindParameters& params) // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(PostFxMaterialShaderData) : nullptr; + bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(PostFxMaterialShaderData) : nullptr; bindMeta.Input = params.Input; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = true; diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 37f932a5a..68c5fbd20 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -60,7 +60,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(TerrainMaterialShaderData) : nullptr; + bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(TerrainMaterialShaderData) : nullptr; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp index befd5b25d..cd16a9c52 100644 --- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp +++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp @@ -155,7 +155,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCB ? _cbData.Get() + sizeof(GPUParticlesData) : nullptr; + bindMeta.Constants = hasCB ? _cbData.Get() + sizeof(GPUParticlesData) : nullptr; bindMeta.Input = nullptr; if (viewTask) { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp index 90103c165..32af18364 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp @@ -163,27 +163,4 @@ void MaterialGenerator::linearizeSceneDepth(Node* caller, const Value& depth, Va value = writeLocal(VariantType::Float, String::Format(TEXT("ViewInfo.w / ({0}.x - ViewInfo.z)"), depth.Value), caller); } -byte MaterialGenerator::getStartSrvRegister(MaterialLayer* baseLayer) -{ - // Note: this must match material templates - switch (baseLayer->Domain) - { - case MaterialDomain::Surface: - return baseLayer->BlendMode == MaterialBlendMode::Transparent ? 3 : 3; - case MaterialDomain::PostProcess: - return 0; - case MaterialDomain::Decal: - return 1; - case MaterialDomain::GUI: - return 0; - case MaterialDomain::Terrain: - return 6; - case MaterialDomain::Particle: - return 5; - default: - CRASH; - return 0; - } -} - #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 7abd29af9..61c05149d 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -4,7 +4,9 @@ #include "MaterialGenerator.h" #include "Engine/Visject/ShaderGraphUtilities.h" +#include "Engine/Platform/File.h" #include "Engine/Graphics/Materials/MaterialShader.h" +#include "Engine/Graphics/Materials/MaterialShaderFeatures.h" /// /// Material shader source code template has special marks for generated code. @@ -20,10 +22,86 @@ enum MaterialTemplateInputsMapping In_GetMaterialVS = 5, In_GetMaterialDS = 6, In_Includes = 7, + In_Utilities = 8, + In_Shaders = 9, In_MAX }; +/// +/// Material shader feature source code template has special marks for generated code. Each starts with '@' char and index of the mapped string. +/// +enum class FeatureTemplateInputsMapping +{ + Defines = 0, + Includes = 1, + Constants = 2, + Resources = 3, + Utilities = 4, + Shaders = 5, + MAX +}; + +struct FeatureData +{ + MaterialShaderFeature::GeneratorData Data; + String Inputs[(int32)FeatureTemplateInputsMapping::MAX]; + + bool Init(); +}; + +namespace +{ + // Loaded and parsed features data cache + Dictionary Features; +} + +bool FeatureData::Init() +{ + // Load template file + const String path = Globals::EngineContentFolder / TEXT("Editor/MaterialTemplates/Features/") + Data.Template; + String contents; + if (File::ReadAllText(path, contents)) + { + LOG(Error, "Cannot open file {0}", path); + return true; + } + + int32 i = 0; + const int32 length = contents.Length(); + + // Skip until input start + for (; i < length; i++) + { + if (contents[i] == '@') + break; + } + + // Load all inputs + do + { + // Parse input type + i++; + const int32 inIndex = contents[i++] - '0'; + ASSERT_LOW_LAYER(Math::IsInRange(inIndex, 0, (int32)FeatureTemplateInputsMapping::MAX - 1)); + + // Read until next input start + const Char* start = &contents[i]; + for (; i < length; i++) + { + const auto c = contents[i]; + if (c == '@') + break; + } + const Char* end = &contents[i]; + + // Set input + Inputs[inIndex].Set(start, (int32)(end - start)); + } while (i < length); + + return false; +} + MaterialValue MaterialGenerator::getUVs(VariantType::Vector2, TEXT("input.TexCoord")); MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam")); MaterialValue MaterialGenerator::getNormal(VariantType::Vector3, TEXT("input.TBN[2]")); @@ -53,6 +131,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ASSERT_LOW_LAYER(_layers.Count() > 0); String inputs[In_MAX]; + Array> features; // Setup and prepare layers _writer.Clear(); @@ -87,6 +166,32 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo const MaterialGraphBox* layerInputBox = baseLayer->Root->GetBox(0); const bool isLayered = layerInputBox->HasConnection(); + // Initialize features +#define ADD_FEATURE(type) \ + { \ + StringAnsiView typeName(#type, ARRAY_COUNT(#type) - 1); \ + features.Add(typeName); \ + if (!Features.ContainsKey(typeName)) \ + { \ + auto& feature = Features[typeName]; \ + type::Generate(feature.Data); \ + if (feature.Init()) \ + return true; \ + } \ + } + switch (baseLayer->Domain) + { + case MaterialDomain::Surface: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(LightmapFeature); + break; + default: + break; + } +#undef ADD_FEATURE + // Check if material is using special features and update the metadata flags if (!isLayered) { @@ -240,11 +345,13 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; +#define WRITE_FEATURES(input) for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); // Defines { _writer.Write(TEXT("#define MATERIAL_MASK_THRESHOLD ({0})\n"), baseLayer->MaskThreshold); _writer.Write(TEXT("#define CUSTOM_VERTEX_INTERPOLATORS_COUNT ({0})\n"), _vsToPsInterpolants.Count()); - _writer.Write(TEXT("#define MATERIAL_OPACITY_THRESHOLD ({0})"), baseLayer->OpacityThreshold); + _writer.Write(TEXT("#define MATERIAL_OPACITY_THRESHOLD ({0})\n"), baseLayer->OpacityThreshold); + WRITE_FEATURES(Defines); inputs[In_Defines] = _writer.ToString(); _writer.Clear(); } @@ -252,31 +359,87 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Includes { for (auto& include : _includes) - { _writer.Write(TEXT("#include \"{0}\"\n"), include.Item); - } + WRITE_FEATURES(Includes); inputs[In_Includes] = _writer.ToString(); _writer.Clear(); } - // Check if material is using any parameters - if (_parameters.HasItems()) + // Constants { - ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); + WRITE_FEATURES(Constants); + if (_parameters.HasItems()) + ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); inputs[In_Constants] = _writer.ToString(); _writer.Clear(); + } - const int32 startRegister = getStartSrvRegister(baseLayer); - const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, startRegister); - if (error) + // Resources + { + int32 srv = 0; + switch (baseLayer->Domain) { - OnError(nullptr, nullptr, error); - return true; + case MaterialDomain::Surface: + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + srv = 3; // Forward shading resources + break; + case MaterialDomain::Decal: + srv = 1; + break; + case MaterialDomain::Terrain: + srv = 6; + break; + case MaterialDomain::Particle: + srv = 5; + break; + } + for (auto f : features) + { + const auto& text = Features[f].Inputs[(int32)FeatureTemplateInputsMapping::Resources]; + const Char* str = text.Get(); + int32 prevIdx = 0, idx = 0; + while (true) + { + idx = text.Find(TEXT("__SRV__"), StringSearchCase::CaseSensitive, prevIdx); + if (idx == -1) + break; + int32 len = idx - prevIdx; + _writer.Write(StringView(str, len)); + str += len; + _writer.Write(StringUtils::ToString(srv)); + srv++; + str += ARRAY_COUNT("__SRV__") - 1; + prevIdx = idx + ARRAY_COUNT("__SRV__") - 1; + } + _writer.Write(StringView(str)); + } + if (_parameters.HasItems()) + { + const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, srv); + if (error) + { + OnError(nullptr, nullptr, error); + return true; + } } inputs[In_ShaderResources] = _writer.ToString(); _writer.Clear(); } + // Utilities + { + WRITE_FEATURES(Utilities); + inputs[In_Utilities] = _writer.ToString(); + _writer.Clear(); + } + + // Shaders + { + WRITE_FEATURES(Shaders); + inputs[In_Shaders] = _writer.ToString(); + _writer.Clear(); + } + // Save material parameters data if (_parameters.HasItems()) MaterialParams::Save(parametersData, &_parameters); @@ -315,16 +478,15 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo LOG(Warning, "Unknown material domain."); return true; } - auto file = FileReadStream::Open(path); if (file == nullptr) { - LOG(Warning, "Cannot load material base source code."); + LOG(Error, "Cannot open file {0}", path); return true; } // Format template - uint32 length = file->GetLength(); + const uint32 length = file->GetLength(); Array tmp; for (uint32 i = 0; i < length; i++) { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 005b9c58c..9ae85cac8 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -209,8 +209,6 @@ public: static MaterialGraphBoxesMapping MaterialGraphBoxesMappings[]; static const MaterialGraphBoxesMapping& GetMaterialRootNodeBox(MaterialGraphBoxes box); - - static byte getStartSrvRegister(MaterialLayer* baseLayer); }; #endif diff --git a/Source/Engine/Utilities/TextWriter.h b/Source/Engine/Utilities/TextWriter.h index d2702bf3c..54f2e996a 100644 --- a/Source/Engine/Utilities/TextWriter.h +++ b/Source/Engine/Utilities/TextWriter.h @@ -4,6 +4,8 @@ #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Formatting.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Serialization/MemoryWriteStream.h" /// @@ -97,6 +99,24 @@ public: WriteLine(); } + /// + /// Write text to the buffer + /// + /// Data + void Write(const StringViewBase& text) + { + _buffer.WriteBytes((void*)text.Get(), text.Length() * sizeof(CharType)); + } + + /// + /// Write text to the buffer + /// + /// Data + void Write(const StringBase& text) + { + _buffer.WriteBytes((void*)text.Get(), text.Length() * sizeof(CharType)); + } + /// /// Write text to the buffer /// diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index c84a500e4..079b6e602 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -177,10 +177,6 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri } } } - - if (startRegister != registerIndex) - writer.WriteLine(); - return nullptr; } diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 8f47124dc..aef3fc579 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -173,24 +173,4 @@ float3 AOMultiBounce(float visibility, float3 albedo) return max(visibility, ((visibility * a + b) * visibility + c) * visibility); } -#if CAN_USE_LIGHTMAP - -// Evaluates the H-Basis coefficients in the tangent space normal direction -float3 GetHBasisIrradiance(in float3 n, in float3 h0, in float3 h1, in float3 h2, in float3 h3) -{ - float3 color = 0.0f; - - // Band 0 - color += h0 * (1.0f / sqrt(2.0f * PI)); - - // Band 1 - color += h1 * -sqrt(1.5f / PI) * n.y; - color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); - color += h3 * -sqrt(1.5f / PI) * n.x; - - return color; -} - -#endif - #endif From 9e6243adcc1587a833182c1a7024ab755f8c19fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 4 Feb 2021 14:58:01 +0100 Subject: [PATCH 156/222] Refactor material shaders generator to use modular features as extensions --- Content/Editor/MaterialTemplates/Decal.shader | 3 - .../Features/Tessellation.hlsl | 159 +------ Content/Editor/MaterialTemplates/GUI.shader | 3 - .../Editor/MaterialTemplates/Particle.shader | 3 - .../MaterialTemplates/PostProcess.shader | 3 - .../MaterialTemplates/SurfaceDeferred.shader | 224 +++++---- .../MaterialTemplates/SurfaceForward.shader | 28 -- .../Editor/MaterialTemplates/Terrain.shader | 431 ++++-------------- .../Materials/DeferredMaterialShader.cpp | 13 +- .../Materials/MaterialShaderFeatures.cpp | 10 +- .../Materials/TerrainMaterialShader.cpp | 80 ++-- Source/Engine/Renderer/DrawCall.h | 12 +- .../MaterialGenerator/MaterialGenerator.cpp | 7 +- Source/Shaders/MaterialCommon.hlsl | 9 +- 14 files changed, 308 insertions(+), 677 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index 35446b7e2..452937955 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -116,9 +116,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Input macro specified by the material: DECAL_BLEND_MODE #define DECAL_BLEND_MODE_TRANSLUCENT 0 diff --git a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl index c5ebbba92..f2fc714ee 100644 --- a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl +++ b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. @0// Tessellation: Defines +#define TessalationProjectOntoPlane(planeNormal, planePosition, pointToProject) pointToProject - dot(pointToProject - planePosition, planeNormal) * planeNormal @1// Tessellation: Includes @2// Tessellation: Constants @3// Tessellation: Resources @@ -11,22 +12,10 @@ // Interpolants passed from the hull shader to the domain shader struct TessalationHSToDS { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; #endif float TessellationMultiplier : TESS; }; @@ -34,41 +23,19 @@ struct TessalationHSToDS // Interpolants passed from the domain shader and to the pixel shader struct TessalationDSToPS { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; #endif }; MaterialInput GetMaterialInput(TessalationDSToPS input) { MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; result.SvPosition = input.Position; + GetGeometryMaterialInput(result, input.Geometry); + result.TwoSidedSign = WorldDeterminantSign; #if USE_CUSTOM_VERTEX_INTERPOLATORS result.CustomVSToPS = input.CustomVSToPS; #endif @@ -78,7 +45,7 @@ MaterialInput GetMaterialInput(TessalationDSToPS input) struct TessalationPatch { float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; + float InsideTessFactor : SV_InsideTessFactor; #if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN float3 B210 : POSITION4; float3 B120 : POSITION5; @@ -110,20 +77,19 @@ TessalationPatch HS_PatchConstant(InputPatch input) #if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN // Calculate PN Triangle control points // Reference: [Vlachos 2001] - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; + float3 p1 = input[0].Geometry.WorldPosition; + float3 p2 = input[1].Geometry.WorldPosition; + float3 p3 = input[2].Geometry.WorldPosition; + float3 n1 = input[0].Geometry.WorldNormal; + float3 n2 = input[1].Geometry.WorldNormal; + float3 n3 = input[2].Geometry.WorldNormal; output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; + float3 e = (output.B210 + output.B120 + output.B021 + output.B012 + output.B102 + output.B201) / 6.0f; float3 v = (p1 + p2 + p3) / 3.0f; output.B111 = e + ((e - v) / 2.0f); #endif @@ -132,8 +98,6 @@ TessalationPatch HS_PatchConstant(InputPatch input) } META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) [domain("tri")] [partitioning("fractional_odd")] @@ -148,19 +112,7 @@ TessalationHSToDS HS(InputPatch in // Pass through shader #define COPY(thing) output.thing = input[ControlPointID].thing; COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(WorldNormal); - COPY(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if IS_MOTION_VECTORS_PASS - COPY(PrevWorldPosition); -#endif + COPY(Geometry); COPY(TessellationMultiplier); #if USE_CUSTOM_VERTEX_INTERPOLATORS COPY(CustomVSToPS); @@ -170,19 +122,7 @@ TessalationHSToDS HS(InputPatch in return output; } -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePosition, float3 pointToProject) -{ - return pointToProject - dot(pointToProject - planePosition, planeNormal) * planeNormal; -} - -#endif - META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) [domain("tri")] TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) { @@ -194,22 +134,18 @@ TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : S float W = barycentricCoords.z; // Interpolate patch attributes to generated vertices + output.Geometry = InterpolateGeometry(input[0].Geometry, U, input[1].Geometry, V, input[2].Geometry, W); #define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing INTERPOLATE(Position); #if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + // Interpolate using barycentric coordinates and PN Triangle control points float UU = U * U; float VV = V * V; float WW = W * W; float UU3 = UU * 3.0f; float VV3 = VV * 3.0f; float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + + float3 offset = constantData.B210 * UU3 * V + constantData.B120 * VV3 * U + constantData.B021 * VV3 * W + @@ -217,76 +153,31 @@ TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : S constantData.B102 * WW3 * U + constantData.B201 * UU3 * W + constantData.B111 * 6.0f * W * U * V; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = - input[0].PrevWorldPosition * UU * U + - input[1].PrevWorldPosition * VV * V + - input[2].PrevWorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#endif + InterpolateGeometryPositions(output.Geometry, input[0].Geometry, UU * U, input[1].Geometry, VV * V, input[2].Geometry, WW * W, offset); #else - INTERPOLATE(WorldPosition); -#if IS_MOTION_VECTORS_PASS - INTERPOLATE(PrevWorldPosition); + InterpolateGeometryPositions(output.Geometry, input[0].Geometry, U, input[1].Geometry, V, input[2].Geometry, W, float3(0, 0, 0)); #endif -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(WorldNormal); - INTERPOLATE(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); #if USE_CUSTOM_VERTEX_INTERPOLATORS UNROLL for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { INTERPOLATE(CustomVSToPS[i]); - } #endif #undef INTERPOLATE -#undef COPY - - // Interpolating tangents can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); #if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#if IS_MOTION_VECTORS_PASS - posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].PrevWorldPosition, output.PrevWorldPosition); - posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].PrevWorldPosition, output.PrevWorldPosition); - posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].PrevWorldPosition, output.PrevWorldPosition); - output.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif + // Orthogonal projection in the tangent planes with interpolation + ApplyGeometryPositionsPhongTess(output.Geometry, input[0].Geometry, input[1].Geometry, input[2].Geometry, U, V, W); #endif // Perform displacement mapping #if USE_DISPLACEMENT MaterialInput materialInput = GetMaterialInput(output); Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition += material.WorldDisplacement; -#endif + OffsetGeometryPositions(output.Geometry, material.WorldDisplacement); #endif // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); return output; } diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index 08fddde79..b927755ad 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -199,9 +199,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Vertex Shader function for GUI materials rendering META_VS(true, FEATURE_LEVEL_ES2) META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index e8d00d1db..f3121b6b0 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -332,9 +332,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates the transform matrix from mesh tangent space to local space half3x3 CalcTangentToLocal(ModelInput input) { diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader index a3e5e42c7..681a04248 100644 --- a/Content/Editor/MaterialTemplates/PostProcess.shader +++ b/Content/Editor/MaterialTemplates/PostProcess.shader @@ -136,9 +136,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Pixel Shader function for PostFx materials rendering META_PS(true, FEATURE_LEVEL_ES2) float4 PS_PostFx(PixelInput input) : SV_Target0 diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader index 303cb2fee..dcee96822 100644 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader @@ -32,25 +32,30 @@ float Dummy1; // Material shader resources @2 + +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD3; + float4 WorldTangent : TEXCOORD4; + float3 InstanceOrigin : TEXCOORD6; + float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor + float3 PrevWorldPosition : TEXCOORD8; +}; + // Interpolants passed from the vertex shader struct VertexOutput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; #endif #if USE_TESSELLATION float TessellationMultiplier : TESS; @@ -60,24 +65,12 @@ struct VertexOutput // Interpolants passed to the pixel shader struct PixelInput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif - bool IsFrontFace : SV_IsFrontFace; + bool IsFrontFace : SV_IsFrontFace; }; // Material properties generation input @@ -108,24 +101,75 @@ struct MaterialInput #endif }; -float3x3 CalcTangentBasis(float3 normal, float4 tangent) +// Extracts geometry data to the material input +void GetGeometryMaterialInput(inout MaterialInput result, in GeometryData geometry) { - float3 bitangent = cross(normal, tangent.xyz) * tangent.w; - return float3x3(tangent.xyz, bitangent, normal); + result.WorldPosition = geometry.WorldPosition; + result.TexCoord = geometry.TexCoord; +#if USE_LIGHTMAP + result.LightmapUV = geometry.LightmapUV; +#endif +#if USE_VERTEX_COLOR + result.VertexColor = geometry.VertexColor; +#endif + result.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + result.InstanceOrigin = geometry.InstanceOrigin; + result.InstanceParams = geometry.InstanceParams; } +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset; output.PrevWorldPosition = p0.PrevWorldPosition * w0 + p1.PrevWorldPosition * w1 + p2.PrevWorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset; geometry.PrevWorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; \ + posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.PrevWorldPosition, geometry.PrevWorldPosition); \ + posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.PrevWorldPosition, geometry.PrevWorldPosition); \ + posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.PrevWorldPosition, geometry.PrevWorldPosition); \ + geometry.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +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; +#if USE_LIGHTMAP + output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2; +#endif +#if USE_VERTEX_COLOR + output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; +#endif + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent = p0.WorldTangent * w0 + p1.WorldTangent * w1 + p2.WorldTangent * w2; + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + output.InstanceOrigin = p0.InstanceOrigin; + output.InstanceParams = p0.InstanceParams; + return output; +} + +#endif + MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) { MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; + result.WorldPosition = output.Geometry.WorldPosition; + result.TexCoord = output.Geometry.TexCoord; #if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; + result.LightmapUV = output.Geometry.LightmapUV; #endif #if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; + result.VertexColor = output.Geometry.VertexColor; #endif - result.TBN = CalcTangentBasis(output.WorldNormal, output.WorldTangent); + result.TBN = CalcTangentBasis(output.Geometry.WorldNormal, output.Geometry.WorldTangent); result.TwoSidedSign = WorldDeterminantSign; result.SvPosition = output.Position; result.PreSkinnedPosition = input.Position.xyz; @@ -146,15 +190,15 @@ MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 loc MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) { MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; + result.WorldPosition = output.Geometry.WorldPosition; + result.TexCoord = output.Geometry.TexCoord; #if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; + result.LightmapUV = output.Geometry.LightmapUV; #endif #if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; + result.VertexColor = output.Geometry.VertexColor; #endif - result.TBN = CalcTangentBasis(output.WorldNormal, output.WorldTangent); + result.TBN = CalcTangentBasis(output.Geometry.WorldNormal, output.Geometry.WorldTangent); result.TwoSidedSign = WorldDeterminantSign; result.InstanceOrigin = WorldMatrix[3].xyz; result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); @@ -167,18 +211,18 @@ MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 MaterialInput GetMaterialInput(PixelInput input) { MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; + result.WorldPosition = input.Geometry.WorldPosition; + result.TexCoord = input.Geometry.TexCoord; #if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; + result.LightmapUV = input.Geometry.LightmapUV; #endif #if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; + result.VertexColor = input.Geometry.VertexColor; #endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); + result.TBN = CalcTangentBasis(input.Geometry.WorldNormal, input.Geometry.WorldTangent); result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; + result.InstanceOrigin = input.Geometry.InstanceOrigin; + result.InstanceParams = input.Geometry.InstanceParams; result.SvPosition = input.Position; #if USE_CUSTOM_VERTEX_INTERPOLATORS result.CustomVSToPS = input.CustomVSToPS; @@ -330,9 +374,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates the transform matrix from mesh tangent space to local space float3x3 CalcTangentToLocal(ModelInput input) { @@ -353,7 +394,6 @@ float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) META_PERMUTATION_1(USE_INSTANCING=0) META_PERMUTATION_1(USE_INSTANCING=1) -META_PERMUTATION_2(USE_INSTANCING=0, IS_MOTION_VECTORS_PASS=1) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) @@ -371,34 +411,32 @@ VertexOutput VS(ModelInput input) // Compute world space vertex position float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; -#endif + output.Geometry.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; + output.Geometry.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes - output.TexCoord = input.TexCoord; + output.Geometry.TexCoord = input.TexCoord; #if USE_VERTEX_COLOR - output.VertexColor = input.Color; + output.Geometry.VertexColor = input.Color; #endif - output.InstanceOrigin = world[3].xyz; + output.Geometry.InstanceOrigin = world[3].xyz; #if USE_INSTANCING - output.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); + output.Geometry.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; + output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); #else - output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); + output.Geometry.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; + output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); #endif // Calculate tanget space to world space transformation matrix for unit vectors float3x3 tangentToLocal = CalcTangentToLocal(input); float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; - output.WorldTangent.xyz = tangentToWorld[0]; - output.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS @@ -408,8 +446,8 @@ VertexOutput VS(ModelInput input) // Apply world position offset per-vertex #if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); #endif // Get tessalation multiplier (per vertex) @@ -439,7 +477,7 @@ float4 VS_Depth(ModelInput_PosOnly input) : SV_Position { float4x4 world = GetInstanceTransform(input); float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz; - float4 position = mul(float4(worldPosition.xyz, 1), ViewProjectionMatrix); + float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); return position; } @@ -524,8 +562,7 @@ float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) // Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) META_PERMUTATION_1(USE_SKINNING=1) -META_PERMUTATION_2(USE_SKINNING=1, IS_MOTION_VECTORS_PASS=1) -META_PERMUTATION_3(USE_SKINNING=1, IS_MOTION_VECTORS_PASS=1, PER_BONE_MOTION_BLUR=1) +META_PERMUTATION_2(USE_SKINNING=1, PER_BONE_MOTION_BLUR=1) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) @@ -544,37 +581,35 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) // Compute world space vertex position float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(position, 1), world).xyz; -#if IS_MOTION_VECTORS_PASS + output.Geometry.WorldPosition = mul(float4(position, 1), world).xyz; #if PER_BONE_MOTION_BLUR float3 prevPosition = SkinPrevPosition(input); - output.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; + output.Geometry.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; #else - output.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; -#endif + output.Geometry.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; #endif // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes - output.TexCoord = input.TexCoord; + output.Geometry.TexCoord = input.TexCoord; #if USE_VERTEX_COLOR - output.VertexColor = float4(0, 0, 0, 1); + output.Geometry.VertexColor = float4(0, 0, 0, 1); #endif - output.LightmapUV = float2(0, 0); - output.InstanceOrigin = world[3].xyz; + output.Geometry.LightmapUV = float2(0, 0); + output.Geometry.InstanceOrigin = world[3].xyz; #if USE_INSTANCING - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); + output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); #else - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); + output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); #endif // Calculate tanget space to world space transformation matrix for unit vectors float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; - output.WorldTangent.xyz = tangentToWorld[0]; - output.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS @@ -584,8 +619,8 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) // Apply world position offset per-vertex #if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); #endif // Get tessalation multiplier (per vertex) @@ -721,10 +756,8 @@ void PS_Depth(PixelInput input // Pixel Shader function for Motion Vectors Pass META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) float4 PS_MotionVectors(PixelInput input) : SV_Target0 { -#if IS_MOTION_VECTORS_PASS // LOD masking ClipLODTransition(input); @@ -736,8 +769,8 @@ float4 PS_MotionVectors(PixelInput input) : SV_Target0 #endif // Calculate this and previosu frame pixel locations in clip space - float4 prevClipPos = mul(float4(input.PrevWorldPosition, 1), PrevViewProjectionMatrix); - float4 curClipPos = mul(float4(input.WorldPosition, 1), ViewProjectionMatrix); + float4 prevClipPos = mul(float4(input.Geometry.PrevWorldPosition, 1), PrevViewProjectionMatrix); + float4 curClipPos = mul(float4(input.Geometry.WorldPosition, 1), ViewProjectionMatrix); float2 prevHPos = prevClipPos.xy / prevClipPos.w; float2 curHPos = curClipPos.xy / curClipPos.w; @@ -753,9 +786,6 @@ float4 PS_MotionVectors(PixelInput input) : SV_Target0 // Calculate per-pixel motion vector return float4(vPosCur - vPosPrev, 0, 1); -#else - return float4(0, 0, 0, 1); -#endif } @9 diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader index 9ce0a5f5a..0e9c6db5f 100644 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ b/Content/Editor/MaterialTemplates/SurfaceForward.shader @@ -338,9 +338,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates the transform matrix from mesh tangent space to local space half3x3 CalcTangentToLocal(ModelInput input) { @@ -432,31 +429,6 @@ VertexOutput VS(ModelInput input) // The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) Buffer BoneMatrices : register(t0); -#if PER_BONE_MOTION_BLUR - -// The skeletal bones matrix buffer from the previous frame -Buffer PrevBoneMatrices : register(t1); - -float3x4 GetPrevBoneMatrix(int index) -{ - float4 a = PrevBoneMatrices[index * 3]; - float4 b = PrevBoneMatrices[index * 3 + 1]; - float4 c = PrevBoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -float3 SkinPrevPosition(ModelInput_Skinned input) -{ - float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); - return mul(boneMatrix, position); -} - -#endif - // Cached skinning data to avoid multiple calculation struct SkinningData { diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index 0a6cb8028..c36d7f0c5 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - // Enables/disables smooth terrain chunks LOD transitions (with morphing higher LOD near edges to the lower LOD in the neighbour) #define USE_SMOOTH_LOD_TRANSITION 1 @@ -26,7 +25,6 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float4 LightmapArea; float3 WorldInvScale; float WorldDeterminantSign; float PerInstanceRandom; @@ -39,31 +37,32 @@ float2 OffsetUV; float2 Dummy0; @1META_CB_END -// Irradiance and directionality prebaked lightmaps -Texture2D Lightmap0 : register(t0); -Texture2D Lightmap1 : register(t1); -Texture2D Lightmap2 : register(t2); - // Terrain data -Texture2D Heightmap : register(t3); -Texture2D Splatmap0 : register(t4); -Texture2D Splatmap1 : register(t5); +Texture2D Heightmap : register(t0); +Texture2D Splatmap0 : register(t1); +Texture2D Splatmap1 : register(t2); // Material shader resources @2 -// Interpolants passed from the vertex shader -struct VertexOutput +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; + float3 WorldNormal : TEXCOORD3; + float HolesMask : TEXCOORD4; #if USE_TERRAIN_LAYERS float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; #endif +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif @@ -75,19 +74,12 @@ struct VertexOutput // Interpolants passed to the pixel shader struct PixelInput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - bool IsFrontFace : SV_IsFrontFace; + bool IsFrontFace : SV_IsFrontFace; }; // Material properties generation input @@ -112,20 +104,69 @@ struct MaterialInput #endif }; +// Extracts geometry data to the material input +void GetGeometryMaterialInput(inout MaterialInput result, in GeometryData geometry) +{ + result.WorldPosition = geometry.WorldPosition; + result.TexCoord = geometry.TexCoord; +#if USE_LIGHTMAP + result.LightmapUV = geometry.LightmapUV; +#endif + result.TBN = CalcTangentBasisFromWorldNormal(geometry.WorldNormal); + result.HolesMask = geometry.HolesMask; +#if USE_TERRAIN_LAYERS + result.Layers = geometry.Layers; +#endif +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +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.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.HolesMask = p0.HolesMask * w0 + p1.HolesMask * w1 + p2.HolesMask * w2; +#if USE_TERRAIN_LAYERS + UNROLL + for (int i = 0; i < TERRAIN_LAYERS_DATA_SIZE; i++) + output.Layers[i] = p0.Layers[i] * w0 + p1.Layers[i] * w1 + p2.Layers[i] * w2; +#endif + return output; +} + +#endif + MaterialInput GetMaterialInput(PixelInput input) { MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; + result.WorldPosition = input.Geometry.WorldPosition; + result.TexCoord = input.Geometry.TexCoord; #if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; + result.LightmapUV = input.Geometry.LightmapUV; #endif - result.TBN = CalcTangentBasisFromWorldNormal(input.WorldNormal); + result.TBN = CalcTangentBasisFromWorldNormal(input.Geometry.WorldNormal); result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); result.SvPosition = input.Position; - result.HolesMask = input.HolesMask; + result.HolesMask = input.Geometry.HolesMask; #if USE_TERRAIN_LAYERS - result.Layers = input.Layers; + result.Layers = input.Geometry.Layers; #endif #if USE_CUSTOM_VERTEX_INTERPOLATORS result.CustomVSToPS = input.CustomVSToPS; @@ -212,20 +253,6 @@ float4 GetVertexColor(MaterialInput input) return 1; } -// Evaluates the H-Basis coefficients in the tangent space normal direction -float3 GetHBasisIrradiance(float3 n, float3 h0, float3 h1, float3 h2, float3 h3) -{ - // Band 0 - float3 color = h0 * (1.0f / sqrt(2.0f * PI)); - - // Band 1 - color += h1 * -sqrt(1.5f / PI) * n.y; - color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); - color += h3 * -sqrt(1.5f / PI) * n.x; - - return color; -} - @8 // Get material properties function (for vertex shader) @@ -246,9 +273,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates LOD value (with fractional part for blending) float CalcLOD(float2 xy, float4 morph) { @@ -297,7 +321,7 @@ float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) struct TerrainVertexInput { float2 TexCoord : TEXCOORD0; - float4 Morph : TEXCOORD1; + float4 Morph : TEXCOORD1; }; // Vertex Shader function for terrain rendering @@ -348,7 +372,7 @@ VertexOutput VS(TerrainVertexInput input) float2 normalTemp = float2(heightmapValue.b, heightmapValue.a) * 2.0f - 1.0f; float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); normal = normalize(normal); - output.HolesMask = isHole ? 0 : 1; + output.Geometry.HolesMask = isHole ? 0 : 1; if (isHole) { normal = float3(0, 1, 0); @@ -365,10 +389,10 @@ VertexOutput VS(TerrainVertexInput input) float3 position = float3(positionXZ.x, height, positionXZ.y); // Compute world space vertex position - output.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes #if USE_SMOOTH_LOD_TRANSITION @@ -376,46 +400,46 @@ VertexOutput VS(TerrainVertexInput input) #else float2 texCoord = input.TexCoord; #endif - output.TexCoord = positionXZ * (1.0f / TerrainChunkSizeLOD0) + OffsetUV; - output.LightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; + output.Geometry.TexCoord = positionXZ * (1.0f / TerrainChunkSizeLOD0) + OffsetUV; + output.Geometry.LightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; // Extract terrain layers weights from the splatmap #if USE_TERRAIN_LAYERS - output.Layers[0] = splatmap0Value; + output.Geometry.Layers[0] = splatmap0Value; #if TERRAIN_LAYERS_DATA_SIZE > 1 - output.Layers[1] = splatmap1Value; + output.Geometry.Layers[1] = splatmap1Value; #endif #endif // Compute world space normal vector float3x3 tangentToLocal = CalcTangentBasisFromWorldNormal(normal); float3x3 tangentToWorld = CalcTangentToWorld(WorldMatrix, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldNormal = tangentToWorld[2]; // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS MaterialInput materialInput = (MaterialInput)0; - materialInput.WorldPosition = output.WorldPosition; - materialInput.TexCoord = output.TexCoord; + materialInput.WorldPosition = output.Geometry.WorldPosition; + materialInput.TexCoord = output.Geometry.TexCoord; #if USE_LIGHTMAP - materialInput.LightmapUV = output.LightmapUV; + materialInput.LightmapUV = output.Geometry.LightmapUV; #endif - materialInput.TBN = CalcTangentBasisFromWorldNormal(output.WorldNormal); + materialInput.TBN = CalcTangentBasisFromWorldNormal(output.Geometry.WorldNormal); materialInput.TwoSidedSign = WorldDeterminantSign; materialInput.SvPosition = output.Position; materialInput.PreSkinnedPosition = position; materialInput.PreSkinnedNormal = normal; - materialInput.HolesMask = output.HolesMask; + materialInput.HolesMask = output.Geometry.HolesMask; #if USE_TERRAIN_LAYERS - materialInput.Layers = output.Layers; + materialInput.Layers = output.Geometry.Layers; #endif Material material = GetMaterialVS(materialInput); #endif // Apply world position offset per-vertex #if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); #endif // Get tessalation multiplier (per vertex) @@ -431,277 +455,6 @@ VertexOutput VS(TerrainVertexInput input) return output; } -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif - result.TBN = CalcTangentBasisFromWorldNormal(input.WorldNormal); - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = input.Position; - result.HolesMask = input.HolesMask; -#if USE_TERRAIN_LAYERS - result.Layers = input.Layers; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 TessellationMultipliers; - TessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - TessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - TessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - TessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - - TessellationMultipliers = clamp(TessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = TessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = TessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = TessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = TessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); - COPY(WorldNormal); - COPY(HolesMask); - COPY(TessellationMultiplier); -#if USE_TERRAIN_LAYERS - COPY(Layers); -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#else - INTERPOLATE(WorldPosition); -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); - INTERPOLATE(WorldNormal); - INTERPOLATE(HolesMask); -#if USE_TERRAIN_LAYERS - UNROLL - for (int i = 0; i < TERRAIN_LAYERS_DATA_SIZE; i++) - { - INTERPOLATE(Layers[i]); - } -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating normal can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_LIGHTMAP - -float3 SampleLightmap(Material material, MaterialInput materialInput) -{ - // Sample lightmaps - float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); - - // Unpack H-basis - float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); - float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); - float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); - float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); - - // Sample baked diffuse irradiance from the H-basis coefficients - float3 normal = material.TangentNormal; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - normal *= material.TangentNormal; -#endif - return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; -} - -#endif - // Pixel Shader function for GBuffer Pass META_PS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_1(USE_LIGHTMAP=0) diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 322726a3e..585f4f1fd 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -194,7 +194,7 @@ bool DeferredMaterialShader::Load() psDesc.VS = _shader->GetVS("VS", 1); _cacheInstanced.Default.Init(psDesc); - // GBuffer Pass with lightmap (use pixel shader permutation for USE_LIGHTMAP=1) + // GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1) psDesc.VS = _shader->GetVS("VS"); psDesc.PS = _shader->GetPS("PS_GBuffer", 1); _cache.DefaultLightmap.Init(psDesc); @@ -210,21 +210,16 @@ bool DeferredMaterialShader::Load() psDesc.DepthWriteEnable = false; psDesc.DepthTestEnable = true; psDesc.DepthFunc = ComparisonFunc::LessEqual; - if (useTess) - { - psDesc.HS = _shader->GetHS("HS", 1); - psDesc.DS = _shader->GetDS("DS", 1); - } - psDesc.VS = _shader->GetVS("VS", 2); + psDesc.VS = _shader->GetVS("VS"); psDesc.PS = _shader->GetPS("PS_MotionVectors"); _cache.MotionVectors.Init(psDesc); // Motion Vectors pass with skinning - psDesc.VS = _shader->GetVS("VS_Skinned", 1); + psDesc.VS = _shader->GetVS("VS_Skinned"); _cache.MotionVectorsSkinned.Init(psDesc); // Motion Vectors pass with skinning (with per-bone motion blur) - psDesc.VS = _shader->GetVS("VS_Skinned", 2); + psDesc.VS = _shader->GetVS("VS_Skinned", 1); _cache.MotionVectorsSkinnedPerBone.Init(psDesc); // Depth Pass diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index a07e740f8..0355b7435 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -139,13 +139,13 @@ bool LightmapFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, in { // Bind lightmap textures GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Surface.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); + drawCall.Features.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); + context->BindSR(srv + 0, lightmap0); + context->BindSR(srv + 1, lightmap1); + context->BindSR(srv + 2, lightmap2); // Set lightmap data - data.LightmapArea = drawCall.Surface.LightmapUVsArea; + data.LightmapArea = drawCall.Features.LightmapUVsArea; } srv += SRVs; diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 68c5fbd20..633448fb5 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "TerrainMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUContext.h" @@ -25,7 +26,6 @@ PACK_STRUCT(struct TerrainMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; float PerInstanceRandom; @@ -56,11 +56,21 @@ void TerrainMaterialShader::Bind(BindParameters& params) auto& drawCall = *params.FirstDrawCall; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0->GetSize() != 0; + ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing + byte* cb = _cb0Data.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(TerrainMaterialShaderData); + int32 srv = 3; + + // Setup features + if (_info.TessellationMode != TessellationMethod::None) + TessellationFeature::Bind(params, cb, srv); + const bool useLightmap = LightmapFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(TerrainMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; @@ -68,64 +78,42 @@ void TerrainMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Setup material constants data - auto data = reinterpret_cast(_cb0Data.Get()); if (hasCb0) { - Matrix::Transpose(view.Frustum.GetMatrix(), data->ViewProjectionMatrix); - Matrix::Transpose(drawCall.World, data->WorldMatrix); - Matrix::Transpose(view.View, data->ViewMatrix); + Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); + Matrix::Transpose(drawCall.World, materialData->WorldMatrix); + Matrix::Transpose(view.View, materialData->ViewMatrix); - data->ViewPos = view.Position; - data->ViewFar = view.Far; - data->ViewDir = view.Direction; - data->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); - data->ViewInfo = view.ViewInfo; - data->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform + materialData->ViewPos = view.Position; + materialData->ViewFar = view.Far; + materialData->ViewDir = view.Direction; + materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); + materialData->ViewInfo = view.ViewInfo; + materialData->ScreenSize = view.ScreenSize; const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - data->WorldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - data->WorldDeterminantSign = drawCall.WorldDeterminantSign; - data->PerInstanceRandom = drawCall.PerInstanceRandom; - data->CurrentLOD = drawCall.Terrain.CurrentLOD; - data->ChunkSizeNextLOD = drawCall.Terrain.ChunkSizeNextLOD; - data->TerrainChunkSizeLOD0 = drawCall.Terrain.TerrainChunkSizeLOD0; - data->HeightmapUVScaleBias = drawCall.Terrain.HeightmapUVScaleBias; - data->NeighborLOD = drawCall.Terrain.NeighborLOD; - data->OffsetUV = drawCall.Terrain.OffsetUV; - } - const bool useLightmap = view.Flags & ViewFlags::GI -#if USE_EDITOR - && EnableLightmapsUsage -#endif - && view.Pass == DrawPass::GBuffer - && drawCall.Terrain.Lightmap != nullptr; - if (useLightmap) - { - // Bind lightmap textures - GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Terrain.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); - - // Set lightmap data - data->LightmapArea = drawCall.Terrain.LightmapUVsArea; + materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; + materialData->PerInstanceRandom = drawCall.PerInstanceRandom; + materialData->CurrentLOD = drawCall.Terrain.CurrentLOD; + materialData->ChunkSizeNextLOD = drawCall.Terrain.ChunkSizeNextLOD; + materialData->TerrainChunkSizeLOD0 = drawCall.Terrain.TerrainChunkSizeLOD0; + materialData->HeightmapUVScaleBias = drawCall.Terrain.HeightmapUVScaleBias; + materialData->NeighborLOD = drawCall.Terrain.NeighborLOD; + materialData->OffsetUV = drawCall.Terrain.OffsetUV; } // Bind terrain textures - const auto heightmap = drawCall.Terrain.Patch->Heightmap.Get()->GetTexture(); + const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture(); const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr; const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr; - context->BindSR(3, heightmap); - context->BindSR(4, splatmap0); - context->BindSR(5, splatmap1); + context->BindSR(0, heightmap); + context->BindSR(1, splatmap0); + context->BindSR(2, splatmap1); // Bind constants if (hasCb0) diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 31656145b..0e91366e2 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -167,8 +167,14 @@ struct DrawCall struct { const Lightmap* Lightmap; - SkinnedMeshDrawData* Skinning; Rectangle LightmapUVsArea; + } Features; + + struct + { + const Lightmap* Lightmap; + Rectangle LightmapUVsArea; + SkinnedMeshDrawData* Skinning; Vector3 GeometrySize; // Object geometry size in the world (unscaled). float LODDitherFactor; // The model LOD transition dither progress. Matrix PrevWorld; @@ -176,15 +182,15 @@ struct DrawCall struct { + const Lightmap* Lightmap; + Rectangle LightmapUVsArea; Vector4 HeightmapUVScaleBias; Vector4 NeighborLOD; Vector2 OffsetUV; float CurrentLOD; float ChunkSizeNextLOD; float TerrainChunkSizeLOD0; - Rectangle LightmapUVsArea; const class TerrainPatch* Patch; - const Lightmap* Lightmap; } Terrain; struct diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 61c05149d..b6b1b55e1 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -187,6 +187,11 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo if (materialInfo.BlendMode == MaterialBlendMode::Opaque) ADD_FEATURE(LightmapFeature); break; + case MaterialDomain::Terrain: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + ADD_FEATURE(LightmapFeature); + break; default: break; } @@ -387,7 +392,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo srv = 1; break; case MaterialDomain::Terrain: - srv = 6; + srv = 3; // Heightmap + 2 splatmaps break; case MaterialDomain::Particle: srv = 5; diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index aef3fc579..8fc981cd7 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -61,9 +61,6 @@ #ifndef MAX_TESSELLATION_FACTOR #define MAX_TESSELLATION_FACTOR 15 #endif -#ifndef IS_MOTION_VECTORS_PASS - #define IS_MOTION_VECTORS_PASS 0 -#endif #ifndef PER_BONE_MOTION_BLUR #define PER_BONE_MOTION_BLUR 0 #endif @@ -164,6 +161,12 @@ float3x3 CalcTangentBasisFromWorldNormal(float3 normal) return float3x3(tangent, bitangent, normal); } +float3x3 CalcTangentBasis(float3 normal, float4 tangent) +{ + float3 bitangent = cross(normal, tangent.xyz) * tangent.w; + return float3x3(tangent.xyz, bitangent, normal); +} + // [Jimenez et al. 2016, "Practical Realtime Strategies for Accurate Indirect Occlusion"] float3 AOMultiBounce(float visibility, float3 albedo) { From b5847eb0d632cbf2fa9f31e2465b97114709d090 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 4 Feb 2021 15:27:38 +0100 Subject: [PATCH 157/222] Refactor material shaders generator to use modular features as extensions --- .../Features/Distortion.hlsl | 50 ++++++++++++++++++ .../Editor/MaterialTemplates/Particle.shader | 38 -------------- .../MaterialTemplates/SurfaceForward.shader | 51 ++----------------- .../Materials/DeferredMaterialShader.cpp | 2 - .../Materials/ForwardMaterialShader.cpp | 2 - .../Materials/MaterialShaderFeatures.cpp | 9 +++- .../Materials/MaterialShaderFeatures.h | 13 +++-- .../Materials/ParticleMaterialShader.cpp | 13 +++-- .../Materials/TerrainMaterialShader.cpp | 2 - .../MaterialGenerator/MaterialGenerator.cpp | 6 +++ 10 files changed, 86 insertions(+), 100 deletions(-) create mode 100644 Content/Editor/MaterialTemplates/Features/Distortion.hlsl diff --git a/Content/Editor/MaterialTemplates/Features/Distortion.hlsl b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl new file mode 100644 index 000000000..c74014cca --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Distortion: Defines +@1// Distortion: Includes +@2// Distortion: Constants +@3// Distortion: Resources +@4// Distortion: Utilities +@5// Distortion: Shaders +#if USE_DISTORTION + +// Pixel Shader function for Distortion Pass +META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) +float4 PS_Distortion(PixelInput input) : SV_Target0 +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); + float airIOR = 1.0f; +#if USE_PIXEL_NORMAL_OFFSET_REFRACTION + float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); + float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); +#else + float2 distortion = viewNormal.xy * (material.Refraction - airIOR); +#endif + + // Clip if the distortion distance (squared) is too small to be noticed + clip(dot(distortion, distortion) - 0.00001); + + // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction + distortion *= 4.0f; + + // Use separate storage for positive and negative offsets + float2 addOffset = max(distortion, 0); + float2 subOffset = abs(min(distortion, 0)); + return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); +} + +#endif diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index f3121b6b0..72d633028 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -802,44 +802,6 @@ float4 PS_Forward(PixelInput input) : SV_Target0 return output; } -#if USE_DISTORTION - -// Pixel Shader function for Distortion Pass -META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) -float4 PS_Distortion(PixelInput input) : SV_Target0 -{ - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); - float airIOR = 1.0f; -#if USE_PIXEL_NORMAL_OFFSET_REFRACTION - float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); - float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); -#else - float2 distortion = viewNormal.xy * (material.Refraction - airIOR); -#endif - - // Clip if the distortion distance (squared) is too small to be noticed - clip(dot(distortion, distortion) - 0.00001); - - // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction - distortion *= 4.0f; - - // Use separate storage for positive and negative offsets - float2 addOffset = max(distortion, 0); - float2 subOffset = abs(min(distortion, 0)); - return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); -} - -#endif - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader index 0e9c6db5f..cece1b231 100644 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ b/Content/Editor/MaterialTemplates/SurfaceForward.shader @@ -562,12 +562,6 @@ void ClipLODTransition(PixelInput input) } } -#else - -void ClipLODTransition(PixelInput input) -{ -} - #endif // Pixel Shader function for Forward Pass @@ -576,9 +570,11 @@ float4 PS_Forward(PixelInput input) : SV_Target0 { float4 output = 0; +#if USE_DITHERED_LOD_TRANSITION // LOD masking ClipLODTransition(input); // TODO: make model LOD transition smoother for transparent materials by using opacity to reduce aliasing +#endif // Get material parameters MaterialInput materialInput = GetMaterialInput(input); @@ -665,47 +661,6 @@ float4 PS_Forward(PixelInput input) : SV_Target0 return output; } -#if USE_DISTORTION - -// Pixel Shader function for Distortion Pass -META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) -float4 PS_Distortion(PixelInput input) : SV_Target0 -{ - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); - float airIOR = 1.0f; -#if USE_PIXEL_NORMAL_OFFSET_REFRACTION - float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); - float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); -#else - float2 distortion = viewNormal.xy * (material.Refraction - airIOR); -#endif - - // Clip if the distortion distance (squared) is too small to be noticed - clip(dot(distortion, distortion) - 0.00001); - - // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction - distortion *= 4.0f; - - // Use separate storage for positive and negative offsets - float2 addOffset = max(distortion, 0); - float2 subOffset = abs(min(distortion, 0)); - return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); -} - -#endif - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input @@ -714,8 +669,10 @@ void PS_Depth(PixelInput input #endif ) { +#if USE_DITHERED_LOD_TRANSITION // LOD masking ClipLODTransition(input); +#endif // Get material parameters MaterialInput materialInput = GetMaterialInput(input); diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 585f4f1fd..3bd71290d 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -70,8 +70,6 @@ void DeferredMaterialShader::Bind(BindParameters& params) int32 srv = 0; // Setup features - if (_info.TessellationMode != TessellationMethod::None) - TessellationFeature::Bind(params, cb, srv); const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); // Setup parameters diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 7dc68a85a..f410d477c 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -79,8 +79,6 @@ void ForwardMaterialShader::Bind(BindParameters& params) int32 srv = 0; // Setup features - if (_info.TessellationMode != TessellationMethod::None) - TessellationFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 0355b7435..0043a8d6e 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -159,7 +159,7 @@ void TessellationFeature::Generate(GeneratorData& data) { data.Template = TEXT("Tessellation.hlsl"); data.ConstantsSize = 0; - data.ResourcesCount = SRVs; + data.ResourcesCount = 0; } void LightmapFeature::Generate(GeneratorData& data) @@ -169,4 +169,11 @@ void LightmapFeature::Generate(GeneratorData& data) data.ResourcesCount = SRVs; } +void DistortionFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Distortion.hlsl"); + data.ConstantsSize = 0; + data.ResourcesCount = 0; +} + #endif diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index 0a2349dfe..9d2c06307 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -43,11 +43,6 @@ struct ForwardShadingFeature : MaterialShaderFeature // Material shader feature that adds geometry hardware tessellation (using Hull and Domain shaders). struct TessellationFeature : MaterialShaderFeature { - enum { SRVs = 0 }; - - static void Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) - { - } #if USE_EDITOR static void Generate(GeneratorData& data); #endif @@ -68,3 +63,11 @@ struct LightmapFeature : MaterialShaderFeature static void Generate(GeneratorData& data); #endif }; + +// Material shader feature that adds distortion vectors rendering pass. +struct DistortionFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 5cfad0126..bfab56daf 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -72,16 +72,23 @@ void ParticleMaterialShader::Bind(BindParameters& params) auto& view = params.RenderContext.View; auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; + const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0->GetSize() != 0; + ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1->GetSize() != 0; - const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; + const bool hasCb1 = cb1 && cb1->GetSize() != 0; + byte* cb = _cb0Data.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(ParticleMaterialShaderData); + int32 srv = 0; + + // Setup features // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(ParticleMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 633448fb5..89a96d902 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -63,8 +63,6 @@ void TerrainMaterialShader::Bind(BindParameters& params) int32 srv = 3; // Setup features - if (_info.TessellationMode != TessellationMethod::None) - TessellationFeature::Bind(params, cb, srv); const bool useLightmap = LightmapFeature::Bind(params, cb, srv); // Setup parameters diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index b6b1b55e1..8550febad 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -186,6 +186,8 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(TessellationFeature); if (materialInfo.BlendMode == MaterialBlendMode::Opaque) ADD_FEATURE(LightmapFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) + ADD_FEATURE(DistortionFeature); break; case MaterialDomain::Terrain: if (materialInfo.TessellationMode != TessellationMethod::None) @@ -193,6 +195,10 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(LightmapFeature); break; default: + case MaterialDomain::Particle: + if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) + ADD_FEATURE(DistortionFeature); + break; break; } #undef ADD_FEATURE From 01777a2c1b5f87409b67ceeccac31d8ee637cdc5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Feb 2021 11:22:47 +0100 Subject: [PATCH 158/222] Refactor material shaders generator to use modular features as extensions --- .../Features/ForwardShading.hlsl | 123 ++++++++++++ Content/Editor/MaterialTemplates/GUI.shader | 3 +- .../Editor/MaterialTemplates/Particle.shader | 126 +----------- .../MaterialTemplates/PostProcess.shader | 2 +- .../MaterialTemplates/SurfaceDeferred.shader | 3 +- .../MaterialTemplates/SurfaceForward.shader | 124 ------------ .../Editor/MaterialTemplates/Terrain.shader | 3 +- .../Materials/ForwardMaterialShader.cpp | 136 +------------ .../Graphics/Materials/MaterialShader.cpp | 6 - .../Graphics/Materials/MaterialShader.h | 1 - .../Materials/MaterialShaderFeatures.cpp | 13 +- .../Materials/MaterialShaderFeatures.h | 3 + .../Materials/ParticleMaterialShader.cpp | 180 +++--------------- .../Materials/PostFxMaterialShader.cpp | 1 - .../DirectX/DX11/GPUContextDX11.cpp | 6 +- .../MaterialGenerator/MaterialGenerator.cpp | 15 +- 16 files changed, 182 insertions(+), 563 deletions(-) create mode 100644 Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl new file mode 100644 index 000000000..085c66291 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -0,0 +1,123 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Forward Shading: Defines +#define MAX_LOCAL_LIGHTS 4 +@1// Forward Shading: Includes +#include "./Flax/LightingCommon.hlsl" +#if USE_REFLECTIONS +#include "./Flax/ReflectionsCommon.hlsl" +#endif +#include "./Flax/Lighting.hlsl" +#include "./Flax/ShadowsSampling.hlsl" +#include "./Flax/ExponentialHeightFog.hlsl" +@2// Forward Shading: Constants +LightData DirectionalLight; +LightShadowData DirectionalLightShadow; +LightData SkyLight; +ProbeData EnvironmentProbe; +ExponentialHeightFogData ExponentialHeightFog; +float3 Dummy2; +uint LocalLightsCount; +LightData LocalLights[MAX_LOCAL_LIGHTS]; +@3// Forward Shading: Resources +TextureCube EnvProbe : register(t__SRV__); +TextureCube SkyLightTexture : register(t__SRV__); +Texture2DArray DirectionalLightShadowMap : register(t__SRV__); +@4// Forward Shading: Utilities +DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); +@5// Forward Shading: Shaders +// Pixel Shader function for Forward Pass +META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) +float4 PS_Forward(PixelInput input) : SV_Target0 +{ + float4 output = 0; + +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + // Add emissive light + output = float4(material.Emissive, material.Opacity); + +#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT + + // Setup GBuffer data as proxy for lighting + GBufferSample gBuffer; + gBuffer.Normal = material.WorldNormal; + gBuffer.Roughness = material.Roughness; + gBuffer.Metalness = material.Metalness; + gBuffer.Color = material.Color; + gBuffer.Specular = material.Specular; + gBuffer.AO = material.AO; + gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE + gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); +#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); +#else + gBuffer.CustomData = float4(0, 0, 0, 0); +#endif + gBuffer.WorldPos = materialInput.WorldPosition; + gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; + + // Calculate lighting from a single directional light + float4 shadowMask = 1.0f; + if (DirectionalLight.CastShadows > 0) + { + LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); + shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); + } + float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); + + // Calculate lighting from sky light + light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); + + // Calculate lighting from local lights + LOOP + for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) + { + const LightData localLight = LocalLights[localLightIndex]; + bool isSpotLight = localLight.SpotAngles.x > -2.0f; + shadowMask = 1.0f; + light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); + } + +#if USE_REFLECTIONS + // Calculate reflections + light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; +#endif + + // Add lighting (apply ambient occlusion) + output.rgb += light.rgb * gBuffer.AO; + +#if USE_FOG + // Calculate exponential height fog + float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); + + // Apply fog to the output color +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + output = float4(output.rgb * fog.a + fog.rgb, output.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT + output = float4(output.rgb * fog.a + fog.rgb, output.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE + output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY + output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); +#endif + +#endif + +#endif + + return output; +} diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index b927755ad..7eced6390 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" @@ -22,7 +21,7 @@ float4 ViewInfo; float4 ScreenSize; @1META_CB_END -// Material shader resources +// Shader resources @2 // Interpolants passed from the vertex shader struct VertexOutput diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 72d633028..a3befc24f 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -2,19 +2,13 @@ // Version: @0 #define MATERIAL 1 -#define MAX_LOCAL_LIGHTS 4 @3 +// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot +#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" -#include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS -#include "./Flax/ReflectionsCommon.hlsl" -#endif -#include "./Flax/Lighting.hlsl" -#include "./Flax/ShadowsSampling.hlsl" -#include "./Flax/ExponentialHeightFog.hlsl" #include "./Flax/Matrix.hlsl" @7 struct SpriteInput @@ -55,44 +49,19 @@ uint RibbonSegmentCount; float4x4 WorldMatrixInverseTransposed; @1META_CB_END -// Secondary constantant buffer (for lighting) -META_CB_BEGIN(1, LightingData) -LightData DirectionalLight; -LightShadowData DirectionalLightShadow; -LightData SkyLight; -ProbeData EnvironmentProbe; -ExponentialHeightFogData ExponentialHeightFog; -float3 Dummy1; -uint LocalLightsCount; -LightData LocalLights[MAX_LOCAL_LIGHTS]; -META_CB_END - -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); - // Particles attributes buffer ByteAddressBuffer ParticlesData : register(t0); -// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot -#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) - #if HAS_SORTED_INDICES - // Sorted particles indices Buffer SortedIndices : register(t1); - #else - // Ribbon particles segments distances buffer Buffer SegmentDistances : register(t1); - #endif // Shader resources -TextureCube EnvProbe : register(t2); -TextureCube SkyLightTexture : register(t3); -Texture2DArray DirectionalLightShadowMap : register(t4); @2 - // Interpolants passed from the vertex shader struct VertexOutput { @@ -711,97 +680,6 @@ VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) return output; } -// Pixel Shader function for Forward Pass -META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) -float4 PS_Forward(PixelInput input) : SV_Target0 -{ - float4 output = 0; - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Add emissive light - output = float4(material.Emissive, material.Opacity); - -#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT - - // Setup GBuffer data as proxy for lighting - GBufferSample gBuffer; - gBuffer.Normal = material.WorldNormal; - gBuffer.Roughness = material.Roughness; - gBuffer.Metalness = material.Metalness; - gBuffer.Color = material.Color; - gBuffer.Specular = material.Specular; - gBuffer.AO = material.AO; - gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#else - gBuffer.CustomData = float4(0, 0, 0, 0); -#endif - gBuffer.WorldPos = materialInput.WorldPosition; - gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; - - // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } - float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); - - // Calculate lighting from sky light - light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); - - // Calculate lighting from local lights - LOOP - for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) - { - const LightData localLight = LocalLights[localLightIndex]; - bool isSpotLight = localLight.SpotAngles.x > -2.0f; - shadowMask = 1.0f; - light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); - } - -#if USE_REFLECTIONS - // Calculate reflections - light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; -#endif - - // Add lighting (apply ambient occlusion) - output.rgb += light.rgb * gBuffer.AO; - -#if USE_FOG - // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); - - // Apply fog to the output color -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE - output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY - output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); -#endif - -#endif - -#endif - - return output; -} - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader index 681a04248..f20bdc610 100644 --- a/Content/Editor/MaterialTemplates/PostProcess.shader +++ b/Content/Editor/MaterialTemplates/PostProcess.shader @@ -20,7 +20,7 @@ float4 ScreenSize; float4 TemporalAAJitter; @1META_CB_END -// Material shader resources +// Shader resources @2 // Interpolants passed to the pixel shader struct PixelInput diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader index dcee96822..9ba1ea3de 100644 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader @@ -30,9 +30,8 @@ float3 GeometrySize; float Dummy1; @1META_CB_END -// Material shader resources +// Shader resources @2 - // Geometry data passed though the graphics rendering stages up to the pixel shader struct GeometryData { diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader index cece1b231..e7a86fb81 100644 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ b/Content/Editor/MaterialTemplates/SurfaceForward.shader @@ -2,19 +2,10 @@ // Version: @0 #define MATERIAL 1 -#define MAX_LOCAL_LIGHTS 4 @3 - #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" -#include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS -#include "./Flax/ReflectionsCommon.hlsl" -#endif -#include "./Flax/Lighting.hlsl" -#include "./Flax/ShadowsSampling.hlsl" -#include "./Flax/ExponentialHeightFog.hlsl" @7 // Primary constant buffer (with additional material parameters) META_CB_BEGIN(0, Data) @@ -38,26 +29,8 @@ float3 GeometrySize; float Dummy1; @1META_CB_END -// Secondary constantant buffer (for lighting) -META_CB_BEGIN(1, LightingData) -LightData DirectionalLight; -LightShadowData DirectionalLightShadow; -LightData SkyLight; -ProbeData EnvironmentProbe; -ExponentialHeightFogData ExponentialHeightFog; -float3 Dummy2; -uint LocalLightsCount; -LightData LocalLights[MAX_LOCAL_LIGHTS]; -META_CB_END - -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); - // Shader resources -TextureCube EnvProbe : register(t0); -TextureCube SkyLightTexture : register(t1); -Texture2DArray DirectionalLightShadowMap : register(t2); @2 - // Interpolants passed from the vertex shader struct VertexOutput { @@ -564,103 +537,6 @@ void ClipLODTransition(PixelInput input) #endif -// Pixel Shader function for Forward Pass -META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) -float4 PS_Forward(PixelInput input) : SV_Target0 -{ - float4 output = 0; - -#if USE_DITHERED_LOD_TRANSITION - // LOD masking - ClipLODTransition(input); - // TODO: make model LOD transition smoother for transparent materials by using opacity to reduce aliasing -#endif - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Add emissive light - output = float4(material.Emissive, material.Opacity); - -#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT - - // Setup GBuffer data as proxy for lighting - GBufferSample gBuffer; - gBuffer.Normal = material.WorldNormal; - gBuffer.Roughness = material.Roughness; - gBuffer.Metalness = material.Metalness; - gBuffer.Color = material.Color; - gBuffer.Specular = material.Specular; - gBuffer.AO = material.AO; - gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#else - gBuffer.CustomData = float4(0, 0, 0, 0); -#endif - gBuffer.WorldPos = materialInput.WorldPosition; - gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; - - // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } - float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); - - // Calculate lighting from sky light - light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); - - // Calculate lighting from local lights - LOOP - for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) - { - const LightData localLight = LocalLights[localLightIndex]; - bool isSpotLight = localLight.SpotAngles.x > -2.0f; - shadowMask = 1.0f; - light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); - } - -#if USE_REFLECTIONS - // Calculate reflections - light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; -#endif - - // Add lighting (apply ambient occlusion) - output.rgb += light.rgb * gBuffer.AO; - -#if USE_FOG - // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); - - // Apply fog to the output color -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE - output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY - output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); -#endif - -#endif - -#endif - - return output; -} - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index c36d7f0c5..f1c257b04 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -42,9 +42,8 @@ Texture2D Heightmap : register(t0); Texture2D Splatmap0 : register(t1); Texture2D Splatmap1 : register(t2); -// Material shader resources +// Shader resources @2 - // Geometry data passed though the graphics rendering stages up to the pixel shader struct GeometryData { diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index f410d477c..0d9108566 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -4,13 +4,14 @@ #include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" -#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" #include "Engine/Graphics/RenderView.h" -#include "Engine/Level/Actors/EnvironmentProbe.h" -#include "Engine/Renderer/DepthOfFieldPass.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Renderer/DrawCall.h" -#include "Engine/Renderer/ShadowsPass.h" #include "Engine/Renderer/RenderList.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" @@ -39,17 +40,6 @@ PACK_STRUCT(struct ForwardMaterialShaderData { float Dummy1; }); -PACK_STRUCT(struct ForwardMaterialShaderLightingData { - LightData DirectionalLight; - LightShadowData DirectionalLightShadow; - LightData SkyLight; - ProbeData EnvironmentProbe; - ExponentialHeightFogData ExponentialHeightFog; - Vector3 Dummy2; - uint32 LocalLightsCount; - LightData LocalLights[MAX_LOCAL_LIGHTS]; - }); - DrawPass ForwardMaterialShader::GetDrawModes() const { return _drawModes; @@ -66,19 +56,17 @@ void ForwardMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0 && cb0->GetSize() != 0; ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing - const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1 && cb1->GetSize() != 0; byte* cb = _cb0Data.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(ForwardMaterialShaderData); int32 srv = 0; // Setup features + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; @@ -131,124 +119,12 @@ void ForwardMaterialShader::Bind(BindParameters& params) materialData->GeometrySize = drawCall.Surface.GeometrySize; } - // Setup lighting constants data - if (hasCb1) - { - auto& lightingData = *reinterpret_cast(_cb1Data.Get()); - const int32 envProbeShaderRegisterIndex = 0; - const int32 skyLightShaderRegisterIndex = 1; - const int32 dirLightShaderRegisterIndex = 2; - - // Set fog input - if (cache->Fog) - { - cache->Fog->GetExponentialHeightFogData(view, lightingData.ExponentialHeightFog); - } - else - { - lightingData.ExponentialHeightFog.FogMinOpacity = 1.0f; - lightingData.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - - // Set directional light input - if (cache->DirectionalLights.HasItems()) - { - const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; - if (useShadow) - { - lightingData.DirectionalLightShadow = shadowPass->LastDirLight; - context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - context->UnBindSR(dirLightShaderRegisterIndex); - } - dirLight.SetupLightData(&lightingData.DirectionalLight, view, useShadow); - } - else - { - lightingData.DirectionalLight.Color = Vector3::Zero; - lightingData.DirectionalLight.CastShadows = 0.0f; - context->UnBindSR(dirLightShaderRegisterIndex); - } - - // Set sky light - if (cache->SkyLights.HasItems()) - { - auto& skyLight = cache->SkyLights.First(); - skyLight.SetupLightData(&lightingData.SkyLight, view, false); - const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; - context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); - } - else - { - Platform::MemoryClear(&lightingData.SkyLight, sizeof(lightingData.SkyLight)); - context->UnBindSR(skyLightShaderRegisterIndex); - } - - // Set reflection probe data - EnvironmentProbe* probe = nullptr; - // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it - for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) - { - const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - probe = p; - break; - } - } - if (probe && probe->GetProbe()) - { - probe->SetupProbeData(&lightingData.EnvironmentProbe); - const auto texture = probe->GetProbe()->GetTexture(); - context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); - } - else - { - lightingData.EnvironmentProbe.Data1 = Vector4::Zero; - context->UnBindSR(envProbeShaderRegisterIndex); - } - - // Set local lights - lightingData.LocalLightsCount = 0; - for (int32 i = 0; i < cache->PointLights.Count(); i++) - { - const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - for (int32 i = 0; i < cache->SpotLights.Count(); i++) - { - const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - } - // Bind constants if (hasCb0) { context->UpdateCB(cb0, _cb0Data.Get()); context->BindCB(0, cb0); } - if (hasCb1) - { - context->UpdateCB(cb1, _cb1Data.Get()); - context->BindCB(1, cb1); - } // Select pipeline state based on current pass and render mode const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 478992959..ee1cf22c4 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -143,11 +143,6 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf { _cb0Data.Resize(cb0->GetSize(), false); } - const auto cb1 = _shader->GetCB(1); - if (cb1) - { - _cb1Data.Resize(cb1->GetSize(), false); - } // Initialize the material based on type (create pipeline states and setup) if (Load()) @@ -163,6 +158,5 @@ void MaterialShader::Unload() { _isLoaded = false; _cb0Data.Resize(0, false); - _cb1Data.Resize(0, false); _shader->ReleaseGPU(); } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 58ba297fa..baa463d3f 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -53,7 +53,6 @@ protected: bool _isLoaded; GPUShader* _shader; Array _cb0Data; - Array _cb1Data; MaterialInfo _info; protected: diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 0043a8d6e..3ed31908c 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -155,23 +155,30 @@ bool LightmapFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, in #if USE_EDITOR +void ForwardShadingFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/ForwardShading.hlsl"); + data.ConstantsSize = sizeof(Data); + data.ResourcesCount = SRVs; +} + void TessellationFeature::Generate(GeneratorData& data) { - data.Template = TEXT("Tessellation.hlsl"); + data.Template = TEXT("Features/Tessellation.hlsl"); data.ConstantsSize = 0; data.ResourcesCount = 0; } void LightmapFeature::Generate(GeneratorData& data) { - data.Template = TEXT("Lightmap.hlsl"); + data.Template = TEXT("Features/Lightmap.hlsl"); data.ConstantsSize = sizeof(Data); data.ResourcesCount = SRVs; } void DistortionFeature::Generate(GeneratorData& data) { - data.Template = TEXT("Distortion.hlsl"); + data.Template = TEXT("Features/Distortion.hlsl"); data.ConstantsSize = 0; data.ResourcesCount = 0; } diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index 9d2c06307..1ee46e2d1 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -38,6 +38,9 @@ struct ForwardShadingFeature : MaterialShaderFeature }); static void Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif }; // Material shader feature that adds geometry hardware tessellation (using Hull and Domain shaders). diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index bfab56daf..8ee49ae78 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -1,20 +1,19 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" -#include "Engine/Renderer/DrawCall.h" -#include "Engine/Renderer/ShadowsPass.h" -#include "Engine/Graphics/RenderView.h" -#include "Engine/Renderer/RenderList.h" -#include "Engine/Graphics/GPUContext.h" -#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Engine/Time.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" -#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h" -#include "Engine/Content/Assets/CubeTexture.h" -#include "Engine/Level/Actors/EnvironmentProbe.h" #define MAX_LOCAL_LIGHTS 4 @@ -49,17 +48,6 @@ PACK_STRUCT(struct ParticleMaterialShaderData { Matrix WorldMatrixInverseTransposed; }); -PACK_STRUCT(struct ParticleMaterialShaderLightingData { - LightData DirectionalLight; - LightShadowData DirectionalLightShadow; - LightData SkyLight; - ProbeData EnvironmentProbe; - ExponentialHeightFogData ExponentialHeightFog; - Vector3 Dummy1; - uint32 LocalLightsCount; - LightData LocalLights[MAX_LOCAL_LIGHTS]; - }); - DrawPass ParticleMaterialShader::GetDrawModes() const { return _drawModes; @@ -70,20 +58,18 @@ void ParticleMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0->GetSize() != 0; ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing - const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1 && cb1->GetSize() != 0; byte* cb = _cb0Data.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(ParticleMaterialShaderData); - int32 srv = 0; - + int32 srv = 2; + // Setup features + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; @@ -95,25 +81,23 @@ void ParticleMaterialShader::Bind(BindParameters& params) bindMeta.CanSampleGBuffer = true; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup particles data and attributes binding info + // Setup particles data + context->BindSR(0, drawCall.Particle.Particles->GPU.Buffer->View()); + context->BindSR(1, drawCall.Particle.Particles->GPU.SortedIndices ? drawCall.Particle.Particles->GPU.SortedIndices->View() : nullptr); + + // Setup particles attributes binding info + if (hasCb0) { - context->BindSR(0, drawCall.Particle.Particles->GPU.Buffer->View()); - if (drawCall.Particle.Particles->GPU.SortedIndices) - context->BindSR(1, drawCall.Particle.Particles->GPU.SortedIndices->View()); - - if (hasCb0) + const auto& p = *params.ParamsLink->This; + for (int32 i = 0; i < p.Count(); i++) { - const auto& p = *params.ParamsLink->This; - for (int32 i = 0; i < p.Count(); i++) + const auto& param = p.At(i); + if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle."))) { - const auto& param = p.At(i); - if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle."))) - { - auto name = StringView(param.GetName().Get() + 9); + auto name = StringView(param.GetName().Get() + 9); - const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); - *((int32*)(bindMeta.Constants + param.GetBindOffset())) = offset; - } + const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); + *((int32*)(bindMeta.Constants + param.GetBindOffset())) = offset; } } } @@ -147,8 +131,6 @@ void ParticleMaterialShader::Bind(BindParameters& params) if (hasCb0) { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); @@ -173,8 +155,6 @@ void ParticleMaterialShader::Bind(BindParameters& params) // Setup material constants data if (hasCb0) { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - static StringView ParticlePosition(TEXT("Position")); static StringView ParticleSpriteSize(TEXT("SpriteSize")); static StringView ParticleSpriteFacingMode(TEXT("SpriteFacingMode")); @@ -207,124 +187,12 @@ void ParticleMaterialShader::Bind(BindParameters& params) Matrix::Invert(drawCall.World, materialData->WorldMatrixInverseTransposed); } - // Setup lighting constants data - if (hasCb1) - { - auto& lightingData = *reinterpret_cast(_cb1Data.Get()); - const int32 envProbeShaderRegisterIndex = 2; - const int32 skyLightShaderRegisterIndex = 3; - const int32 dirLightShaderRegisterIndex = 4; - - // Set fog input - if (cache->Fog) - { - cache->Fog->GetExponentialHeightFogData(view, lightingData.ExponentialHeightFog); - } - else - { - lightingData.ExponentialHeightFog.FogMinOpacity = 1.0f; - lightingData.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - - // Set directional light input - if (cache->DirectionalLights.HasItems()) - { - const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; - if (useShadow) - { - lightingData.DirectionalLightShadow = shadowPass->LastDirLight; - context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - context->UnBindSR(dirLightShaderRegisterIndex); - } - dirLight.SetupLightData(&lightingData.DirectionalLight, view, useShadow); - } - else - { - lightingData.DirectionalLight.Color = Vector3::Zero; - lightingData.DirectionalLight.CastShadows = 0.0f; - context->UnBindSR(dirLightShaderRegisterIndex); - } - - // Set sky light - if (cache->SkyLights.HasItems()) - { - auto& skyLight = cache->SkyLights.Last(); - skyLight.SetupLightData(&lightingData.SkyLight, view, false); - const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; - context->BindSR(skyLightShaderRegisterIndex, texture); - } - else - { - Platform::MemoryClear(&lightingData.SkyLight, sizeof(lightingData.SkyLight)); - context->UnBindSR(skyLightShaderRegisterIndex); - } - - // Set reflection probe data - EnvironmentProbe* probe = nullptr; - // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it - for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) - { - const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - probe = p; - break; - } - } - if (probe && probe->GetProbe()) - { - probe->SetupProbeData(&lightingData.EnvironmentProbe); - const auto texture = probe->GetProbe()->GetTexture(); - context->BindSR(envProbeShaderRegisterIndex, texture); - } - else - { - lightingData.EnvironmentProbe.Data1 = Vector4::Zero; - context->UnBindSR(envProbeShaderRegisterIndex); - } - - // Set local lights - lightingData.LocalLightsCount = 0; - for (int32 i = 0; i < cache->PointLights.Count(); i++) - { - const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - for (int32 i = 0; i < cache->SpotLights.Count(); i++) - { - const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - } - // Bind constants if (hasCb0) { context->UpdateCB(cb0, _cb0Data.Get()); context->BindCB(0, cb0); } - if (hasCb1) - { - context->UpdateCB(cb1, _cb1Data.Get()); - context->BindCB(1, cb1); - } // Bind pipeline context->SetState(state); diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 2d9840103..8be86a4ac 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -26,7 +26,6 @@ void PostFxMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto& drawCall = *params.FirstDrawCall; const auto cb0 = _shader->GetCB(0); const bool hasCb0 = cb0->GetSize() != 0; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 45076f39f..9c9af3145 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -1,16 +1,16 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "GPUShaderDX11.h" #if GRAPHICS_API_DIRECTX11 #include "GPUContextDX11.h" -#include "Engine/Core/Math/Viewport.h" -#include "Engine/Core/Math/Rectangle.h" +#include "GPUShaderDX11.h" #include "GPUShaderProgramDX11.h" #include "GPUPipelineStateDX11.h" #include "GPUTextureDX11.h" #include "GPUBufferDX11.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Core/Math/Viewport.h" +#include "Engine/Core/Math/Rectangle.h" #include "Engine/Profiler/RenderStats.h" #define DX11_CLEAR_SR_ON_STAGE_DISABLE 0 diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 8550febad..ea342bd53 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -59,7 +59,7 @@ namespace bool FeatureData::Init() { // Load template file - const String path = Globals::EngineContentFolder / TEXT("Editor/MaterialTemplates/Features/") + Data.Template; + const String path = Globals::EngineContentFolder / TEXT("Editor/MaterialTemplates/") + Data.Template; String contents; if (File::ReadAllText(path, contents)) { @@ -188,17 +188,20 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(LightmapFeature); if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) ADD_FEATURE(DistortionFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + ADD_FEATURE(ForwardShadingFeature); break; case MaterialDomain::Terrain: if (materialInfo.TessellationMode != TessellationMethod::None) ADD_FEATURE(TessellationFeature); ADD_FEATURE(LightmapFeature); break; - default: case MaterialDomain::Particle: if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) ADD_FEATURE(DistortionFeature); + ADD_FEATURE(ForwardShadingFeature); break; + default: break; } #undef ADD_FEATURE @@ -390,18 +393,14 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo int32 srv = 0; switch (baseLayer->Domain) { - case MaterialDomain::Surface: - if (materialInfo.BlendMode != MaterialBlendMode::Opaque) - srv = 3; // Forward shading resources - break; case MaterialDomain::Decal: - srv = 1; + srv = 1; // Depth buffer break; case MaterialDomain::Terrain: srv = 3; // Heightmap + 2 splatmaps break; case MaterialDomain::Particle: - srv = 5; + srv = 2; // Particles data + Sorted indices/Ribbon segments break; } for (auto f : features) From 089f0dab3acdc963a81845a999455e92bf99dd7a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Feb 2021 11:55:02 +0100 Subject: [PATCH 159/222] Refactor material shaders generator to use modular features as extensions --- .../Features/DeferredShading.hlsl | 84 +++++++++++++++++++ .../Features/ForwardShading.hlsl | 1 + .../MaterialTemplates/Features/Lightmap.hlsl | 2 +- .../MaterialTemplates/SurfaceDeferred.shader | 81 +----------------- .../Editor/MaterialTemplates/Terrain.shader | 72 ---------------- .../Materials/MaterialShaderFeatures.cpp | 7 ++ .../Materials/MaterialShaderFeatures.h | 8 ++ .../MaterialGenerator/MaterialGenerator.cpp | 3 + 8 files changed, 108 insertions(+), 150 deletions(-) create mode 100644 Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl diff --git a/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl new file mode 100644 index 000000000..d3a7a2518 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Deferred Shading: Defines +@1// Deferred Shading: Includes +@2// Deferred Shading: Constants +@3// Deferred Shading: Resources +@4// Deferred Shading: Utilities +@5// Deferred Shading: Shaders + +// Pixel Shader function for GBuffer Pass +META_PS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_LIGHTMAP=0) +META_PERMUTATION_1(USE_LIGHTMAP=1) +void PS_GBuffer( + in PixelInput input + ,out float4 Light : SV_Target0 +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + // GBuffer + ,out float4 RT0 : SV_Target1 + ,out float4 RT1 : SV_Target2 + ,out float4 RT2 : SV_Target3 +#if USE_GBUFFER_CUSTOM_DATA + ,out float4 RT3 : SV_Target4 +#endif +#endif + ) +{ + Light = 0; + +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + +#if USE_LIGHTMAP + float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); + float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); + + // Sample lightmap + float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); + + // Apply static indirect light + Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); +#endif + +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + + // Pack material properties to GBuffer + RT0 = float4(material.Color, material.AO); + RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); + RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); + + // Custom data +#if USE_GBUFFER_CUSTOM_DATA +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE + RT3 = float4(material.SubsurfaceColor, material.Opacity); +#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + RT3 = float4(material.SubsurfaceColor, material.Opacity); +#else + RT3 = float4(0, 0, 0, 0); +#endif +#endif + + // Add light emission +#if USE_EMISSIVE + Light.rgb += material.Emissive; +#endif + +#else + + // Handle blending as faked forward pass (use Light buffer and skip GBuffer modification) + Light = float4(material.Emissive, material.Opacity); + +#endif +} diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 085c66291..c21fe37d2 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -26,6 +26,7 @@ Texture2DArray DirectionalLightShadowMap : register(t__SRV__); @4// Forward Shading: Utilities DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); @5// Forward Shading: Shaders + // Pixel Shader function for Forward Pass META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) float4 PS_Forward(PixelInput input) : SV_Target0 diff --git a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl index 6f6a44d8b..33ef567ca 100644 --- a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl +++ b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl @@ -50,4 +50,4 @@ float3 SampleLightmap(Material material, MaterialInput materialInput) } #endif -@5// Lightmap: Shaders \ No newline at end of file +@5// Lightmap: Shaders diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader index 9ba1ea3de..17f88e179 100644 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader @@ -651,85 +651,8 @@ void ClipLODTransition(PixelInput input) } } -#else - -void ClipLODTransition(PixelInput input) -{ -} - #endif -// Pixel Shader function for GBuffer Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_LIGHTMAP=0) -META_PERMUTATION_1(USE_LIGHTMAP=1) -void PS_GBuffer( - in PixelInput input - ,out float4 Light : SV_Target0 -#if MATERIAL_DOMAIN == MATERIAL_DOMAIN_SURFACE - // GBuffer - ,out float4 RT0 : SV_Target1 - ,out float4 RT1 : SV_Target2 - ,out float4 RT2 : SV_Target3 -#if USE_GBUFFER_CUSTOM_DATA - ,out float4 RT3 : SV_Target4 -#endif -#endif - ) -{ - Light = 0; - - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - -#if MATERIAL_DOMAIN == MATERIAL_DOMAIN_SURFACE - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if USE_LIGHTMAP - - float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); - float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); - - // Sample lightmap - float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); - - // Apply static indirect light - Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); - -#endif - - // Pack material properties to GBuffer - RT0 = float4(material.Color, material.AO); - RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); - RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); - - // Custom data -#if USE_GBUFFER_CUSTOM_DATA -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#else - RT3 = float4(0, 0, 0, 0); -#endif -#endif - -#endif - - // Add light emission -#if USE_EMISSIVE - Light.rgb += material.Emissive; -#endif -} - // Pixel Shader function for Depth Pass META_PS(IS_SURFACE, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input @@ -738,8 +661,10 @@ void PS_Depth(PixelInput input #endif ) { +#if USE_DITHERED_LOD_TRANSITION // LOD masking ClipLODTransition(input); +#endif #if MATERIAL_MASKED // Perform per pixel clipping if material requries it @@ -757,8 +682,10 @@ void PS_Depth(PixelInput input META_PS(true, FEATURE_LEVEL_ES2) float4 PS_MotionVectors(PixelInput input) : SV_Target0 { +#if USE_DITHERED_LOD_TRANSITION // LOD masking ClipLODTransition(input); +#endif #if MATERIAL_MASKED // Perform per pixel clipping if material requries it diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index f1c257b04..ef8afa2a6 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -454,78 +454,6 @@ VertexOutput VS(TerrainVertexInput input) return output; } -// Pixel Shader function for GBuffer Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_LIGHTMAP=0) -META_PERMUTATION_1(USE_LIGHTMAP=1) -void PS_GBuffer( - in PixelInput input - ,out float4 Light : SV_Target0 -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - ,out float4 RT0 : SV_Target1 - ,out float4 RT1 : SV_Target2 - ,out float4 RT2 : SV_Target3 -#if USE_GBUFFER_CUSTOM_DATA - ,out float4 RT3 : SV_Target4 -#endif -#endif - ) -{ - Light = 0; - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if USE_LIGHTMAP - - float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); - float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); - - // Sample lightmap - float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); - - // Apply static indirect light - Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); - -#endif - -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - - // Pack material properties to GBuffer - RT0 = float4(material.Color, material.AO); - RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); - RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); - - // Custom data -#if USE_GBUFFER_CUSTOM_DATA -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#else - RT3 = float4(0, 0, 0, 0); -#endif -#endif - - // Add light emission -#if USE_EMISSIVE - Light.rgb += material.Emissive; -#endif - -#else - - // Handle blending as faked forward pass (use Light buffer and skip GBuffer modification) - Light = float4(material.Emissive, material.Opacity); - -#endif -} - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 3ed31908c..2a6a97ffa 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -162,6 +162,13 @@ void ForwardShadingFeature::Generate(GeneratorData& data) data.ResourcesCount = SRVs; } +void DeferredShadingFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/DeferredShading.hlsl"); + data.ConstantsSize = 0; + data.ResourcesCount = 0; +} + void TessellationFeature::Generate(GeneratorData& data) { data.Template = TEXT("Features/Tessellation.hlsl"); diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index 1ee46e2d1..3128a688d 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -43,6 +43,14 @@ struct ForwardShadingFeature : MaterialShaderFeature #endif }; +// Material shader feature that add support for Deferred shading inside the material shader. +struct DeferredShadingFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + // Material shader feature that adds geometry hardware tessellation (using Hull and Domain shaders). struct TessellationFeature : MaterialShaderFeature { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index ea342bd53..531efe315 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -186,6 +186,8 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(TessellationFeature); if (materialInfo.BlendMode == MaterialBlendMode::Opaque) ADD_FEATURE(LightmapFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(DeferredShadingFeature); if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) ADD_FEATURE(DistortionFeature); if (materialInfo.BlendMode != MaterialBlendMode::Opaque) @@ -195,6 +197,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo if (materialInfo.TessellationMode != TessellationMethod::None) ADD_FEATURE(TessellationFeature); ADD_FEATURE(LightmapFeature); + ADD_FEATURE(DeferredShadingFeature); break; case MaterialDomain::Particle: if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) From 2da9ed455660f4898e13ee350ff59f38864e1072 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Feb 2021 11:55:21 +0100 Subject: [PATCH 160/222] Fix crash when using Find/FindLast on empty String --- Source/Engine/Core/Types/String.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 4d1ef4e7f..cf21a1236 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -220,7 +220,7 @@ public: /// The index of the found substring or -1 if not found. int32 Find(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive, int32 startPosition = -1) const { - if (subStr == nullptr) + if (subStr == nullptr || !_data) return -1; const T* start = _data; if (startPosition != -1) @@ -241,7 +241,7 @@ public: int32 FindLast(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive, int32 startPosition = -1) const { const int32 subStrLen = StringUtils::Length(subStr); - if (subStrLen == 0) + if (subStrLen == 0 || !_data) return -1; if (startPosition == -1) startPosition = Length(); From 300f948515d487266e6bd7c74429f01cce5c0b4b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Feb 2021 13:57:00 +0100 Subject: [PATCH 161/222] Refactor material shaders generator to use modular features as extensions --- ...{SurfaceDeferred.shader => Surface.shader} | 55 +- .../MaterialTemplates/SurfaceForward.shader | 568 ------------------ .../Materials/DeferredMaterialShader.cpp | 2 +- .../Materials/ForwardMaterialShader.cpp | 3 +- .../MaterialGenerator/MaterialGenerator.cpp | 8 +- 5 files changed, 45 insertions(+), 591 deletions(-) rename Content/Editor/MaterialTemplates/{SurfaceDeferred.shader => Surface.shader} (94%) delete mode 100644 Content/Editor/MaterialTemplates/SurfaceForward.shader diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/Surface.shader similarity index 94% rename from Content/Editor/MaterialTemplates/SurfaceDeferred.shader rename to Content/Editor/MaterialTemplates/Surface.shader index 17f88e179..8c09e7611 100644 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -229,28 +229,38 @@ MaterialInput GetMaterialInput(PixelInput input) return result; } -#if USE_INSTANCING -#define INSTANCE_TRANS_WORLD float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) -#else -#define INSTANCE_TRANS_WORLD WorldMatrix -#endif - // Gets the local to world transform matrix (supports instancing) float4x4 GetInstanceTransform(ModelInput input) { - return INSTANCE_TRANS_WORLD; +#if USE_INSTANCING + return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); +#else + return WorldMatrix; +#endif } float4x4 GetInstanceTransform(ModelInput_Skinned input) { - return INSTANCE_TRANS_WORLD; +#if USE_INSTANCING + return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); +#else + return WorldMatrix; +#endif } float4x4 GetInstanceTransform(ModelInput_PosOnly input) { - return INSTANCE_TRANS_WORLD; +#if USE_INSTANCING + return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); +#else + return WorldMatrix; +#endif } float4x4 GetInstanceTransform(MaterialInput input) { - return INSTANCE_TRANS_WORLD; +#if USE_INSTANCING + return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); +#else + return WorldMatrix; +#endif } // Removes the scale vector from the local to world transformation matrix (supports instancing) @@ -390,7 +400,7 @@ float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) } // Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) +META_VS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_1(USE_INSTANCING=0) META_PERMUTATION_1(USE_INSTANCING=1) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) @@ -426,7 +436,11 @@ VertexOutput VS(ModelInput input) output.Geometry.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); #else +#if USE_LIGHTMAP output.Geometry.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; +#else + output.Geometry.LightmapUV = input.LightmapUV; +#endif output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); #endif @@ -463,7 +477,7 @@ VertexOutput VS(ModelInput input) } // Vertex Shader function for Depth Pass -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) +META_VS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_1(USE_INSTANCING=0) META_PERMUTATION_1(USE_INSTANCING=1) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) @@ -559,7 +573,7 @@ float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) } // Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) +META_VS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_1(USE_SKINNING=1) META_PERMUTATION_2(USE_SKINNING=1, PER_BONE_MOTION_BLUR=1) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) @@ -654,7 +668,7 @@ void ClipLODTransition(PixelInput input) #endif // Pixel Shader function for Depth Pass -META_PS(IS_SURFACE, FEATURE_LEVEL_ES2) +META_PS(true, FEATURE_LEVEL_ES2) void PS_Depth(PixelInput input #if GLSL , out float4 OutColor : SV_Target0 @@ -666,12 +680,19 @@ void PS_Depth(PixelInput input ClipLODTransition(input); #endif -#if MATERIAL_MASKED - // Perform per pixel clipping if material requries it +#if MATERIAL_MASKED || MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + // Get material parameters MaterialInput materialInput = GetMaterialInput(input); Material material = GetMaterialPS(materialInput); + + // Perform per pixel clipping +#if MATERIAL_MASKED clip(material.Mask - MATERIAL_MASK_THRESHOLD); #endif +#if MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); +#endif +#endif #if GLSL OutColor = 0; @@ -679,7 +700,7 @@ void PS_Depth(PixelInput input } // Pixel Shader function for Motion Vectors Pass -META_PS(true, FEATURE_LEVEL_ES2) +META_PS(USE_DEFERRED, FEATURE_LEVEL_ES2) float4 PS_MotionVectors(PixelInput input) : SV_Target0 { #if USE_DITHERED_LOD_TRANSITION diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader deleted file mode 100644 index e7a86fb81..000000000 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ /dev/null @@ -1,568 +0,0 @@ -// File generated by Flax Materials Editor -// Version: @0 - -#define MATERIAL 1 -@3 -#include "./Flax/Common.hlsl" -#include "./Flax/MaterialCommon.hlsl" -#include "./Flax/GBufferCommon.hlsl" -@7 -// Primary constant buffer (with additional material parameters) -META_CB_BEGIN(0, Data) -float4x4 ViewProjectionMatrix; -float4x4 WorldMatrix; -float4x4 ViewMatrix; -float4x4 PrevViewProjectionMatrix; -float4x4 PrevWorldMatrix; -float3 ViewPos; -float ViewFar; -float3 ViewDir; -float TimeParam; -float4 ViewInfo; -float4 ScreenSize; -float3 WorldInvScale; -float WorldDeterminantSign; -float2 Dummy0; -float LODDitherFactor; -float PerInstanceRandom; -float3 GeometrySize; -float Dummy1; -@1META_CB_END - -// Shader resources -@2 -// Interpolants passed from the vertex shader -struct VertexOutput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if USE_TESSELLATION - float TessellationMultiplier : TESS; -#endif -}; - -// Interpolants passed to the pixel shader -struct PixelInput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor - bool IsFrontFace : SV_IsFrontFace; -}; - -// Material properties generation input -struct MaterialInput -{ - float3 WorldPosition; - float TwoSidedSign; - float2 TexCoord; -#if USE_LIGHTMAP - float2 LightmapUV; -#endif -#if USE_VERTEX_COLOR - half4 VertexColor; -#endif - float3x3 TBN; - float4 SvPosition; - float3 PreSkinnedPosition; - float3 PreSkinnedNormal; - float3 InstanceOrigin; - float2 InstanceParams; -#if USE_INSTANCING - float3 InstanceTransform1; - float3 InstanceTransform2; - float3 InstanceTransform3; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; -#endif -}; - -MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = output.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = output.Position; - result.PreSkinnedPosition = input.Position.xyz; - result.PreSkinnedNormal = localNormal; -#if USE_INSTANCING - result.InstanceOrigin = input.InstanceOrigin.xyz; - result.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); - result.InstanceTransform1 = input.InstanceTransform1.xyz; - result.InstanceTransform2 = input.InstanceTransform2.xyz; - result.InstanceTransform3 = input.InstanceTransform3.xyz; -#else - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - return result; -} - -MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = output.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); - result.SvPosition = output.Position; - result.PreSkinnedPosition = localPosition; - result.PreSkinnedNormal = localNormal; - return result; -} - -MaterialInput GetMaterialInput(PixelInput input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = input.TBN; - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -#if USE_INSTANCING -#define INSTANCE_TRANS_WORLD float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) -#else -#define INSTANCE_TRANS_WORLD WorldMatrix -#endif - -// Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_Skinned input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return INSTANCE_TRANS_WORLD; -} - -// Removes the scale vector from the local to world transformation matrix (supports instancing) -float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) -{ -#if USE_INSTANCING - // Extract per axis scales from localToWorld transform - float scaleX = length(localToWorld[0]); - float scaleY = length(localToWorld[1]); - float scaleZ = length(localToWorld[2]); - float3 invScale = float3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); -#else - float3 invScale = WorldInvScale; -#endif - localToWorld[0] *= invScale.x; - localToWorld[1] *= invScale.y; - localToWorld[2] *= invScale.z; - return localToWorld; -} - -// Transforms a vector from tangent space to world space -float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) -{ - return mul(tangentVector, input.TBN); -} - -// Transforms a vector from world space to tangent space -float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) -{ - return mul(input.TBN, worldVector); -} - -// Transforms a vector from world space to view space -float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) -{ - return mul(worldVector, (float3x3)ViewMatrix); -} - -// Transforms a vector from view space to world space -float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) -{ - return mul((float3x3)ViewMatrix, viewVector); -} - -// Transforms a vector from local space to world space -float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localVector, localToWorld); -} - -// Transforms a vector from local space to world space -float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localToWorld, worldVector); -} - -// Gets the current object position (supports instancing) -float3 GetObjectPosition(MaterialInput input) -{ - return input.InstanceOrigin.xyz; -} - -// Gets the current object size (supports instancing) -float3 GetObjectSize(MaterialInput input) -{ - float4x4 world = GetInstanceTransform(input); - return GeometrySize * float3(world._m00, world._m11, world._m22); -} - -// Get the current object random value (supports instancing) -float GetPerInstanceRandom(MaterialInput input) -{ - return input.InstanceParams.x; -} - -// Get the current object LOD transition dither factor (supports instancing) -float GetLODDitherFactor(MaterialInput input) -{ -#if USE_DITHERED_LOD_TRANSITION - return input.InstanceParams.y; -#else - return 0; -#endif -} - -// Gets the interpolated vertex color (in linear space) -float4 GetVertexColor(MaterialInput input) -{ -#if USE_VERTEX_COLOR - return input.VertexColor; -#else - return 1; -#endif -} - -@8 - -// Get material properties function (for vertex shader) -Material GetMaterialVS(MaterialInput input) -{ -@5 -} - -// Get material properties function (for domain shader) -Material GetMaterialDS(MaterialInput input) -{ -@6 -} - -// Get material properties function (for pixel shader) -Material GetMaterialPS(MaterialInput input) -{ -@4 -} - -// Calculates the transform matrix from mesh tangent space to local space -half3x3 CalcTangentToLocal(ModelInput input) -{ - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - 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); -} - -half3x3 CalcTangentToWorld(in float4x4 world, in half3x3 tangentToLocal) -{ - half3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); - return mul(tangentToLocal, localToWorld); -} - -// Vertex Shader function for Forward/Depth Pass -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -VertexOutput VS(ModelInput input) -{ - VertexOutput output; - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = input.Color; -#endif - output.LightmapUV = input.LightmapUV; -#if USE_INSTANCING - output.InstanceOrigin = world[3].xyz; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.InstanceOrigin = WorldMatrix[3].xyz; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToLocal = CalcTangentToLocal(input); - half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.TBN = tangentToWorld; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(input, output, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#if USE_SKINNING - -// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) -Buffer BoneMatrices : register(t0); - -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - -// Calculates the transposed transform matrix for the given bone index -float3x4 GetBoneMatrix(int index) -{ - float4 a = BoneMatrices[index * 3]; - float4 b = BoneMatrices[index * 3 + 1]; - float4 c = BoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -// Calculates the transposed transform matrix for the given vertex (uses blending) -float3x4 GetBoneMatrix(ModelInput_Skinned input) -{ - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); - return boneMatrix; -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) -{ - float4 position = float4(input.Position.xyz, 1); - return mul(data.BlendMatrix, position); -} - -// Transforms the vertex position by weighted sum of the skinning matrices -half3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) -{ - // Unpack vertex tangent frame - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - - // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); - - float3 bitangent = cross(normal, tangent) * bitangentSign; - return half3x3(tangent, bitangent, normal); -} - -// Vertex Shader function for Forward/Depth Pass (skinned mesh rendering) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_SKINNING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -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) -VertexOutput VS_Skinned(ModelInput_Skinned input) -{ - VertexOutput output; - - // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - half3x3 tangentToLocal = SkinTangents(input, data); - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(position, 1), world).xyz; - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = float4(0, 0, 0, 1); -#endif - output.LightmapUV = float2(0, 0); -#if USE_INSTANCING - output.InstanceOrigin = world[3].xyz; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.InstanceOrigin = WorldMatrix[3].xyz; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.TBN = tangentToWorld; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(output, input.Position.xyz, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#endif - -#if USE_DITHERED_LOD_TRANSITION - -void ClipLODTransition(PixelInput input) -{ - float ditherFactor = input.InstanceParams.y; - if (abs(ditherFactor) > 0.001) - { - float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); - float randGridFrac = frac(randGrid * 1000.0); - half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); - clip(mask - 0.001); - } -} - -#endif - -// Pixel Shader function for Depth Pass -META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) -{ -#if USE_DITHERED_LOD_TRANSITION - // LOD masking - ClipLODTransition(input); -#endif - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Perform per pixel clipping -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); - -#if GLSL - OutColor = 0; -#endif -} - -@9 diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 3bd71290d..4afbdd60d 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -67,7 +67,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) byte* cb = _cb0Data.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(DeferredMaterialShaderData); - int32 srv = 0; + int32 srv = 2; // Setup features const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 0d9108566..bf9f566ef 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -36,6 +36,7 @@ PACK_STRUCT(struct ForwardMaterialShaderData { Vector2 Dummy0; float LODDitherFactor; float PerInstanceRandom; + Vector4 TemporalAAJitter; Vector3 GeometrySize; float Dummy1; }); @@ -63,7 +64,7 @@ void ForwardMaterialShader::Bind(BindParameters& params) byte* cb = _cb0Data.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(ForwardMaterialShaderData); - int32 srv = 0; + int32 srv = 2; // Setup features ForwardShadingFeature::Bind(params, cb, srv); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 531efe315..fb0585f83 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -396,6 +396,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo int32 srv = 0; switch (baseLayer->Domain) { + case MaterialDomain::Surface: + srv = 2; // Skinning Bones + Prev Bones + break; case MaterialDomain::Decal: srv = 1; // Depth buffer break; @@ -467,10 +470,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo switch (materialInfo.Domain) { case MaterialDomain::Surface: - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) - path /= TEXT("SurfaceDeferred.shader"); - else - path /= TEXT("SurfaceForward.shader"); + path /= TEXT("Surface.shader"); break; case MaterialDomain::PostProcess: path /= TEXT("PostProcess.shader"); From d8304a217875a2308b5771c1355560250de216a2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Feb 2021 14:54:29 +0100 Subject: [PATCH 162/222] Refactor material shaders generator to use modular features as extensions --- .../Features/MotionVectors.hlsl | 44 +++++ .../Features/Tessellation.hlsl | 11 +- .../Editor/MaterialTemplates/Particle.shader | 13 +- .../Editor/MaterialTemplates/Surface.shader | 173 ++++-------------- .../Materials/DecalMaterialShader.cpp | 17 +- .../Materials/DeferredMaterialShader.cpp | 13 +- .../Materials/ForwardMaterialShader.cpp | 12 +- .../Graphics/Materials/GUIMaterialShader.cpp | 18 +- .../Graphics/Materials/MaterialShader.cpp | 32 ++-- .../Graphics/Materials/MaterialShader.h | 21 ++- .../Materials/MaterialShaderFeatures.cpp | 15 +- .../Materials/MaterialShaderFeatures.h | 10 +- .../Materials/ParticleMaterialShader.cpp | 34 ++-- .../Materials/PostFxMaterialShader.cpp | 17 +- .../Materials/TerrainMaterialShader.cpp | 12 +- .../MaterialGenerator/MaterialGenerator.cpp | 2 + 16 files changed, 185 insertions(+), 259 deletions(-) create mode 100644 Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl diff --git a/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl new file mode 100644 index 000000000..992eb6805 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl @@ -0,0 +1,44 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Motion Vectors: Defines +@1// Motion Vectors: Includes +@2// Motion Vectors: Constants +@3// Motion Vectors: Resources +@4// Motion Vectors: Utilities +@5// Motion Vectors: Shaders + +// Pixel Shader function for Motion Vectors Pass +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_MotionVectors(PixelInput input) : SV_Target0 +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + +#if MATERIAL_MASKED + // Perform per pixel clipping if material requries it + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + // Calculate this and previosu frame pixel locations in clip space + float4 prevClipPos = mul(float4(input.Geometry.PrevWorldPosition, 1), PrevViewProjectionMatrix); + float4 curClipPos = mul(float4(input.Geometry.WorldPosition, 1), ViewProjectionMatrix); + float2 prevHPos = prevClipPos.xy / prevClipPos.w; + float2 curHPos = curClipPos.xy / curClipPos.w; + + // Revert temporal jitter offset + prevHPos -= TemporalAAJitter.zw; + curHPos -= TemporalAAJitter.xy; + + // Clip Space -> UV Space + float2 vPosPrev = prevHPos.xy * 0.5f + 0.5f; + float2 vPosCur = curHPos.xy * 0.5f + 0.5f; + vPosPrev.y = 1.0 - vPosPrev.y; + vPosCur.y = 1.0 - vPosCur.y; + + // Calculate per-pixel motion vector + return float4(vPosCur - vPosPrev, 0, 1); +} diff --git a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl index f2fc714ee..e2e0b54ea 100644 --- a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl +++ b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl @@ -32,14 +32,13 @@ struct TessalationDSToPS MaterialInput GetMaterialInput(TessalationDSToPS input) { - MaterialInput result = (MaterialInput)0; - result.SvPosition = input.Position; - GetGeometryMaterialInput(result, input.Geometry); - result.TwoSidedSign = WorldDeterminantSign; + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.SvPosition = input.Position; + output.TwoSidedSign = WorldDeterminantSign; #if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; + output.CustomVSToPS = input.CustomVSToPS; #endif - return result; + return output; } struct TessalationPatch diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index a3befc24f..109fe668b 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -141,14 +141,11 @@ MaterialInput GetMaterialInput(PixelInput input) } // Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return WorldMatrix; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return WorldMatrix; -} +#if USE_INSTANCING +#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) +#else +#define GetInstanceTransform(input) WorldMatrix; +#endif // Removes the scale vector from the local to world transformation matrix (supports instancing) float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index 8c09e7611..f3cc8499e 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -101,19 +101,21 @@ struct MaterialInput }; // Extracts geometry data to the material input -void GetGeometryMaterialInput(inout MaterialInput result, in GeometryData geometry) +MaterialInput GetGeometryMaterialInput(GeometryData geometry) { - result.WorldPosition = geometry.WorldPosition; - result.TexCoord = geometry.TexCoord; + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; #if USE_LIGHTMAP - result.LightmapUV = geometry.LightmapUV; + output.LightmapUV = geometry.LightmapUV; #endif #if USE_VERTEX_COLOR - result.VertexColor = geometry.VertexColor; + output.VertexColor = geometry.VertexColor; #endif - result.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); - result.InstanceOrigin = geometry.InstanceOrigin; - result.InstanceParams = geometry.InstanceParams; + output.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + output.InstanceOrigin = geometry.InstanceOrigin; + output.InstanceParams = geometry.InstanceParams; + return output; } #if USE_TESSELLATION @@ -157,111 +159,23 @@ GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, flo #endif -MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.Geometry.WorldPosition; - result.TexCoord = output.Geometry.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.Geometry.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.Geometry.VertexColor; -#endif - result.TBN = CalcTangentBasis(output.Geometry.WorldNormal, output.Geometry.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = output.Position; - result.PreSkinnedPosition = input.Position.xyz; - result.PreSkinnedNormal = localNormal; -#if USE_INSTANCING - result.InstanceOrigin = input.InstanceOrigin.xyz; - result.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); - result.InstanceTransform1 = input.InstanceTransform1.xyz; - result.InstanceTransform2 = input.InstanceTransform2.xyz; - result.InstanceTransform3 = input.InstanceTransform3.xyz; -#else - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - return result; -} - -MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.Geometry.WorldPosition; - result.TexCoord = output.Geometry.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.Geometry.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.Geometry.VertexColor; -#endif - result.TBN = CalcTangentBasis(output.Geometry.WorldNormal, output.Geometry.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); - result.SvPosition = output.Position; - result.PreSkinnedPosition = localPosition; - result.PreSkinnedNormal = localNormal; - return result; -} - MaterialInput GetMaterialInput(PixelInput input) { - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.Geometry.WorldPosition; - result.TexCoord = input.Geometry.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.Geometry.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.Geometry.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.Geometry.WorldNormal, input.Geometry.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.Geometry.InstanceOrigin; - result.InstanceParams = input.Geometry.InstanceParams; - result.SvPosition = input.Position; + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; #if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; + output.CustomVSToPS = input.CustomVSToPS; #endif - return result; + return output; } // Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ #if USE_INSTANCING - return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); +#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) #else - return WorldMatrix; +#define GetInstanceTransform(input) WorldMatrix; #endif -} -float4x4 GetInstanceTransform(ModelInput_Skinned input) -{ -#if USE_INSTANCING - return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); -#else - return WorldMatrix; -#endif -} -float4x4 GetInstanceTransform(ModelInput_PosOnly input) -{ -#if USE_INSTANCING - return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); -#else - return WorldMatrix; -#endif -} -float4x4 GetInstanceTransform(MaterialInput input) -{ -#if USE_INSTANCING - return float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)); -#else - return WorldMatrix; -#endif -} // Removes the scale vector from the local to world transformation matrix (supports instancing) float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) @@ -453,7 +367,16 @@ VertexOutput VS(ModelInput input) // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(input, output, tangentToLocal[2].xyz); + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; +#if USE_INSTANCING + materialInput.InstanceTransform1 = input.InstanceTransform1.xyz; + materialInput.InstanceTransform2 = input.InstanceTransform2.xyz; + materialInput.InstanceTransform3 = input.InstanceTransform3.xyz; +#endif Material material = GetMaterialVS(materialInput); #endif @@ -626,7 +549,11 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(output, input.Position.xyz, tangentToLocal[2].xyz); + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; Material material = GetMaterialVS(materialInput); #endif @@ -699,40 +626,4 @@ void PS_Depth(PixelInput input #endif } -// Pixel Shader function for Motion Vectors Pass -META_PS(USE_DEFERRED, FEATURE_LEVEL_ES2) -float4 PS_MotionVectors(PixelInput input) : SV_Target0 -{ -#if USE_DITHERED_LOD_TRANSITION - // LOD masking - ClipLODTransition(input); -#endif - -#if MATERIAL_MASKED - // Perform per pixel clipping if material requries it - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Calculate this and previosu frame pixel locations in clip space - float4 prevClipPos = mul(float4(input.Geometry.PrevWorldPosition, 1), PrevViewProjectionMatrix); - float4 curClipPos = mul(float4(input.Geometry.WorldPosition, 1), ViewProjectionMatrix); - float2 prevHPos = prevClipPos.xy / prevClipPos.w; - float2 curHPos = curClipPos.xy / curClipPos.w; - - // Revert temporal jitter offset - prevHPos -= TemporalAAJitter.zw; - curHPos -= TemporalAAJitter.xy; - - // Clip Space -> UV Space - float2 vPosPrev = prevHPos.xy * 0.5f + 0.5f; - float2 vPosCur = curHPos.xy * 0.5f + 0.5f; - vPosPrev.y = 1.0 - vPosPrev.y; - vPosCur.y = 1.0 - vPosCur.y; - - // Calculate per-pixel motion vector - return float4(vPosCur - vPosPrev, 0, 1); -} - @9 diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index 27e9c7b38..e141996dc 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -37,14 +37,16 @@ void DecalMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DecalMaterialShaderData); + int32 srv = 0; const bool isCameraInside = OrientedBoundingBox(Vector3::Half, params.FirstDrawCall->World).Contains(view.Position) == ContainmentType::Contains; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(DecalMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = true; @@ -55,10 +57,7 @@ void DecalMaterialShader::Bind(BindParameters& params) context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer)); // Setup material constants data - if (hasCb0) { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); @@ -85,10 +84,10 @@ void DecalMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 4afbdd60d..d8b40c3a6 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -61,10 +61,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0 && cb0->GetSize() != 0; - ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing - byte* cb = _cb0Data.Get(); + byte* cb = _cbData.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(DeferredMaterialShaderData); int32 srv = 2; @@ -82,8 +79,6 @@ void DeferredMaterialShader::Bind(BindParameters& params) bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - if (hasCb0) { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); @@ -131,10 +126,10 @@ void DeferredMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index bf9f566ef..addd7e96a 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -58,10 +58,7 @@ void ForwardMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0 && cb0->GetSize() != 0; - ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing - byte* cb = _cb0Data.Get(); + byte* cb = _cbData.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(ForwardMaterialShaderData); int32 srv = 2; @@ -89,7 +86,6 @@ void ForwardMaterialShader::Bind(BindParameters& params) } // Setup material constants data - if (hasCb0) { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); @@ -121,10 +117,10 @@ void ForwardMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 23b327a1b..9e1d9ea21 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -28,15 +28,16 @@ void GUIMaterialShader::Bind(BindParameters& params) { // Prepare auto context = params.GPUContext; - auto& view = params.RenderContext.View; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(GUIMaterialShaderData); + int32 srv = 0; const auto ps = context->IsDepthBufferBinded() ? _cache.Depth : _cache.NoDepth; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(GUIMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; @@ -44,10 +45,7 @@ void GUIMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Setup material constants data - if (hasCb0) { - auto materialData = reinterpret_cast(_cb0Data.Get()); - const auto viewProjectionMatrix = (Matrix*)params.CustomData; Matrix::Transpose(*viewProjectionMatrix, materialData->ViewProjectionMatrix); Matrix::Transpose(Matrix::Identity, materialData->WorldMatrix); @@ -62,10 +60,10 @@ void GUIMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index ee1cf22c4..2d7998b8a 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -14,18 +14,15 @@ #include "GUIMaterialShader.h" #include "TerrainMaterialShader.h" #include "ParticleMaterialShader.h" +//#include "DeformableMaterialShader.h" -GPUPipelineState* MaterialShader::PipelineStateCache::GetPS(CullMode mode, bool wireframe) +GPUPipelineState* MaterialShader::PipelineStateCache::InitPS(CullMode mode, bool wireframe) { - const int32 index = static_cast(mode) + (wireframe ? 3 : 0); - if (PS[index]) - return PS[index]; - Desc.CullMode = mode; Desc.Wireframe = wireframe; - PS[index] = GPUDevice::Instance->CreatePipelineState(); - PS[index]->Init(Desc); - return PS[index]; + auto ps = GPUDevice::Instance->CreatePipelineState(); + ps->Init(Desc); + return ps; } MaterialShader::MaterialShader(const String& name) @@ -65,6 +62,9 @@ MaterialShader* MaterialShader::Create(const String& name, MemoryReadStream& sha case MaterialDomain::Particle: material = New(name); break; + /*case MaterialDomain::Deformable: + material = New(name); + break;*/ default: LOG(Fatal, "Unknown material type."); return nullptr; @@ -138,10 +138,17 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf } // Init memory for a constant buffer - const auto cb0 = _shader->GetCB(0); - if (cb0) + _cb = _shader->GetCB(0); + if (_cb) { - _cb0Data.Resize(cb0->GetSize(), false); + int32 cbSize = _cb->GetSize(); + if (cbSize == 0) + { + // Handle unused constant buffer (eg. postFx returning solid color) + cbSize = 1024; + _cb = nullptr; + } + _cbData.Resize(cbSize, false); } // Initialize the material based on type (create pipeline states and setup) @@ -157,6 +164,7 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf void MaterialShader::Unload() { _isLoaded = false; - _cb0Data.Resize(0, false); + _cb = nullptr; + _cbData.Resize(0, false); _shader->ReleaseGPU(); } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index baa463d3f..102ac4d41 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -9,10 +9,11 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 146 +#define MATERIAL_GRAPH_VERSION 147 class Material; class GPUShader; +class GPUConstantBuffer; class MemoryReadStream; /// @@ -37,7 +38,16 @@ protected: Desc = desc; } - GPUPipelineState* GetPS(CullMode mode, bool wireframe); + GPUPipelineState* GetPS(CullMode mode, bool wireframe) + { + const int32 index = static_cast(mode) + (wireframe ? 3 : 0); + auto ps = PS[index]; + if (!ps) + PS[index] = ps = InitPS(mode, wireframe); + return ps; + } + + GPUPipelineState* InitPS(CullMode mode, bool wireframe); void Release() { @@ -52,7 +62,8 @@ protected: bool _isLoaded; GPUShader* _shader; - Array _cb0Data; + GPUConstantBuffer* _cb; + Array _cbData; MaterialInfo _info; protected: @@ -89,10 +100,8 @@ public: /// The created and loaded material or null if failed. static MaterialShader* CreateDummy(MemoryReadStream& shaderCacheStream, const MaterialInfo& info); -public: - /// - /// Clear loaded data + /// Clears the loaded data. /// virtual void Unload(); diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 2a6a97ffa..abc28a0de 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -158,36 +158,31 @@ bool LightmapFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, in void ForwardShadingFeature::Generate(GeneratorData& data) { data.Template = TEXT("Features/ForwardShading.hlsl"); - data.ConstantsSize = sizeof(Data); - data.ResourcesCount = SRVs; } void DeferredShadingFeature::Generate(GeneratorData& data) { data.Template = TEXT("Features/DeferredShading.hlsl"); - data.ConstantsSize = 0; - data.ResourcesCount = 0; } void TessellationFeature::Generate(GeneratorData& data) { data.Template = TEXT("Features/Tessellation.hlsl"); - data.ConstantsSize = 0; - data.ResourcesCount = 0; } void LightmapFeature::Generate(GeneratorData& data) { data.Template = TEXT("Features/Lightmap.hlsl"); - data.ConstantsSize = sizeof(Data); - data.ResourcesCount = SRVs; } void DistortionFeature::Generate(GeneratorData& data) { data.Template = TEXT("Features/Distortion.hlsl"); - data.ConstantsSize = 0; - data.ResourcesCount = 0; +} + +void MotionVectorsFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/MotionVectors.hlsl"); } #endif diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index 3128a688d..a70d9ada8 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -12,8 +12,6 @@ struct MaterialShaderFeature struct GeneratorData { const Char* Template; - int32 ConstantsSize; - int32 ResourcesCount; }; #endif; }; @@ -82,3 +80,11 @@ struct DistortionFeature : MaterialShaderFeature static void Generate(GeneratorData& data); #endif }; + +// Material shader feature that adds motion vectors rendering pass. +struct MotionVectorsFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 8ee49ae78..eda7fff22 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -60,10 +60,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; - ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing - byte* cb = _cb0Data.Get(); + byte* cb = _cbData.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(ParticleMaterialShaderData); int32 srv = 2; @@ -86,7 +83,6 @@ void ParticleMaterialShader::Bind(BindParameters& params) context->BindSR(1, drawCall.Particle.Particles->GPU.SortedIndices ? drawCall.Particle.Particles->GPU.SortedIndices->View() : nullptr); // Setup particles attributes binding info - if (hasCb0) { const auto& p = *params.ParamsLink->This; for (int32 i = 0; i < p.Count(); i++) @@ -129,19 +125,16 @@ void ParticleMaterialShader::Bind(BindParameters& params) static StringView ParticleRibbonTwist(TEXT("RibbonTwist")); static StringView ParticleRibbonFacingVector(TEXT("RibbonFacingVector")); - if (hasCb0) - { - materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance; - materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX; - materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY; - materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX; - materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY; - materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount; - } + materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance; + materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX; + materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY; + materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX; + materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY; + materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount; if (drawCall.Particle.Ribbon.SegmentDistances) context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View()); @@ -153,7 +146,6 @@ void ParticleMaterialShader::Bind(BindParameters& params) GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); // Setup material constants data - if (hasCb0) { static StringView ParticlePosition(TEXT("Position")); static StringView ParticleSpriteSize(TEXT("SpriteSize")); @@ -188,10 +180,10 @@ void ParticleMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 8be86a4ac..890275f68 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -26,13 +26,15 @@ void PostFxMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(PostFxMaterialShaderData); + int32 srv = 0; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Constants = hasCb0 ? _cb0Data.Get() + sizeof(PostFxMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = params.Input; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = true; @@ -40,10 +42,7 @@ void PostFxMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Setup material constants data - if (hasCb0) { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - Matrix::Transpose(view.View, materialData->ViewMatrix); materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; @@ -55,10 +54,10 @@ void PostFxMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 89a96d902..80988e405 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -54,10 +54,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; - ASSERT(hasCb0 && "TODO: fix it"); // TODO: always make cb pointer valid even if cb is missing - byte* cb = _cb0Data.Get(); + byte* cb = _cbData.Get(); auto materialData = reinterpret_cast(cb); cb += sizeof(TerrainMaterialShaderData); int32 srv = 3; @@ -76,7 +73,6 @@ void TerrainMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Setup material constants data - if (hasCb0) { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); @@ -114,10 +110,10 @@ void TerrainMaterialShader::Bind(BindParameters& params) context->BindSR(2, splatmap1); // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index fb0585f83..2f54efe82 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -185,6 +185,8 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo if (materialInfo.TessellationMode != TessellationMethod::None) ADD_FEATURE(TessellationFeature); if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(MotionVectorsFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) ADD_FEATURE(LightmapFeature); if (materialInfo.BlendMode == MaterialBlendMode::Opaque) ADD_FEATURE(DeferredShadingFeature); From 5a561b1278128e22a8770aefce06680193ee92a9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Feb 2021 15:30:01 +0100 Subject: [PATCH 163/222] Update materials --- Content/Editor/Camera/M_Camera.flax | 4 +-- .../Editor/CubeTexturePreviewMaterial.flax | 4 +-- Content/Editor/DefaultFontMaterial.flax | 4 +-- .../Editor/Gizmo/FoliageBrushMaterial.flax | 4 +-- Content/Editor/Gizmo/Material.flax | 4 +-- Content/Editor/Gizmo/MaterialWire.flax | 4 +-- .../Gizmo/SelectionOutlineMaterial.flax | 4 +-- .../Gizmo/VertexColorsPreviewMaterial.flax | 4 +-- Content/Editor/Highlight Material.flax | 4 +-- Content/Editor/Icons/IconsMaterial.flax | 4 +-- Content/Editor/IesProfilePreviewMaterial.flax | 4 +-- .../Editor/MaterialTemplates/Terrain.shader | 36 ++++++++----------- .../Particles/Particle Material Color.flax | 4 +-- Content/Editor/Particles/Smoke Material.flax | 4 +-- .../Editor/Terrain/Circle Brush Material.flax | 4 +-- .../Terrain/Highlight Terrain Material.flax | 4 +-- Content/Editor/TexturePreviewMaterial.flax | 4 +-- Content/Editor/Wires Debug Material.flax | 4 +-- Content/Engine/DefaultMaterial.flax | 4 +-- Content/Engine/DefaultTerrainMaterial.flax | 4 +-- Content/Engine/SingleColorMaterial.flax | 4 +-- Content/Engine/SkyboxMaterial.flax | 4 +-- 22 files changed, 56 insertions(+), 64 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index b5473caa3..9e2154ed9 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:b8b77651be81af931c1e63c1cb411b9f2764b4b0be4a7fa51e2c3c043a063435 -size 38085 +oid sha256:919822dc6234c58fe9efa48676845cb3f780cc2ad3a8b8edcb2badb6cfb8183c +size 30119 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index 90bc9f368..c16307620 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:959f9d4006dc11b0ecbd57cbb406213b2519a388de0d798bff69d61c2042c89b -size 39859 +oid sha256:16b75ef1da889ff19d3f7cbd52073358cd0a433cd84912bd9802e3c21fcf90f2 +size 31676 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index a0f703a85..9fc057b01 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:e424d2436f4973a0ecef28c241e222e7e5f992a2ae017e943f4ba033608b3bd7 -size 38383 +oid sha256:7addee999e9325dac84f0ed70a817dbbdd549b84dfa7d0260403cca89abb399a +size 30200 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index d8cc3fb6a..8b33f3404 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:e3b239a3122fe40c386c3161576cf74c94248d92a3d501b18ed2d658f18742a2 -size 41087 +oid sha256:792fd8ca00b3ad5b06a43a66cbca624e96af845c157519bf6038c08ae3d6dcd6 +size 34661 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index b4917f502..fb9729627 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:85435f0342a96eeebad705692df74ec19871e28bc35a3d3046f8f63dee4d9334 -size 35945 +oid sha256:28de48669af8729327e4d3797076c1c28674ec86edf90d365d5f7426d7f23f01 +size 31187 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 888846500..d0a37c641 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:9ab58f495cdd9fee4c81332a291bbee4f1adc327d7d14b5158d4360aac1ef8fa -size 35158 +oid sha256:e00d2943edb8ab1a1cdd2c7adfabc5e418fd9c0f414ed54a97ba33a3c1b23aac +size 30400 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 48a87ba88..6a082e985 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:f578d4b9e0dbd295de0674cf46cfc5d2894db225fd2c3cfc443c6f2e9c7167d3 -size 15941 +oid sha256:ae4775c4ade6c4e50f81b68a8a8546696f58bb1d701f2d8397a47d757707782f +size 15863 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index e42b74129..5dbab8006 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:c2abe350d858eeb42158b6c5fa8b0875a332bc003038fe846bf63aa3a9117577 -size 39316 +oid sha256:d5c14383f163282e144d9af8d89a817ea4f450ef200164dcd6f4af1406936d4a +size 31134 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 9f1de8fcd..48ec19fdb 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:0eba0cb1115fb6510ed32159a8fa5a81bbfabfd0e6ec00f4d5ccbfa0426fad97 -size 35356 +oid sha256:6bf05e8133b46a764b6bda2a0b80a9a7dd44fcf3a9169e4da7a99d72fade6c67 +size 28995 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 91bba0296..30c96a8ef 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:51bceae1877ea1859c8757a1e21abade8cf21eda8857d3204bf4b0239fc66d18 -size 35289 +oid sha256:6820dc041a4dc8fa66ac4dbacaf28efffb9a7083ac16b53850d8bfdb3b4fe80a +size 28927 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index f9e249e81..7dbe080c9 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:946c1c4723c46a55c2d47a67009810a58c6e790b0af27137507e2c5cb9f3d64e -size 18669 +oid sha256:58bba43288fb4b2dbcc8a6401ac765fd269bb231c1b19c64aa2bb9ca72e9c106 +size 18415 diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index ef8afa2a6..d7624dfd6 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -104,18 +104,20 @@ struct MaterialInput }; // Extracts geometry data to the material input -void GetGeometryMaterialInput(inout MaterialInput result, in GeometryData geometry) +MaterialInput GetGeometryMaterialInput(GeometryData geometry) { - result.WorldPosition = geometry.WorldPosition; - result.TexCoord = geometry.TexCoord; + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; #if USE_LIGHTMAP - result.LightmapUV = geometry.LightmapUV; + output.LightmapUV = geometry.LightmapUV; #endif - result.TBN = CalcTangentBasisFromWorldNormal(geometry.WorldNormal); - result.HolesMask = geometry.HolesMask; + output.TBN = CalcTangentBasisFromWorldNormal(geometry.WorldNormal); + output.HolesMask = geometry.HolesMask; #if USE_TERRAIN_LAYERS - result.Layers = geometry.Layers; + output.Layers = geometry.Layers; #endif + return output; } #if USE_TESSELLATION @@ -154,23 +156,13 @@ GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, flo MaterialInput GetMaterialInput(PixelInput input) { - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.Geometry.WorldPosition; - result.TexCoord = input.Geometry.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.Geometry.LightmapUV; -#endif - result.TBN = CalcTangentBasisFromWorldNormal(input.Geometry.WorldNormal); - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.SvPosition = input.Position; - result.HolesMask = input.Geometry.HolesMask; -#if USE_TERRAIN_LAYERS - result.Layers = input.Geometry.Layers; -#endif + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; #if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; + output.CustomVSToPS = input.CustomVSToPS; #endif - return result; + return output; } // Removes the scale vector from the local to world transformation matrix diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 6c83bfc35..577b1689e 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:0bf5ada0acae289fa7fbbbd6135779ef544d36999de9df0f29b2b9ce10ad52bb -size 30474 +oid sha256:15b814eb7cf2a263562004f3d440835ef50e6cf11be211c8b0171040ce87972d +size 29326 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index a3ae100ca..a29ea1649 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:4c8642a2a7024ae38ae371d95c3411d8e362f42ef98c54dc9da9c061d2a568c1 -size 36552 +oid sha256:6f0df0dea34c8105df1f26a60f5ba013e28d125297b1d630b62d6333ce3ec966 +size 35425 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index dc30e39fe..08aed3e07 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:bd0529d1088670eb9edd3533f9493d259bcc9797d0e01f12922f6f3b05daef4d -size 33136 +oid sha256:df1d754dc7a6046740e50ba20bc90278aa4894d83d01f41d7fdb50455e6d7763 +size 27923 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 49f76cb07..8e39ed806 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:820f0265ca7249f84f0c91b402f817dc5ca12a0a5884756b6d0bf712c12a9b06 -size 26815 +oid sha256:db149ec425c1baf245356eff3a819c3c06e857550e86d419c7bd70c55bde4b94 +size 21602 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 0a68763cd..cc3b32eb9 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:9029bd6c09c686f551356800b6f16c60f7392fb3a396d2fe583e6cb1f83c46f7 -size 10907 +oid sha256:f92a749ea24fc5d7b45e0e4f2be5619402163f4292d565e5fe014268a46a1275 +size 10653 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 9dedbfea2..2839044c1 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:cb08e24a379cd67db22025bf5e3ae84979f0686179db871387d388f11aae20f0 -size 35356 +oid sha256:0ce7aa7e754b27beda160a78e0636a6d6e12f99c59c127ca0557fc3d5a75285e +size 28995 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index cd7cd19e0..2472013e3 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:d195b3434472636eff8e34a2052f1ef5d02b1415802414bad6e15f21d0b16de8 -size 39864 +oid sha256:88a4aa4b72322807ce9fcf30faca1d2e20ae78a65bdefd29ae16bf2596d93d8f +size 31898 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index be5145720..9104a5a20 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:98a8347591e895fccbe327f393d6376ad1113321daa8a7ad4f47d0753c2e76a1 -size 28681 +oid sha256:c82750f189ac803962c8b6d885b54aa3332af7249d4a53c00a6e530161d157e7 +size 23373 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index d11046007..fe51af1bb 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:502f451e0b232b4288a85a274d81d22a7053c84f174286a05043707dc75fe193 -size 38381 +oid sha256:0569cdb10e6c832ba0d741144e54e92c31e25b9e8555743ae21c6a1bd8cc784e +size 30199 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 24d56521b..f099cb9e4 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:e4b60416f9e2deb1f717860ae9a4cc1a74be0517db921526d21a79434b7506e7 -size 39603 +oid sha256:a29b3b287910fecb955b5627bca91a202cb1663f78ea642e4dfde911add86ee6 +size 31420 From da784e98e5913b8bc616d63a1ad722a9e26efd92 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 8 Feb 2021 15:44:38 +0100 Subject: [PATCH 164/222] Add Deformable material domain --- .../MaterialTemplates/Deformable.shader | 375 ++++++++++++++++++ Source/Editor/Surface/Archetypes/Material.cs | 1 + .../Viewport/Previews/MaterialPreview.cs | 3 + Source/Engine/Content/Assets/Material.cpp | 5 +- .../Materials/DeformableMaterialShader.cpp | 195 +++++++++ .../Materials/DeformableMaterialShader.h | 63 +++ Source/Engine/Graphics/Materials/IMaterial.h | 8 + .../Engine/Graphics/Materials/MaterialInfo.h | 5 + .../Graphics/Materials/MaterialShader.cpp | 8 +- Source/Engine/Graphics/Models/Mesh.cpp | 2 +- Source/Engine/Graphics/Models/Mesh.h | 2 +- Source/Engine/Renderer/DrawCall.h | 11 + .../MaterialGenerator/MaterialGenerator.cpp | 16 +- 13 files changed, 685 insertions(+), 9 deletions(-) create mode 100644 Content/Editor/MaterialTemplates/Deformable.shader create mode 100644 Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp create mode 100644 Source/Engine/Graphics/Materials/DeformableMaterialShader.h diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader new file mode 100644 index 000000000..43afa566a --- /dev/null +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -0,0 +1,375 @@ +// File generated by Flax Materials Editor +// Version: @0 + +#define MATERIAL 1 +@3 +#include "./Flax/Common.hlsl" +#include "./Flax/MaterialCommon.hlsl" +#include "./Flax/GBufferCommon.hlsl" +@7 +// Primary constant buffer (with additional material parameters) +META_CB_BEGIN(0, Data) +float4x4 ViewProjectionMatrix; +float4x4 WorldMatrix; +float4x4 LocalMatrix; +float4x4 ViewMatrix; +float3 ViewPos; +float ViewFar; +float3 ViewDir; +float TimeParam; +float4 ViewInfo; +float4 ScreenSize; +float3 WorldInvScale; +float WorldDeterminantSign; +float MeshMinZ; +float Segment; +float ChunksPerSegment; +float PerInstanceRandom; +float4 TemporalAAJitter; +float3 GeometrySize; +float MeshMaxZ; +@1META_CB_END + +// Shader resources +@2 +// The spline deformation buffer (stored as 4x3, 3 float4 behind each other) +Buffer SplineDeformation : register(t0); + +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD2; + float4 WorldTangent : TEXCOORD3; +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +#if USE_TESSELLATION + float TessellationMultiplier : TESS; +#endif +}; + +// Interpolants passed to the pixel shader +struct PixelInput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + bool IsFrontFace : SV_IsFrontFace; +}; + +// Material properties generation input +struct MaterialInput +{ + float3 WorldPosition; + float TwoSidedSign; + float2 TexCoord; +#if USE_VERTEX_COLOR + half4 VertexColor; +#endif + float3x3 TBN; + float4 SvPosition; + float3 PreSkinnedPosition; + float3 PreSkinnedNormal; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; +#endif +}; + +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_VERTEX_COLOR + output.VertexColor = geometry.VertexColor; +#endif + output.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +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; +#if USE_VERTEX_COLOR + output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; +#endif + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent = p0.WorldTangent * w0 + p1.WorldTangent * w1 + p2.WorldTangent * w2; + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + return output; +} + +#endif + +MaterialInput GetMaterialInput(PixelInput input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +// Removes the scale vector from the local to world transformation matrix +float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) +{ + localToWorld[0] *= WorldInvScale.x; + localToWorld[1] *= WorldInvScale.y; + localToWorld[2] *= WorldInvScale.z; + return localToWorld; +} + +// Transforms a vector from tangent space to world space +float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) +{ + return mul(tangentVector, input.TBN); +} + +// Transforms a vector from world space to tangent space +float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) +{ + return mul(input.TBN, worldVector); +} + +// Transforms a vector from world space to view space +float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) +{ + return mul(worldVector, (float3x3)ViewMatrix); +} + +// Transforms a vector from view space to world space +float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) +{ + return mul((float3x3)ViewMatrix, viewVector); +} + +// Transforms a vector from local space to world space +float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) +{ + float3x3 localToWorld = (float3x3)WorldMatrix; + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localVector, localToWorld); +} + +// Transforms a vector from local space to world space +float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) +{ + float3x3 localToWorld = (float3x3)WorldMatrix; + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localToWorld, worldVector); +} + +// Gets the current object position +float3 GetObjectPosition(MaterialInput input) +{ + return WorldMatrix[3].xyz; +} + +// Gets the current object size +float3 GetObjectSize(MaterialInput input) +{ + float4x4 world = WorldMatrix; + return GeometrySize * float3(world._m00, world._m11, world._m22); +} + +// Get the current object random value +float GetPerInstanceRandom(MaterialInput input) +{ + return PerInstanceRandom; +} + +// Get the current object LOD transition dither factor +float GetLODDitherFactor(MaterialInput input) +{ + return 0; +} + +// Gets the interpolated vertex color (in linear space) +float4 GetVertexColor(MaterialInput input) +{ +#if USE_VERTEX_COLOR + return input.VertexColor; +#else + return 1; +#endif +} + +float3 SampleLightmap(Material material, MaterialInput materialInput) +{ + return 0; +} + +@8 + +// Get material properties function (for vertex shader) +Material GetMaterialVS(MaterialInput input) +{ +@5 +} + +// Get material properties function (for domain shader) +Material GetMaterialDS(MaterialInput input) +{ +@6 +} + +// Get material properties function (for pixel shader) +Material GetMaterialPS(MaterialInput input) +{ +@4 +} + +// Calculates the transform matrix from mesh tangent space to local space +float3x3 CalcTangentToLocal(ModelInput input) +{ + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + 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); +} + +// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) +META_VS(true, FEATURE_LEVEL_ES2) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) +VertexOutput VS_SplineModel(ModelInput input) +{ + VertexOutput output; + + // Apply local transformation of the geometry before deformation + float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz; + + // Apply spline curve deformation + float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ)); + int splineIndex = (int)((Segment + splineAlpha) * ChunksPerSegment); + position.z = splineAlpha; + float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]); + position = mul(splineMatrix, float4(position, 1)); + + // Compute world space vertex position + output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = input.Color; +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToLocal = CalcTangentToLocal(input); + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)WorldMatrix); + float3x3 tangentToWorld = mul(tangentToLocal, localToWorld); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +// Vertex Shader function for Depth Pass +META_VS(true, FEATURE_LEVEL_ES2) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +float4 VS_Depth(ModelInput_PosOnly input) : SV_Position +{ + float3 worldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz; + float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); + return position; +} + +#if USE_DITHERED_LOD_TRANSITION + +void ClipLODTransition(PixelInput input) +{ +} + +#endif + +// Pixel Shader function for Depth Pass +META_PS(true, FEATURE_LEVEL_ES2) +void PS_Depth(PixelInput input) +{ +#if MATERIAL_MASKED || MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Perform per pixel clipping +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif +#if MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); +#endif +#endif +} + +@9 diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 6e828fca4..9edbedcfb 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -156,6 +156,7 @@ namespace FlaxEditor.Surface.Archetypes case MaterialDomain.Surface: case MaterialDomain.Terrain: case MaterialDomain.Particle: + case MaterialDomain.Deformable: { bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit; bool isTransparent = info.BlendMode == MaterialBlendMode.Transparent; diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index 6ff695b69..41275ef9a 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -172,6 +172,9 @@ namespace FlaxEditor.Viewport.Previews usePreviewActor = false; particleMaterial = _material; break; + case MaterialDomain.Deformable: + // TODO: preview Deformable material (eg. by using Spline with Spline Model) + break; default: throw new ArgumentOutOfRangeException(); } } diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 966253aba..2930aff24 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -392,12 +392,12 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) auto& info = _shaderHeader.Material.Info; const bool isSurfaceOrTerrain = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; - const bool useForward = (info.Domain == MaterialDomain::Surface && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; + const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; const bool useTess = info.TessellationMode != TessellationMethod::None && RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrain; const bool useDistortion = - (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Particle) && + (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && info.BlendMode != MaterialBlendMode::Opaque && (info.UsageFlags & MaterialUsageFlags::UseRefraction) != 0 && (info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0; @@ -455,6 +455,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "IS_DECAL", Numbers[info.Domain == MaterialDomain::Decal ? 1 : 0] }); options.Macros.Add({ "IS_TERRAIN", Numbers[info.Domain == MaterialDomain::Terrain ? 1 : 0] }); options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); + options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrain && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp new file mode 100644 index 000000000..5adb18470 --- /dev/null +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -0,0 +1,195 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "DeformableMaterialShader.h" +#include "MaterialShaderFeatures.h" +#include "MaterialParams.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/GPULimits.h" +#include "Engine/Engine/Time.h" +#include "Engine/Graphics/RenderTask.h" + +PACK_STRUCT(struct DeformableMaterialShaderData { + Matrix ViewProjectionMatrix; + Matrix WorldMatrix; + Matrix LocalMatrix; + Matrix ViewMatrix; + Vector3 ViewPos; + float ViewFar; + Vector3 ViewDir; + float TimeParam; + Vector4 ViewInfo; + Vector4 ScreenSize; + Vector3 WorldInvScale; + float WorldDeterminantSign; + float MeshMinZ; + float Segment; + float ChunksPerSegment; + float PerInstanceRandom; + Vector4 TemporalAAJitter; + Vector3 GeometrySize; + float MeshMaxZ; + }); + +DrawPass DeformableMaterialShader::GetDrawModes() const +{ + return _drawModes; +} + +void DeformableMaterialShader::Bind(BindParameters& params) +{ + // Prepare + auto context = params.GPUContext; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DeformableMaterialShaderData); + int32 srv = 1; + + // Setup features + if (_info.BlendMode != MaterialBlendMode::Opaque) + ForwardShadingFeature::Bind(params, cb, srv); + + // Setup parameters + MaterialParameter::BindMeta bindMeta; + bindMeta.Context = context; + bindMeta.Constants = cb; + bindMeta.Input = nullptr; + bindMeta.Buffers = nullptr; + bindMeta.CanSampleDepth = false; + bindMeta.CanSampleGBuffer = false; + MaterialParams::Bind(params.ParamsLink, bindMeta); + + // Setup material constants + { + Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); + Matrix::Transpose(drawCall.World, materialData->WorldMatrix); + Matrix::Transpose(drawCall.Deformable.LocalMatrix, materialData->LocalMatrix); + Matrix::Transpose(view.View, materialData->ViewMatrix); + materialData->ViewPos = view.Position; + materialData->ViewFar = view.Far; + materialData->ViewDir = view.Direction; + materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); + materialData->ViewInfo = view.ViewInfo; + materialData->ScreenSize = view.ScreenSize; + const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); + const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); + const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); + materialData->WorldInvScale = Vector3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; + materialData->Segment = drawCall.Deformable.Segment; + materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment; + materialData->MeshMinZ = drawCall.Deformable.MeshMinZ; + materialData->MeshMaxZ = drawCall.Deformable.MeshMaxZ; + materialData->PerInstanceRandom = drawCall.PerInstanceRandom; + materialData->TemporalAAJitter = view.TemporalAAJitter; + materialData->GeometrySize = drawCall.Deformable.GeometrySize; + } + + // Bind spline deformation buffer + context->BindSR(0, drawCall.Deformable.SplineDeformation->View()); + + // Bind constants + if (_cb) + { + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); + } + + // Select pipeline state based on current pass and render mode + const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; + CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode; + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) + { + // Invert culling when scale is negative + if (cullMode == CullMode::Normal) + cullMode = CullMode::Inverted; + else + cullMode = CullMode::Normal; + } + PipelineStateCache* psCache = _cache.GetPS(view.Pass); + ASSERT(psCache); + GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); + + // Bind pipeline + context->SetState(state); +} + +void DeformableMaterialShader::Unload() +{ + // Base + MaterialShader::Unload(); + + _cache.Release(); +} + +bool DeformableMaterialShader::Load() +{ + _drawModes = DrawPass::Depth; + auto psDesc = GPUPipelineState::Description::Default; + psDesc.DepthTestEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthTest) == 0; + psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == 0; + + // Check if use tessellation (both material and runtime supports it) + const bool useTess = _info.TessellationMode != TessellationMethod::None && GPUDevice::Instance->Limits.HasTessellation; + if (useTess) + { + psDesc.HS = _shader->GetHS("HS"); + psDesc.DS = _shader->GetDS("DS"); + } + + if (_info.BlendMode == MaterialBlendMode::Opaque) + { + _drawModes = DrawPass::GBuffer; + + // GBuffer Pass + psDesc.VS = _shader->GetVS("VS_SplineModel"); + psDesc.PS = _shader->GetPS("PS_GBuffer"); + _cache.Default.Init(psDesc); + } + else + { + _drawModes = DrawPass::Forward; + + // Forward Pass + psDesc.VS = _shader->GetVS("VS_SplineModel"); + psDesc.PS = _shader->GetPS("PS_Forward"); + psDesc.DepthWriteEnable = false; + psDesc.BlendMode = BlendingMode::AlphaBlend; + switch (_info.BlendMode) + { + case MaterialBlendMode::Transparent: + psDesc.BlendMode = BlendingMode::AlphaBlend; + break; + case MaterialBlendMode::Additive: + psDesc.BlendMode = BlendingMode::Additive; + break; + case MaterialBlendMode::Multiply: + psDesc.BlendMode = BlendingMode::Multiply; + break; + } + _cache.Default.Init(psDesc); + } + + // Depth Pass + psDesc.CullMode = CullMode::TwoSided; + psDesc.DepthClipEnable = false; + psDesc.DepthWriteEnable = true; + psDesc.DepthTestEnable = true; + psDesc.DepthFunc = ComparisonFunc::Less; + psDesc.HS = nullptr; + psDesc.DS = nullptr; + _cache.Depth.Init(psDesc); + + return false; +} diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h new file mode 100644 index 000000000..f2c796a23 --- /dev/null +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "MaterialShader.h" + +/// +/// Represents material that can be used to render objects that can be deformed. +/// +class DeformableMaterialShader : public MaterialShader +{ +private: + + struct Cache + { + PipelineStateCache Default; + PipelineStateCache Depth; + + FORCE_INLINE PipelineStateCache* GetPS(const DrawPass pass) + { + switch (pass) + { + case DrawPass::Depth: + return &Depth; + case DrawPass::GBuffer: + case DrawPass::Forward: + return &Default; + default: + return nullptr; + } + } + + FORCE_INLINE void Release() + { + Default.Release(); + Depth.Release(); + } + }; + +private: + + Cache _cache; + DrawPass _drawModes = DrawPass::None; + +public: + + DeformableMaterialShader(const String& name) + : MaterialShader(name) + { + } + +public: + + // [MaterialShader] + DrawPass GetDrawModes() const override; + void Bind(BindParameters& params) override; + void Unload() override; + +protected: + + // [MaterialShader] + bool Load() override; +}; diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index e85328133..4d61656e8 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -74,6 +74,14 @@ public: return GetInfo().Domain == MaterialDomain::Particle; } + /// + /// Determines whether material is a deformable shader. + /// + FORCE_INLINE bool IsDeformable() const + { + return GetInfo().Domain == MaterialDomain::Deformable; + } + /// /// Returns true if material is ready for rendering. /// diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h index 50b4c1e35..862f8f221 100644 --- a/Source/Engine/Graphics/Materials/MaterialInfo.h +++ b/Source/Engine/Graphics/Materials/MaterialInfo.h @@ -39,6 +39,11 @@ API_ENUM() enum class MaterialDomain : byte /// The particle shader. Can be used only with particles geometry (sprites, trails and ribbons). Supports reading particle data on a GPU. /// Particle = 5, + + /// + /// The deformable shader. Can be used only with objects that can be deformed (spline models). + /// + Deformable = 6, }; /// diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 2d7998b8a..caac3b5f2 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -14,7 +14,7 @@ #include "GUIMaterialShader.h" #include "TerrainMaterialShader.h" #include "ParticleMaterialShader.h" -//#include "DeformableMaterialShader.h" +#include "DeformableMaterialShader.h" GPUPipelineState* MaterialShader::PipelineStateCache::InitPS(CullMode mode, bool wireframe) { @@ -62,9 +62,9 @@ MaterialShader* MaterialShader::Create(const String& name, MemoryReadStream& sha case MaterialDomain::Particle: material = New(name); break; - /*case MaterialDomain::Deformable: - material = New(name); - break;*/ + case MaterialDomain::Deformable: + material = New(name); + break; default: LOG(Fatal, "Unknown material type."); return nullptr; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 99262cd51..e7007f4ec 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -350,7 +350,7 @@ bool Mesh::Intersects(const Ray& ray, const Matrix& world, float& distance, Vect #endif } -void Mesh::GetDrawCallGeometry(DrawCall& drawCall) +void Mesh::GetDrawCallGeometry(DrawCall& drawCall) const { drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 80d566fa3..94d4979a3 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -377,7 +377,7 @@ public: /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. /// /// The draw call. - void GetDrawCallGeometry(DrawCall& drawCall); + void GetDrawCallGeometry(DrawCall& drawCall) const; /// /// Model instance drawing packed data. diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 0e91366e2..bb25c0a57 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -211,6 +211,17 @@ struct DrawCall } Ribbon; } Particle; + struct + { + GPUBuffer* SplineDeformation; + Matrix LocalMatrix; // Geometry transformation applied before deformation. + Vector3 GeometrySize; // Object geometry size in the world (unscaled). + float Segment; + float ChunksPerSegment; + float MeshMinZ; + float MeshMaxZ; + } Deformable; + struct { byte Raw[96]; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 2f54efe82..a3a599529 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -206,6 +206,14 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ADD_FEATURE(DistortionFeature); ADD_FEATURE(ForwardShadingFeature); break; + case MaterialDomain::Deformable: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(DeferredShadingFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + ADD_FEATURE(ForwardShadingFeature); + break; default: break; } @@ -228,7 +236,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo { materialVarPS = Value(VariantType::Void, baseLayer->GetVariableName(nullptr)); _writer.Write(TEXT("\tMaterial {0} = (Material)0;\n"), materialVarPS.Value); - if (baseLayer->Domain == MaterialDomain::Surface || baseLayer->Domain == MaterialDomain::Terrain || baseLayer->Domain == MaterialDomain::Particle) + if (baseLayer->Domain == MaterialDomain::Surface || baseLayer->Domain == MaterialDomain::Terrain || baseLayer->Domain == MaterialDomain::Particle || baseLayer->Domain == MaterialDomain::Deformable) { eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Emissive); eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Normal); @@ -410,6 +418,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo case MaterialDomain::Particle: srv = 2; // Particles data + Sorted indices/Ribbon segments break; + case MaterialDomain::Deformable: + srv = 1; // Mesh deformation buffer + break; } for (auto f : features) { @@ -489,6 +500,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo case MaterialDomain::Particle: path /= TEXT("Particle.shader"); break; + case MaterialDomain::Deformable: + path /= TEXT("Deformable.shader"); + break; default: LOG(Warning, "Unknown material domain."); return true; From 8e87c986446095837f7e11996729964b87fb7873 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 8 Feb 2021 15:45:22 +0100 Subject: [PATCH 165/222] Cleanup material shaders code --- .../Editor/MaterialTemplates/Particle.shader | 10 +--------- .../Editor/MaterialTemplates/Surface.shader | 19 +++++-------------- .../Editor/MaterialTemplates/Terrain.shader | 10 +--------- .../Materials/DecalMaterialShader.cpp | 2 +- .../Materials/DeferredMaterialShader.cpp | 8 ++------ .../Materials/ForwardMaterialShader.cpp | 9 ++------- .../Graphics/Materials/GUIMaterialShader.cpp | 2 +- .../Materials/ParticleMaterialShader.cpp | 2 +- .../Materials/PostFxMaterialShader.cpp | 2 +- .../Materials/TerrainMaterialShader.cpp | 3 +-- 10 files changed, 16 insertions(+), 51 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 109fe668b..726ad225a 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -679,11 +679,7 @@ VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { // Get material parameters MaterialInput materialInput = GetMaterialInput(input); @@ -696,10 +692,6 @@ void PS_Depth(PixelInput input #if MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); #endif - -#if GLSL - OutColor = 0; -#endif } @9 diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index f3cc8499e..1038a73a3 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -43,9 +43,9 @@ struct GeometryData #endif float3 WorldNormal : TEXCOORD3; float4 WorldTangent : TEXCOORD4; - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor - float3 PrevWorldPosition : TEXCOORD8; + float3 InstanceOrigin : TEXCOORD5; + float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor + float3 PrevWorldPosition : TEXCOORD7; }; // Interpolants passed from the vertex shader @@ -475,8 +475,7 @@ float3x4 GetBoneMatrix(ModelInput_Skinned input) // Transforms the vertex position by weighted sum of the skinning matrices float3 SkinPosition(ModelInput_Skinned input, SkinningData data) { - float4 position = float4(input.Position.xyz, 1); - return mul(data.BlendMatrix, position); + return mul(data.BlendMatrix, float4(input.Position.xyz, 1)); } // Transforms the vertex position by weighted sum of the skinning matrices @@ -596,11 +595,7 @@ void ClipLODTransition(PixelInput input) // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { #if USE_DITHERED_LOD_TRANSITION // LOD masking @@ -620,10 +615,6 @@ void PS_Depth(PixelInput input clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); #endif #endif - -#if GLSL - OutColor = 0; -#endif } @9 diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index d7624dfd6..b1c3a2910 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -448,11 +448,7 @@ VertexOutput VS(TerrainVertexInput input) // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { #if MATERIAL_MASKED // Perform per pixel clipping if material requries it @@ -460,10 +456,6 @@ void PS_Depth(PixelInput input Material material = GetMaterialPS(materialInput); clip(material.Mask - MATERIAL_MASK_THRESHOLD); #endif - -#if GLSL - OutColor = 0; -#endif } @9 diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index e141996dc..d8d68e912 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -56,7 +56,7 @@ void DecalMaterialShader::Bind(BindParameters& params) // Decals use depth buffer to draw on top of the objects context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer)); - // Setup material constants data + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index d8b40c3a6..babfaf830 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -79,30 +79,26 @@ void DeferredMaterialShader::Bind(BindParameters& params) bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - const Vector3 worldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index addd7e96a..2ea17c9cd 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -85,31 +85,26 @@ void ForwardMaterialShader::Bind(BindParameters& params) context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); } - // Setup material constants data + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - const Vector3 worldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 9e1d9ea21..769d36557 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -44,7 +44,7 @@ void GUIMaterialShader::Bind(BindParameters& params) bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data + // Setup material constants { const auto viewProjectionMatrix = (Matrix*)params.CustomData; Matrix::Transpose(*viewProjectionMatrix, materialData->ViewProjectionMatrix); diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index eda7fff22..7a718ceb7 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -145,7 +145,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) ASSERT(psCache); GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); - // Setup material constants data + // Setup material constants { static StringView ParticlePosition(TEXT("Position")); static StringView ParticleSpriteSize(TEXT("SpriteSize")); diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 890275f68..a55455173 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -41,7 +41,7 @@ void PostFxMaterialShader::Bind(BindParameters& params) bindMeta.CanSampleGBuffer = true; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data + // Setup material constants { Matrix::Transpose(view.View, materialData->ViewMatrix); materialData->ViewPos = view.Position; diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 80988e405..ec3c038a5 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -72,12 +72,11 @@ void TerrainMaterialShader::Bind(BindParameters& params) bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; From 85167b2d4b8d7229f2d829b32a683f9212cde772 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 8 Feb 2021 15:45:39 +0100 Subject: [PATCH 166/222] Fix using Bezier curve for Transform --- Source/Engine/Animations/AnimationUtils.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index c90afcea3..5df8cc2c0 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -70,7 +70,7 @@ namespace AnimationUtils FORCE_INLINE void GetTangent(const Quaternion& a, const Quaternion& b, float length, Quaternion& result) { const float oneThird = 1.0f / 3.0f; - Quaternion::Slerp(a, b, oneThird, result); + Quaternion::Slerp(a, b, length * oneThird, result); } template<> @@ -79,8 +79,8 @@ namespace AnimationUtils const float oneThird = 1.0f / 3.0f; const float oneThirdLength = length * oneThird; result.Translation = a.Translation + b.Translation * oneThirdLength; - Quaternion::Slerp(a.Orientation, b.Orientation, oneThird, result.Orientation); - result.Scale = a.Scale + b.Scale * oneThirdLength; + Quaternion::Slerp(a.Orientation, b.Orientation, oneThirdLength, result.Orientation); + result.Scale = a.Scale + (b.Scale - a.Scale) * oneThirdLength; } template @@ -101,6 +101,14 @@ namespace AnimationUtils Quaternion::Slerp(a, b, t, result); } + template<> + FORCE_INLINE void Interpolate(const Transform& a, const Transform& b, float t, Transform& result) + { + Vector3::Lerp(a.Translation, b.Translation, t, result.Translation); + Quaternion::Slerp(a.Orientation, b.Orientation, t, result.Orientation); + Vector3::Lerp(a.Scale, b.Scale, t, result.Scale); + } + static void WrapTime(float& time, float start, float end, bool loop) { const float length = end - start; From 2b962e43893519e223547873cccdeb644a418d53 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 9 Feb 2021 10:58:13 +0100 Subject: [PATCH 167/222] Fix invalid comment --- Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs index 3415501bd..32a408828 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs @@ -312,7 +312,7 @@ namespace Flax.Deps.Dependencies } // Get the source - //CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1"); + CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1"); foreach (var platform in options.Platforms) { From 311dad7b824d9b9c539aeb0bfd3c068e31a6ea20 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 9 Feb 2021 16:04:47 +0100 Subject: [PATCH 168/222] Add Spline Model --- .../MaterialTemplates/Deformable.shader | 32 +- Source/Editor/SceneGraph/Actors/SplineNode.cs | 29 ++ Source/Engine/Content/Assets/Material.cpp | 6 +- .../Materials/DeformableMaterialShader.cpp | 9 +- Source/Engine/Level/Actors/SplineModel.cpp | 473 ++++++++++++++++++ Source/Engine/Level/Actors/SplineModel.h | 116 +++++ 6 files changed, 639 insertions(+), 26 deletions(-) create mode 100644 Source/Engine/Level/Actors/SplineModel.cpp create mode 100644 Source/Engine/Level/Actors/SplineModel.h diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader index 43afa566a..044f7184c 100644 --- a/Content/Editor/MaterialTemplates/Deformable.shader +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -19,7 +19,7 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float3 WorldInvScale; +float3 Dummy0; float WorldDeterminantSign; float MeshMinZ; float Segment; @@ -148,9 +148,17 @@ MaterialInput GetMaterialInput(PixelInput input) // Removes the scale vector from the local to world transformation matrix float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) { - localToWorld[0] *= WorldInvScale.x; - localToWorld[1] *= WorldInvScale.y; - localToWorld[2] *= WorldInvScale.z; + // Extract per axis scales from localToWorld transform + float scaleX = length(localToWorld[0]); + float scaleY = length(localToWorld[1]); + float scaleZ = length(localToWorld[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + localToWorld[0] *= invScale.x; + localToWorld[1] *= invScale.y; + localToWorld[2] *= invScale.z; return localToWorld; } @@ -278,6 +286,7 @@ VertexOutput VS_SplineModel(ModelInput input) // Apply local transformation of the geometry before deformation float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz; + float4x4 world = LocalMatrix; // Apply spline curve deformation float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ)); @@ -285,9 +294,12 @@ VertexOutput VS_SplineModel(ModelInput input) position.z = splineAlpha; float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]); position = mul(splineMatrix, float4(position, 1)); + float4x3 splineMatrixT = transpose(splineMatrix); + world = mul(world, float4x4(float4(splineMatrixT[0], 0), float4(splineMatrixT[1], 0), float4(splineMatrixT[2], 0), float4(splineMatrixT[3], 1))); // Compute world space vertex position output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + world = mul(world, WorldMatrix); // Compute clip space position output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); @@ -300,7 +312,7 @@ VertexOutput VS_SplineModel(ModelInput input) // Calculate tanget space to world space transformation matrix for unit vectors float3x3 tangentToLocal = CalcTangentToLocal(input); - float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)WorldMatrix); + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); float3x3 tangentToWorld = mul(tangentToLocal, localToWorld); output.Geometry.WorldNormal = tangentToWorld[2]; output.Geometry.WorldTangent.xyz = tangentToWorld[0]; @@ -335,16 +347,6 @@ VertexOutput VS_SplineModel(ModelInput input) return output; } -// Vertex Shader function for Depth Pass -META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -float4 VS_Depth(ModelInput_PosOnly input) : SV_Position -{ - float3 worldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz; - float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); - return position; -} - #if USE_DITHERED_LOD_TRANSITION void ClipLODTransition(PixelInput input) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 179f3267e..1eeb55511 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.Json; @@ -178,6 +179,11 @@ namespace FlaxEditor.SceneGraph.Actors } } + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + public static SceneGraphNode Create(StateData state) { var data = JsonSerializer.Deserialize(state.State); @@ -239,6 +245,11 @@ namespace FlaxEditor.SceneGraph.Actors DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false); } + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + public override void OnDispose() { _node = null; @@ -292,6 +303,24 @@ namespace FlaxEditor.SceneGraph.Actors spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); } + /// + public override void OnContextMenu(ContextMenu contextMenu) + { + base.OnContextMenu(contextMenu); + + contextMenu.AddButton("Add spline model", OnAddSplineMode); + } + + private void OnAddSplineMode() + { + var actor = new SplineModel + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + /// public override void OnDispose() { diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 2930aff24..4d1e5d283 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -390,12 +390,12 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) // Prepare auto& info = _shaderHeader.Material.Info; - const bool isSurfaceOrTerrain = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain; + const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; const bool useTess = info.TessellationMode != TessellationMethod::None && - RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrain; + RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable; const bool useDistortion = (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && info.BlendMode != MaterialBlendMode::Opaque && @@ -457,7 +457,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); - options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrain && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); + options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); #endif } diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp index 5adb18470..dafe13d46 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -26,7 +26,7 @@ PACK_STRUCT(struct DeformableMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Vector3 WorldInvScale; + Vector3 Dummy0; float WorldDeterminantSign; float MeshMinZ; float Segment; @@ -79,13 +79,6 @@ void DeformableMaterialShader::Bind(BindParameters& params) materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); - const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); - const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - materialData->WorldInvScale = Vector3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; materialData->Segment = drawCall.Deformable.Segment; materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp new file mode 100644 index 000000000..7380bf920 --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -0,0 +1,473 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineModel.h" +#include "Spline.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Core/Math/Matrix3x4.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#endif + +#define SPLINE_RESOLUTION 32.0f + +SplineModel::SplineModel(const SpawnParams& params) + : ModelInstanceActor(params) +{ + Model.Changed.Bind(this); + Model.Loaded.Bind(this); +} + +SplineModel::~SplineModel() +{ + SAFE_DELETE_GPU_RESOURCE(_deformationBuffer); + if (_deformationBufferData) + Allocator::Free(_deformationBufferData); +} + +float SplineModel::GetQuality() const +{ + return _quality; +} + +void SplineModel::SetQuality(float value) +{ + value = Math::Clamp(value, 0.0f, 100.0f); + if (Math::NearEqual(value, _quality)) + return; + _quality = value; + OnSplineUpdated(); +} + +float SplineModel::GetBoundsScale() const +{ + return _boundsScale; +} + +void SplineModel::SetBoundsScale(float value) +{ + if (Math::NearEqual(_boundsScale, value)) + return; + _boundsScale = value; + OnSplineUpdated(); +} + +int32 SplineModel::GetLODBias() const +{ + return static_cast(_lodBias); +} + +void SplineModel::SetLODBias(int32 value) +{ + _lodBias = static_cast(Math::Clamp(value, -100, 100)); +} + +int32 SplineModel::GetForcedLOD() const +{ + return static_cast(_forcedLod); +} + +void SplineModel::SetForcedLOD(int32 value) +{ + _forcedLod = static_cast(Math::Clamp(value, -1, 100)); +} + +void SplineModel::OnModelChanged() +{ + Entries.Release(); + + if (Model && !Model->IsLoaded()) + { + OnSplineUpdated(); + } +} + +void SplineModel::OnModelLoaded() +{ + Entries.SetupIfInvalid(Model); + + OnSplineUpdated(); +} + +void SplineModel::OnSplineUpdated() +{ + // Skip updates when actor is disabled or something is missing + if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || !IsDuringPlay() || _spline->GetSplinePointsCount() < 2) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + PROFILE_CPU(); + + // Setup model instances over the spline segments + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + const Transform splineTransform = _spline->GetTransform(); + _instances.Resize(segments, false); + BoundingBox localModelBounds; + { + auto& meshes = Model->LODs[0].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.GetCorners(corners); + + for (int32 i = 0; i < 8; i++) + { + //Vector3::Transform(corners[i], localTransform, tmp); + //_localTransform.LocalToWorld(corners[i], tmp); + + // Transform mesh corner using local spline model transformation but use double-precision to prevent issues when rotating model + tmp = corners[i] * _localTransform.Scale; + double rotation[4] = { (double)_localTransform.Orientation.X, (double)_localTransform.Orientation.Y, (double)_localTransform.Orientation.Z, (double)_localTransform.Orientation.W }; + const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); + const double inv = 1.0 / length; + rotation[0] *= inv; + rotation[1] *= inv; + rotation[2] *= inv; + rotation[3] *= inv; + double pos[3] = { (double)tmp.X, (double)tmp.Y, (double)tmp.Z }; + const double x = rotation[0] + rotation[0]; + const double y = rotation[1] + rotation[1]; + const double z = rotation[2] + rotation[2]; + const double wx = rotation[3] * x; + const double wy = rotation[3] * y; + const double wz = rotation[3] * z; + const double xx = rotation[0] * x; + const double xy = rotation[0] * y; + const double xz = rotation[0] * z; + const double yy = rotation[1] * y; + const double yz = rotation[1] * z; + const double zz = rotation[2] * z; + tmp = Vector3( + (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)), + (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)), + (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy))); + Vector3::Add(tmp, _localTransform.Translation, tmp); + + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + localModelBounds = BoundingBox(min, max); + } + _meshMinZ = localModelBounds.Minimum.Z; + _meshMaxZ = localModelBounds.Maximum.Z; + Transform chunkLocal, chunkWorld, leftTangent, rightTangent; + Array segmentPoints; + segmentPoints.Resize(chunksPerSegment); + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Find maximum scale over the segment spline and collect the segment positions for bounds + segmentPoints.Clear(); + segmentPoints.Add(end.Value.Translation); + float maxScale = end.Value.Scale.GetAbsolute().MaxValue(); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, chunkLocal); + splineTransform.LocalToWorld(chunkLocal, chunkWorld); + segmentPoints.Add(chunkWorld.Translation); + maxScale = Math::Max(maxScale, chunkWorld.Scale.GetAbsolute().MaxValue()); + } + maxScale = Math::Max(maxScale, _localTransform.Scale.GetAbsolute().MaxValue()); + BoundingSphere::FromPoints(segmentPoints.Get(), segmentPoints.Count(), instance.Sphere); + instance.Sphere.Center += _localTransform.Translation; + instance.Sphere.Radius *= maxScale * _boundsScale; + } + + // Update deformation buffer during next drawing + _deformationDirty = true; + + // Update bounds + _sphere = _instances.First().Sphere; + for (int32 i = 1; i < _instances.Count(); i++) + BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); + BoundingBox::FromSphere(_sphere, _box); +} + +void SplineModel::UpdateDeformationBuffer() +{ + PROFILE_CPU(); + + // Deformation buffer contains precomputed matrices for each chunk of the spline segment (packed with transposed float3x4 matrix) + _deformationDirty = false; + if (!_deformationBuffer) + _deformationBuffer = GPUDevice::Instance->CreateBuffer(GetName()); + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const int32 count = (chunksPerSegment * segments + 1) * 3; + const uint32 size = count * sizeof(Vector4); + if (_deformationBuffer->GetSize() != size) + { + if (_deformationBufferData) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } + if (_deformationBuffer->Init(GPUBufferDescription::Typed(count, PixelFormat::R32G32B32A32_Float, false, IsTransformStatic() ? GPUResourceUsage::Default : GPUResourceUsage::Dynamic))) + { + LOG(Error, "Failed to initialize the spline model {0} deformation buffer.", ToString()); + return; + } + } + if (!_deformationBufferData) + _deformationBufferData = Allocator::Allocate(size); + _chunksPerSegment = (float)chunksPerSegment; + + // Update pre-calculated matrices for spline chunks + auto ptr = (Matrix3x4*)_deformationBufferData; + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + Matrix m; + Transform transform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + + // Write transform into deformation buffer + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + ptr++; + } + instance.RotDeterminant = m.RotDeterminant(); + } + + // Add last transformation to prevent issues when sampling spline deformation buffer with alpha=1 + { + const auto& start = keyframes[segments - 1]; + const auto& end = keyframes[segments]; + const float length = end.Time - start.Time; + const float alpha = 1.0f - ZeroTolerance; // Offset to prevent zero derivative at the end of the curve + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + } + + // Flush data with GPU + auto context = GPUDevice::Instance->GetMainContext(); + context->UpdateBuffer(_deformationBuffer, _deformationBufferData, size); + + // Static splines are rarely updated so release scratch memory + if (IsTransformStatic()) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } +} + +void SplineModel::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Actor::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +bool SplineModel::HasContentLoaded() const +{ + return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); +} + +void SplineModel::Draw(RenderContext& renderContext) +{ + const DrawPass actorDrawModes = (DrawPass)(DrawModes & renderContext.View.Pass); + if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None) + return; + auto model = Model.Get(); + if (!Entries.IsValidFor(model)) + Entries.Setup(model); + + // Build mesh deformation buffer for the whole spline + if (_deformationDirty) + UpdateDeformationBuffer(); + + // Draw all segments + DrawCall drawCall; + drawCall.InstanceCount = 1; + drawCall.IndirectArgsBuffer = nullptr; + drawCall.IndirectArgsOffset = 0; + drawCall.Deformable.SplineDeformation = _deformationBuffer; + drawCall.Deformable.ChunksPerSegment = _chunksPerSegment; + drawCall.Deformable.MeshMinZ = _meshMinZ; + drawCall.Deformable.MeshMaxZ = _meshMaxZ; + drawCall.Deformable.GeometrySize = _box.GetSize(); + drawCall.PerInstanceRandom = GetPerInstanceRandom(); + _localTransform.GetWorld(drawCall.Deformable.LocalMatrix); + const Transform splineTransform = _spline->GetTransform(); + splineTransform.GetWorld(drawCall.World); + drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); + const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); + for (int32 segment = 0; segment < _instances.Count(); segment++) + { + auto& instance = _instances[segment]; + if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere)) + continue; + drawCall.Deformable.Segment = (float)segment; + + // Select a proper LOD index (model may be culled) + int32 lodIndex; + if (_forcedLod != -1) + { + lodIndex = _forcedLod; + } + else + { + lodIndex = RenderTools::ComputeModelLOD(model, instance.Sphere.Center, instance.Sphere.Radius, renderContext); + if (lodIndex == -1) + continue; + } + lodIndex += _lodBias + renderContext.View.ModelLODBias; + lodIndex = model->ClampLODIndex(lodIndex); + + // Draw + const auto& lod = model->LODs[lodIndex]; + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + const auto mesh = &lod.Meshes[i]; + + // Cache data + const auto& entry = Entries[mesh->GetMaterialSlotIndex()]; + if (!entry.Visible || !mesh->IsInitialized()) + continue; + const MaterialSlot& slot = model->MaterialSlots[mesh->GetMaterialSlotIndex()]; + + // Check if skip rendering + const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); + const auto drawModes = static_cast(actorDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)); + if (drawModes == DrawPass::None) + continue; + + // Select material + MaterialBase* material; + if (entry.Material && entry.Material->IsLoaded()) + material = entry.Material; + else if (slot.Material && slot.Material->IsLoaded()) + material = slot.Material; + else + material = nullptr; + if (!material || !material->IsDeformable()) + continue; + + // Submit draw call + mesh->GetDrawCallGeometry(drawCall); + drawCall.Material = material; + drawCall.WorldDeterminantSign = Math::FloatSelect(worldDeterminantSign * instance.RotDeterminant, 1, -1); + renderContext.List->AddDrawCall(drawModes, _staticFlags, drawCall, entry.ReceiveDecals); + } + } +} + +void SplineModel::DrawGeneric(RenderContext& renderContext) +{ + Draw(renderContext); +} + +bool SplineModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + return false; +} + +void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + ModelInstanceActor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineModel); + + SERIALIZE_MEMBER(Quality, _quality); + SERIALIZE_MEMBER(BoundsScale, _boundsScale); + SERIALIZE_MEMBER(LODBias, _lodBias); + SERIALIZE_MEMBER(ForcedLOD, _forcedLod); + SERIALIZE(Model); + SERIALIZE(DrawModes); + + stream.JKEY("Buffer"); + stream.Object(&Entries, other ? &other->Entries : nullptr); +} + +void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + ModelInstanceActor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(Quality, _quality); + DESERIALIZE_MEMBER(BoundsScale, _boundsScale); + DESERIALIZE_MEMBER(LODBias, _lodBias); + DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); + DESERIALIZE(Model); + DESERIALIZE(DrawModes); + + Entries.DeserializeIfExists(stream, "Buffer", modifier); +} + +void SplineModel::OnTransformChanged() +{ + // Base + ModelInstanceActor::OnTransformChanged(); + + OnSplineUpdated(); +} diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h new file mode 100644 index 000000000..a7d53af49 --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ModelInstanceActor.h" +#include "Engine/Content/Assets/Model.h" + +class Spline; + +/// +/// Renders model over the spline segments. +/// +API_CLASS() class FLAXENGINE_API SplineModel : public ModelInstanceActor +{ +DECLARE_SCENE_OBJECT(SplineModel); +private: + + struct Instance + { + BoundingSphere Sphere; + float RotDeterminant; + }; + + float _boundsScale = 1.0f, _quality = 1.0f; + char _lodBias = 0; + char _forcedLod = -1; + bool _deformationDirty = false; + Array _instances; + Spline* _spline = nullptr; + GPUBuffer* _deformationBuffer = nullptr; + void* _deformationBufferData = nullptr; + float _chunksPerSegment, _meshMinZ, _meshMaxZ; + +public: + + ~SplineModel(); + + /// + /// The model asset to draw. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")") + AssetReference Model; + + /// + /// The draw passes to use for rendering this object. + /// + API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")") + DrawPass DrawModes = DrawPass::Default; + + /// + /// Gets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY(Attributes="EditorOrder(11), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0.1f, 100.0f, 0.1f)") + float GetQuality() const; + + /// + /// Sets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY() void SetQuality(float value); + + /// + /// Gets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY(Attributes="EditorOrder(12), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0, 10.0f, 0.1f)") + float GetBoundsScale() const; + + /// + /// Sets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY() void SetBoundsScale(float value); + + /// + /// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Model\", \"LOD Bias\")") + int32 GetLODBias() const; + + /// + /// Sets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY() void SetLODBias(int32 value); + + /// + /// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Model\", \"Forced LOD\")") + int32 GetForcedLOD() const; + + /// + /// Sets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY() void SetForcedLOD(int32 value); + +private: + + void OnModelChanged(); + void OnModelLoaded(); + void OnSplineUpdated(); + void UpdateDeformationBuffer(); + +public: + + // [ModelInstanceActor] + bool HasContentLoaded() const override; + void Draw(RenderContext& renderContext) override; + void DrawGeneric(RenderContext& renderContext) override; + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + +protected: + + // [ModelInstanceActor] + void OnTransformChanged() override; +}; From 15c4b7df54ae72c1fe59b9f02df73a49b56d7e82 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 9 Feb 2021 16:08:51 +0100 Subject: [PATCH 169/222] Update materials --- Content/Editor/Camera/M_Camera.flax | 4 ++-- Content/Editor/CubeTexturePreviewMaterial.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/VertexColorsPreviewMaterial.flax | 4 ++-- 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/Editor/Terrain/Circle Brush Material.flax | 4 ++-- Content/Editor/Terrain/Highlight Terrain Material.flax | 4 ++-- Content/Editor/Wires Debug Material.flax | 4 ++-- Content/Engine/DefaultMaterial.flax | 4 ++-- Content/Engine/DefaultTerrainMaterial.flax | 4 ++-- Content/Engine/SingleColorMaterial.flax | 4 ++-- Content/Engine/SkyboxMaterial.flax | 4 ++-- 18 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 9e2154ed9..768cf1986 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:919822dc6234c58fe9efa48676845cb3f780cc2ad3a8b8edcb2badb6cfb8183c -size 30119 +oid sha256:2d62539b39925798a3d526d20b3fc4e30c268629427c3920e1ece9f4c446ec45 +size 29995 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index c16307620..f4047b652 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:16b75ef1da889ff19d3f7cbd52073358cd0a433cd84912bd9802e3c21fcf90f2 -size 31676 +oid sha256:33afbfa5c2b3e24499de14fdabf87bdc809ce703f859b718241e203115db3961 +size 31552 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index 9fc057b01..3cb4ff8dd 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:7addee999e9325dac84f0ed70a817dbbdd549b84dfa7d0260403cca89abb399a -size 30200 +oid sha256:bdca03c927766e9d4bb5409e2507b3a9d22f13342fad159182fdd8da122312cb +size 30076 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index 8b33f3404..8c87ae3b3 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:792fd8ca00b3ad5b06a43a66cbca624e96af845c157519bf6038c08ae3d6dcd6 -size 34661 +oid sha256:5129d65fa3a24af617b25b9058294b89e65290581b2992e77ba46a12ade7a8ee +size 34537 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index fb9729627..6bc4e8f93 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:28de48669af8729327e4d3797076c1c28674ec86edf90d365d5f7426d7f23f01 -size 31187 +oid sha256:1e470f838ed5ff78b311169f974a3832b387730be2030dc67e3ddf0d601dc3c0 +size 31063 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index d0a37c641..2b85a3941 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:e00d2943edb8ab1a1cdd2c7adfabc5e418fd9c0f414ed54a97ba33a3c1b23aac -size 30400 +oid sha256:083633783ea5733720803b285a2378a6130fec506b9a2abe93b146cdf736d0ed +size 30276 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index 5dbab8006..1314e5596 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:d5c14383f163282e144d9af8d89a817ea4f450ef200164dcd6f4af1406936d4a -size 31134 +oid sha256:770a2498f0a712ae1705fd74204c62ded862b73613ed8cec70974bee0802350a +size 31010 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 48ec19fdb..824fbf090 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:6bf05e8133b46a764b6bda2a0b80a9a7dd44fcf3a9169e4da7a99d72fade6c67 -size 28995 +oid sha256:50fcefc75caa48fe73ed23bf3fe2c023f9a6ac146d3a3bc16b4386a39aaa56f7 +size 28871 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 30c96a8ef..e7b91ff38 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:6820dc041a4dc8fa66ac4dbacaf28efffb9a7083ac16b53850d8bfdb3b4fe80a -size 28927 +oid sha256:aae6e991a3623c9c84165c30fceab674d4173bb27303a164bd3dcef61f30654d +size 28803 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 577b1689e..e2fdb046b 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:15b814eb7cf2a263562004f3d440835ef50e6cf11be211c8b0171040ce87972d -size 29326 +oid sha256:14d75ef13449f9ed9a6e18ee82fe59fe4e83ac519b62b1e87aaadabd89673995 +size 29232 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index a29ea1649..66c3466e8 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:6f0df0dea34c8105df1f26a60f5ba013e28d125297b1d630b62d6333ce3ec966 -size 35425 +oid sha256:586e1e4620df43c638ca5a059dc1b705a82b47f398c973dd526e3f58570cf645 +size 35331 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index 08aed3e07..30093ee5e 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:df1d754dc7a6046740e50ba20bc90278aa4894d83d01f41d7fdb50455e6d7763 -size 27923 +oid sha256:2ce01e5aeaf46ed0ed2285b2b6c5b0d8f0bebdb0a7f5fbd246eef22a8ead21fa +size 27520 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 8e39ed806..a942d7848 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:db149ec425c1baf245356eff3a819c3c06e857550e86d419c7bd70c55bde4b94 -size 21602 +oid sha256:3f8b40c2622b1422ae062bafec002cd120e160f2ed56aab10e3390ba3dbd3a55 +size 21199 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 2839044c1..0b90a7474 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:0ce7aa7e754b27beda160a78e0636a6d6e12f99c59c127ca0557fc3d5a75285e -size 28995 +oid sha256:9563e2a8c60bac96093c614dde7a581bc5eb1ce6f7da00339f4b8f2f1607c2a4 +size 28871 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index 2472013e3..a746a0e4e 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:88a4aa4b72322807ce9fcf30faca1d2e20ae78a65bdefd29ae16bf2596d93d8f -size 31898 +oid sha256:77076bc1e501bc2f630f559dfb699c965d2d95c5a611b4619a2819bc1f6eb8ca +size 31774 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index 9104a5a20..c7fbfec0f 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:c82750f189ac803962c8b6d885b54aa3332af7249d4a53c00a6e530161d157e7 -size 23373 +oid sha256:909a9b45c735f3356d68885f6d294cb0627f5107316edef32b4b56328e8a0353 +size 23279 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index fe51af1bb..2aceb44be 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:0569cdb10e6c832ba0d741144e54e92c31e25b9e8555743ae21c6a1bd8cc784e -size 30199 +oid sha256:075200d4b5a1353b463aff9c019212bea19aaee6ff38e34a3ddefceb816c00ec +size 30075 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index f099cb9e4..7a7822ac7 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:a29b3b287910fecb955b5627bca91a202cb1663f78ea642e4dfde911add86ee6 -size 31420 +oid sha256:b58ddef0b73414d33f88a4e15df151b0a95a1093116b3d1e194d9b781ff09051 +size 31296 From d4f959b68132c2cd7df98f2f3cd90ea5a43c5c46 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 9 Feb 2021 16:46:10 +0100 Subject: [PATCH 170/222] Add support for Deformable material preview --- .../Viewport/Previews/MaterialPreview.cs | 27 ++++++++++++++++++- Source/Engine/Level/Actors/SplineModel.cpp | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index 41275ef9a..4f7d71b18 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -35,6 +35,8 @@ namespace FlaxEditor.Viewport.Previews private StaticModel _previewModel; private Decal _decal; private Terrain _terrain; + private Spline _spline; + private SplineModel _splineModel; private ParticleEffect _particleEffect; private MaterialBase _particleEffectMaterial; private ParticleEmitter _particleEffectEmitter; @@ -140,6 +142,7 @@ namespace FlaxEditor.Viewport.Previews MaterialBase guiMaterial = null; MaterialBase terrainMaterial = null; MaterialBase particleMaterial = null; + MaterialBase deformableMaterial = null; bool usePreviewActor = true; if (_material != null) { @@ -173,7 +176,8 @@ namespace FlaxEditor.Viewport.Previews particleMaterial = _material; break; case MaterialDomain.Deformable: - // TODO: preview Deformable material (eg. by using Spline with Spline Model) + usePreviewActor = false; + deformableMaterial = _material; break; default: throw new ArgumentOutOfRangeException(); } @@ -282,6 +286,25 @@ namespace FlaxEditor.Viewport.Previews } } } + + // Deformable + if (deformableMaterial && _spline == null) + { + _spline = new Spline(); + _spline.AddSplineLocalPoint(new Vector3(0, 0, -50.0f), false); + _spline.AddSplineLocalPoint(new Vector3(0, 0, 50.0f)); + _splineModel = new SplineModel + { + Scale = new Vector3(0.45f), + Parent = _spline, + }; + Task.AddCustomActor(_spline); + } + if (_splineModel != null) + { + _splineModel.Model = _previewModel.Model; + _splineModel.SetMaterial(0, deformableMaterial); + } } /// @@ -298,6 +321,8 @@ namespace FlaxEditor.Viewport.Previews Object.Destroy(ref _previewModel); Object.Destroy(ref _decal); Object.Destroy(ref _terrain); + Object.Destroy(ref _spline); + Object.Destroy(ref _splineModel); Object.Destroy(ref _particleEffect); Object.Destroy(ref _particleEffectEmitter); Object.Destroy(ref _particleEffectSystem); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 7380bf920..69fcf90bc 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -95,7 +95,7 @@ void SplineModel::OnModelLoaded() void SplineModel::OnSplineUpdated() { // Skip updates when actor is disabled or something is missing - if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || !IsDuringPlay() || _spline->GetSplinePointsCount() < 2) + if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2) { _box = BoundingBox(_transform.Translation, _transform.Translation); BoundingSphere::FromBox(_box, _sphere); From 191694725ad1cca2a0dd046eba8dfa6659aceafb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 11:12:05 +0100 Subject: [PATCH 171/222] Add `SoftObjectReference` for lazy references to assets and objects --- Source/Engine/Scripting/Object.cs | 5 + .../Engine/Scripting/SoftObjectReference.cs | 80 +++++ Source/Engine/Scripting/SoftObjectReference.h | 327 ++++++++++++++++++ Source/Engine/Serialization/JsonSerializer.cs | 33 ++ Source/Engine/Serialization/Serialization.h | 22 ++ .../Bindings/BindingsGenerator.CSharp.cs | 12 +- .../Bindings/BindingsGenerator.Cpp.cs | 12 +- 7 files changed, 479 insertions(+), 12 deletions(-) create mode 100644 Source/Engine/Scripting/SoftObjectReference.cs create mode 100644 Source/Engine/Scripting/SoftObjectReference.h diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 1c649ba9b..a4e2424e9 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -205,6 +205,11 @@ namespace FlaxEngine return obj?.__unmanagedPtr ?? IntPtr.Zero; } + internal static IntPtr GetUnmanagedPtr(SoftObjectReference reference) + { + return GetUnmanagedPtr(reference.Get()); + } + /// public override int GetHashCode() { diff --git a/Source/Engine/Scripting/SoftObjectReference.cs b/Source/Engine/Scripting/SoftObjectReference.cs new file mode 100644 index 000000000..df098d02a --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectReference.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). + /// + public struct SoftObjectReference : IComparable, IComparable + { + private Guid _id; + private Object _object; + + /// + /// Gets or sets the object identifier. + /// + public Guid ID + { + get => _id; + set + { + if (_id == value) + return; + _id = value; + _object = null; + } + } + + /// + /// Gets the object reference. + /// + /// The object type. + /// The resolved object or null. + public T Get() where T : Object + { + if (!_object) + _object = Object.Find(ref _id, typeof(T)); + return _object as T; + } + + /// + /// Sets the object reference. + /// + /// The object. + public void Set(Object obj) + { + _object = obj; + _id = obj?.ID ?? Guid.Empty; + } + + /// + public override string ToString() + { + if (_object) + return _object.ToString(); + return _id.ToString(); + } + + /// + public override int GetHashCode() + { + return _id.GetHashCode(); + } + + /// + public int CompareTo(object obj) + { + if (obj is SoftObjectReference other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(SoftObjectReference other) + { + return _id.CompareTo(other._id); + } + } +} diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h new file mode 100644 index 000000000..1befaa0f7 --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -0,0 +1,327 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" + +// Don't include Scripting.h but just FindObject method +extern FLAXENGINE_API ScriptingObject* FindObject(const Guid& id, MClass* type); + +/// +/// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). +/// +class FLAXENGINE_API SoftObjectReferenceBase +{ +public: + + typedef Delegate<> EventType; + +protected: + + ScriptingObject* _object = nullptr; + Guid _id = Guid::Empty; + +public: + + /// + /// Action fired when reference gets changed. + /// + EventType Changed; + +public: + + /// + /// Initializes a new instance of the class. + /// + SoftObjectReferenceBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectReferenceBase(ScriptingObject* obj) + { + OnSet(obj); + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectReferenceBase() + { + if (_object) + _object->Deleted.Unbind(this); + } + +public: + + /// + /// Gets the object ID. + /// + /// The object ID or Guid::Empty if nothing assigned. + Guid GetID() const + { + return _object ? _object->GetID() : _id; + } + +protected: + + /// + /// Sets the object. + /// + /// The object. + void OnSet(ScriptingObject* object) + { + auto e = _object; + if (e != object) + { + if (e) + e->Deleted.Unbind(this); + _object = e = object; + _id = e ? e->GetID() : Guid::Empty; + if (e) + e->Deleted.Bind(this); + Changed(); + } + } + + void OnDeleted(ScriptingObject* obj) + { + ASSERT(_object == obj); + _object->Deleted.Unbind(this); + _object = nullptr; + Changed(); + } +}; + +/// +/// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). +/// +template +API_CLASS(InBuild) class SoftObjectReference : public SoftObjectReferenceBase +{ +public: + + typedef SoftObjectReference Type; + +public: + + /// + /// Initializes a new instance of the class. + /// + SoftObjectReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectReference(T* obj) + : SoftObjectReferenceBase(obj) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + SoftObjectReference(const SoftObjectReference& other) + : SoftObjectReferenceBase(other.Get()) + { + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectReference() + { + } + +public: + + /// + /// Compares the property value with the given object. + /// + /// The other. + /// True if property object equals the given value. + FORCE_INLINE bool operator==(T* other) + { + return Get() == other; + } + + /// + /// Compares the property value with the other property value. + /// + /// The other property. + /// True if properties are equal. + FORCE_INLINE bool operator==(const SoftObjectReference& other) + { + return _id == other._id; + } + + /// + /// Compares the property value with the given object. + /// + /// The other. + /// True if property object not equals the given value. + FORCE_INLINE bool operator!=(T* other) + { + return Get() != other; + } + + /// + /// Compares the property value with the other property value. + /// + /// The other property. + /// True if properties are not equal. + FORCE_INLINE bool operator!=(const SoftObjectReference& other) + { + return _id != other._id; + } + + /// + /// Sets the property to the given property value. + /// + /// The other property. + /// The reference to this property. + SoftObjectReference& operator=(const SoftObjectReference& other) + { + if (this != &other) + OnSet(other.Get()); + return *this; + } + + /// + /// Sets the property to the given value. + /// + /// The object. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(const T& other) + { + OnSet(&other); + return *this; + } + + /// + /// Sets the property to the given value. + /// + /// The object. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(T* other) + { + OnSet(other); + return *this; + } + + /// + /// Sets the property to the object of the given ID. + /// + /// The object ID. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(const Guid& id) + { + Set(id); + return *this; + } + + /// + /// Implicit conversion to the object. + /// + /// The object reference. + FORCE_INLINE operator T*() const + { + return (T*)Get(); + } + + /// + /// Implicit conversion to boolean value. + /// + /// True if object has been assigned, otherwise false + FORCE_INLINE operator bool() const + { + return _object != nullptr || _id.IsValid(); + } + + /// + /// Object accessor. + /// + /// The object reference. + FORCE_INLINE T* operator->() const + { + return (T*)Get(); + } + + /// + /// Gets the object pointer. + /// + /// The object reference. + T* Get() const + { + if (!_object) + const_cast(this)->OnSet(FindObject(_id, T::GetStaticClass())); + return (T*)_object; + } + + /// + /// Gets the object as a given type (static cast). + /// + /// Asset + template + FORCE_INLINE U* As() const + { + return static_cast(Get()); + } + +public: + + /// + /// Gets managed instance object (or null if no object linked). + /// + /// The managed object instance. + MonoObject* GetManagedInstance() const + { + auto object = Get(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Determines whether object is assigned and managed instance of the object is alive. + /// + /// True if managed object has been created and exists, otherwise false. + bool HasManagedInstance() const + { + auto object = Get(); + return object && object->HasManagedInstance(); + } + + /// + /// Gets the managed instance object or creates it if missing or null if not assigned. + /// + /// The Mono managed object. + MonoObject* GetOrCreateManagedInstance() const + { + auto object = Get(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Sets the object. + /// + /// The object ID. Uses Scripting to find the registered object of the given ID. + FORCE_INLINE void Set(const Guid& id) + { + Set(static_cast(FindObject(id, T::GetStaticClass()))); + } + + /// + /// Sets the object. + /// + /// The object. + FORCE_INLINE void Set(T* object) + { + OnSet(object); + } +}; diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 535499442..d3300dd23 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -87,6 +87,38 @@ namespace FlaxEngine.Json } } + /// + /// Serialize SoftObjectReference as Guid in internal format. + /// + /// + internal class SoftObjectReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var id = ((SoftObjectReference)value).ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = new SoftObjectReference(); + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + result.ID = id; + } + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SoftObjectReference); + } + } + /* /// /// Serialize Guid values using `N` format @@ -184,6 +216,7 @@ namespace FlaxEngine.Json ObjectConverter = new FlaxObjectConverter(); settings.Converters.Add(ObjectConverter); settings.Converters.Add(new SceneReferenceConverter()); + settings.Converters.Add(new SoftObjectReferenceConverter()); settings.Converters.Add(new VersionConverter()); //settings.Converters.Add(new GuidConverter()); return settings; diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index b760e9689..039d9df3e 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -9,6 +9,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Scripting/SoftObjectReference.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/WeakAssetReference.h" @@ -457,6 +458,27 @@ namespace Serialization v = id; } + // Soft Object Reference + + template + inline bool ShouldSerialize(const SoftObjectReference& v, const void* otherObj) + { + return !otherObj || v.Get() != ((SoftObjectReference*)otherObj)->Get(); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, SoftObjectReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + modifier->IdsMapping.TryGet(id, id); + v = id; + } + // Asset Reference template diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 8b679fb7a..861d96a1a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -132,8 +132,8 @@ namespace Flax.Build.Bindings return result; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return typeInfo.GenericArgs[0].Type.Replace("::", "."); // Array or Span @@ -188,8 +188,8 @@ namespace Flax.Build.Bindings return "IntPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "IntPtr"; return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); @@ -231,8 +231,8 @@ namespace Flax.Build.Bindings return "FlaxEngine.Object.GetUnmanagedPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "FlaxEngine.Object.GetUnmanagedPtr"; // Default diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 11fe6a414..ff2a2fadd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -100,7 +100,7 @@ namespace Flax.Build.Bindings return value; if (typeInfo.Type == "String") return $"Variant(StringView({value}))"; - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference") + if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -149,7 +149,7 @@ namespace Flax.Build.Bindings return $"((StringView){value}).GetText()"; if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; - if (typeInfo.Type == "ScriptingObjectReference") + if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); @@ -340,8 +340,8 @@ namespace Flax.Build.Bindings } } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { type = "MonoObject*"; return "{0}.GetManagedInstance()"; @@ -448,8 +448,8 @@ namespace Flax.Build.Bindings type = "MonoReflectionType*"; return "MUtils::UnboxVariantType({0})"; default: - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) From adbb467206b2ff24c5fa69bd32b37291052a6cc8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 11:29:07 +0100 Subject: [PATCH 172/222] Add support for `mutable` keyword on `API_FIELD` --- .../Bindings/BindingsGenerator.Parsing.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 1c4cadb4a..fb6ce7d52 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1022,11 +1022,30 @@ namespace Flax.Build.Bindings // Read parameters from the tag var tagParams = ParseTagParameters(ref context); + context.Tokenizer.SkipUntil(TokenType.Identifier); - // Read 'static' keyword - desc.IsStatic = context.Tokenizer.NextToken().Value == "static"; - if (!desc.IsStatic) - context.Tokenizer.PreviousToken(); + // Read 'static' or 'mutable' + Token token; + var isMutable = false; + while (true) + { + token = context.Tokenizer.CurrentToken; + if (!desc.IsStatic && token.Value == "static") + { + desc.IsStatic = true; + context.Tokenizer.NextToken(); + } + else if (!isMutable && token.Value == "mutable") + { + isMutable = true; + context.Tokenizer.NextToken(); + } + else + { + context.Tokenizer.PreviousToken(); + break; + } + } // Read type desc.Type = ParseType(ref context); @@ -1035,7 +1054,7 @@ namespace Flax.Build.Bindings desc.Name = ParseName(ref context); // Read ';' or default value or array size or bit-field size - var token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); + token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); if (token.Type == TokenType.Equal) { context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false); From 81e41d51c88848abdfacfdb89f1ce404bce83cc9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 11:37:14 +0100 Subject: [PATCH 173/222] Remove Unlink from asset reference --- .../Editor/Utilities/ViewportIconsRenderer.cpp | 2 +- Source/Engine/Content/AssetReference.h | 10 +--------- Source/Engine/Content/Assets/SkeletonMask.cpp | 2 +- Source/Engine/Debug/DebugDraw.cpp | 2 +- Source/Engine/Foliage/Foliage.cpp | 2 +- Source/Engine/Graphics/GPUDevice.cpp | 10 +++++----- Source/Engine/Level/Scene/Lightmap.cpp | 4 ++-- Source/Engine/Level/Scene/Scene.cpp | 4 ++-- Source/Engine/Navigation/NavMesh.cpp | 3 +-- Source/Engine/Particles/ParticleManager.cpp | 2 +- Source/Engine/Render2D/Render2D.cpp | 2 +- Source/Engine/Renderer/AmbientOcclusionPass.cpp | 2 +- Source/Engine/Renderer/AntiAliasing/FXAA.cpp | 6 ++---- Source/Engine/Renderer/AntiAliasing/SMAA.cpp | 10 ++++------ Source/Engine/Renderer/AntiAliasing/TAA.cpp | 3 ++- Source/Engine/Renderer/AtmospherePreCompute.cpp | 2 +- Source/Engine/Renderer/ColorGradingPass.cpp | 6 ++---- Source/Engine/Renderer/DepthOfFieldPass.cpp | 16 ++++++---------- Source/Engine/Renderer/EyeAdaptationPass.cpp | 6 ++---- Source/Engine/Renderer/ForwardPass.cpp | 6 ++---- Source/Engine/Renderer/GBufferPass.cpp | 11 ++++------- Source/Engine/Renderer/HistogramPass.cpp | 3 ++- Source/Engine/Renderer/LightPass.cpp | 6 ++---- Source/Engine/Renderer/MotionBlurPass.cpp | 6 ++---- Source/Engine/Renderer/PostProcessingPass.cpp | 12 +++++------- Source/Engine/Renderer/ProbesRenderer.cpp | 2 +- Source/Engine/Renderer/ReflectionsPass.cpp | 10 ++++------ .../Renderer/ScreenSpaceReflectionsPass.cpp | 8 +++----- Source/Engine/Renderer/ShadowsPass.cpp | 5 +++-- Source/Engine/Renderer/Utils/BitonicSort.cpp | 6 ++---- Source/Engine/Renderer/Utils/MultiScaler.cpp | 10 ++++------ Source/Engine/Renderer/VolumetricFogPass.cpp | 15 ++++----------- Source/Engine/ShadowsOfMordor/Builder.cpp | 2 +- Source/Engine/Terrain/TerrainChunk.cpp | 2 +- Source/Engine/Terrain/TerrainPatch.cpp | 6 +++--- 35 files changed, 80 insertions(+), 124 deletions(-) diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 631e8b56f..889d94052 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -138,7 +138,7 @@ bool ViewportIconsRendererService::Init() void ViewportIconsRendererService::Dispose() { - QuadModel.Unlink(); + QuadModel = nullptr; for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++) InstanceBuffers[i].Release(); ActorTypeToIconType.Clear(); diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index c78aa5aef..658545d1a 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -78,14 +78,6 @@ public: return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; } - /// - /// Clears the asset reference. - /// - FORCE_INLINE void Unlink() - { - OnSet(nullptr); - } - /// /// Gets the asset property value as string. /// @@ -135,7 +127,7 @@ protected: if (_asset == asset) { Unload(); - Unlink(); + OnSet(nullptr); } } }; diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 4747e0e44..0742e2951 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -38,7 +38,7 @@ Asset::LoadResult SkeletonMask::load() void SkeletonMask::unload(bool isReloading) { - Skeleton.Unlink(); + Skeleton = nullptr; _maskedNodes.Resize(0); _mask.Resize(0); } diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 320a991a3..1f7e0c596 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -487,7 +487,7 @@ void DebugDrawService::Dispose() DebugDrawPsDepthTest.Release(); DebugDrawPsDepthTest.Release(); SAFE_DELETE(DebugDrawVB); - DebugDrawShader.Unlink(); + DebugDrawShader = nullptr; } void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index dcfd50212..c38191adf 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -201,7 +201,7 @@ void Foliage::RemoveFoliageType(int32 index) FoliageTypes[i].Index--; } auto& item = FoliageTypes[index]; - item.Model.Unlink(); + item.Model = nullptr; item.Entries.Release(); FoliageTypes.RemoveAtKeepOrder(index); diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 47ec6d3a6..821031454 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -13,10 +13,10 @@ #include "Engine/Platform/Windows/WindowsWindow.h" #include "Engine/Render2D/Render2D.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Renderer/RenderList.h" -#include "Engine/Engine/Engine.h" #include "Engine/Core/Utilities.h" GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params) @@ -230,10 +230,10 @@ void GPUDevice::preDispose() RenderTargetPool::Flush(); // Release resources - _res->DefaultMaterial.Unlink(); - _res->DefaultNormalMap.Unlink(); - _res->DefaultWhiteTexture.Unlink(); - _res->DefaultBlackTexture.Unlink(); + _res->DefaultMaterial = nullptr; + _res->DefaultNormalMap = nullptr; + _res->DefaultWhiteTexture = nullptr; + _res->DefaultBlackTexture = nullptr; SAFE_DELETE_GPU_RESOURCE(_res->PS_CopyLinear); SAFE_DELETE_GPU_RESOURCE(_res->PS_Clear); SAFE_DELETE_GPU_RESOURCE(_res->FullscreenTriangleVB); diff --git a/Source/Engine/Level/Scene/Lightmap.cpp b/Source/Engine/Level/Scene/Lightmap.cpp index b55b603f5..4a2eec796 100644 --- a/Source/Engine/Level/Scene/Lightmap.cpp +++ b/Source/Engine/Level/Scene/Lightmap.cpp @@ -79,7 +79,7 @@ void Lightmap::EnsureSize(int32 size) { // Unlink texture that cannot be loaded LOG(Warning, "Lightmap::EnsureSize failed to load texture"); - texture.Unlink(); + texture = nullptr; } else { @@ -88,7 +88,7 @@ void Lightmap::EnsureSize(int32 size) { // Unlink texture and import new with valid size LOG(Info, "Changing lightmap {0}:{1} size from {2} to {3}", _index, textureIndex, texture->GetTexture()->Size(), size); - texture.Unlink(); + texture = nullptr; } } } diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index b86610ab9..d38bd4225 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -298,8 +298,8 @@ void Scene::OnDeleteObject() { // Cleanup LightmapsData.UnloadLightmaps(); - CSGData.Model.Unlink(); - CSGData.CollisionData.Unlink(); + CSGData.Model = nullptr; + CSGData.CollisionData = nullptr; // Base Actor::OnDeleteObject(); diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index ef90e2989..fe7756049 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -40,8 +40,7 @@ void NavMesh::SaveNavMesh() // Check if has no navmesh data generated (someone could just remove navmesh volumes or generate for empty scene) if (Data.Tiles.IsEmpty()) { - // Keep asset reference valid - DataAsset.Unlink(); + DataAsset = nullptr; return; } diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp index c83aacbbf..429420950 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/ParticleManager.cpp @@ -557,7 +557,7 @@ void OnShaderReloading(Asset* obj) void CleanupGPUParticlesSorting() { - GPUParticlesSorting.Unlink(); + GPUParticlesSorting = nullptr; } void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCall& drawCall, DrawPass drawModes, StaticFlags staticFlags, ParticleEmitterInstance& emitterData, const RenderModulesIndices& renderModulesIndices) diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 2413686aa..1aed94a8e 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -522,7 +522,7 @@ void Render2DService::Dispose() Lines.Resize(0); Lines2.Resize(0); - GUIShader.Unlink(); + GUIShader = nullptr; PsoDepth.Dispose(); PsoNoDepth.Dispose(); diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index dfae20792..e72de509b 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -196,7 +196,7 @@ void AmbientOcclusionPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_psApplyHalf); // Release asset - _shader.Unlink(); + _shader = nullptr; } void AmbientOcclusionPass::Render(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp index 159e481a6..3dcb6ae71 100644 --- a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp @@ -55,11 +55,9 @@ void FXAA::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup _psFXAA.Delete(); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) diff --git a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp index cf7e53f40..7c1ee0c16 100644 --- a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp @@ -82,15 +82,13 @@ void SMAA::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psEdge.Delete(); _psBlend.Delete(); SAFE_DELETE_GPU_RESOURCE(_psNeighbor); - - // Release assets - _shader.Unlink(); - _areaTex.Unlink(); - _searchTex.Unlink(); + _shader = nullptr; + _areaTex = nullptr; + _searchTex = nullptr; } void SMAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index 7b8dfe17f..a1fee93ad 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -42,8 +42,9 @@ void TAA::Dispose() // Base RendererPass::Dispose(); + // Cleanup _psTAA = nullptr; - _shader.Unlink(); + _shader = nullptr; } bool TAA::NeedMotionVectors(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/AtmospherePreCompute.cpp b/Source/Engine/Renderer/AtmospherePreCompute.cpp index 66576e55f..cef9964ca 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.cpp +++ b/Source/Engine/Renderer/AtmospherePreCompute.cpp @@ -305,7 +305,7 @@ void release() SAFE_DELETE_GPU_RESOURCE(_psInscatterS); SAFE_DELETE_GPU_RESOURCE(_psInscatterN); SAFE_DELETE_GPU_RESOURCE(_psCopyInscatterNAdd); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE(_task); SAFE_DELETE_GPU_RESOURCE(AtmosphereTransmittance); SAFE_DELETE_GPU_RESOURCE(AtmosphereIrradiance); diff --git a/Source/Engine/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index 94caf2b46..c40248caa 100644 --- a/Source/Engine/Renderer/ColorGradingPass.cpp +++ b/Source/Engine/Renderer/ColorGradingPass.cpp @@ -121,11 +121,9 @@ void ColorGradingPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup _psLut.Delete(); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index b5382ec72..4e4ee7ed1 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -66,21 +66,17 @@ void DepthOfFieldPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psDofDepthBlurGeneration); SAFE_DELETE_GPU_RESOURCE(_psBokehGeneration); SAFE_DELETE_GPU_RESOURCE(_psDoNotGenerateBokeh); SAFE_DELETE_GPU_RESOURCE(_psBokeh); SAFE_DELETE_GPU_RESOURCE(_psBokehComposite); - - // Release assets - _shader.Unlink(); - _defaultBokehHexagon.Unlink(); - _defaultBokehOctagon.Unlink(); - _defaultBokehCircle.Unlink(); - _defaultBokehCross.Unlink(); - - // Release resources + _shader = nullptr; + _defaultBokehHexagon = nullptr; + _defaultBokehOctagon = nullptr; + _defaultBokehCircle = nullptr; + _defaultBokehCross = nullptr; SAFE_DELETE_GPU_RESOURCE(_bokehBuffer); SAFE_DELETE_GPU_RESOURCE(_bokehIndirectArgsBuffer); } diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index a85e0a72b..3bfbb9daa 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -244,15 +244,13 @@ void EyeAdaptationPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psManual); SAFE_DELETE_GPU_RESOURCE(_psLuminanceMap); SAFE_DELETE_GPU_RESOURCE(_psBlendLuminance); SAFE_DELETE_GPU_RESOURCE(_psApplyLuminance); SAFE_DELETE_GPU_RESOURCE(_psHistogram); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } bool EyeAdaptationPass::setupResources() diff --git a/Source/Engine/Renderer/ForwardPass.cpp b/Source/Engine/Renderer/ForwardPass.cpp index ad178fe67..93ccf0750 100644 --- a/Source/Engine/Renderer/ForwardPass.cpp +++ b/Source/Engine/Renderer/ForwardPass.cpp @@ -62,11 +62,9 @@ void ForwardPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psApplyDistortion); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index b9e91ee01..2165e25dc 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -77,14 +77,11 @@ void GBufferPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psDebug); - - // Release assets - _gBufferShader.Unlink(); - _skyModel.Unlink(); - _boxModel.Unlink(); - + _gBufferShader = nullptr; + _skyModel = nullptr; + _boxModel = nullptr; #if USE_EDITOR SAFE_DELETE(_lightmapUVsDensityMaterialShader); SAFE_DELETE(_vertexColorsMaterialShader); diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index fe16dbd19..e8bfc9fad 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -102,8 +102,9 @@ void HistogramPass::Dispose() // Base RendererPass::Dispose(); + // Cleanup SAFE_DELETE_GPU_RESOURCE(_histogramBuffer); - _shader.Unlink(); + _shader = nullptr; } bool HistogramPass::setupResources() diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index d021ede01..c79033c37 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -164,7 +164,7 @@ void LightPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psLightDir.Delete(); _psLightPointNormal.Delete(); _psLightPointInverted.Delete(); @@ -172,9 +172,7 @@ void LightPass::Dispose() _psLightSpotInverted.Delete(); SAFE_DELETE_GPU_RESOURCE(_psLightSkyNormal); SAFE_DELETE_GPU_RESOURCE(_psLightSkyInverted); - - // Release assets - _sphereModel.Unlink(); + _sphereModel = nullptr; } void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightBuffer) diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index f10ef442b..689916708 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -134,16 +134,14 @@ void MotionBlurPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psCameraMotionVectors); SAFE_DELETE_GPU_RESOURCE(_psMotionVectorsDebug); SAFE_DELETE_GPU_RESOURCE(_psTileMax); SAFE_DELETE_GPU_RESOURCE(_psTileMaxVariable); SAFE_DELETE_GPU_RESOURCE(_psNeighborMax); SAFE_DELETE_GPU_RESOURCE(_psMotionBlur); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 02469aa81..4e5b4d4fb 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -167,19 +167,17 @@ void PostProcessingPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psThreshold); SAFE_DELETE_GPU_RESOURCE(_psScale); SAFE_DELETE_GPU_RESOURCE(_psBlurH); SAFE_DELETE_GPU_RESOURCE(_psBlurV); SAFE_DELETE_GPU_RESOURCE(_psGenGhosts); _psComposite.Delete(); - - // Release assets - _shader.Unlink(); - _defaultLensColor.Unlink(); - _defaultLensDirt.Unlink(); - _defaultLensStar.Unlink(); + _shader = nullptr; + _defaultLensColor = nullptr; + _defaultLensDirt = nullptr; + _defaultLensStar = nullptr; } void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT) diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 6688c4716..3f48f8c6e 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -333,7 +333,7 @@ void ProbesRenderer::Release() SAFE_DELETE_GPU_RESOURCE(_psAccDiffuseIrradiance); SAFE_DELETE_GPU_RESOURCE(_psAccumulateCubeFaces); SAFE_DELETE_GPU_RESOURCE(_psCopyFrameLHB); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_output); SAFE_DELETE(_task); SAFE_DELETE_GPU_RESOURCE(_probe); diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 40f3aeb30..c3108cf0d 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -324,15 +324,13 @@ void ReflectionsPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psProbeNormal); SAFE_DELETE_GPU_RESOURCE(_psProbeInverted); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); - - // Release assets - _shader.Unlink(); - _sphereModel.Unlink(); - _preIntegratedGF.Unlink(); + _shader = nullptr; + _sphereModel = nullptr; + _preIntegratedGF = nullptr; } bool sortProbes(EnvironmentProbe* const& p1, EnvironmentProbe* const& p2) diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp index 491f53262..5e81db45c 100644 --- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp +++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp @@ -152,16 +152,14 @@ void ScreenSpaceReflectionsPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psRayTracePass); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); SAFE_DELETE_GPU_RESOURCE(_psTemporalPass); SAFE_DELETE_GPU_RESOURCE(_psMixPass); _psResolvePass.Delete(); - - // Release assets - _shader.Unlink(); - _preIntegratedGF.Unlink(); + _shader = nullptr; + _preIntegratedGF = nullptr; } void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer) diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 11812a45c..4c3260045 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -193,11 +193,12 @@ void ShadowsPass::Dispose() // Base RendererPass::Dispose(); + // Cleanup _psShadowDir.Delete(); _psShadowPoint.Delete(); _psShadowSpot.Delete(); - _shader.Unlink(); - _sphereModel.Unlink(); + _shader = nullptr; + _sphereModel = nullptr; SAFE_DELETE_GPU_RESOURCE(_shadowMapCSM); SAFE_DELETE_GPU_RESOURCE(_shadowMapCube); } diff --git a/Source/Engine/Renderer/Utils/BitonicSort.cpp b/Source/Engine/Renderer/Utils/BitonicSort.cpp index 5dc4fffee..89313a95d 100644 --- a/Source/Engine/Renderer/Utils/BitonicSort.cpp +++ b/Source/Engine/Renderer/Utils/BitonicSort.cpp @@ -69,7 +69,7 @@ void BitonicSort::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_dispatchArgsBuffer); _cb = nullptr; _indirectArgsCS = nullptr; @@ -77,9 +77,7 @@ void BitonicSort::Dispose() _innerSortCS = nullptr; _outerSortCS = nullptr; _copyIndicesCS = nullptr; - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void BitonicSort::Sort(GPUContext* context, GPUBuffer* sortingKeysBuffer, GPUBuffer* countBuffer, uint32 counterOffset, bool sortAscending, GPUBuffer* sortedIndicesBuffer) diff --git a/Source/Engine/Renderer/Utils/MultiScaler.cpp b/Source/Engine/Renderer/Utils/MultiScaler.cpp index c415c1900..2c6e8ba14 100644 --- a/Source/Engine/Renderer/Utils/MultiScaler.cpp +++ b/Source/Engine/Renderer/Utils/MultiScaler.cpp @@ -81,14 +81,12 @@ void MultiScaler::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psHalfDepth); _psBlur5.Delete(); _psBlur9.Delete(); _psBlur13.Delete(); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp) @@ -121,7 +119,7 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 ps = &_psBlur13; break; default: - CRASH; + CRASH; return; } @@ -177,7 +175,7 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 ps = &_psBlur13; break; default: - CRASH; + CRASH; return; } diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 0bf8d838e..ff8f469ae 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -89,26 +89,19 @@ void VolumetricFogPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psInjectLight.Delete(); - _csInitialize = nullptr; _csLightScattering.Clear(); _csFinalIntegration = nullptr; - SAFE_DELETE_GPU_RESOURCE(_vbCircleRasterize); SAFE_DELETE_GPU_RESOURCE(_ibCircleRasterize); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } -float ComputeZSliceFromDepth(float SceneDepth, const VolumetricFogOptions& options, int32 GridSizeZ) +float ComputeZSliceFromDepth(float sceneDepth, const VolumetricFogOptions& options, int32 gridSizeZ) { - // This must match frustum voxels depth distribution - // See ComputeNormalizedZSliceFromDepth() in VolumetricFog.shader - - return SceneDepth / options.Distance * GridSizeZ; + return sceneDepth / options.Distance * (float)gridSizeZ; } bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) diff --git a/Source/Engine/ShadowsOfMordor/Builder.cpp b/Source/Engine/ShadowsOfMordor/Builder.cpp index 715803839..b9cb57376 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.cpp @@ -499,7 +499,7 @@ void ShadowsOfMordor::Builder::releaseResources() SAFE_DELETE_GPU_RESOURCE(_psRenderCacheModel); SAFE_DELETE_GPU_RESOURCE(_psRenderCacheTerrain); SAFE_DELETE_GPU_RESOURCE(_psBlurCache); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_irradianceReduction); diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 6cff541f3..9dab924c2 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -21,7 +21,7 @@ void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) _yHeight = 1; _heightmapUVScaleBias = Vector4(1.0f, 1.0f, _x, _z) * (1.0f / TerrainPatch::CHUNKS_COUNT_EDGE); _perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / MAX_uint32); - OverrideMaterial.Unlink(); + OverrideMaterial = nullptr; } bool TerrainChunk::PrepareDraw(const RenderContext& renderContext) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 791e93c7f..61e6f456e 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -64,12 +64,12 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { Chunks[i].Init(this, i % CHUNKS_COUNT_EDGE, i / CHUNKS_COUNT_EDGE); } - Heightmap.Unlink(); + Heightmap = nullptr; for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) { - Splatmap[i].Unlink(); + Splatmap[i] = nullptr; } - _heightfield.Unlink(); + _heightfield = nullptr; #if TERRAIN_UPDATING _cachedHeightMap.Resize(0); _cachedHolesMask.Resize(0); From 5cb232227063c33af48dd3ddece7ac475380a5dc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 11:37:34 +0100 Subject: [PATCH 174/222] Add default Deformable material for splines --- Content/Editor/Particles/Particle Material Color.flax | 2 +- Content/Engine/DefaultDeformableMaterial.flax | 3 +++ Source/Editor/Cooker/Steps/DeployDataStep.cpp | 1 + Source/Engine/Graphics/GPUDevice.cpp | 9 +++++++++ Source/Engine/Graphics/GPUDevice.h | 5 +++++ Source/Engine/Level/Actors/SplineModel.cpp | 2 +- Source/Engine/Scripting/SoftObjectReference.h | 5 +++-- 7 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 Content/Engine/DefaultDeformableMaterial.flax diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index e2fdb046b..81fcbea72 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:14d75ef13449f9ed9a6e18ee82fe59fe4e83ac519b62b1e87aaadabd89673995 +oid sha256:e44da38b26f222f7081278dc64b7fa932c6f9bfd0b79694da784bd4d944e838a size 29232 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax new file mode 100644 index 000000000..3b2e45a87 --- /dev/null +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dab7eb4f71a407eae7d6b051cbad701f234742b0d96646811c8a615de80e94eb +size 18800 diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 9ce3a8a5d..96f8bfad2 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -74,6 +74,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/SSR")); data.AddRootEngineAsset(TEXT("Shaders/VolumetricFog")); data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial")); + data.AddRootEngineAsset(TEXT("Engine/DefaultDeformableMaterial")); data.AddRootEngineAsset(TEXT("Engine/DefaultTerrainMaterial")); if (!gameSettings->NoSplashScreen && !gameSettings->SplashScreen.IsValid()) data.AddRootEngineAsset(TEXT("Engine/Textures/Logo")); diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 821031454..51d92cd3f 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -18,6 +18,7 @@ #include "Engine/Profiler/Profiler.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Core/Utilities.h" +#include "Engine/Scripting/SoftObjectReference.h" GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params) { @@ -127,6 +128,7 @@ struct GPUDevice::PrivateData GPUPipelineState* PS_Clear = nullptr; GPUBuffer* FullscreenTriangleVB = nullptr; AssetReference DefaultMaterial; + SoftObjectReference DefaultDeformableMaterial; AssetReference DefaultNormalMap; AssetReference DefaultWhiteTexture; AssetReference DefaultBlackTexture; @@ -206,6 +208,7 @@ bool GPUDevice::LoadContent() _res->DefaultMaterial = Content::LoadAsyncInternal(TEXT("Engine/DefaultMaterial")); if (_res->DefaultMaterial == nullptr) return true; + _res->DefaultDeformableMaterial = Guid(0x639e12c0, 0x42d34bae, 0x89dd8b81, 0x7e1efc2d); // Load default normal map _res->DefaultNormalMap = Content::LoadAsyncInternal(TEXT("Engine/Textures/NormalTexture")); @@ -231,6 +234,7 @@ void GPUDevice::preDispose() // Release resources _res->DefaultMaterial = nullptr; + _res->DefaultDeformableMaterial = nullptr; _res->DefaultNormalMap = nullptr; _res->DefaultWhiteTexture = nullptr; _res->DefaultBlackTexture = nullptr; @@ -380,6 +384,11 @@ MaterialBase* GPUDevice::GetDefaultMaterial() const return _res->DefaultMaterial; } +MaterialBase* GPUDevice::GetDefaultDeformableMaterial() const +{ + return _res->DefaultDeformableMaterial.Get(); +} + GPUTexture* GPUDevice::GetDefaultNormalMap() const { return _res->DefaultNormalMap ? _res->DefaultNormalMap->GetTexture() : nullptr; diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index 0b5fb46b9..e1deb16cc 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -234,6 +234,11 @@ public: /// MaterialBase* GetDefaultMaterial() const; + /// + /// Gets the default material (Deformable domain). + /// + MaterialBase* GetDefaultDeformableMaterial() const; + /// /// Gets the default normal map texture. /// diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 69fcf90bc..c8a7b7b1f 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -408,7 +408,7 @@ void SplineModel::Draw(RenderContext& renderContext) else if (slot.Material && slot.Material->IsLoaded()) material = slot.Material; else - material = nullptr; + material = GPUDevice::Instance->GetDefaultDeformableMaterial(); if (!material || !material->IsDeformable()) continue; diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h index 1befaa0f7..b6f08db4a 100644 --- a/Source/Engine/Scripting/SoftObjectReference.h +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -311,9 +311,10 @@ public: /// Sets the object. /// /// The object ID. Uses Scripting to find the registered object of the given ID. - FORCE_INLINE void Set(const Guid& id) + void Set(const Guid& id) { - Set(static_cast(FindObject(id, T::GetStaticClass()))); + _id = id; + _object = nullptr; } /// From 8bce445ab01176eaf16bd828ea06a15a6ebafc5a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 14:33:16 +0100 Subject: [PATCH 175/222] Refactor Collider base class to improve code sharing across collider shape types --- .../Engine/Physics/Colliders/BoxCollider.cpp | 50 +------ Source/Engine/Physics/Colliders/BoxCollider.h | 16 +-- .../Physics/Colliders/CapsuleCollider.cpp | 51 +------ .../Physics/Colliders/CapsuleCollider.h | 16 +-- .../Physics/Colliders/CharacterController.cpp | 28 +--- .../Physics/Colliders/CharacterController.h | 10 +- Source/Engine/Physics/Colliders/Collider.cpp | 105 +++++++++++++-- Source/Engine/Physics/Colliders/Collider.h | 30 +++-- .../Engine/Physics/Colliders/MeshCollider.cpp | 127 ++---------------- .../Engine/Physics/Colliders/MeshCollider.h | 9 +- .../Physics/Colliders/SphereCollider.cpp | 50 +------ .../Engine/Physics/Colliders/SphereCollider.h | 12 +- Source/Engine/Physics/Types.h | 1 + 13 files changed, 154 insertions(+), 351 deletions(-) diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index da78e6a2f..487059148 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif BoxCollider::BoxCollider(const SpawnParams& params) : Collider(params) @@ -123,26 +120,6 @@ void BoxCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(Size, _size); } -#if USE_EDITOR - -void BoxCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void BoxCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void BoxCollider::UpdateBounds() { // Cache bounds @@ -152,32 +129,11 @@ void BoxCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void BoxCollider::CreateShape() +void BoxCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); Vector3 size = _size * _cachedScale; size.Absolute(); const float minSize = 0.001f; - const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); - - // Setup shape - CreateShapeBase(geometry); -} - -void BoxCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - Vector3 size = _size * _cachedScale; - size.Absolute(); - const float minSize = 0.001f; - const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); - - // Setup shape - _shape->setGeometry(geometry); + const PxBoxGeometry box(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); + geometry.storeAny(box); } diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index ac381e878..89850b101 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -47,12 +47,6 @@ public: return _bounds; } -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -67,11 +61,9 @@ public: protected: // [Collider] -#if USE_EDITOR - void OnEnable() override; - void OnDisable() override; -#endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif }; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index e3e717839..d3bc4dc30 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif CapsuleCollider::CapsuleCollider(const SpawnParams& params) : Collider(params) @@ -93,26 +90,6 @@ void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Height, _height); } -#if USE_EDITOR - -void CapsuleCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void CapsuleCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void CapsuleCollider::UpdateBounds() { // Cache bounds @@ -123,34 +100,12 @@ void CapsuleCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void CapsuleCollider::CreateShape() +void CapsuleCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const PxCapsuleGeometry geometry(radius, height * 0.5f); - - // Setup shape - CreateShapeBase(geometry); -} - -void CapsuleCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const PxCapsuleGeometry geometry(radius, height * 0.5f); - - // Setup shape - _shape->setGeometry(geometry); + const PxCapsuleGeometry capsule(radius, height * 0.5f); + geometry.storeAny(capsule); } diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 5efe18c13..4a66ca877 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -63,12 +63,6 @@ public: /// API_PROPERTY() void SetHeight(float value); -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -82,11 +76,9 @@ public: protected: // [Collider] -#if USE_EDITOR - void OnEnable() override; - void OnDisable() override; -#endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif }; diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index e0630573e..c7809dd78 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -7,9 +7,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Engine/Time.h" #include "Engine/Physics/PhysicalMaterial.h" -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif #include #include #include @@ -276,6 +273,11 @@ void CharacterController::UpdateGeometry() UpdateSize(); } +void CharacterController::GetGeometry(PxGeometryHolder& geometry) +{ + // Unused +} + void CharacterController::UpdateLayerBits() { // Base @@ -309,26 +311,6 @@ void CharacterController::EndPlay() _shape = nullptr; } -#if USE_EDITOR - -void CharacterController::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void CharacterController::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void CharacterController::OnActiveInTreeChanged() { // Skip collider base diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 15ca75a7e..f850b9124 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -212,12 +212,6 @@ protected: /// void UpdateSize() const; -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -240,12 +234,12 @@ protected: // [PhysicsActor] void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; void UpdateLayerBits() override; void BeginPlay(SceneBeginData* data) override; void EndPlay() override; #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void OnActiveInTreeChanged() override; void OnParentChanged() override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 2eeb266aa..a1a84a523 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -2,6 +2,9 @@ #include "Collider.h" #include "Engine/Core/Log.h" +#if USE_EDITOR +#include "Engine/Level/Scene/SceneRendering.h" +#endif #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/PhysicsSettings.h" @@ -182,6 +185,16 @@ bool Collider::IsAttached() const return _shape && _shape->getActor() != nullptr; } +bool Collider::CanAttach(RigidBody* rigidBody) const +{ + return true; +} + +bool Collider::CanBeTrigger() const +{ + return true; +} + RigidBody* Collider::GetAttachedRigidBody() const { if (_shape && _staticActor == nullptr) @@ -193,6 +206,26 @@ RigidBody* Collider::GetAttachedRigidBody() const return nullptr; } +#if USE_EDITOR + +void Collider::OnEnable() +{ + GetSceneRendering()->AddPhysicsDebug(this); + + // Base + Actor::OnEnable(); +} + +void Collider::OnDisable() +{ + // Base + Actor::OnDisable(); + + GetSceneRendering()->RemovePhysicsDebug(this); +} + +#endif + void Collider::Attach(RigidBody* rigidBody) { ASSERT(CanAttach(rigidBody)); @@ -238,11 +271,14 @@ void Collider::UpdateLayerBits() _shape->setQueryFilterData(filterData); } -void Collider::CreateShapeBase(const PxGeometry& geometry) +void Collider::CreateShape() { - ASSERT(_shape == nullptr); + // Setup shape geometry + _cachedScale = GetScale(); + PxGeometryHolder geometry; + GetGeometry(geometry); - // Prepare + // Create shape const bool isTrigger = _isTrigger && CanBeTrigger(); const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy()); PxMaterial* material = Physics::GetDefaultMaterial(); @@ -250,17 +286,62 @@ void Collider::CreateShapeBase(const PxGeometry& geometry) { material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial(); } - - // Create shape - _shape = CPhysX->createShape(geometry, *material, true, shapeFlags); + ASSERT(_shape == nullptr); + _shape = CPhysX->createShape(geometry.any(), *material, true, shapeFlags); ASSERT(_shape); _shape->userData = this; - - // Setup properties _shape->setContactOffset(Math::Max(_shape->getRestOffset() + ZeroTolerance, _contactOffset)); UpdateLayerBits(); } +void Collider::UpdateGeometry() +{ + if (_shape == nullptr) + return; + + // Setup shape geometry + _cachedScale = GetScale(); + PxGeometryHolder geometry; + GetGeometry(geometry); + + // Recreate shape if geometry has different type + if (_shape->getGeometryType() != geometry.getType()) + { + // Detach from the actor + auto actor = _shape->getActor(); + if (actor) + actor->detachShape(*_shape); + + // Release shape + Physics::RemoveCollider(this); + _shape->release(); + _shape = nullptr; + + // Recreate shape + CreateShape(); + + // Reattach again (only if can, see CanAttach function) + if (actor) + { + const auto rigidBody = dynamic_cast(GetParent()); + if (_staticActor != nullptr || (rigidBody && CanAttach(rigidBody))) + { + actor->attachShape(*_shape); + } + else + { + // Be static triangle mesh + CreateStaticActor(); + } + } + + return; + } + + // Update shape + _shape->setGeometry(geometry.any()); +} + void Collider::CreateStaticActor() { ASSERT(_staticActor == nullptr); @@ -285,6 +366,14 @@ void Collider::RemoveStaticActor() _staticActor = nullptr; } +#if USE_EDITOR + +void Collider::DrawPhysicsDebug(RenderView& view) +{ +} + +#endif + void Collider::OnMaterialChanged() { // Update the shape material diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index c836a7886..3e7b5ecfd 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -160,19 +160,13 @@ public: /// /// The rigid body. /// true if this collider can be attached the specified rigid body; otherwise, false. - virtual bool CanAttach(RigidBody* rigidBody) const - { - return true; - } + virtual bool CanAttach(RigidBody* rigidBody) const; /// /// Determines whether this collider can be a trigger shape. /// /// true if this collider can be trigger; otherwise, false. - virtual bool CanBeTrigger() const - { - return true; - } + virtual bool CanBeTrigger() const; /// /// Attaches collider to the specified rigid body. @@ -198,20 +192,20 @@ protected: virtual void UpdateBounds() = 0; /// - /// Creates the collider shape. + /// Gets the collider shape geometry. /// - virtual void CreateShape() = 0; + /// The output geometry. + virtual void GetGeometry(PxGeometryHolder& geometry) = 0; /// - /// Creates the collider shape from the given geometry. + /// Creates the collider shape. /// - /// The geometry. - void CreateShapeBase(const PxGeometry& geometry); + virtual void CreateShape(); /// /// Updates the shape geometry. /// - virtual void UpdateGeometry() = 0; + virtual void UpdateGeometry(); /// /// Creates the static actor. @@ -223,6 +217,10 @@ protected: /// void RemoveStaticActor(); +#if USE_EDITOR + virtual void DrawPhysicsDebug(RenderView& view); +#endif + private: void OnMaterialChanged(); @@ -237,6 +235,10 @@ public: protected: // [PhysicsColliderActor] +#if USE_EDITOR + void OnEnable() override; + void OnDisable() override; +#endif void BeginPlay(SceneBeginData* data) override; void EndPlay() override; void OnActiveInTreeChanged() override; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 1b72d83eb..7ba76f963 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -5,11 +5,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/Physics.h" -#include -#include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif MeshCollider::MeshCollider(const SpawnParams& params) : Collider(params) @@ -117,26 +112,6 @@ void MeshCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(CollisionData); } -#if USE_EDITOR - -void MeshCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void MeshCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void MeshCollider::UpdateBounds() { // Cache bounds @@ -149,11 +124,10 @@ void MeshCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void MeshCollider::CreateShape() +void MeshCollider::GetGeometry(PxGeometryHolder& geometry) { // Prepare scale - Vector3 scale = GetScale(); - _cachedScale = scale; + Vector3 scale = _cachedScale; scale.Absolute(); const float minSize = 0.001f; scale = Vector3::Max(scale, minSize); @@ -165,100 +139,23 @@ void MeshCollider::CreateShape() if (type == CollisionDataType::ConvexMesh) { // Convex mesh - PxConvexMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.convexMesh = CollisionData->GetConvex(); - CreateShapeBase(geometry); + PxConvexMeshGeometry convexMesh; + convexMesh.scale.scale = C2P(scale); + convexMesh.convexMesh = CollisionData->GetConvex(); + geometry.storeAny(convexMesh); } else if (type == CollisionDataType::TriangleMesh) { // Triangle mesh - PxTriangleMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.triangleMesh = CollisionData->GetTriangle(); - CreateShapeBase(geometry); + PxTriangleMeshGeometry triangleMesh; + triangleMesh.scale.scale = C2P(scale); + triangleMesh.triangleMesh = CollisionData->GetTriangle(); + geometry.storeAny(triangleMesh); } else { // Dummy geometry - const PxSphereGeometry geometry(0.01f); - CreateShapeBase(geometry); - } -} - -void MeshCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Recreate shape if geometry has different type - CollisionDataType type = CollisionDataType::None; - if (CollisionData && CollisionData->IsLoaded()) - type = CollisionData->GetOptions().Type; - if ((type == CollisionDataType::ConvexMesh && _shape->getGeometryType() != PxGeometryType::eCONVEXMESH) - || (type == CollisionDataType::TriangleMesh && _shape->getGeometryType() != PxGeometryType::eTRIANGLEMESH) - || (type == CollisionDataType::None && _shape->getGeometryType() != PxGeometryType::eSPHERE) - ) - { - // Detach from the actor - auto actor = _shape->getActor(); - if (actor) - actor->detachShape(*_shape); - - // Release shape - Physics::RemoveCollider(this); - _shape->release(); - _shape = nullptr; - - // Recreate shape - CreateShape(); - - // Reattach again (only if can, see CanAttach function) - if (actor) - { - if (_staticActor != nullptr || type != CollisionDataType::TriangleMesh) - { - actor->attachShape(*_shape); - } - else - { - // Be static triangle mesh - CreateStaticActor(); - } - } - - return; - } - - // Prepare scale - Vector3 scale = GetScale(); - _cachedScale = scale; - scale.Absolute(); - const float minSize = 0.001f; - scale = Vector3::Max(scale, minSize); - - // Setup shape (based on type) - if (type == CollisionDataType::ConvexMesh) - { - // Convex mesh - PxConvexMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.convexMesh = CollisionData->GetConvex(); - _shape->setGeometry(geometry); - } - else if (type == CollisionDataType::TriangleMesh) - { - // Triangle mesh - PxTriangleMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.triangleMesh = CollisionData->GetTriangle(); - _shape->setGeometry(geometry); - } - else - { - // Dummy geometry - const PxSphereGeometry geometry(0.01f); - _shape->setGeometry(geometry); + const PxSphereGeometry sphere(minSize); + geometry.storeAny(sphere); } } diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 22dbd755f..efcf60cee 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -25,9 +25,6 @@ private: void OnCollisionDataChanged(); void OnCollisionDataLoaded(); -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif public: @@ -45,10 +42,8 @@ protected: // [Collider] #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; }; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp index ef308cf8a..b602b8f84 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.cpp +++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif SphereCollider::SphereCollider(const SpawnParams& params) : Collider(params) @@ -67,26 +64,6 @@ void SphereCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Radius, _radius); } -#if USE_EDITOR - -void SphereCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void SphereCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void SphereCollider::UpdateBounds() { // Cache bounds @@ -95,32 +72,11 @@ void SphereCollider::UpdateBounds() _sphere.GetBoundingBox(_box); } -void SphereCollider::CreateShape() +void SphereCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float radius = Math::Abs(_radius) * scaling; const float minSize = 0.001f; - const PxSphereGeometry geometry(Math::Max(radius, minSize)); - - // Setup shape - CreateShapeBase(geometry); -} - -void SphereCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Abs(_radius) * scaling; - const float minSize = 0.001f; - const PxSphereGeometry geometry(Math::Max(radius, minSize)); - - // Setup shape - _shape->setGeometry(geometry); + const PxSphereGeometry sphere(Math::Max(radius, minSize)); + geometry.storeAny(sphere); } diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 0835152f0..ed2d4cd09 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -37,12 +37,6 @@ public: /// API_PROPERTY() void SetRadius(float value); -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -57,10 +51,8 @@ protected: // [Collider] #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; }; diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index 80a4e30b9..6465dd900 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -28,6 +28,7 @@ namespace physx class PxFoundation; class PxShape; class PxGeometry; + class PxGeometryHolder; class PxProfileZoneManager; class PxMaterial; class PxPvd; From 29521b1b03aeaa586e3c19930bf3b096d1000ec3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 17:19:45 +0100 Subject: [PATCH 176/222] Remove Spline init from OnEnable to prevent double setup on SplineModel and SplineCollider --- Source/Engine/Level/Actors/Spline.cpp | 9 --------- Source/Engine/Level/Actors/Spline.h | 1 - 2 files changed, 10 deletions(-) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 08a06d433..1a5e580bb 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -505,12 +505,3 @@ void Spline::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier UpdateSpline(); } } - -void Spline::OnEnable() -{ - // Base - Actor::OnEnable(); - - // Initialize spline - UpdateSpline(); -} diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index 7d7e8dcb1..747530703 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -382,5 +382,4 @@ public: #endif void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; - void OnEnable() override; }; From 6c253ce8922e37df96efe2dd174c31f95ce77302 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Feb 2021 17:20:05 +0100 Subject: [PATCH 177/222] Fix DebugDraw DrawTriangles crash --- Source/Engine/Debug/DebugDraw.cpp | 18 ++++++++++++++---- Source/Engine/Debug/DebugDraw.h | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 1f7e0c596..4341695b7 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -869,9 +869,9 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Color& color, list = duration > 0 ? &DebugDrawDepthTest.DefaultTriangles : &DebugDrawDepthTest.OneFrameTriangles; else list = duration > 0 ? &DebugDrawDefault.DefaultTriangles : &DebugDrawDefault.OneFrameTriangles; - list->EnsureCapacity(list->Count() + vertices.Length()); + list->EnsureCapacity(list->Count() + vertices.Length() / 3); - for (int32 i = 0; i < vertices.Length() * 3;) + for (int32 i = 0; i < vertices.Length();) { t.V0 = vertices[i++]; t.V1 = vertices[i++]; @@ -881,6 +881,11 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Color& color, } } +void DebugDraw::DrawTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) +{ + DrawTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration, bool depthTest) { ASSERT(indices.Length() % 3 == 0); @@ -894,9 +899,9 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Span& list = duration > 0 ? &DebugDrawDepthTest.DefaultTriangles : &DebugDrawDepthTest.OneFrameTriangles; else list = duration > 0 ? &DebugDrawDefault.DefaultTriangles : &DebugDrawDefault.OneFrameTriangles; - list->EnsureCapacity(list->Count() + indices.Length()); + list->EnsureCapacity(list->Count() + indices.Length() / 3); - for (int32 i = 0; i < indices.Length() * 3;) + for (int32 i = 0; i < indices.Length();) { t.V0 = vertices[indices[i++]]; t.V1 = vertices[indices[i++]]; @@ -906,6 +911,11 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Span& } } +void DebugDraw::DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration, bool depthTest) +{ + DrawTriangles(Span(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration, bool depthTest) { // Check if has no length (just sphere) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 6027c6697..81c704894 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -113,6 +113,15 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the triangles using the given index buffer. /// @@ -123,6 +132,16 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the wireframe box. /// From 49758fbfff6fd0dc13bdcddd0426d81b0a7fdeaf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 11 Feb 2021 16:47:43 +0100 Subject: [PATCH 178/222] Add Spline Collider --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 16 +- Source/Engine/Level/Actors/Spline.cpp | 5 + Source/Engine/Level/Actors/Spline.h | 5 +- .../Physics/Colliders/SplineCollider.cpp | 329 ++++++++++++++++++ .../Engine/Physics/Colliders/SplineCollider.h | 61 ++++ 5 files changed, 410 insertions(+), 6 deletions(-) create mode 100644 Source/Engine/Physics/Colliders/SplineCollider.cpp create mode 100644 Source/Engine/Physics/Colliders/SplineCollider.h diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 1eeb55511..9ce64efa9 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -308,10 +308,11 @@ namespace FlaxEditor.SceneGraph.Actors { base.OnContextMenu(contextMenu); - contextMenu.AddButton("Add spline model", OnAddSplineMode); + contextMenu.AddButton("Add spline model", OnAddSplineModel); + contextMenu.AddButton("Add spline collider", OnAddSplineCollider); } - private void OnAddSplineMode() + private void OnAddSplineModel() { var actor = new SplineModel { @@ -321,6 +322,17 @@ namespace FlaxEditor.SceneGraph.Actors Editor.Instance.SceneEditing.Spawn(actor, Actor); } + private void OnAddSplineCollider() + { + var actor = new SplineCollider + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + // TODO: auto pick the collision data if already using spline model + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + /// public override void OnDispose() { diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 1a5e580bb..76ecb9653 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -10,6 +10,11 @@ Spline::Spline(const SpawnParams& params) { } +bool Spline::GetIsLoop() const +{ + return _loop; +} + void Spline::SetIsLoop(bool value) { if (_loop != value) diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h index 747530703..f19f9a443 100644 --- a/Source/Engine/Level/Actors/Spline.h +++ b/Source/Engine/Level/Actors/Spline.h @@ -28,10 +28,7 @@ public: /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. /// API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")") - FORCE_INLINE bool GetIsLoop() const - { - return _loop; - } + bool GetIsLoop() const; /// /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp new file mode 100644 index 000000000..a529baf7d --- /dev/null +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -0,0 +1,329 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineCollider.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Level/Actors/Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Physics/Utilities.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if COMPILE_WITH_PHYSICS_COOKING +#include "Engine/Physics/CollisionCooking.h" +#include +#include +#endif +#include + +SplineCollider::SplineCollider(const SpawnParams& params) + : Collider(params) +{ + CollisionData.Changed.Bind(this); + CollisionData.Loaded.Bind(this); +} + +void SplineCollider::OnCollisionDataChanged() +{ + // This should not be called during physics simulation, if it happened use write lock on physx scene + ASSERT(!Physics::IsDuringSimulation()); + + if (CollisionData) + { + // Ensure that collision asset is loaded (otherwise objects might fall though collider that is not yet loaded on play begin) + CollisionData->WaitForLoaded(); + } + + UpdateGeometry(); +} + +void SplineCollider::OnCollisionDataLoaded() +{ + UpdateGeometry(); +} + +void SplineCollider::OnSplineUpdated() +{ + if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + + UpdateGeometry(); +} + +bool SplineCollider::CanAttach(RigidBody* rigidBody) const +{ + return false; +} + +bool SplineCollider::CanBeTrigger() const +{ + return false; +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +void SplineCollider::DrawPhysicsDebug(RenderView& view) +{ + DEBUG_DRAW_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow * 0.8f, 0, true); +} + +void SplineCollider::OnDebugDrawSelected() +{ + DEBUG_DRAW_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow, 0, false); + + // Base + Collider::OnDebugDrawSelected(); +} + +#endif + +bool SplineCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + // Use detailed hit + if (_shape) + { + RayCastHit hitInfo; + if (!RayCast(ray.Position, ray.Direction, hitInfo)) + return false; + distance = hitInfo.Distance; + normal = hitInfo.Normal; + return true; + } + + // Fallback to AABB + return _box.Intersects(ray, distance, normal); +} + +void SplineCollider::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Collider::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineCollider); + + SERIALIZE(CollisionData); +} + +void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Collider::Deserialize(stream, modifier); + + DESERIALIZE(CollisionData); +} + +void SplineCollider::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Collider::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +void SplineCollider::EndPlay() +{ + // Base + Collider::EndPlay(); + + // Cleanup + if (_triangleMesh) + { + Physics::RemoveObject(_triangleMesh); + _triangleMesh = nullptr; + } +} + +void SplineCollider::UpdateBounds() +{ + // Unused as bounds are updated during collision building +} + +void SplineCollider::GetGeometry(PxGeometryHolder& geometry) +{ + // Reset bounds + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + + // Skip if sth is missing + if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) + { + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + PROFILE_CPU(); + + // Extract collision geometry + // TODO: cache memory allocation for dynamic colliders + Array collisionVertices; + Array collisionIndices; + CollisionData->ExtractGeometry(collisionVertices, collisionIndices); + if (collisionIndices.IsEmpty()) + { + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + + // Apply local mesh transformation + const Transform localTransform = _localTransform; + if (!localTransform.IsIdentity()) + { + for (int32 i = 0; i < collisionVertices.Count(); i++) + collisionVertices[i] = localTransform.LocalToWorld(collisionVertices[i]); + } + + // Find collision geometry local bounds + BoundingBox localModelBounds; + localModelBounds.Minimum = localModelBounds.Maximum = collisionVertices[0]; + for (int32 i = 1; i < collisionVertices.Count(); i++) + { + Vector3 v = collisionVertices[i]; + localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, v); + localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, v); + } + auto localModelBoundsSize = localModelBounds.GetSize(); + + // Deform geometry over the spline + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + _vertexBuffer.Resize(collisionVertices.Count() * segments); + _indexBuffer.Resize(collisionIndices.Count() * segments); + const Transform splineTransform = _spline->GetTransform(); + const Transform colliderTransform = GetTransform(); + Transform curveTransform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + // Setup for the spline segment + auto offsetVertices = segment * collisionVertices.Count(); + auto offsetIndices = segment * collisionIndices.Count(); + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Vertex buffer is deformed along the spline + auto srcVertices = collisionVertices.Get(); + auto dstVertices = _vertexBuffer.Get() + offsetVertices; + for (int32 i = 0; i < collisionVertices.Count(); i++) + { + Vector3 v = srcVertices[i]; + const float alpha = Math::Saturate((v.Z - localModelBounds.Minimum.Z) / localModelBoundsSize.Z); + v.Z = alpha; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, curveTransform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + curveTransform.Orientation = orientation * curveTransform.Orientation; + + // Transform vertex + v = curveTransform.LocalToWorld(v); + v = splineTransform.LocalToWorld(v); + v = colliderTransform.WorldToLocal(v); + + dstVertices[i] = v; + } + + // Index buffer is the same for every segment except it's shifted + auto srcIndices = collisionIndices.Get(); + auto dstIndices = _indexBuffer.Get() + offsetIndices; + for (int32 i = 0; i < collisionIndices.Count(); i++) + dstIndices[i] = srcIndices[i] + offsetVertices; + } + + // Prepare scale + Vector3 scale = _cachedScale; + scale.Absolute(); + const float minSize = 0.001f; + scale = Vector3::Max(scale, minSize); + + // TODO: add support for cooking collision for static splines in editor and reusing it in game + +#if COMPILE_WITH_PHYSICS_COOKING + // Cook triangle mesh collision + CollisionCooking::CookingInput cookingInput; + cookingInput.VertexCount = _vertexBuffer.Count(); + cookingInput.VertexData = _vertexBuffer.Get(); + cookingInput.IndexCount = _indexBuffer.Count(); + cookingInput.IndexData = _indexBuffer.Get(); + cookingInput.Is16bitIndexData = false; + BytesContainer collisionData; + if (!CollisionCooking::CookTriangleMesh(cookingInput, collisionData)) + { + // Create triangle mesh + if (_triangleMesh) + { + Physics::RemoveObject(_triangleMesh); + _triangleMesh = nullptr; + } + PxDefaultMemoryInputData input(collisionData.Get(), collisionData.Length()); + // TODO: try using getVerticesForModification for dynamic triangle mesh vertices updating when changing curve in the editor + _triangleMesh = Physics::GetPhysics()->createTriangleMesh(input); + if (!_triangleMesh) + { + LOG(Error, "Failed to create triangle mesh from collision data of {0}.", ToString()); + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + +#if USE_EDITOR + // Transform vertices back to world space for debug shapes drawing + for (int32 i = 0; i < _vertexBuffer.Count(); i++) + _vertexBuffer[i] = colliderTransform.LocalToWorld(_vertexBuffer[i]); +#endif + + // Update bounds + _box = P2C(_triangleMesh->getLocalBounds()); + Matrix splineWorld; + colliderTransform.GetWorld(splineWorld); + BoundingBox::Transform(_box, splineWorld, _box); + BoundingSphere::FromBox(_box, _sphere); + + // Setup geometry + PxTriangleMeshGeometry triangleMesh; + triangleMesh.scale.scale = C2P(scale); + triangleMesh.triangleMesh = _triangleMesh; + geometry.storeAny(triangleMesh); + +#if !USE_EDITOR + // Free memory for static splines (if editor collision preview is not needed) + if (IsStatic()) + { + _vertexBuffer.Resize(0); + _indexBuffer.Resize(0); + } +#endif + + return; + } +#endif + + LOG(Error, "Cannot build collision data for {0} due to runtime collision cooking diabled.", ToString()); + geometry.storeAny(PxSphereGeometry(0.001f)); +} diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h new file mode 100644 index 000000000..b5c0cf318 --- /dev/null +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Collider.h" +#include "Engine/Content/AssetReference.h" +#include "Engine/Physics/CollisionData.h" + +class Spline; + +/// +/// A collider represented by an arbitrary mesh that goes over the spline. +/// +/// +/// +API_CLASS() class FLAXENGINE_API SplineCollider : public Collider +{ +DECLARE_SCENE_OBJECT(SplineCollider); +private: + Spline* _spline = nullptr; + PxTriangleMesh* _triangleMesh = nullptr; + Array _vertexBuffer; + Array _indexBuffer; + +public: + + /// + /// Linked collision data asset that contains convex mesh or triangle mesh used to represent a spline collider shape. + /// + API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Collider\")") + AssetReference CollisionData; + +private: + + void OnCollisionDataChanged(); + void OnCollisionDataLoaded(); + void OnSplineUpdated(); + +public: + + // [Collider] + bool CanAttach(RigidBody* rigidBody) const override; + bool CanBeTrigger() const override; +#if USE_EDITOR + void OnDebugDrawSelected() override; +#endif + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + void EndPlay() override; + +protected: + + // [Collider] +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif + void UpdateBounds() override; + void GetGeometry(PxGeometryHolder& geometry) override; +}; From 4ba82047e00ce38199798aabd6e0f238c26df858 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 11 Feb 2021 16:47:50 +0100 Subject: [PATCH 179/222] Improve CollisionCooking --- Source/Engine/Physics/CollisionCooking.cpp | 33 +++++++++++++--------- Source/Engine/Physics/CollisionCooking.h | 33 ++++++++-------------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 070f49936..6769e50fc 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -10,6 +10,8 @@ #include #include +#define CONVEX_VERTEX_MIN 8 +#define CONVEX_VERTEX_MAX 255 #define ENSURE_CAN_COOK \ if (Physics::GetCooking() == nullptr) \ { \ @@ -27,7 +29,10 @@ bool CollisionCooking::CookConvexMesh(CookingInput& input, BytesContainer& outpu desc.points.stride = sizeof(Vector3); desc.points.data = input.VertexData; desc.flags = PxConvexFlag::eCOMPUTE_CONVEX; - desc.vertexLimit = input.ConvexVertexLimit; + if (input.ConvexVertexLimit == 0) + desc.vertexLimit = CONVEX_VERTEX_MAX; + else + desc.vertexLimit = (PxU16)Math::Clamp(input.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (input.ConvexFlags & ConvexMeshGenerationFlags::SkipValidation) desc.flags |= PxConvexFlag::Enum::eDISABLE_MESH_VALIDATION; if (input.ConvexFlags & ConvexMeshGenerationFlags::UsePlaneShifting) @@ -83,9 +88,9 @@ bool CollisionCooking::CookTriangleMesh(CookingInput& input, BytesContainer& out bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::SerializedOptions& outputOptions, BytesContainer& outputData) { - int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, 8, 255); + int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (arg.ConvexVertexLimit == 0) - convexVertexLimit = 255; + convexVertexLimit = CONVEX_VERTEX_MAX; DataContainer finalVertexData; DataContainer finalIndexData; @@ -106,19 +111,19 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali auto lod = &arg.OverrideModelData->LODs[lodIndex]; const int32 meshesCount = lod->Meshes.Count(); - // Count vertex/index buffer sizes - int32 vCount = 0; - int32 iCount = 0; - for (int32 i = 0; i < meshesCount; i++) - { - const auto mesh = lod->Meshes[i]; - vCount += mesh->Positions.Count(); + // Count vertex/index buffer sizes + int32 vCount = 0; + int32 iCount = 0; + for (int32 i = 0; i < meshesCount; i++) + { + const auto mesh = lod->Meshes[i]; + vCount += mesh->Positions.Count(); - if (needIndexBuffer) - { - iCount += mesh->Indices.Count() * 3; - } + if (needIndexBuffer) + { + iCount += mesh->Indices.Count() * 3; } + } if (meshesCount == 1) { diff --git a/Source/Engine/Physics/CollisionCooking.h b/Source/Engine/Physics/CollisionCooking.h index b368af547..658f88428 100644 --- a/Source/Engine/Physics/CollisionCooking.h +++ b/Source/Engine/Physics/CollisionCooking.h @@ -24,13 +24,13 @@ public: struct CookingInput { - int32 VertexCount; - Vector3* VertexData; - int32 IndexCount; - void* IndexData; - bool Is16bitIndexData; - ConvexMeshGenerationFlags ConvexFlags; - int32 ConvexVertexLimit; + int32 VertexCount = 0; + Vector3* VertexData = nullptr; + int32 IndexCount = 0; + void* IndexData = nullptr; + bool Is16bitIndexData = false; + ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; + int32 ConvexVertexLimit = 255; }; /// @@ -38,21 +38,12 @@ public: /// struct Argument { - CollisionDataType Type; - ModelData* OverrideModelData; + CollisionDataType Type = CollisionDataType::None; + ModelData* OverrideModelData = nullptr; AssetReference Model; - int32 ModelLodIndex; - ConvexMeshGenerationFlags ConvexFlags; - int32 ConvexVertexLimit; - - Argument() - { - Type = CollisionDataType::None; - OverrideModelData = nullptr; - ModelLodIndex = 0; - ConvexFlags = ConvexMeshGenerationFlags::None; - ConvexVertexLimit = 255; - } + int32 ModelLodIndex = 0; + ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; + int32 ConvexVertexLimit = 255; }; /// From 04b650cbc36a885ca37493a5a31663d210e14479 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 11 Feb 2021 16:48:10 +0100 Subject: [PATCH 180/222] Add Pre Rotation to Spline Model and Spline Collider --- Source/Engine/Level/Actors/SplineModel.cpp | 39 +++++++++++-------- Source/Engine/Level/Actors/SplineModel.h | 12 ++++++ .../Physics/Colliders/SplineCollider.cpp | 18 +++++++-- .../Engine/Physics/Colliders/SplineCollider.h | 12 ++++++ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index c8a7b7b1f..c06dfe75e 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -28,6 +28,19 @@ SplineModel::~SplineModel() Allocator::Free(_deformationBufferData); } +Quaternion SplineModel::GetPreRotation() const +{ + return _preRotation; +} + +void SplineModel::SetPreRotation(const Quaternion& value) +{ + if (_preRotation == value) + return; + _preRotation = value; + OnSplineUpdated(); +} + float SplineModel::GetQuality() const { return _quality; @@ -108,12 +121,11 @@ void SplineModel::OnSplineUpdated() const int32 segments = keyframes.Count() - 1; const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; - const Transform splineTransform = _spline->GetTransform(); + const Transform splineTransform = GetTransform(); _instances.Resize(segments, false); - BoundingBox localModelBounds; + BoundingBox localModelBounds(Vector3::Maximum, Vector3::Minimum); { auto& meshes = Model->LODs[0].Meshes; - Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; Vector3 corners[8]; for (int32 j = 0; j < meshes.Count(); j++) { @@ -122,12 +134,9 @@ void SplineModel::OnSplineUpdated() for (int32 i = 0; i < 8; i++) { - //Vector3::Transform(corners[i], localTransform, tmp); - //_localTransform.LocalToWorld(corners[i], tmp); - - // Transform mesh corner using local spline model transformation but use double-precision to prevent issues when rotating model - tmp = corners[i] * _localTransform.Scale; - double rotation[4] = { (double)_localTransform.Orientation.X, (double)_localTransform.Orientation.Y, (double)_localTransform.Orientation.Z, (double)_localTransform.Orientation.W }; + // Transform mesh corner using pre-transform but use double-precision to prevent issues when rotating model + Vector3 tmp = corners[i]; + double rotation[4] = { (double)_preRotation.X, (double)_preRotation.Y, (double)_preRotation.Z, (double)_preRotation.W }; const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); const double inv = 1.0 / length; rotation[0] *= inv; @@ -151,13 +160,11 @@ void SplineModel::OnSplineUpdated() (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)), (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)), (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy))); - Vector3::Add(tmp, _localTransform.Translation, tmp); - min = Vector3::Min(min, tmp); - max = Vector3::Max(max, tmp); + localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, tmp); + localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, tmp); } } - localModelBounds = BoundingBox(min, max); } _meshMinZ = localModelBounds.Minimum.Z; _meshMaxZ = localModelBounds.Maximum.Z; @@ -185,9 +192,7 @@ void SplineModel::OnSplineUpdated() segmentPoints.Add(chunkWorld.Translation); maxScale = Math::Max(maxScale, chunkWorld.Scale.GetAbsolute().MaxValue()); } - maxScale = Math::Max(maxScale, _localTransform.Scale.GetAbsolute().MaxValue()); BoundingSphere::FromPoints(segmentPoints.Get(), segmentPoints.Count(), instance.Sphere); - instance.Sphere.Center += _localTransform.Translation; instance.Sphere.Radius *= maxScale * _boundsScale; } @@ -356,8 +361,8 @@ void SplineModel::Draw(RenderContext& renderContext) drawCall.Deformable.MeshMaxZ = _meshMaxZ; drawCall.Deformable.GeometrySize = _box.GetSize(); drawCall.PerInstanceRandom = GetPerInstanceRandom(); - _localTransform.GetWorld(drawCall.Deformable.LocalMatrix); - const Transform splineTransform = _spline->GetTransform(); + Matrix::RotationQuaternion(_preRotation, drawCall.Deformable.LocalMatrix); + const Transform splineTransform = GetTransform(); splineTransform.GetWorld(drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index a7d53af49..9a2d5420b 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -26,6 +26,7 @@ private: char _forcedLod = -1; bool _deformationDirty = false; Array _instances; + Quaternion _preRotation = Quaternion::Identity; Spline* _spline = nullptr; GPUBuffer* _deformationBuffer = nullptr; void* _deformationBufferData = nullptr; @@ -41,6 +42,17 @@ public: API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")") AssetReference Model; + /// + /// Gets the rotation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY(Attributes="EditorOrder(21), EditorDisplay(\"Model\")") + Quaternion GetPreRotation() const; + + /// + /// Sets the rotation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY() void SetPreRotation(const Quaternion& value); + /// /// The draw passes to use for rendering this object. /// diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index a529baf7d..ff670fcd5 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -22,6 +22,19 @@ SplineCollider::SplineCollider(const SpawnParams& params) CollisionData.Loaded.Bind(this); } +Quaternion SplineCollider::GetPreRotation() const +{ + return _preRotation; +} + +void SplineCollider::SetPreRotation(const Quaternion& value) +{ + if (_preRotation == value) + return; + _preRotation = value; + UpdateGeometry(); +} + void SplineCollider::OnCollisionDataChanged() { // This should not be called during physics simulation, if it happened use write lock on physx scene @@ -180,11 +193,10 @@ void SplineCollider::GetGeometry(PxGeometryHolder& geometry) } // Apply local mesh transformation - const Transform localTransform = _localTransform; - if (!localTransform.IsIdentity()) + if (!_preRotation.IsIdentity()) { for (int32 i = 0; i < collisionVertices.Count(); i++) - collisionVertices[i] = localTransform.LocalToWorld(collisionVertices[i]); + collisionVertices[i] = Vector3::Transform(collisionVertices[i], _preRotation); } // Find collision geometry local bounds diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h index b5c0cf318..e7ca5a831 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.h +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -21,6 +21,7 @@ private: PxTriangleMesh* _triangleMesh = nullptr; Array _vertexBuffer; Array _indexBuffer; + Quaternion _preRotation = Quaternion::Identity; public: @@ -30,6 +31,17 @@ public: API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Collider\")") AssetReference CollisionData; + /// + /// Gets the rotation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY(Attributes="EditorOrder(101), EditorDisplay(\"Collider\")") + Quaternion GetPreRotation() const; + + /// + /// Sets the rotation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY() void SetPreRotation(const Quaternion& value); + private: void OnCollisionDataChanged(); From 62acae10a532e6f615a298f46e5fe5c6bd47c6d5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 11 Feb 2021 16:48:21 +0100 Subject: [PATCH 181/222] Fix compile error --- Source/Engine/Core/Types/DataContainer.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 954ff24e2..8b504e58f 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -324,9 +324,7 @@ public: void Append(T* data, int32 length) { if (length <= 0) - { return; - } if (Base::Length() == 0) { Copy(data, length); @@ -337,16 +335,13 @@ public: const auto prevLength = Base::_length; Base::_length = prevLength + length; - Base::_data = Allocator::Allocate(Base::_length * sizeof(T)); + Base::_data = (T*)Allocator::Allocate(Base::_length * sizeof(T)); Platform::MemoryCopy(Base::_data, prev, prevLength * sizeof(T)); Platform::MemoryCopy(Base::_data + prevLength * sizeof(T), data, length * sizeof(T)); if (_isAllocated && prev) - { Allocator::Free(prev); - } - _isAllocated = true; } From 5a23df6478d266886085f2837c7e17a2eaaf3526 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 10:34:50 +0100 Subject: [PATCH 182/222] Fix missing saving Pre Rotation --- Source/Engine/Level/Actors/SplineModel.cpp | 2 ++ Source/Engine/Physics/Colliders/SplineCollider.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index c06dfe75e..2a0972ffa 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -447,6 +447,7 @@ void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(BoundsScale, _boundsScale); SERIALIZE_MEMBER(LODBias, _lodBias); SERIALIZE_MEMBER(ForcedLOD, _forcedLod); + SERIALIZE_MEMBER(PreRotation, _preRotation) SERIALIZE(Model); SERIALIZE(DrawModes); @@ -463,6 +464,7 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(BoundsScale, _boundsScale); DESERIALIZE_MEMBER(LODBias, _lodBias); DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); + DESERIALIZE_MEMBER(PreRotation, _preRotation); DESERIALIZE(Model); DESERIALIZE(DrawModes); diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index ff670fcd5..6855eae69 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -120,6 +120,7 @@ void SplineCollider::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_GET_OTHER_OBJ(SplineCollider); SERIALIZE(CollisionData); + SERIALIZE_MEMBER(PreRotation, _preRotation) } void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -128,6 +129,7 @@ void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* Collider::Deserialize(stream, modifier); DESERIALIZE(CollisionData); + DESERIALIZE_MEMBER(PreRotation, _preRotation); } void SplineCollider::OnParentChanged() @@ -325,7 +327,7 @@ void SplineCollider::GetGeometry(PxGeometryHolder& geometry) #if !USE_EDITOR // Free memory for static splines (if editor collision preview is not needed) - if (IsStatic()) + if (IsTransformStatic()) { _vertexBuffer.Resize(0); _indexBuffer.Resize(0); From 80e2aee92b5ad61d39e172ccec38552b1dec11f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 10:57:45 +0100 Subject: [PATCH 183/222] Fixes and tweaks --- Source/Engine/Audio/AudioClip.cpp | 2 +- Source/Engine/Content/Assets/Model.cpp | 2 +- Source/Engine/Content/Assets/SkinnedModel.cpp | 2 +- Source/Engine/Content/BinaryAsset.cpp | 2 +- .../Engine/Content/Loading/Tasks/LoadAssetDataTask.h | 2 +- Source/Engine/Content/Loading/Tasks/LoadAssetTask.h | 2 +- Source/Engine/Content/WeakAssetReference.h | 11 ++--------- Source/Engine/Engine/Base/GameBase.cpp | 4 ++-- Source/Engine/Graphics/GPUResourceProperty.h | 9 --------- Source/Engine/Physics/CollisionCooking.cpp | 2 +- 10 files changed, 11 insertions(+), 27 deletions(-) diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 4005e46b9..29dd2a153 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -125,7 +125,7 @@ void AudioClip::StreamingTask::OnEnd() { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 897e589a8..8cdd0601e 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -104,7 +104,7 @@ protected: { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 5309cf6b9..dfccfec19 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -97,7 +97,7 @@ protected: { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 7160cb287..1dbc672f2 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -491,7 +491,7 @@ protected: void OnEnd() override { _dataLock.Release(); - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h index 9d97a439f..5d552b996 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h @@ -82,7 +82,7 @@ protected: void OnEnd() override { _dataLock.Release(); - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 7eed30399..7a7ea285f 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -68,7 +68,7 @@ protected: void OnEnd() override { - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index 9ed81b521..76f6b1fff 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -66,14 +66,6 @@ public: return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; } - /// - /// Clears the asset reference. - /// - FORCE_INLINE void Unlink() - { - OnSet(nullptr); - } - /// /// Gets the asset property value as string. /// @@ -103,7 +95,8 @@ protected: { ASSERT(_asset == asset); Unload(); - Unlink(); + asset->OnUnloaded.Unbind(this); + asset = nullptr; } }; diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index c313b0737..62e187e7d 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -259,13 +259,13 @@ void GameBaseImpl::OnSplashScreenEnd() { // Hide splash screen SplashScreenTime = 0; - SplashScreen.Unlink(); + SplashScreen = nullptr; MainRenderTask::Instance->PostRender.Unbind(&OnPostRender); // Load the first scene LOG(Info, "Loading the first scene"); const auto sceneId = FirstScene ? FirstScene.GetID() : Guid::Empty; - FirstScene.Unlink(); + FirstScene = nullptr; if (Level::LoadSceneAsync(sceneId)) { LOG(Fatal, "Cannot load the first scene."); diff --git a/Source/Engine/Graphics/GPUResourceProperty.h b/Source/Engine/Graphics/GPUResourceProperty.h index 5ad31439d..49e3c7b9b 100644 --- a/Source/Engine/Graphics/GPUResourceProperty.h +++ b/Source/Engine/Graphics/GPUResourceProperty.h @@ -75,12 +75,8 @@ public: GPUResourceProperty& operator=(const GPUResourceProperty& other) { - // Protect against invalid self-assignment if (this != &other) - { Set(other.Get()); - } - return *this; } @@ -158,7 +154,6 @@ public: /// Value to assign void Set(T* value) { - // Check if value will change if (_resource != value) { // Remove reference from the old one @@ -179,7 +174,6 @@ public: /// void Unlink() { - // Check if value will change if (_resource) { // Remove reference from the old one @@ -194,10 +188,7 @@ private: { if (_resource) { - // Unlink _resource = nullptr; - - // Fire event OnUnload(this); } } diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 6769e50fc..9076d979e 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -15,7 +15,7 @@ #define ENSURE_CAN_COOK \ if (Physics::GetCooking() == nullptr) \ { \ - LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settings option SupportCookingAtRuntime to use terrain generation at runtime."); \ + LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settings option SupportCookingAtRuntime to use collision generation at runtime."); \ return true; \ } From 0e78d13146b50c19a4e528d8c3d8b9c9f79d5b34 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 11:15:51 +0100 Subject: [PATCH 184/222] Add support for navmesh on spline collider --- Source/Engine/Navigation/NavMeshBuilder.cpp | 13 +++++++++++++ Source/Engine/Physics/Colliders/SplineCollider.cpp | 10 ++++++++++ Source/Engine/Physics/Colliders/SplineCollider.h | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index fc0e9d51c..55319bb9e 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -16,6 +16,7 @@ #include "Engine/Physics/Colliders/SphereCollider.h" #include "Engine/Physics/Colliders/CapsuleCollider.h" #include "Engine/Physics/Colliders/MeshCollider.h" +#include "Engine/Physics/Colliders/SplineCollider.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/Terrain.h" @@ -259,6 +260,18 @@ struct NavigationSceneRasterization e.RasterizeTriangles(); } + else if (const auto* splineCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("SplineCollider"); + + auto collisionData = splineCollider->CollisionData.Get(); + if (!collisionData || collisionData->WaitForLoaded()) + return true; + + splineCollider->ExtractGeometry(vb, ib); + + e.RasterizeTriangles(); + } else if (const auto* terrain = dynamic_cast(actor)) { PROFILE_CPU_NAMED("Terrain"); diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index 6855eae69..947942fc8 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -35,6 +35,16 @@ void SplineCollider::SetPreRotation(const Quaternion& value) UpdateGeometry(); } +#if USE_EDITOR + +void SplineCollider::ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const +{ + vertexBuffer.Add(_vertexBuffer); + indexBuffer.Add(_indexBuffer); +} + +#endif + void SplineCollider::OnCollisionDataChanged() { // This should not be called during physics simulation, if it happened use write lock on physx scene diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h index e7ca5a831..3bfcfacf1 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.h +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -42,6 +42,17 @@ public: /// API_PROPERTY() void SetPreRotation(const Quaternion& value); +#if USE_EDITOR + + /// + /// Extracts the collision data geometry into list of triangles. + /// + /// The output vertex buffer. + /// The output index buffer. + void ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const; + +#endif + private: void OnCollisionDataChanged(); From 17d0f40e1cb1f49c1130bc3359f1cc9731e1a0fb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 11:27:13 +0100 Subject: [PATCH 185/222] Add automatic navmesh update when editing spline with navmesh-relevant collider --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 9ce64efa9..a9dec21b6 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -46,6 +46,7 @@ namespace FlaxEditor.SceneGraph.Actors { var actor = (Spline)_node.Actor; actor.SetSplineTransform(Index, value); + OnSplineEdited(actor); } } @@ -111,6 +112,7 @@ namespace FlaxEditor.SceneGraph.Actors { var spline = Object.Find(ref SplineId); sceneModule.MarkSceneEdited(spline.Scene); + OnSplineEdited(spline); } } @@ -142,6 +144,7 @@ namespace FlaxEditor.SceneGraph.Actors undoAction = action; var splineNode = (SplineNode)SceneGraphFactory.FindNode(action.SplineId); splineNode.OnUpdate(); + OnSplineEdited(actor); return splineNode.ActorChildNodes[newIndex]; } @@ -194,6 +197,7 @@ namespace FlaxEditor.SceneGraph.Actors if (splineNode == null) return null; splineNode.OnUpdate(); + OnSplineEdited(spline); return splineNode.ActorChildNodes[data.Index]; } } @@ -333,6 +337,19 @@ namespace FlaxEditor.SceneGraph.Actors Editor.Instance.SceneEditing.Spawn(actor, Actor); } + private static void OnSplineEdited(Spline spline) + { + var collider = spline.GetChild(); + if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode) + { + var options = Editor.Instance.Options.Options.General; + if (options.AutoRebuildNavMesh) + { + Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs); + } + } + } + /// public override void OnDispose() { From 9fd5e8928c3b00ae74c1fc114f0dc2baf2a144d8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 11:27:30 +0100 Subject: [PATCH 186/222] Add missing scene graph nodes types mapping --- Source/Editor/SceneGraph/SceneGraphFactory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 5f3dd8b0f..9abf44af3 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -66,6 +66,8 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(ParticleEffect), typeof(ParticleEffectNode)); CustomNodesTypes.Add(typeof(SceneAnimationPlayer), typeof(SceneAnimationPlayerNode)); CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode)); + CustomNodesTypes.Add(typeof(SplineModel), typeof(ActorNode)); + CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode)); } /// From 4dd8e19f791fe43491b063587a7ee7afa25303c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 11:27:47 +0100 Subject: [PATCH 187/222] Fix Scene Queries to lock scene access --- Source/Engine/Level/SceneQuery.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/SceneQuery.h b/Source/Engine/Level/SceneQuery.h index 4dedf0b8d..245c4c8a7 100644 --- a/Source/Engine/Level/SceneQuery.h +++ b/Source/Engine/Level/SceneQuery.h @@ -3,10 +3,10 @@ #pragma once // Enables locking scenes during scene query execution can provide some safety when using scene queries from other threads but may provide stalls on a main thread -#define SCENE_QUERIES_WITH_LOCK 0 +#define SCENE_QUERIES_WITH_LOCK 1 -#include "Scene/Scene.h" #include "Level.h" +#include "Scene/Scene.h" #if SCENE_QUERIES_WITH_LOCK #include "Engine/Threading/Threading.h" #endif From 74c1ac89c812efa7f7186348afa9f3cd0723a659 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 11:43:46 +0100 Subject: [PATCH 188/222] Add `DebugDraw.DrawWireTriangles` for wireframe geometry debug drawing --- Source/Engine/Debug/DebugDraw.cpp | 148 +++++++++++++++--- Source/Engine/Debug/DebugDraw.h | 42 +++++ .../Physics/Colliders/SplineCollider.cpp | 4 +- 3 files changed, 168 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 4341695b7..afc2311ff 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -130,6 +130,8 @@ struct DebugDrawData Array OneFrameLines; Array DefaultTriangles; Array OneFrameTriangles; + Array DefaultWireTriangles; + Array OneFrameWireTriangles; inline int32 Count() const { @@ -143,7 +145,7 @@ struct DebugDrawData inline int32 TrianglesCount() const { - return DefaultTriangles.Count() + OneFrameTriangles.Count(); + return DefaultTriangles.Count() + OneFrameTriangles.Count() + DefaultWireTriangles.Count() + OneFrameWireTriangles.Count(); } inline void Add(const DebugLine& l) @@ -162,13 +164,23 @@ struct DebugDrawData OneFrameTriangles.Add(t); } + inline void AddWire(const DebugTriangle& t) + { + if (t.TimeLeft > 0) + DefaultWireTriangles.Add(t); + else + OneFrameWireTriangles.Add(t); + } + inline void Update(float deltaTime) { UpdateList(deltaTime, DefaultLines); UpdateList(deltaTime, DefaultTriangles); + UpdateList(deltaTime, DefaultWireTriangles); OneFrameLines.Clear(); OneFrameTriangles.Clear(); + OneFrameWireTriangles.Clear(); } inline void Clear() @@ -177,6 +189,8 @@ struct DebugDrawData OneFrameLines.Clear(); DefaultTriangles.Clear(); OneFrameTriangles.Clear(); + DefaultWireTriangles.Clear(); + OneFrameWireTriangles.Clear(); } inline void Release() @@ -185,16 +199,20 @@ struct DebugDrawData OneFrameLines.Resize(0); DefaultTriangles.Resize(0); OneFrameTriangles.Resize(0); + DefaultWireTriangles.Resize(0); + OneFrameWireTriangles.Resize(0); } }; DebugDrawData DebugDrawDefault; DebugDrawData DebugDrawDepthTest; AssetReference DebugDrawShader; -PsData DebugDrawPsWireDefault; -PsData DebugDrawPsWireDepthTest; -PsData DebugDrawPsDefault; -PsData DebugDrawPsDepthTest; +PsData DebugDrawPsLinesDefault; +PsData DebugDrawPsLinesDepthTest; +PsData DebugDrawPsWireTrianglesDefault; +PsData DebugDrawPsWireTrianglesDepthTest; +PsData DebugDrawPsTrianglesDefault; +PsData DebugDrawPsTrianglesDepthTest; DynamicVertexBuffer* DebugDrawVB = nullptr; Vector3 SphereCache[DEBUG_DRAW_SPHERE_VERTICES]; Vector3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; @@ -453,16 +471,21 @@ void DebugDrawService::Update() // Default desc.PS = shader->GetPS("PS"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; - failed |= DebugDrawPsWireDefault.Create(desc); + failed |= DebugDrawPsLinesDefault.Create(desc); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; - failed |= DebugDrawPsDefault.Create(desc); + failed |= DebugDrawPsTrianglesDefault.Create(desc); + desc.Wireframe = true; + failed |= DebugDrawPsWireTrianglesDefault.Create(desc); // Depth Test + desc.Wireframe = false; desc.PS = shader->GetPS("PS_DepthTest"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; - failed |= DebugDrawPsWireDepthTest.Create(desc); + failed |= DebugDrawPsLinesDepthTest.Create(desc); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; - failed |= DebugDrawPsDepthTest.Create(desc); + failed |= DebugDrawPsTrianglesDepthTest.Create(desc); + desc.Wireframe = true; + failed |= DebugDrawPsWireTrianglesDepthTest.Create(desc); if (failed) { @@ -482,10 +505,12 @@ void DebugDrawService::Dispose() // Release resources SphereTriangleCache.Resize(0); - DebugDrawPsWireDefault.Release(); - DebugDrawPsWireDepthTest.Release(); - DebugDrawPsDepthTest.Release(); - DebugDrawPsDepthTest.Release(); + DebugDrawPsLinesDefault.Release(); + DebugDrawPsLinesDepthTest.Release(); + DebugDrawPsWireTrianglesDefault.Release(); + DebugDrawPsWireTrianglesDepthTest.Release(); + DebugDrawPsTrianglesDefault.Release(); + DebugDrawPsTrianglesDepthTest.Release(); SAFE_DELETE(DebugDrawVB); DebugDrawShader = nullptr; } @@ -495,9 +520,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe PROFILE_GPU_CPU("Debug Draw"); // Ensure to have shader loaded and any lines to render - const int32 DebugDrawDepthTestCount = DebugDrawDepthTest.Count(); - const int32 DebugDrawDefaultCount = DebugDrawDefault.Count(); - if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || DebugDrawDepthTestCount + DebugDrawDefaultCount == 0) + const int32 debugDrawDepthTestCount = DebugDrawDepthTest.Count(); + const int32 debugDrawDefaultCount = DebugDrawDefault.Count(); + if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0) return; if (renderContext.Buffers == nullptr || !DebugDrawVB) return; @@ -517,6 +542,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe const DebugDrawCall defaultLines = WriteLists(vertexCounter, DebugDrawDefault.DefaultLines, DebugDrawDefault.OneFrameLines); const DebugDrawCall depthTestTriangles = WriteLists(vertexCounter, DebugDrawDepthTest.DefaultTriangles, DebugDrawDepthTest.OneFrameTriangles); const DebugDrawCall defaultTriangles = WriteLists(vertexCounter, DebugDrawDefault.DefaultTriangles, DebugDrawDefault.OneFrameTriangles); + const DebugDrawCall depthTestWireTriangles = WriteLists(vertexCounter, DebugDrawDepthTest.DefaultWireTriangles, DebugDrawDepthTest.OneFrameWireTriangles); + const DebugDrawCall defaultWireTriangles = WriteLists(vertexCounter, DebugDrawDefault.DefaultWireTriangles, DebugDrawDefault.OneFrameWireTriangles); DebugDrawVB->Flush(context); #if COMPILE_WITH_PROFILER ProfilerCPU::EndEvent(updateBufferProfileKey); @@ -536,7 +563,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe #define DRAW(drawCall) if (drawCall.VertexCount) // Draw with depth test - if (depthTestLines.VertexCount + depthTestTriangles.VertexCount > 0) + if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount > 0) { if (data.EnableDepthTest) context->BindSR(0, renderContext.Buffers->DepthBuffer); @@ -547,16 +574,25 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe // Lines if (depthTestLines.VertexCount) { - auto state = data.EnableDepthTest ? &DebugDrawPsWireDepthTest : &DebugDrawPsWireDefault; + auto state = data.EnableDepthTest ? &DebugDrawPsLinesDepthTest : &DebugDrawPsLinesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&vb, 1)); context->Draw(depthTestLines.StartVertex, depthTestLines.VertexCount); } + + // Wire Triangles + if (depthTestWireTriangles.VertexCount) + { + auto state = data.EnableDepthTest ? &DebugDrawPsWireTrianglesDepthTest : &DebugDrawPsWireTrianglesDefault; + context->SetState(state->Get(enableDepthWrite, true)); + context->BindVB(ToSpan(&vb, 1)); + context->Draw(depthTestWireTriangles.StartVertex, depthTestWireTriangles.VertexCount); + } // Triangles if (depthTestTriangles.VertexCount) { - auto state = data.EnableDepthTest ? &DebugDrawPsDepthTest : &DebugDrawPsDefault; + auto state = data.EnableDepthTest ? &DebugDrawPsTrianglesDepthTest : &DebugDrawPsTrianglesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&vb, 1)); context->Draw(depthTestTriangles.StartVertex, depthTestTriangles.VertexCount); @@ -567,22 +603,30 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe } // Draw without depth - if (defaultLines.VertexCount + defaultTriangles.VertexCount > 0) + if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount > 0) { context->SetRenderTarget(target); // Lines if (defaultLines.VertexCount) { - context->SetState(DebugDrawPsWireDefault.Get(false, false)); + context->SetState(DebugDrawPsLinesDefault.Get(false, false)); context->BindVB(ToSpan(&vb, 1)); context->Draw(defaultLines.StartVertex, defaultLines.VertexCount); } + // Wire Triangles + if (defaultWireTriangles.VertexCount) + { + context->SetState(DebugDrawPsWireTrianglesDefault.Get(false, false)); + context->BindVB(ToSpan(&vb, 1)); + context->Draw(defaultWireTriangles.StartVertex, defaultWireTriangles.VertexCount); + } + // Triangles if (defaultTriangles.VertexCount) { - context->SetState(DebugDrawPsDefault.Get(false, false)); + context->SetState(DebugDrawPsTrianglesDefault.Get(false, false)); context->BindVB(ToSpan(&vb, 1)); context->Draw(defaultTriangles.StartVertex, defaultTriangles.VertexCount); } @@ -876,7 +920,6 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Color& color, t.V0 = vertices[i++]; t.V1 = vertices[i++]; t.V2 = vertices[i++]; - list->Add(t); } } @@ -906,7 +949,6 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Span& t.V0 = vertices[indices[i++]]; t.V1 = vertices[indices[i++]]; t.V2 = vertices[indices[i++]]; - list->Add(t); } } @@ -916,6 +958,64 @@ void DebugDraw::DrawTriangles(const Array& vertices, const Array(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); } +void DebugDraw::DrawWireTriangles(const Span& vertices, const Color& color, float duration, bool depthTest) +{ + ASSERT(vertices.Length() % 3 == 0); + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + + Array* list; + if (depthTest) + list = duration > 0 ? &DebugDrawDepthTest.DefaultWireTriangles : &DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &DebugDrawDefault.DefaultWireTriangles : &DebugDrawDefault.OneFrameWireTriangles; + list->EnsureCapacity(list->Count() + vertices.Length() / 3); + + for (int32 i = 0; i < vertices.Length();) + { + t.V0 = vertices[i++]; + t.V1 = vertices[i++]; + t.V2 = vertices[i++]; + list->Add(t); + } +} + +void DebugDraw::DrawWireTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) +{ + DrawWireTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); +} + +void DebugDraw::DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration, bool depthTest) +{ + ASSERT(indices.Length() % 3 == 0); + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + + Array* list; + if (depthTest) + list = duration > 0 ? &DebugDrawDepthTest.DefaultWireTriangles : &DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &DebugDrawDefault.DefaultWireTriangles : &DebugDrawDefault.OneFrameWireTriangles; + list->EnsureCapacity(list->Count() + indices.Length() / 3); + + for (int32 i = 0; i < indices.Length();) + { + t.V0 = vertices[indices[i++]]; + t.V1 = vertices[indices[i++]]; + t.V2 = vertices[indices[i++]]; + list->Add(t); + } +} + +void DebugDraw::DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration, bool depthTest) +{ + DrawWireTriangles(Span(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration, bool depthTest) { // Check if has no length (just sphere) diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 81c704894..69c3d4fed 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -142,6 +142,44 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the wireframe triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the wireframe box. /// @@ -251,6 +289,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) @@ -270,6 +310,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index 947942fc8..8e7266755 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -92,12 +92,12 @@ bool SplineCollider::CanBeTrigger() const void SplineCollider::DrawPhysicsDebug(RenderView& view) { - DEBUG_DRAW_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow * 0.8f, 0, true); + DEBUG_DRAW_WIRE_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow * 0.8f, 0, true); } void SplineCollider::OnDebugDrawSelected() { - DEBUG_DRAW_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow, 0, false); + DEBUG_DRAW_WIRE_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow, 0, false); // Base Collider::OnDebugDrawSelected(); From 34b6cfebc3763d6e4c68fddd2047cf9bc746a482 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 12:20:58 +0100 Subject: [PATCH 189/222] Fix navmesh update on some spline editing operations --- Source/Editor/CustomEditors/Dedicated/SplineEditor.cs | 3 +++ Source/Editor/SceneGraph/Actors/SplineNode.cs | 2 +- Source/Editor/Undo/Actions/EditSplineAction.cs | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 9eedd0ed3..51d137ea9 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEditor.Actions; +using FlaxEditor.SceneGraph.Actors; using FlaxEngine; using FlaxEngine.GUI; @@ -40,6 +41,7 @@ namespace FlaxEditor.CustomEditors.Dedicated spline.SetTangentsLinear(); if (enableUndo) Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + SplineNode.OnSplineEdited(spline); Editor.Instance.Scene.MarkSceneEdited(spline.Scene); } } @@ -56,6 +58,7 @@ namespace FlaxEditor.CustomEditors.Dedicated spline.SetTangentsSmooth(); if (enableUndo) Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + SplineNode.OnSplineEdited(spline); Editor.Instance.Scene.MarkSceneEdited(spline.Scene); } } diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index a9dec21b6..f0db64fca 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -337,7 +337,7 @@ namespace FlaxEditor.SceneGraph.Actors Editor.Instance.SceneEditing.Spawn(actor, Actor); } - private static void OnSplineEdited(Spline spline) + internal static void OnSplineEdited(Spline spline) { var collider = spline.GetChild(); if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode) diff --git a/Source/Editor/Undo/Actions/EditSplineAction.cs b/Source/Editor/Undo/Actions/EditSplineAction.cs index 3bb8dac5c..7fb836459 100644 --- a/Source/Editor/Undo/Actions/EditSplineAction.cs +++ b/Source/Editor/Undo/Actions/EditSplineAction.cs @@ -2,6 +2,7 @@ using System; using FlaxEditor.Modules; +using FlaxEditor.SceneGraph.Actors; using FlaxEngine; namespace FlaxEditor.Actions @@ -45,6 +46,7 @@ namespace FlaxEditor.Actions if (spline == null) return; spline.SplineKeyframes = _after; + SplineNode.OnSplineEdited(spline); } /// @@ -54,6 +56,7 @@ namespace FlaxEditor.Actions if (spline == null) return; spline.SplineKeyframes = _before; + SplineNode.OnSplineEdited(spline); } /// From 8661091337073f411904642970bf42c0acdb546a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 15:16:04 +0100 Subject: [PATCH 190/222] Add Spline Rope Body actor for ropes, chains and cables physics --- Source/Editor/SceneGraph/Actors/SplineNode.cs | 11 + Source/Editor/SceneGraph/SceneGraphFactory.cs | 1 + .../Engine/Physics/Actors/SplineRopeBody.cpp | 213 ++++++++++++++++++ Source/Engine/Physics/Actors/SplineRopeBody.h | 70 ++++++ 4 files changed, 295 insertions(+) create mode 100644 Source/Engine/Physics/Actors/SplineRopeBody.cpp create mode 100644 Source/Engine/Physics/Actors/SplineRopeBody.h diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index f0db64fca..3d41d0904 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -314,6 +314,7 @@ namespace FlaxEditor.SceneGraph.Actors contextMenu.AddButton("Add spline model", OnAddSplineModel); contextMenu.AddButton("Add spline collider", OnAddSplineCollider); + contextMenu.AddButton("Add spline rope body", OnAddSplineRopeBody); } private void OnAddSplineModel() @@ -337,6 +338,16 @@ namespace FlaxEditor.SceneGraph.Actors Editor.Instance.SceneEditing.Spawn(actor, Actor); } + private void OnAddSplineRopeBody() + { + var actor = new SplineRopeBody + { + StaticFlags = StaticFlags.None, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + internal static void OnSplineEdited(Spline spline) { var collider = spline.GetChild(); diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 9abf44af3..071cb6e5b 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -68,6 +68,7 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode)); CustomNodesTypes.Add(typeof(SplineModel), typeof(ActorNode)); CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode)); + CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode)); } /// diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp new file mode 100644 index 000000000..acdc8ef59 --- /dev/null +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -0,0 +1,213 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineRopeBody.h" +#include "Engine/Level/Actors/Spline.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Engine/Time.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" + +SplineRopeBody::SplineRopeBody(const SpawnParams& params) + : Actor(params) +{ +} + +void SplineRopeBody::Tick() +{ + if (!_spline || _spline->GetSplinePointsCount() < 2) + return; + PROFILE_CPU(); + + // TODO: add as configs + const float SubstepTime = 0.02f; + const int32 SolverIterations = 1; + + // Cache data + const Vector3 gravity = Physics::GetGravity() * GravityScale; + auto& keyframes = _spline->Curve.GetKeyframes(); + const Transform splineTransform = _spline->GetTransform(); + const int32 keyframesCount = keyframes.Count(); + const float substepTime = SubstepTime; + const float substepTimeSqr = substepTime * substepTime; + bool splineDirty = false; + + // Synchronize spline keyframes with simulated masses + if (_masses.Count() > keyframesCount) + _masses.Resize(keyframesCount); + else + { + _masses.EnsureCapacity(keyframesCount); + while (_masses.Count() < keyframesCount) + { + const int32 i = _masses.Count(); + auto& mass = _masses.AddOne(); + mass.PrevPosition = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + if (i != 0) + mass.SegmentLength = Vector3::Distance(mass.PrevPosition, _masses[i - 1].PrevPosition); + else + mass.SegmentLength = 0.0f; + } + } + { + // Rope start + auto& mass = _masses.First(); + mass.Position = mass.PrevPosition = GetPosition(); + mass.Unconstrained = false; + if (splineTransform.LocalToWorld(keyframes.First().Value.Translation) != mass.Position) + splineDirty = true; + } + for (int32 i = 1; i < keyframesCount; i++) + { + auto& mass = _masses[i]; + mass.Unconstrained = true; + mass.Position = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + } + if (AttachEnd) + { + // Rope end + auto& mass = _masses.Last(); + mass.Position = mass.PrevPosition = AttachEnd->GetPosition(); + mass.Unconstrained = false; + if (splineTransform.LocalToWorld(keyframes.Last().Value.Translation) != mass.Position) + splineDirty = true; + } + + // Perform simulation in substeps to have better stability + _time += Time::Update.DeltaTime.GetTotalSeconds(); + while (_time > substepTime) + { + // Verlet integration + // [Reference: https://en.wikipedia.org/wiki/Verlet_integration] + const Vector3 force = gravity + AdditionalForce; + for (int32 i = 0; i < keyframesCount; i++) + { + auto& mass = _masses[i]; + if (mass.Unconstrained) + { + const Vector3 velocity = mass.Position - mass.PrevPosition; + mass.PrevPosition = mass.Position; + mass.Position = mass.Position + velocity + (substepTimeSqr * force); + keyframes[i].Value.Translation = splineTransform.WorldToLocal(mass.Position); + } + } + + // Constraints solving + for (int32 iteration = 0; iteration < SolverIterations; iteration++) + { + // Distance constraint + for (int32 i = 1; i < keyframesCount; i++) + { + auto& massA = _masses[i - 1]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) + { + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + + // Stiffness constraint + if (EnableStiffness) + { + for (int32 i = 2; i < keyframesCount; i++) + { + auto& massA = _masses[i - 2]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) + { + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + } + } + + _time -= substepTime; + splineDirty = true; + } + + // Update spline and relevant components (eg. spline model) + if (splineDirty) + { + for (int32 i = 0; i < keyframesCount; i++) + keyframes[i].Value.Translation = splineTransform.WorldToLocal(_masses[i].Position); + + _spline->UpdateSpline(); + } +} + +void SplineRopeBody::Serialize(SerializeStream& stream, const void* otherObj) +{ + Actor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineRopeBody); + + SERIALIZE(AttachEnd); + SERIALIZE(GravityScale); + SERIALIZE(AdditionalForce); + SERIALIZE(EnableStiffness); +} + +void SplineRopeBody::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + Actor::Deserialize(stream, modifier); + + DESERIALIZE(AttachEnd); + DESERIALIZE(GravityScale); + DESERIALIZE(AdditionalForce); + DESERIALIZE(EnableStiffness); +} + +void SplineRopeBody::OnEnable() +{ + GetScene()->Ticking.Update.AddTick(this); + + Actor::OnEnable(); +} + +void SplineRopeBody::OnDisable() +{ + Actor::OnDisable(); + + GetScene()->Ticking.Update.RemoveTick(this); +} + +void SplineRopeBody::OnParentChanged() +{ + Actor::OnParentChanged(); + + _spline = Cast(_parent); +} + +void SplineRopeBody::OnTransformChanged() +{ + Actor::OnTransformChanged(); + + _box = BoundingBox(_transform.Translation, _transform.Translation); + _sphere = BoundingSphere(_transform.Translation, 0.0f); +} diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.h b/Source/Engine/Physics/Actors/SplineRopeBody.h new file mode 100644 index 000000000..5ac0506a5 --- /dev/null +++ b/Source/Engine/Physics/Actors/SplineRopeBody.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actor.h" +#include "Engine/Scripting/ScriptingObjectReference.h" + +class Spline; + +/// +/// Physical simulation actor for ropes, chains and cables represented by a spline. +/// +/// +API_CLASS() class FLAXENGINE_API SplineRopeBody : public Actor +{ +DECLARE_SCENE_OBJECT(SplineRopeBody); +private: + + struct Mass + { + Vector3 Position; + float SegmentLength; + Vector3 PrevPosition; + bool Unconstrained; + }; + + Spline* _spline = nullptr; + float _time = 0.0f; + Array _masses; + +public: + + /// + /// The target actor too attach the rope end to. If unset the rope end will run freely. + /// + API_FIELD(Attributes="EditorOrder(0), DefaultValue(null), EditorDisplay(\"Rope\")") + ScriptingObjectReference AttachEnd; + + /// + /// The world gravity scale applied to the rope. Can be used to adjust gravity force or disable it. + /// + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Rope\")") + float GravityScale = 1.0f; + + /// + /// The additional, external force applied to rope (world-space). This can be eg. wind force. + /// + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Rope\")") + Vector3 AdditionalForce = Vector3::Zero; + + /// + /// If checked, the physics solver will use stiffness constraint for rope. It will be less likely to bend over and will preserve more it's shape. + /// + API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Rope\")") + bool EnableStiffness = false; + +private: + + void Tick(); + +public: + + // [Actor] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnEnable() override; + void OnDisable() override; + void OnTransformChanged() override; + void OnParentChanged() override; +}; From 55194edd2cd9d1f146dfbf6b2121f8a2e0c51bed Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 15:28:45 +0100 Subject: [PATCH 191/222] Fix missing NavMesh node type --- Source/Editor/SceneGraph/SceneGraphFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 071cb6e5b..8103f5f05 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -69,6 +69,7 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(SplineModel), typeof(ActorNode)); CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode)); CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode)); + CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode)); } /// From bd5897eaab88bae9670cbb89c7531dc9dd5c89f0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 12 Feb 2021 17:18:32 +0100 Subject: [PATCH 192/222] Fixes and tweaks to SplineRopeBody --- .../Engine/Physics/Actors/SplineRopeBody.cpp | 92 +++++++------------ Source/Engine/Physics/Actors/SplineRopeBody.h | 9 +- 2 files changed, 38 insertions(+), 63 deletions(-) diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp index acdc8ef59..a610e76ce 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.cpp +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -19,10 +19,6 @@ void SplineRopeBody::Tick() return; PROFILE_CPU(); - // TODO: add as configs - const float SubstepTime = 0.02f; - const int32 SolverIterations = 1; - // Cache data const Vector3 gravity = Physics::GetGravity() * GravityScale; auto& keyframes = _spline->Curve.GetKeyframes(); @@ -92,17 +88,40 @@ void SplineRopeBody::Tick() } } - // Constraints solving - for (int32 iteration = 0; iteration < SolverIterations; iteration++) + // Distance constraint + for (int32 i = 1; i < keyframesCount; i++) { - // Distance constraint - for (int32 i = 1; i < keyframesCount; i++) + auto& massA = _masses[i - 1]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) { - auto& massA = _masses[i - 1]; + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + + // Stiffness constraint + if (EnableStiffness) + { + for (int32 i = 2; i < keyframesCount; i++) + { + auto& massA = _masses[i - 2]; auto& massB = _masses[i]; Vector3 offset = massB.Position - massA.Position; const float distance = offset.Length(); - const float scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance); + const float scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance); if (massA.Unconstrained && massB.Unconstrained) { offset *= scale * 0.5f; @@ -118,33 +137,6 @@ void SplineRopeBody::Tick() massB.Position -= scale * offset; } } - - // Stiffness constraint - if (EnableStiffness) - { - for (int32 i = 2; i < keyframesCount; i++) - { - auto& massA = _masses[i - 2]; - auto& massB = _masses[i]; - Vector3 offset = massB.Position - massA.Position; - const float distance = offset.Length(); - const float scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance); - if (massA.Unconstrained && massB.Unconstrained) - { - offset *= scale * 0.5f; - massA.Position += offset; - massB.Position -= offset; - } - else if (massA.Unconstrained) - { - massA.Position += scale * offset; - } - else if (massB.Unconstrained) - { - massB.Position -= scale * offset; - } - } - } } _time -= substepTime; @@ -161,31 +153,9 @@ void SplineRopeBody::Tick() } } -void SplineRopeBody::Serialize(SerializeStream& stream, const void* otherObj) -{ - Actor::Serialize(stream, otherObj); - - SERIALIZE_GET_OTHER_OBJ(SplineRopeBody); - - SERIALIZE(AttachEnd); - SERIALIZE(GravityScale); - SERIALIZE(AdditionalForce); - SERIALIZE(EnableStiffness); -} - -void SplineRopeBody::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - Actor::Deserialize(stream, modifier); - - DESERIALIZE(AttachEnd); - DESERIALIZE(GravityScale); - DESERIALIZE(AdditionalForce); - DESERIALIZE(EnableStiffness); -} - void SplineRopeBody::OnEnable() { - GetScene()->Ticking.Update.AddTick(this); + GetScene()->Ticking.FixedUpdate.AddTick(this); Actor::OnEnable(); } @@ -194,7 +164,7 @@ void SplineRopeBody::OnDisable() { Actor::OnDisable(); - GetScene()->Ticking.Update.RemoveTick(this); + GetScene()->Ticking.FixedUpdate.RemoveTick(this); } void SplineRopeBody::OnParentChanged() diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.h b/Source/Engine/Physics/Actors/SplineRopeBody.h index 5ac0506a5..32e175ba7 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.h +++ b/Source/Engine/Physics/Actors/SplineRopeBody.h @@ -13,6 +13,7 @@ class Spline; /// API_CLASS() class FLAXENGINE_API SplineRopeBody : public Actor { +API_AUTO_SERIALIZATION(); DECLARE_SCENE_OBJECT(SplineRopeBody); private: @@ -54,6 +55,12 @@ public: API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Rope\")") bool EnableStiffness = false; + /// + /// The rope simulation update substep (in seconds). Defines the frequency of physics update. + /// + API_FIELD(Attributes="EditorOrder(40), Limit(0, 0.1f, 0.0001f), EditorDisplay(\"Rope\")") + float SubstepTime = 0.02f; + private: void Tick(); @@ -61,8 +68,6 @@ private: public: // [Actor] - void Serialize(SerializeStream& stream, const void* otherObj) override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; void OnEnable() override; void OnDisable() override; void OnTransformChanged() override; From e42a6b0cccbe122c01a476971211fc7f0d771a14 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 10:53:49 +0100 Subject: [PATCH 193/222] Fix default actor bounds --- 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 509fae98a..324e6ada1 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -43,7 +43,7 @@ Actor::Actor(const SpawnParams& params) , _localTransform(Transform::Identity) , _transform(Transform::Identity) , _sphere(BoundingSphere::Empty) - , _box(BoundingBox::Empty) + , _box(BoundingBox::Zero) , HideFlags(HideFlags::None) { } From c9ea812a2462bec0f8cf1ebd5288796afbefda87 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 11:52:06 +0100 Subject: [PATCH 194/222] Optimize DrawCall to pack indirect draw arg and graphics draw data with union --- Source/Engine/Graphics/Models/Mesh.cpp | 16 +++---- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 6 +-- Source/Engine/Level/Actors/SplineModel.cpp | 2 - Source/Engine/Particles/ParticleManager.cpp | 20 ++++----- Source/Engine/Renderer/DrawCall.h | 45 +++++++++++-------- Source/Engine/Renderer/RenderList.cpp | 18 ++++---- .../Engine/ShadowsOfMordor/Builder.Jobs.cpp | 2 +- Source/Engine/Terrain/TerrainChunk.cpp | 4 -- Source/Engine/Terrain/TerrainManager.cpp | 4 +- Source/Engine/UI/TextRender.cpp | 6 +-- 10 files changed, 57 insertions(+), 66 deletions(-) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index e7007f4ec..8285883ae 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -359,8 +359,8 @@ void Mesh::GetDrawCallGeometry(DrawCall& drawCall) const drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; } void Mesh::Render(GPUContext* context) const @@ -386,11 +386,9 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); @@ -449,11 +447,9 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); } - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index e9e7f8438..9e26867ab 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -186,11 +186,9 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; drawCall.ObjectPosition = drawCall.World.GetTranslation(); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 2a0972ffa..01118ba6a 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -353,8 +353,6 @@ void SplineModel::Draw(RenderContext& renderContext) // Draw all segments DrawCall drawCall; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Deformable.SplineDeformation = _deformationBuffer; drawCall.Deformable.ChunksPerSegment = _chunksPerSegment; drawCall.Deformable.MeshMinZ = _meshMinZ; diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp index 429420950..f2e70fee3 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/ParticleManager.cpp @@ -83,8 +83,8 @@ public: drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = IndexCount; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = IndexCount; } }; @@ -173,10 +173,6 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa const auto context = GPUDevice::Instance->GetMainContext(); auto emitter = buffer->Emitter; - drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; - // Check if need to perform any particles sorting if (emitter->Graph.SortModules.HasItems() && renderContext.View.Pass != DrawPass::Depth) { @@ -516,8 +512,8 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = ribbonModulesDrawIndicesStart[ribbonModuleIndex]; - drawCall.Geometry.IndicesCount = ribbonModulesDrawIndicesCount[ribbonModuleIndex]; + drawCall.Draw.StartIndex = ribbonModulesDrawIndicesStart[ribbonModuleIndex]; + drawCall.Draw.IndicesCount = ribbonModulesDrawIndicesCount[ribbonModuleIndex]; drawCall.InstanceCount = 1; renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); @@ -802,8 +798,8 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Submit draw call SpriteRenderer.SetupDrawCall(drawCall); drawCall.InstanceCount = 0; - drawCall.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; - drawCall.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); + drawCall.Draw.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; + drawCall.Draw.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); indirectDrawCallIndex++; @@ -830,8 +826,8 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Execute draw call mesh.GetDrawCallGeometry(drawCall); drawCall.InstanceCount = 0; - drawCall.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; - drawCall.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); + drawCall.Draw.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; + drawCall.Draw.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); indirectDrawCallIndex++; } diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index bb25c0a57..036ba26bd 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -134,16 +134,6 @@ struct DrawCall /// The geometry vertex buffers byte offsets. /// uint32 VertexBuffersOffsets[3]; - - /// - /// The location of the first index read by the GPU from the index buffer. - /// - int32 StartIndex; - - /// - /// The indices count. - /// - int32 IndicesCount; } Geometry; /// @@ -151,15 +141,34 @@ struct DrawCall /// int32 InstanceCount; - /// - /// The indirect draw arguments offset. - /// - uint32 IndirectArgsOffset; + union + { + struct + { + /// + /// The location of the first index read by the GPU from the index buffer. + /// + int32 StartIndex; - /// - /// The indirect draw arguments buffer. - /// - GPUBuffer* IndirectArgsBuffer; + /// + /// The indices count. + /// + int32 IndicesCount; + }; + + struct + { + /// + /// The indirect draw arguments offset. + /// + uint32 IndirectArgsOffset; + + /// + /// The indirect draw arguments buffer. + /// + GPUBuffer* IndirectArgsBuffer; + }; + } Draw; // Per-material shader data packed into union union diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index dd711a5e0..7056e8737 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -514,8 +514,8 @@ namespace return a.Material == b.Material && a.Material->CanUseInstancing(handler) && Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && - a.IndirectArgsBuffer == nullptr && - b.IndirectArgsBuffer == nullptr && + a.InstanceCount != 0 && + b.InstanceCount != 0 && a.WorldDeterminantSign == b.WorldDeterminantSign; } } @@ -690,20 +690,20 @@ DRAW: context->BindIB(drawCall.Geometry.IndexBuffer); - if (drawCall.IndirectArgsBuffer) + if (drawCall.InstanceCount == 0) { // No support for batching indirect draw calls ASSERT(batch.BatchSize == 1); context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); } else { if (batch.BatchSize == 1) { context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } else { @@ -712,7 +712,7 @@ DRAW: vbOffsets[vbCount] = 0; vbCount++; context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Draw.StartIndex); instanceBufferOffset += batch.BatchSize; } @@ -735,13 +735,13 @@ DRAW: context->BindIB(drawCall.Geometry.IndexBuffer); context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets); - if (drawCall.IndirectArgsBuffer) + if (drawCall.InstanceCount == 0) { - context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); } else { - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } } } diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 7ec466eee..20c2fb325 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -173,7 +173,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) context->SetState(_psRenderCacheTerrain); context->BindIB(drawCall.Geometry.IndexBuffer); context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 1)); - context->DrawIndexed(drawCall.Geometry.IndicesCount, 0, drawCall.Geometry.StartIndex); + context->DrawIndexed(drawCall.Draw.IndicesCount, 0, drawCall.Draw.StartIndex); break; } diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 63a7bb04a..08e5946ee 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -83,8 +83,6 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = _cachedDrawMaterial; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); @@ -140,8 +138,6 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); diff --git a/Source/Engine/Terrain/TerrainManager.cpp b/Source/Engine/Terrain/TerrainManager.cpp index 0638acd45..caf620ff0 100644 --- a/Source/Engine/Terrain/TerrainManager.cpp +++ b/Source/Engine/Terrain/TerrainManager.cpp @@ -37,8 +37,8 @@ public: drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = IndicesCount; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = IndicesCount; } }; diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 5707b4461..8e00c2cc3 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -336,14 +336,12 @@ void TextRender::Draw(RenderContext& renderContext) drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; // Submit draw calls for (const auto& e : _drawChunks) { - drawCall.Geometry.IndicesCount = e.IndicesCount; - drawCall.Geometry.StartIndex = e.StartIndex; + drawCall.Draw.IndicesCount = e.IndicesCount; + drawCall.Draw.StartIndex = e.StartIndex; drawCall.Material = e.Material; renderContext.List->AddDrawCall(drawModes, GetStaticFlags(), drawCall, true); } From cd2a02a5c8c34fc71cf0d5085554dc261fe2c31f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 11:55:31 +0100 Subject: [PATCH 195/222] Add `-shaderdebug` cmd line switch to Editor for shaders debugging --- Source/Engine/Engine/CommandLine.cpp | 1 + Source/Engine/Engine/CommandLine.h | 5 +++++ Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp | 8 ++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index 707f99de1..edfc9c268 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -133,6 +133,7 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles); PARSE_ARG_SWITCH("-build ", Build); PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); + PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug); #endif diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 065135b48..278620bf5 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -150,6 +150,11 @@ public: /// Nullable SkipCompile; + /// + /// -shaderdebug (enables debugging data generation for shaders and disables shader compiler optimizations) + /// + Nullable ShaderDebug; + #endif }; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 2446c5ea6..c97b5e5bf 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -3,6 +3,7 @@ #include "ShaderAssetBase.h" #include "ShaderStorage.h" #include "ShaderCacheManager.h" +#include "Engine/Engine/CommandLine.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/ShadowsOfMordor/AtlasChartsPacker.h" @@ -225,8 +226,11 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) options.SourceLength = sourceLength; options.Profile = shaderProfile; options.Output = &cacheStream; - //options.GenerateDebugData = true; - //options.NoOptimize = true; + if (CommandLine::Options.ShaderDebug) + { + options.GenerateDebugData = true; + options.NoOptimize = true; + } auto& platformDefine = options.Macros.AddOne(); #if PLATFORM_WINDOWS platformDefine.Name = "PLATFORM_WINDOWS"; From b6557cb15cfcf497ca29f322fbe96ee24de2a4d4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 12:02:25 +0100 Subject: [PATCH 196/222] Fix showing shader source code window over a calling window --- Source/Editor/Utilities/Utils.cs | 6 ++++-- Source/Editor/Windows/Assets/MaterialWindow.cs | 12 ++++-------- .../Editor/Windows/Assets/ParticleEmitterWindow.cs | 12 ++++-------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 6de74e6ea..95ad9ade5 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1646,7 +1646,8 @@ namespace FlaxEditor.Utilities /// /// The source code. /// The window title. - public static void ShowSourceCodeWindow(string source, string title) + /// The context control used to show source code window popup in a proper location. + public static void ShowSourceCodeWindow(string source, string title, Control control = null) { if (string.IsNullOrEmpty(source)) { @@ -1659,8 +1660,9 @@ namespace FlaxEditor.Utilities settings.AllowMaximize = false; settings.AllowMinimize = false; settings.HasSizingFrame = false; - settings.StartPosition = WindowStartPosition.CenterScreen; + settings.StartPosition = WindowStartPosition.CenterParent; settings.Size = new Vector2(500, 600) * Platform.DpiScale; + settings.Parent = control?.RootWindow?.Window ?? Editor.Instance.Windows.MainWindow; settings.Title = title; var dialog = Platform.CreateWindow(ref settings); diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index f49e6a79c..53d85f493 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -233,18 +233,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, () => ShowSourceCode(_asset)).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); } - /// - /// Shows the material source code window. - /// - /// The material asset. - public static void ShowSourceCode(Material material) + private void ShowSourceCode() { - var source = Editor.GetShaderSourceCode(material); - Utilities.Utils.ShowSourceCodeWindow(source, "Material Source"); + var source = Editor.GetShaderSourceCode(_asset); + Utilities.Utils.ShowSourceCodeWindow(source, "Material Source", this); } /// diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 17b5a928d..ab61a9d68 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -139,18 +139,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, () => ShowSourceCode(_asset)).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); } - /// - /// Shows the ParticleEmitter source code window. - /// - /// The ParticleEmitter asset. - public static void ShowSourceCode(ParticleEmitter particleEmitter) + private void ShowSourceCode() { - var source = Editor.GetShaderSourceCode(particleEmitter); - Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source"); + var source = Editor.GetShaderSourceCode(_asset); + Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source", this); } /// From 788907f3b121f322b44c7a29fabc94984e58ec4d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 12:09:46 +0100 Subject: [PATCH 197/222] Remove deprecated and unused `ISceneObject` and `ITransformable` --- Source/Editor/CustomEditors/CustomEditor.cs | 4 +- .../CustomEditors/Dedicated/ActorEditor.cs | 2 +- .../CustomEditors/Values/ValueContainer.cs | 4 +- Source/Editor/SceneGraph/SceneGraphNode.cs | 2 +- Source/Engine/Engine/ISceneObject.cs | 37 ------------------- Source/Engine/Engine/ITransformable.cs | 15 -------- Source/Engine/Level/Actor.cs | 2 +- Source/Engine/Scripting/Script.cs | 2 +- 8 files changed, 8 insertions(+), 60 deletions(-) delete mode 100644 Source/Engine/Engine/ISceneObject.cs delete mode 100644 Source/Engine/Engine/ITransformable.cs diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 5f992729f..347eb608f 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -624,7 +624,7 @@ namespace FlaxEditor.CustomEditors return FindPrefabRoot(actor.Parent); } - private ISceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) + private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) { if (actor.PrefabObjectID == prefabObjectId) return actor; @@ -667,7 +667,7 @@ namespace FlaxEditor.CustomEditors { // Special case for object references // If prefab object has reference to other object in prefab needs to revert to matching prefab instance object not the reference prefab object value - if (Values.ReferenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (Values.ReferenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) { if (Values.Count > 1) { diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index 924f5aeb3..b8491abfa 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -206,7 +206,7 @@ namespace FlaxEditor.CustomEditors.Dedicated node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name); } // Actor or Script - else if (editor.Values[0] is ISceneObject sceneObject) + else if (editor.Values[0] is SceneObject sceneObject) { node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected; node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name); diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index b2f26bb7b..f8cecb801 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -167,14 +167,14 @@ namespace FlaxEditor.CustomEditors { if (_hasReferenceValue) { - if (_referenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) { for (int i = 0; i < Count; i++) { if (this[i] == referenceSceneObject) continue; - if (this[i] == null || (this[i] is ISceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) + if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) return true; } } diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index cc47c20f2..835295147 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -15,7 +15,7 @@ namespace FlaxEditor.SceneGraph /// A class is responsible for Scene Graph management. /// [HideInEditor] - public abstract class SceneGraphNode : ITransformable + public abstract class SceneGraphNode { /// /// The parent node. diff --git a/Source/Engine/Engine/ISceneObject.cs b/Source/Engine/Engine/ISceneObject.cs deleted file mode 100644 index 2b5ae7706..000000000 --- a/Source/Engine/Engine/ISceneObject.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// Interface for scene objects that unifies various properties used across actors and scripts. - /// - public interface ISceneObject - { - /// - /// Gets the scene object which contains this object. - /// - Scene Scene { get; } - - /// - /// Gets a value indicating whether this object has a valid linkage to the prefab asset. - /// - bool HasPrefabLink { get; } - - /// - /// Gets the prefab asset ID. Empty if no prefab link exists. - /// - Guid PrefabID { get; } - - /// - /// Gets the ID of the object within a object that is used for synchronization with this object. Empty if no prefab link exists. - /// - Guid PrefabObjectID { get; } - - /// - /// Breaks the prefab linkage for this object (including all children). - /// - void BreakPrefabLink(); - } -} diff --git a/Source/Engine/Engine/ITransformable.cs b/Source/Engine/Engine/ITransformable.cs deleted file mode 100644 index a8bbd910c..000000000 --- a/Source/Engine/Engine/ITransformable.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Interface for objects that can be transformed. - /// - public interface ITransformable - { - /// - /// Gets or sets the transform. - /// - Transform Transform { get; set; } - } -} diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 260fef44e..cfb454354 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -4,7 +4,7 @@ using System; namespace FlaxEngine { - partial class Actor : ITransformable, ISceneObject + partial class Actor { /// /// Returns true if object is fully static on the scene, otherwise false. diff --git a/Source/Engine/Scripting/Script.cs b/Source/Engine/Scripting/Script.cs index 7f8561c68..c1a0894f0 100644 --- a/Source/Engine/Scripting/Script.cs +++ b/Source/Engine/Scripting/Script.cs @@ -2,7 +2,7 @@ namespace FlaxEngine { - partial class Script : ISceneObject + partial class Script { /// /// Gets the scene object which contains this script. From 29f0834cc452b09ce7eb298e7f6933941bf1fb2a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 13:23:09 +0100 Subject: [PATCH 198/222] Optimize build tool --- .../Flax.Build/Bindings/BindingsGenerator.cs | 43 +++---- .../Tools/Flax.Build/Build/Builder.Rules.cs | 108 +++++++++++------- .../Build/NativeCpp/Builder.NativeCpp.cs | 7 +- Source/Tools/Flax.Build/Build/Platform.cs | 2 +- Source/Tools/Flax.Build/Build/Sdk.cs | 2 +- Source/Tools/Flax.Build/Deps/DepsBuilder.cs | 2 +- 6 files changed, 99 insertions(+), 65 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 419820f56..93fe10c0b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Flax.Build.NativeCpp; using BuildData = Flax.Build.Builder.BuildData; @@ -58,11 +57,8 @@ namespace Flax.Build.Bindings private static ModuleInfo ParseModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions = null) { - if (buildData.ModulesInfo.TryGetValue(module, out var moduleInfo)) - return moduleInfo; - // Setup bindings module info descriptor - moduleInfo = new ModuleInfo + var moduleInfo = new ModuleInfo { Module = module, Name = module.BinaryModuleName, @@ -81,26 +77,35 @@ namespace Flax.Build.Bindings throw new Exception($"Cannot parse module {module.Name} without options."); // Collect all header files that can have public symbols for API - var headerFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".h")).ToList(); - - // Skip if no header files to process + var headerFiles = new List(moduleOptions.SourceFiles.Count / 2); + for (int i = 0; i < moduleOptions.SourceFiles.Count; i++) + { + if (moduleOptions.SourceFiles[i].EndsWith(".h", StringComparison.OrdinalIgnoreCase)) + headerFiles.Add(moduleOptions.SourceFiles[i]); + } if (headerFiles.Count == 0) return moduleInfo; + if (module.Name == "Core") + { + // Special case for Core module to ignore API tags defines + for (int i = 0; i < headerFiles.Count; i++) + { + if (headerFiles[i].EndsWith("Config.h", StringComparison.Ordinal)) + { + headerFiles.RemoveAt(i); + break; + } + } + } - // TODO: use aynsc tasks or thread pool to load and parse multiple header files at once using multi-threading + // TODO: use async tasks or thread pool to load and parse multiple header files at once using multi-threading // Find and load files with API tags string[] headerFilesContents = null; - //using (new ProfileEventScope("LoadHeaderFiles")) + using (new ProfileEventScope("LoadHeaderFiles")) { - var anyApi = false; for (int i = 0; i < headerFiles.Count; i++) { - // Skip scripting tags definitions file - if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal)) - continue; - - // Check if file contains any valid API tag var contents = File.ReadAllText(headerFiles[i]); for (int j = 0; j < ApiTokens.SearchTags.Length; j++) { @@ -109,15 +114,13 @@ namespace Flax.Build.Bindings if (headerFilesContents == null) headerFilesContents = new string[headerFiles.Count]; headerFilesContents[i] = contents; - anyApi = true; break; } } } - - if (!anyApi) - return moduleInfo; } + if (headerFilesContents == null) + return moduleInfo; // Skip if none of the headers was modified after last time generated C++ file was edited // TODO: skip parsing if module has API cache file -> then load it from disk diff --git a/Source/Tools/Flax.Build/Build/Builder.Rules.cs b/Source/Tools/Flax.Build/Build/Builder.Rules.cs index 9982e3601..1c4609c2e 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Rules.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Rules.cs @@ -12,12 +12,31 @@ namespace Flax.Build partial class Builder { private static RulesAssembly _rules; + private static Type[] _buildTypes; /// /// The build configuration files postfix. /// public static string BuildFilesPostfix = ".Build.cs"; + /// + /// The cached list of types from Flax.Build assembly. Reused by other build tool utilities to improve performance. + /// + internal static Type[] BuildTypes + { + get + { + if (_buildTypes == null) + { + using (new ProfileEventScope("CacheBuildTypes")) + { + _buildTypes = typeof(Program).Assembly.GetTypes(); + } + } + return _buildTypes; + } + } + /// /// The rules assembly data. /// @@ -128,7 +147,7 @@ namespace Flax.Build using (new ProfileEventScope("InitInBuildPlugins")) { - foreach (var type in typeof(Program).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) + foreach (var type in BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) { var plugin = (Plugin)Activator.CreateInstance(type); plugin.Init(); @@ -176,67 +195,74 @@ namespace Flax.Build // Prepare targets and modules objects Type[] types; - Target[] targetObjects; - Module[] moduleObjects; - Plugin[] pluginObjects; + var targetObjects = new List(16); + var moduleObjects = new List(256); + var pluginObjects = new List(); using (new ProfileEventScope("GetTypes")) { types = assembly.GetTypes(); - targetObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Target))).Select(type => + for (var i = 0; i < types.Length; i++) { - var target = (Target)Activator.CreateInstance(type); - - var targetFilename = target.Name + BuildFilesPostfix; - target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); - if (target.FilePath == null) + var type = types[i]; + if (!type.IsClass || type.IsAbstract) + continue; + if (type.IsSubclassOf(typeof(Target))) { - targetFilename = target.Name + "Target" + BuildFilesPostfix; + var target = (Target)Activator.CreateInstance(type); + + var targetFilename = target.Name + BuildFilesPostfix; target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { - if (target.Name.EndsWith("Target")) - { - targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; - target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); - } + targetFilename = target.Name + "Target" + BuildFilesPostfix; + target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { - throw new Exception(string.Format("Failed to find source file path for {0}", target)); + if (target.Name.EndsWith("Target")) + { + targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; + target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); + } + if (target.FilePath == null) + { + throw new Exception(string.Format("Failed to find source file path for {0}", target)); + } } } + target.FolderPath = Path.GetDirectoryName(target.FilePath); + target.Init(); + targetObjects.Add(target); } - target.FolderPath = Path.GetDirectoryName(target.FilePath); - target.Init(); - return target; - }).ToArray(); - moduleObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module))).Select(type => - { - var module = (Module)Activator.CreateInstance(type); - - var moduleFilename = module.Name + BuildFilesPostfix; - module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); - if (module.FilePath == null) + else if (type.IsSubclassOf(typeof(Module))) { - moduleFilename = module.Name + "Module" + BuildFilesPostfix; + var module = (Module)Activator.CreateInstance(type); + + var moduleFilename = module.Name + BuildFilesPostfix; module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); if (module.FilePath == null) { - throw new Exception(string.Format("Failed to find source file path for {0}", module)); + moduleFilename = module.Name + "Module" + BuildFilesPostfix; + module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); + if (module.FilePath == null) + { + throw new Exception(string.Format("Failed to find source file path for {0}", module)); + } } + module.FolderPath = Path.GetDirectoryName(module.FilePath); + module.Init(); + moduleObjects.Add(module); } - module.FolderPath = Path.GetDirectoryName(module.FilePath); - module.Init(); - return module; - }).ToArray(); - pluginObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))).Select(type => - { - var plugin = (Plugin)Activator.CreateInstance(type); - plugin.Init(); - return plugin; - }).ToArray(); + else if (type.IsSubclassOf(typeof(Plugin))) + { + var plugin = (Plugin)Activator.CreateInstance(type); + + plugin.Init(); + pluginObjects.Add(plugin); + } + } } - _rules = new RulesAssembly(assembly, targetObjects, moduleObjects, pluginObjects); + _rules = new RulesAssembly(assembly, targetObjects.ToArray(), moduleObjects.ToArray(), pluginObjects.ToArray()); } return _rules; diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 70d3722fb..af50cfe1e 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -330,7 +330,12 @@ namespace Flax.Build } // Collect all files to compile - var cppFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".cpp")).ToList(); + var cppFiles = new List(moduleOptions.SourceFiles.Count / 2); + for (int i = 0; i < moduleOptions.SourceFiles.Count; i++) + { + if (moduleOptions.SourceFiles[i].EndsWith(".cpp", StringComparison.OrdinalIgnoreCase)) + cppFiles.Add(moduleOptions.SourceFiles[i]); + } if (!string.IsNullOrEmpty(module.BinaryModuleName)) { diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index 07a18a2a9..15addce27 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -157,7 +157,7 @@ namespace Flax.Build if (_platforms == null) { using (new ProfileEventScope("GetPlatforms")) - _platforms = typeof(Platform).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast().ToArray(); + _platforms = Builder.BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast().ToArray(); } foreach (var platform in _platforms) diff --git a/Source/Tools/Flax.Build/Build/Sdk.cs b/Source/Tools/Flax.Build/Build/Sdk.cs index 450d8f43f..5c7044705 100644 --- a/Source/Tools/Flax.Build/Build/Sdk.cs +++ b/Source/Tools/Flax.Build/Build/Sdk.cs @@ -68,7 +68,7 @@ namespace Flax.Build using (new ProfileEventScope("GetSdks")) { _sdks = new Dictionary(); - var types = typeof(Sdk).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk))); + var types = Builder.BuildTypes.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk))); foreach (var type in types) { object instance = null; diff --git a/Source/Tools/Flax.Build/Deps/DepsBuilder.cs b/Source/Tools/Flax.Build/Deps/DepsBuilder.cs index 7ac4b0d69..6d6eaf5b3 100644 --- a/Source/Tools/Flax.Build/Deps/DepsBuilder.cs +++ b/Source/Tools/Flax.Build/Deps/DepsBuilder.cs @@ -51,7 +51,7 @@ namespace Flax.Deps } // Get all deps - var dependencies = typeof(DepsBuilder).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast().ToArray(); + var dependencies = Builder.BuildTypes.Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast().ToArray(); if (dependencies.Length == 0) Log.Warning("No dependencies found!"); for (var i = 0; i < dependencies.Length; i++) From cdd68c9a89705904db8f0c6bac32ef54801a05c3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 13:32:38 +0100 Subject: [PATCH 199/222] Add MaxConcurrency and ConcurrencyProcessorScale options for Flax.Build cmd line --- Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs | 4 ++-- Source/Tools/Flax.Build/Configuration.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs index c874dba51..9b4a571f1 100644 --- a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs +++ b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs @@ -27,12 +27,12 @@ namespace Flax.Build.BuildSystem.Graph /// /// The maximum amount of threads to be used for the parallel execution. /// - public int ThreadCountMax = 1410; + public int ThreadCountMax = Configuration.MaxConcurrency; /// /// The amount of threads to allocate per processor. Use it to allocate more threads for faster execution or use less to keep reduce CPU usage during build. /// - public float ProcessorCountScale = 1.0f; + public float ProcessorCountScale = Configuration.ConcurrencyProcessorScale; /// public override int Execute(List tasks) diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 4fb58c4f2..baa4e30b1 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -133,6 +133,18 @@ namespace Flax.Build [CommandLine("logfile", "", "The log file path relative to the working directory. Set to empty to disable it/")] public static string LogFile = "Cache/Intermediate/Log.txt"; + /// + /// The maximum allowed concurrency for a build system (maximum active worker threads count). + /// + [CommandLine("maxConcurrency", "", "The maximum allowed concurrency for a build system (maximum active worker threads count).")] + public static int MaxConcurrency = 1410; + + /// + /// The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor. + /// + [CommandLine("concurrencyProcessorScale", "", "The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor.")] + public static float ConcurrencyProcessorScale = 1.0f; + /// /// The output binaries folder path relative to the working directory. /// From 5714741a5d55d7fe3da253f19b4f86c281dbccc8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 15:04:23 +0100 Subject: [PATCH 200/222] Add support for multi-threaded scripting API headers parsing --- .../Bindings/BindingsGenerator.Parsing.cs | 19 +- .../Flax.Build/Bindings/BindingsGenerator.cs | 606 +++++++++--------- Source/Tools/Flax.Build/Bindings/FileInfo.cs | 9 +- .../Tools/Flax.Build/Bindings/ModuleInfo.cs | 9 + 4 files changed, 331 insertions(+), 312 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index c5c58c318..66f93fde3 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -17,6 +17,7 @@ namespace Flax.Build.Bindings public Stack ScopeTypeStack; public Stack ScopeAccessStack; public Dictionary PreprocessorDefines; + public List StringCache; public ApiTypeInfo ValidScopeInfoFromStack { @@ -46,14 +47,12 @@ namespace Flax.Build.Bindings } } - private static List _commentCache; - private static string[] ParseComment(ref ParsingContext context) { - if (_commentCache == null) - _commentCache = new List(); + if (context.StringCache == null) + context.StringCache = new List(); else - _commentCache.Clear(); + context.StringCache.Clear(); int tokensCount = 0; bool isValid = true; @@ -77,7 +76,7 @@ namespace Flax.Build.Bindings if (commentLine.StartsWith("// ")) commentLine = "/// " + commentLine.Substring(3); - _commentCache.Insert(0, commentLine); + context.StringCache.Insert(0, commentLine); break; } default: @@ -90,14 +89,14 @@ namespace Flax.Build.Bindings for (var i = 0; i < tokensCount; i++) context.Tokenizer.NextToken(true, true); - if (_commentCache.Count == 1) + if (context.StringCache.Count == 1) { // Ensure to have summary begin/end pair - _commentCache.Insert(0, "/// "); - _commentCache.Add("/// "); + context.StringCache.Insert(0, "/// "); + context.StringCache.Add("/// "); } - return _commentCache.ToArray(); + return context.StringCache.ToArray(); } private struct TagParameter diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 93fe10c0b..1b12cb838 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Flax.Build.NativeCpp; using BuildData = Flax.Build.Builder.BuildData; @@ -98,324 +100,327 @@ namespace Flax.Build.Bindings } } - // TODO: use async tasks or thread pool to load and parse multiple header files at once using multi-threading - - // Find and load files with API tags - string[] headerFilesContents = null; - using (new ProfileEventScope("LoadHeaderFiles")) + // Parse bindings + Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); + int concurrency = Math.Min(Math.Max(1, (int)(Environment.ProcessorCount * Configuration.ConcurrencyProcessorScale)), Configuration.MaxConcurrency); + concurrency = 1; // Disable concurrency for parsing (the gain is unnoticeable or even worse in some cases) + if (concurrency == 1 || headerFiles.Count < 2 * concurrency) { + // Single-threaded for (int i = 0; i < headerFiles.Count; i++) { - var contents = File.ReadAllText(headerFiles[i]); - for (int j = 0; j < ApiTokens.SearchTags.Length; j++) + using (new ProfileEventScope(Path.GetFileName(headerFiles[i]))) { - if (contents.Contains(ApiTokens.SearchTags[j])) - { - if (headerFilesContents == null) - headerFilesContents = new string[headerFiles.Count]; - headerFilesContents[i] = contents; - break; - } + ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i); } } } - if (headerFilesContents == null) - return moduleInfo; - - // Skip if none of the headers was modified after last time generated C++ file was edited - // TODO: skip parsing if module has API cache file -> then load it from disk - /*if (!forceGenerate) + else { - var lastGenerateTime = File.GetLastWriteTime(bindings.GeneratedCppFilePath); - var anyModified = false; - for (int i = 0; i < headerFiles.Count; i++) + // Sort by file size to improve performance (parse larger files first) + headerFiles.Sort((a, b) => new System.IO.FileInfo(b).Length.CompareTo(new System.IO.FileInfo(a).Length)); + + // Multi-threaded + ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); + if (workerThreads != concurrency) + ThreadPool.SetMaxThreads(concurrency, completionPortThreads); + Parallel.For(0, headerFiles.Count, (i, state) => { - if (File.GetLastWriteTime(headerFiles[i]) > lastGenerateTime) + using (new ProfileEventScope(Path.GetFileName(headerFiles[i]))) { - anyModified = true; - break; + ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i); } - } - - if (!anyModified) - return; - }*/ - - Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); - - // Process all header files to generate the module API code reflection - var context = new ParsingContext - { - CurrentAccessLevel = AccessLevel.Public, - ScopeTypeStack = new Stack(), - ScopeAccessStack = new Stack(), - PreprocessorDefines = new Dictionary(), - }; - for (int i = 0; i < headerFiles.Count; i++) - { - if (headerFilesContents[i] == null) - continue; - var fileInfo = new FileInfo - { - Parent = null, - Children = new List(), - Name = headerFiles[i], - Namespace = moduleInfo.Name, - }; - moduleInfo.AddChild(fileInfo); - - try - { - // Tokenize the source - var tokenizer = new Tokenizer(); - tokenizer.Tokenize(headerFilesContents[i]); - - // Init the context - context.Tokenizer = tokenizer; - context.File = fileInfo; - context.ScopeInfo = null; - context.ScopeTypeStack.Clear(); - context.ScopeAccessStack.Clear(); - context.PreprocessorDefines.Clear(); - context.EnterScope(fileInfo); - - // Process the source code - ApiTypeInfo scopeType = null; - Token prevToken = null; - while (true) - { - // Move to the next token - var token = tokenizer.NextToken(); - if (token == null) - continue; - if (token.Type == TokenType.EndOfFile) - break; - - // Parse API_.. tags in source code - if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal)) - { - if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal)) - { - if (!(context.ScopeInfo is FileInfo)) - throw new NotImplementedException("TODO: add support for nested classes in scripting API"); - - var classInfo = ParseClass(ref context); - scopeType = classInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal)) - { - var propertyInfo = ParseProperty(ref context); - } - else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal)) - { - var functionInfo = ParseFunction(ref context); - - if (context.ScopeInfo is ClassInfo classInfo) - classInfo.Functions.Add(functionInfo); - else if (context.ScopeInfo is StructureInfo structureInfo) - structureInfo.Functions.Add(functionInfo); - else - throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal)) - { - var enumInfo = ParseEnum(ref context); - context.ScopeInfo.AddChild(enumInfo); - } - else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal)) - { - var structureInfo = ParseStructure(ref context); - scopeType = structureInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal)) - { - var fieldInfo = ParseField(ref context); - var scopeInfo = context.ValidScopeInfoFromStack; - - if (scopeInfo is ClassInfo classInfo) - classInfo.Fields.Add(fieldInfo); - else if (scopeInfo is StructureInfo structureInfo) - structureInfo.Fields.Add(fieldInfo); - else - throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal)) - { - var eventInfo = ParseEvent(ref context); - var scopeInfo = context.ValidScopeInfoFromStack; - - if (scopeInfo is ClassInfo classInfo) - classInfo.Events.Add(eventInfo); - else - throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal)) - { - var injectCppCodeInfo = ParseInjectCppCode(ref context); - fileInfo.AddChild(injectCppCodeInfo); - } - else if (string.Equals(token.Value, ApiTokens.Interface, StringComparison.Ordinal)) - { - if (!(context.ScopeInfo is FileInfo)) - throw new NotImplementedException("TODO: add support for nested interfaces in scripting API"); - - var interfaceInfo = ParseInterface(ref context); - scopeType = interfaceInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) - { - if (context.ScopeInfo is ClassInfo classInfo) - classInfo.IsAutoSerialization = true; - else if (context.ScopeInfo is StructureInfo structureInfo) - structureInfo.IsAutoSerialization = true; - else - throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings."); - } - } - - // Track access level inside class - if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier) - { - if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Protected; - } - else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Private; - } - } - - // Handle preprocessor blocks - if (token.Type == TokenType.Preprocessor) - { - token = tokenizer.NextToken(); - switch (token.Value) - { - case "define": - { - token = tokenizer.NextToken(); - var name = token.Value; - var value = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - value += token.Value; - token = tokenizer.NextToken(true); - } - value = value.Trim(); - context.PreprocessorDefines[name] = value; - break; - } - case "if": - { - // Parse condition - var condition = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - var tokenValue = token.Value.Trim(); - if (tokenValue.Length == 0) - { - token = tokenizer.NextToken(true); - continue; - } - - // Very simple defines processing - tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys); - tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions); - tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions); - tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions); - tokenValue = tokenValue.Replace("false", "0"); - tokenValue = tokenValue.Replace("true", "1"); - tokenValue = tokenValue.Replace("||", "|"); - if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") - tokenValue = "0"; - - condition += tokenValue; - token = tokenizer.NextToken(true); - } - - // Filter condition - condition = condition.Replace("1|1", "1"); - condition = condition.Replace("1|0", "1"); - condition = condition.Replace("0|1", "1"); - - // Skip chunk of code of condition fails - if (condition != "1") - { - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - case "ifdef": - { - // Parse condition - var define = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - define += token.Value; - token = tokenizer.NextToken(true); - } - - // Check condition - define = define.Trim(); - if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) - { - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - } - } - - // Scope tracking - if (token.Type == TokenType.LeftCurlyBrace) - { - context.ScopeTypeStack.Push(scopeType); - context.ScopeInfo = context.ScopeTypeStack.Peek(); - scopeType = null; - } - else if (token.Type == TokenType.RightCurlyBrace) - { - context.ScopeTypeStack.Pop(); - if (context.ScopeTypeStack.Count == 0) - throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}."); - context.ScopeInfo = context.ScopeTypeStack.Peek(); - if (context.ScopeInfo is FileInfo) - context.CurrentAccessLevel = AccessLevel.Public; - } - - prevToken = token; - } - } - catch (Exception ex) - { - Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings."); - Log.Exception(ex); - throw; - } + }); } // Initialize API - moduleInfo.Init(buildData); + using (new ProfileEventScope("Init")) + { + moduleInfo.Init(buildData); + } return moduleInfo; } + private static void ParseModuleInnerAsync(ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles, int workIndex) + { + // Find and load files with API tags + bool hasApi = false; + string headerFileContents = File.ReadAllText(headerFiles[workIndex]); + for (int j = 0; j < ApiTokens.SearchTags.Length; j++) + { + if (headerFileContents.Contains(ApiTokens.SearchTags[j])) + { + hasApi = true; + break; + } + } + if (!hasApi) + return; + + // Process header file to generate the module API code reflection + var fileInfo = new FileInfo + { + Parent = null, + Children = new List(), + Name = headerFiles[workIndex], + Namespace = moduleInfo.Name, + }; + lock (moduleInfo) + { + moduleInfo.AddChild(fileInfo); + } + + try + { + // Tokenize the source + var tokenizer = new Tokenizer(); + tokenizer.Tokenize(headerFileContents); + + // Init the context + var context = new ParsingContext + { + File = fileInfo, + Tokenizer = tokenizer, + ScopeInfo = null, + CurrentAccessLevel = AccessLevel.Public, + ScopeTypeStack = new Stack(), + ScopeAccessStack = new Stack(), + PreprocessorDefines = new Dictionary(), + }; + context.EnterScope(fileInfo); + + // Process the source code + ApiTypeInfo scopeType = null; + Token prevToken = null; + while (true) + { + // Move to the next token + var token = tokenizer.NextToken(); + if (token == null) + continue; + if (token.Type == TokenType.EndOfFile) + break; + + // Parse API_.. tags in source code + if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal)) + { + if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested classes in scripting API"); + + var classInfo = ParseClass(ref context); + scopeType = classInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal)) + { + var propertyInfo = ParseProperty(ref context); + } + else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal)) + { + var functionInfo = ParseFunction(ref context); + + if (context.ScopeInfo is ClassInfo classInfo) + classInfo.Functions.Add(functionInfo); + else if (context.ScopeInfo is StructureInfo structureInfo) + structureInfo.Functions.Add(functionInfo); + else + throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal)) + { + var enumInfo = ParseEnum(ref context); + context.ScopeInfo.AddChild(enumInfo); + } + else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal)) + { + var structureInfo = ParseStructure(ref context); + scopeType = structureInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal)) + { + var fieldInfo = ParseField(ref context); + var scopeInfo = context.ValidScopeInfoFromStack; + + if (scopeInfo is ClassInfo classInfo) + classInfo.Fields.Add(fieldInfo); + else if (scopeInfo is StructureInfo structureInfo) + structureInfo.Fields.Add(fieldInfo); + else + throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal)) + { + var eventInfo = ParseEvent(ref context); + var scopeInfo = context.ValidScopeInfoFromStack; + + if (scopeInfo is ClassInfo classInfo) + classInfo.Events.Add(eventInfo); + else + throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal)) + { + var injectCppCodeInfo = ParseInjectCppCode(ref context); + fileInfo.AddChild(injectCppCodeInfo); + } + else if (string.Equals(token.Value, ApiTokens.Interface, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested interfaces in scripting API"); + + var interfaceInfo = ParseInterface(ref context); + scopeType = interfaceInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) + { + if (context.ScopeInfo is ClassInfo classInfo) + classInfo.IsAutoSerialization = true; + else if (context.ScopeInfo is StructureInfo structureInfo) + structureInfo.IsAutoSerialization = true; + else + throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings."); + } + } + + // Track access level inside class + if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier) + { + if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Protected; + } + else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Private; + } + } + + // Handle preprocessor blocks + if (token.Type == TokenType.Preprocessor) + { + token = tokenizer.NextToken(); + switch (token.Value) + { + case "define": + { + token = tokenizer.NextToken(); + var name = token.Value; + var value = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + value += token.Value; + token = tokenizer.NextToken(true); + } + value = value.Trim(); + context.PreprocessorDefines[name] = value; + break; + } + case "if": + { + // Parse condition + var condition = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + var tokenValue = token.Value.Trim(); + if (tokenValue.Length == 0) + { + token = tokenizer.NextToken(true); + continue; + } + + // Very simple defines processing + tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions); + tokenValue = tokenValue.Replace("false", "0"); + tokenValue = tokenValue.Replace("true", "1"); + tokenValue = tokenValue.Replace("||", "|"); + if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") + tokenValue = "0"; + + condition += tokenValue; + token = tokenizer.NextToken(true); + } + + // Filter condition + condition = condition.Replace("1|1", "1"); + condition = condition.Replace("1|0", "1"); + condition = condition.Replace("0|1", "1"); + + // Skip chunk of code of condition fails + if (condition != "1") + { + ParsePreprocessorIf(fileInfo, tokenizer, ref token); + } + + break; + } + case "ifdef": + { + // Parse condition + var define = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + define += token.Value; + token = tokenizer.NextToken(true); + } + + // Check condition + define = define.Trim(); + if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) + { + ParsePreprocessorIf(fileInfo, tokenizer, ref token); + } + + break; + } + } + } + + // Scope tracking + if (token.Type == TokenType.LeftCurlyBrace) + { + context.ScopeTypeStack.Push(scopeType); + context.ScopeInfo = context.ScopeTypeStack.Peek(); + scopeType = null; + } + else if (token.Type == TokenType.RightCurlyBrace) + { + context.ScopeTypeStack.Pop(); + if (context.ScopeTypeStack.Count == 0) + throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}."); + context.ScopeInfo = context.ScopeTypeStack.Peek(); + if (context.ScopeInfo is FileInfo) + context.CurrentAccessLevel = AccessLevel.Public; + } + + prevToken = token; + } + } + catch (Exception ex) + { + Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings."); + Log.Exception(ex); + throw; + } + } + private static string ReplacePreProcessorDefines(string text, IEnumerable defines) { foreach (var define in defines) @@ -594,7 +599,6 @@ namespace Flax.Build.Bindings { foreach (var fieldInfo in structureInfo.Fields) { - // TODO: support bit-fields in structure fields if (fieldInfo.Type.IsBitField) throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {structureInfo.Name})"); diff --git a/Source/Tools/Flax.Build/Bindings/FileInfo.cs b/Source/Tools/Flax.Build/Bindings/FileInfo.cs index 25e1e1944..6c4b4dcfe 100644 --- a/Source/Tools/Flax.Build/Bindings/FileInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FileInfo.cs @@ -7,7 +7,7 @@ namespace Flax.Build.Bindings /// /// The native file information for bindings generator. /// - public class FileInfo : ApiTypeInfo, IComparable + public class FileInfo : ApiTypeInfo, IComparable, IComparable { public override void AddChild(ApiTypeInfo apiTypeInfo) { @@ -26,5 +26,12 @@ namespace Flax.Build.Bindings { return System.IO.Path.GetFileName(Name); } + + public int CompareTo(object obj) + { + if (obj is ApiTypeInfo apiTypeInfo) + return Name.CompareTo(apiTypeInfo.Name); + return 0; + } } } diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 6626145a9..7bb6d1608 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -13,5 +13,14 @@ namespace Flax.Build.Bindings { return "module " + Name; } + + /// + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + // Sort module files to prevent bindings rebuild due to order changes (list might be created in async) + Children.Sort(); + } } } From 4e19b85e00489920f93cbb7e325b62ec0289d738 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 15:04:37 +0100 Subject: [PATCH 201/222] Add support for multi-threaded profile events in build tool --- Source/Tools/Flax.Build/Build/Profiling.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Profiling.cs b/Source/Tools/Flax.Build/Build/Profiling.cs index 98bc2c62a..c5cc89bcb 100644 --- a/Source/Tools/Flax.Build/Build/Profiling.cs +++ b/Source/Tools/Flax.Build/Build/Profiling.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Collections.Generic; using System.Text; +using System.Threading; namespace Flax.Build { @@ -12,7 +13,7 @@ namespace Flax.Build /// public class ProfileEventScope : IDisposable { - private int _id; + private readonly int _id; /// /// Initializes a new instance of the class. @@ -61,6 +62,11 @@ namespace Flax.Build /// The event call depth (for parent-children events evaluation). /// public int Depth; + + /// + /// The calling thread id. + /// + public int ThreadId; } private static int _depth; @@ -78,6 +84,7 @@ namespace Flax.Build e.StartTime = DateTime.Now; e.Duration = TimeSpan.Zero; e.Depth = _depth++; + e.ThreadId = Thread.CurrentThread.ManagedThreadId; _events.Add(e); return _events.Count - 1; } @@ -147,8 +154,8 @@ namespace Flax.Build for (int i = 0; i < _events.Count; i++) { var e = _events[i]; - - contents.Append($"{{ \"pid\":1, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n"); + + contents.Append($"{{ \"pid\":{e.ThreadId}, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n"); if (i + 1 != _events.Count) contents.Append(","); From 134aec2d157d8bbed60eb45a8430685f36d943dc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 15 Feb 2021 20:52:35 +0100 Subject: [PATCH 202/222] Simplified name NetworkProtocol. --- Source/Engine/Platform/Base/NetworkBase.cpp | 2 +- Source/Engine/Platform/Base/NetworkBase.h | 4 ++-- Source/Engine/Platform/Win32/Win32Network.cpp | 12 ++++++------ Source/Engine/Platform/Win32/Win32Network.h | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp index 06f297cc1..6ba5208f8 100644 --- a/Source/Engine/Platform/Base/NetworkBase.cpp +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -2,7 +2,7 @@ #include "NetworkBase.h" -bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) +bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) { return true; } diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 6e6821ab4..90195c729 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -9,7 +9,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); #define SOCKGROUP_MAXCOUNT 64 #define SOCKGROUP_ITEMSIZE 16 -enum class FLAXENGINE_API NetworkProtocolType +enum class FLAXENGINE_API NetworkProtocol { /// Not specified. Undefined, @@ -31,7 +31,7 @@ enum class FLAXENGINE_API NetworkIPVersion struct FLAXENGINE_API NetworkSocket { - NetworkProtocolType Protocol = NetworkProtocolType::Undefined; + NetworkProtocol Protocol = NetworkProtocol::Undefined; NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; byte Data[8] = {}; }; diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index 2da68957b..cea22dacb 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -135,13 +135,13 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i } } -bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv) +bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) { socket.Protocol = proto; socket.IPVersion = ipv; const uint8 family = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; - const uint8 stype = socket.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; - const uint8 prot = socket.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + const uint8 stype = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const uint8 prot = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; SOCKET sock; if ((sock = ::socket(family, stype, prot)) == INVALID_SOCKET) @@ -259,7 +259,7 @@ bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) { - if (serverSock.Protocol != NetworkProtocolType::Tcp) + if (serverSock.Protocol != NetworkProtocol::Tcp) { LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSock.Data); return true; @@ -372,7 +372,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, return -1; } uint32 size; - if (endPoint == nullptr && socket.Protocol == NetworkProtocolType::Tcp) + if (endPoint == nullptr && socket.Protocol == NetworkProtocol::Tcp) { if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) { @@ -380,7 +380,7 @@ int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, return -1; } } - else if (endPoint != nullptr && socket.Protocol == NetworkProtocolType::Udp) + else if (endPoint != nullptr && socket.Protocol == NetworkProtocol::Udp) { if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) { diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h index b0e313056..7a1aaabbf 100644 --- a/Source/Engine/Platform/Win32/Win32Network.h +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -13,7 +13,7 @@ class FLAXENGINE_API Win32Network : public NetworkBase public: // [NetworkBase] - static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); static bool DestroySocket(NetworkSocket& socket); static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); From bc6281ee63a39842b9f7e0c89dda3a2357ed0d4a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 15 Feb 2021 20:53:07 +0100 Subject: [PATCH 203/222] Add MTU option. --- Source/Engine/Platform/Base/NetworkBase.h | 2 ++ Source/Engine/Platform/Win32/Win32Network.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 90195c729..0ae8a3dcc 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -76,6 +76,8 @@ enum class FLAXENGINE_API NetworkSocketOption NoDelay, /// Enables IPv6 communication only for TCP socket. IPv6Only, + /// Retrieve the current path MTU, the socket must be connected UDP/TCP. + Mtu }; struct FLAXENGINE_API NetworkSocketState diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp index cea22dacb..38b99a25e 100644 --- a/Source/Engine/Platform/Win32/Win32Network.cpp +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -132,6 +132,7 @@ static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, i SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP , IP_MTU) } } From a2e13460291c4aa29115f7c9ee652f3024a9578a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 15 Feb 2021 20:53:53 +0100 Subject: [PATCH 204/222] Docs tweak. --- Source/Engine/Platform/Base/NetworkBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 0ae8a3dcc..7c0a1cbd0 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -74,7 +74,7 @@ enum class FLAXENGINE_API NetworkSocketOption Error, /// Enables the Nagle algorithm for TCP sockets. NoDelay, - /// Enables IPv6 communication only for TCP socket. + /// Enables IPv6/Ipv4 dual-stacking, UDP/TCP. IPv6Only, /// Retrieve the current path MTU, the socket must be connected UDP/TCP. Mtu From 26e3400a926d0b684e69ee4eb4d08d2fc238e418 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Mon, 15 Feb 2021 20:54:00 +0100 Subject: [PATCH 205/222] Typo. --- Source/Engine/Platform/Base/NetworkBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h index 7c0a1cbd0..e5ad03f98 100644 --- a/Source/Engine/Platform/Base/NetworkBase.h +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -105,7 +105,7 @@ public: /// The protocol. /// The ip version. /// Returns true on error, otherwise false. - static bool CreateSocket(NetworkSocket& socket, NetworkProtocolType proto, NetworkIPVersion ipv); + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); /// /// Closes native socket. From 81be73ad8201c1127fc708ce140412a42bc9d685 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 22:08:37 +0100 Subject: [PATCH 206/222] Fix spline model not drawing when using invalid material --- Source/Engine/Level/Actors/SplineModel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 01118ba6a..7fc623c8d 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -405,12 +405,12 @@ void SplineModel::Draw(RenderContext& renderContext) continue; // Select material - MaterialBase* material; + MaterialBase* material = nullptr; if (entry.Material && entry.Material->IsLoaded()) material = entry.Material; else if (slot.Material && slot.Material->IsLoaded()) material = slot.Material; - else + if (!material || !material->IsDeformable()) material = GPUDevice::Instance->GetDefaultDeformableMaterial(); if (!material || !material->IsDeformable()) continue; From 0841f5b479ad4f12b7f18ad2b3efd3628985e528 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Feb 2021 22:09:05 +0100 Subject: [PATCH 207/222] Refactor Pre Rotation from spline model and spline collider into Pre Transform for more control --- Source/Engine/Level/Actors/SplineModel.cpp | 26 +++++++++---------- Source/Engine/Level/Actors/SplineModel.h | 10 +++---- .../Physics/Colliders/SplineCollider.cpp | 18 ++++++------- .../Engine/Physics/Colliders/SplineCollider.h | 10 +++---- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 7fc623c8d..84306716b 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -28,16 +28,16 @@ SplineModel::~SplineModel() Allocator::Free(_deformationBufferData); } -Quaternion SplineModel::GetPreRotation() const +Transform SplineModel::GetPreTransform() const { - return _preRotation; + return _preTransform; } -void SplineModel::SetPreRotation(const Quaternion& value) +void SplineModel::SetPreTransform(const Transform& value) { - if (_preRotation == value) + if (_preTransform == value) return; - _preRotation = value; + _preTransform = value; OnSplineUpdated(); } @@ -135,8 +135,8 @@ void SplineModel::OnSplineUpdated() for (int32 i = 0; i < 8; i++) { // Transform mesh corner using pre-transform but use double-precision to prevent issues when rotating model - Vector3 tmp = corners[i]; - double rotation[4] = { (double)_preRotation.X, (double)_preRotation.Y, (double)_preRotation.Z, (double)_preRotation.W }; + Vector3 tmp = corners[i] * _preTransform.Scale; + double rotation[4] = { (double)_preTransform.Orientation.X, (double)_preTransform.Orientation.Y, (double)_preTransform.Orientation.Z, (double)_preTransform.Orientation.W }; const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); const double inv = 1.0 / length; rotation[0] *= inv; @@ -157,9 +157,9 @@ void SplineModel::OnSplineUpdated() const double yz = rotation[1] * z; const double zz = rotation[2] * z; tmp = Vector3( - (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)), - (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)), - (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy))); + (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)) + _preTransform.Translation.X, + (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)) + _preTransform.Translation.Y, + (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy)) + _preTransform.Translation.Z); localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, tmp); localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, tmp); @@ -359,7 +359,7 @@ void SplineModel::Draw(RenderContext& renderContext) drawCall.Deformable.MeshMaxZ = _meshMaxZ; drawCall.Deformable.GeometrySize = _box.GetSize(); drawCall.PerInstanceRandom = GetPerInstanceRandom(); - Matrix::RotationQuaternion(_preRotation, drawCall.Deformable.LocalMatrix); + _preTransform.GetWorld(drawCall.Deformable.LocalMatrix); const Transform splineTransform = GetTransform(); splineTransform.GetWorld(drawCall.World); drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); @@ -445,7 +445,7 @@ void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(BoundsScale, _boundsScale); SERIALIZE_MEMBER(LODBias, _lodBias); SERIALIZE_MEMBER(ForcedLOD, _forcedLod); - SERIALIZE_MEMBER(PreRotation, _preRotation) + SERIALIZE_MEMBER(PreTransform, _preTransform) SERIALIZE(Model); SERIALIZE(DrawModes); @@ -462,7 +462,7 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(BoundsScale, _boundsScale); DESERIALIZE_MEMBER(LODBias, _lodBias); DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); - DESERIALIZE_MEMBER(PreRotation, _preRotation); + DESERIALIZE_MEMBER(PreTransform, _preTransform); DESERIALIZE(Model); DESERIALIZE(DrawModes); diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index 9a2d5420b..6a0393cdc 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -26,7 +26,7 @@ private: char _forcedLod = -1; bool _deformationDirty = false; Array _instances; - Quaternion _preRotation = Quaternion::Identity; + Transform _preTransform = Transform::Identity; Spline* _spline = nullptr; GPUBuffer* _deformationBuffer = nullptr; void* _deformationBufferData = nullptr; @@ -43,15 +43,15 @@ public: AssetReference Model; /// - /// Gets the rotation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// Gets the transformation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. /// API_PROPERTY(Attributes="EditorOrder(21), EditorDisplay(\"Model\")") - Quaternion GetPreRotation() const; + Transform GetPreTransform() const; /// - /// Sets the rotation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// Sets the transformation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. /// - API_PROPERTY() void SetPreRotation(const Quaternion& value); + API_PROPERTY() void SetPreTransform(const Transform& value); /// /// The draw passes to use for rendering this object. diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index 8e7266755..f003971ae 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -22,16 +22,16 @@ SplineCollider::SplineCollider(const SpawnParams& params) CollisionData.Loaded.Bind(this); } -Quaternion SplineCollider::GetPreRotation() const +Transform SplineCollider::GetPreTransform() const { - return _preRotation; + return _preTransform; } -void SplineCollider::SetPreRotation(const Quaternion& value) +void SplineCollider::SetPreTransform(const Transform& value) { - if (_preRotation == value) + if (_preTransform == value) return; - _preRotation = value; + _preTransform = value; UpdateGeometry(); } @@ -130,7 +130,7 @@ void SplineCollider::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_GET_OTHER_OBJ(SplineCollider); SERIALIZE(CollisionData); - SERIALIZE_MEMBER(PreRotation, _preRotation) + SERIALIZE_MEMBER(PreTransform, _preTransform) } void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -139,7 +139,7 @@ void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* Collider::Deserialize(stream, modifier); DESERIALIZE(CollisionData); - DESERIALIZE_MEMBER(PreRotation, _preRotation); + DESERIALIZE_MEMBER(PreTransform, _preTransform); } void SplineCollider::OnParentChanged() @@ -205,10 +205,10 @@ void SplineCollider::GetGeometry(PxGeometryHolder& geometry) } // Apply local mesh transformation - if (!_preRotation.IsIdentity()) + if (!_preTransform.IsIdentity()) { for (int32 i = 0; i < collisionVertices.Count(); i++) - collisionVertices[i] = Vector3::Transform(collisionVertices[i], _preRotation); + collisionVertices[i] = _preTransform.LocalToWorld(collisionVertices[i]); } // Find collision geometry local bounds diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h index 3bfcfacf1..42cc25e09 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.h +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -21,7 +21,7 @@ private: PxTriangleMesh* _triangleMesh = nullptr; Array _vertexBuffer; Array _indexBuffer; - Quaternion _preRotation = Quaternion::Identity; + Transform _preTransform = Transform::Identity; public: @@ -32,15 +32,15 @@ public: AssetReference CollisionData; /// - /// Gets the rotation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// Gets the transformation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. /// API_PROPERTY(Attributes="EditorOrder(101), EditorDisplay(\"Collider\")") - Quaternion GetPreRotation() const; + Transform GetPreTransform() const; /// - /// Sets the rotation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// Sets the transformation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. /// - API_PROPERTY() void SetPreRotation(const Quaternion& value); + API_PROPERTY() void SetPreTransform(const Transform& value); #if USE_EDITOR From f11686a7b70c228d46be3e19d457e4ab3951c8b5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 09:41:56 +0100 Subject: [PATCH 208/222] Add support for caching scripting API bindings during build --- .../Tools/Flax.Build/Bindings/ApiTypeInfo.cs | 34 ++- .../Bindings/BindingsGenerator.CSharp.cs | 9 +- .../Bindings/BindingsGenerator.Cache.cs | 266 ++++++++++++++++++ .../Bindings/BindingsGenerator.Cpp.cs | 6 +- .../Bindings/BindingsGenerator.Parsing.cs | 13 - .../Flax.Build/Bindings/BindingsGenerator.cs | 30 +- Source/Tools/Flax.Build/Bindings/ClassInfo.cs | 43 ++- .../Flax.Build/Bindings/ClassStructInfo.cs | 22 +- Source/Tools/Flax.Build/Bindings/EnumInfo.cs | 39 ++- Source/Tools/Flax.Build/Bindings/EventInfo.cs | 16 ++ Source/Tools/Flax.Build/Bindings/FieldInfo.cs | 24 ++ .../Tools/Flax.Build/Bindings/FunctionInfo.cs | 51 +++- .../Flax.Build/Bindings/InjectCppCodeInfo.cs | 16 ++ .../Tools/Flax.Build/Bindings/MemberInfo.cs | 22 +- .../Tools/Flax.Build/Bindings/ModuleInfo.cs | 24 ++ .../Tools/Flax.Build/Bindings/PropertyInfo.cs | 20 ++ .../Flax.Build/Bindings/StructureInfo.cs | 25 +- Source/Tools/Flax.Build/Bindings/TypeInfo.cs | 31 +- Source/Tools/Flax.Build/Flax.Build.csproj | 1 + .../Tools/Flax.Build/Utilities/Utilities.cs | 10 + 20 files changed, 663 insertions(+), 39 deletions(-) create mode 100644 Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 814983e9b..3e2ad1b31 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -1,16 +1,17 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { /// /// The native type information for bindings generator. /// - public class ApiTypeInfo + public class ApiTypeInfo : IBindingsCache { public ApiTypeInfo Parent; - public List Children; + public List Children = new List(); public string NativeName; public string Name; public string Namespace; @@ -91,6 +92,35 @@ namespace Flax.Build.Bindings Children.Add(apiTypeInfo); } + public virtual void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, NativeName); + BindingsGenerator.Write(writer, Name); + BindingsGenerator.Write(writer, Namespace); + BindingsGenerator.Write(writer, Attributes); + BindingsGenerator.Write(writer, Comment); + writer.Write(IsInBuild); + BindingsGenerator.Write(writer, Children); + } + + public virtual void Read(BinaryReader reader) + { + NativeName = BindingsGenerator.Read(reader, NativeName); + Name = BindingsGenerator.Read(reader, Name); + Namespace = BindingsGenerator.Read(reader, Namespace); + Attributes = BindingsGenerator.Read(reader, Attributes); + Comment = BindingsGenerator.Read(reader, Comment); + IsInBuild = reader.ReadBoolean(); + Children = BindingsGenerator.Read(reader, Children); + + // Fix hierarchy + for (int i = 0; i < Children.Count; i++) + { + var child = Children[i]; + child.Parent = this; + } + } + public override string ToString() { return Name; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index d9dc61521..2fd6a6edd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -521,6 +521,7 @@ namespace Flax.Build.Bindings { if (!useUnmanaged) throw new NotImplementedException("TODO: support events inside non-static and non-scripting API class types."); + var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; contents.AppendLine(); @@ -542,10 +543,10 @@ namespace Flax.Build.Bindings if (eventInfo.IsStatic) contents.Append("static "); string eventSignature = "event Action"; - if (eventInfo.Type.GenericArgs.Count != 0) + if (paramsCount != 0) { eventSignature += '<'; - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) eventSignature += ", "; @@ -583,7 +584,7 @@ namespace Flax.Build.Bindings if (eventInfo.IsStatic) contents.Append("static "); contents.Append($"void Internal_{eventInfo.Name}_Invoke("); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); @@ -594,7 +595,7 @@ namespace Flax.Build.Bindings contents.Append(indent).Append('{').AppendLine(); contents.Append(indent).Append(" Internal_").Append(eventInfo.Name); contents.Append('('); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs new file mode 100644 index 000000000..bc141759c --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -0,0 +1,266 @@ +// (c) 2012-2020 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Flax.Build.NativeCpp; + +namespace Flax.Build.Bindings +{ + internal interface IBindingsCache + { + void Write(BinaryWriter writer); + void Read(BinaryReader reader); + } + + partial class BindingsGenerator + { + private static readonly Dictionary _typeCache = new Dictionary(); + + internal static void Write(BinaryWriter writer, string e) + { + var valid = e != null; + writer.Write(valid); + if (valid) + writer.Write(e); + } + + internal static void Write(BinaryWriter writer, string[] list) + { + if (list != null) + { + writer.Write(list.Length); + for (int i = 0; i < list.Length; i++) + writer.Write(list[i]); + } + else + { + writer.Write(0); + } + } + + internal static void Write(BinaryWriter writer, IEnumerable list) + { + if (list != null) + { + writer.Write(list.Count()); + foreach (var e in list) + writer.Write(e); + } + else + { + writer.Write(0); + } + } + + internal static void Write(BinaryWriter writer, T e) where T : IBindingsCache + { + if (e != null) + { + writer.Write(e.GetType().FullName); + e.Write(writer); + } + else + { + writer.Write(string.Empty); + } + } + + internal static void Write(BinaryWriter writer, IList list) where T : IBindingsCache + { + if (list != null) + { + var count = list.Count; + writer.Write(count); + for (int i = 0; i < count; i++) + { + var e = list[i]; + writer.Write(e.GetType().FullName); + e.Write(writer); + } + } + else + { + writer.Write(0); + } + } + + internal static string Read(BinaryReader reader, string e) + { + var valid = reader.ReadBoolean(); + if (valid) + e = reader.ReadString(); + return e; + } + + internal static string[] Read(BinaryReader reader, string[] list) + { + var count = reader.ReadInt32(); + if (count != 0) + { + list = new string[count]; + for (int i = 0; i < count; i++) + list[i] = reader.ReadString(); + } + return list; + } + + internal static T Read(BinaryReader reader, T e) where T : IBindingsCache + { + var typename = reader.ReadString(); + if (string.IsNullOrEmpty(typename)) + return e; + if (!_typeCache.TryGetValue(typename, out var type)) + { + type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); + if (type == null) + { + var msg = $"Missing type {typename}."; + Log.Error(msg); + throw new Exception(msg); + } + _typeCache.Add(typename, type); + } + e = (T)Activator.CreateInstance(type); + e.Read(reader); + return e; + } + + internal static List Read(BinaryReader reader, List list) where T : IBindingsCache + { + var count = reader.ReadInt32(); + if (count != 0) + { + list = new List(count); + for (int i = 0; i < count; i++) + { + var typename = reader.ReadString(); + if (!_typeCache.TryGetValue(typename, out var type)) + { + type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); + if (type == null) + { + var msg = $"Missing type {typename}."; + Log.Error(msg); + throw new Exception(msg); + } + _typeCache.Add(typename, type); + } + var e = (T)Activator.CreateInstance(type); + e.Read(reader); + list.Add(e); + } + } + return list; + } + + private static string GetCachePath(Module module, BuildOptions moduleOptions) + { + return Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.cache"); + } + + private static void SaveCache(ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles) + { + var path = GetCachePath(moduleInfo.Module, moduleOptions); + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var writer = new BinaryWriter(stream, Encoding.UTF8)) + { + // Version + writer.Write(4); + writer.Write(File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks); + + // Build options + writer.Write(moduleOptions.IntermediateFolder); + writer.Write((int)moduleOptions.Platform.Target); + writer.Write((int)moduleOptions.Architecture); + writer.Write((int)moduleOptions.Configuration); + Write(writer, moduleOptions.PublicDefinitions); + Write(writer, moduleOptions.PrivateDefinitions); + Write(writer, moduleOptions.CompileEnv.PreprocessorDefinitions); + + // Header files + writer.Write(headerFiles.Count); + for (int i = 0; i < headerFiles.Count; i++) + { + var headerFile = headerFiles[i]; + writer.Write(headerFile); + writer.Write(File.GetLastWriteTime(headerFile).Ticks); + } + + // Info + moduleInfo.Write(writer); + } + } + + private static bool LoadCache(ref ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles) + { + var path = GetCachePath(moduleInfo.Module, moduleOptions); + if (!File.Exists(path)) + return false; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new BinaryReader(stream, Encoding.UTF8)) + { + // Version + var version = reader.ReadInt32(); + if (version != 4) + return false; + if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64()) + return false; + + // Build options + if (reader.ReadString() != moduleOptions.IntermediateFolder || + reader.ReadInt32() != (int)moduleOptions.Platform.Target || + reader.ReadInt32() != (int)moduleOptions.Architecture || + reader.ReadInt32() != (int)moduleOptions.Configuration) + return false; + var publicDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (publicDefinitions.Length != moduleOptions.PublicDefinitions.Count || publicDefinitions.Any(x => !moduleOptions.PublicDefinitions.Contains(x))) + return false; + var privateDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (privateDefinitions.Length != moduleOptions.PrivateDefinitions.Count || privateDefinitions.Any(x => !moduleOptions.PrivateDefinitions.Contains(x))) + return false; + var preprocessorDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (preprocessorDefinitions.Length != moduleOptions.CompileEnv.PreprocessorDefinitions.Count || preprocessorDefinitions.Any(x => !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(x))) + return false; + + // Header files + var headerFilesCount = reader.ReadInt32(); + if (headerFilesCount != headerFiles.Count) + return false; + for (int i = 0; i < headerFilesCount; i++) + { + var headerFile = headerFiles[i]; + if (headerFile != reader.ReadString()) + return false; + if (File.GetLastWriteTime(headerFile).Ticks > reader.ReadInt64()) + return false; + } + + // Info + var newModuleInfo = new ModuleInfo + { + Module = moduleInfo.Module, + Name = moduleInfo.Name, + Namespace = moduleInfo.Namespace, + Children = new List(), + }; + try + { + newModuleInfo.Read(reader); + + // Skip parsing and use data loaded from cache + moduleInfo = newModuleInfo; + return true; + } + catch + { + // Skip loading cache + return false; + } + } + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index ff2a2fadd..d62a53144 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1091,7 +1091,7 @@ namespace Flax.Build.Bindings { if (!useScripting) continue; - var paramsCount = eventInfo.Type.GenericArgs.Count; + var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; // C# event invoking wrapper (calls C# event from C++ delegate) CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); @@ -1109,7 +1109,7 @@ namespace Flax.Build.Bindings contents.Append(" {").AppendLine(); contents.Append(" static MMethod* mmethod = nullptr;").AppendLine(); contents.Append(" if (!mmethod)").AppendLine(); - contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, eventInfo.Type.GenericArgs.Count).AppendLine(); + contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" CHECK(mmethod);").AppendLine(); contents.Append(" MonoObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) @@ -1174,7 +1174,7 @@ namespace Flax.Build.Bindings if (paramsCount == 0) contents.AppendLine(" Variant* params = nullptr;"); else - contents.AppendLine($" Variant params[{eventInfo.Type.GenericArgs.Count}];"); + contents.AppendLine($" Variant params[{paramsCount}];"); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 66f93fde3..7714e6abd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -475,13 +475,8 @@ namespace Flax.Build.Bindings { var desc = new ClassInfo { - Children = new List(), Access = context.CurrentAccessLevel, BaseTypeInheritance = AccessLevel.Private, - Functions = new List(), - Properties = new List(), - Fields = new List(), - Events = new List(), }; // Read the documentation comment @@ -559,7 +554,6 @@ namespace Flax.Build.Bindings { var desc = new InterfaceInfo { - Children = new List(), Access = context.CurrentAccessLevel, }; @@ -622,7 +616,6 @@ namespace Flax.Build.Bindings var desc = new FunctionInfo { Access = context.CurrentAccessLevel, - Parameters = new List(), }; // Read the documentation comment @@ -821,9 +814,7 @@ namespace Flax.Build.Bindings { var desc = new EnumInfo { - Children = new List(), Access = context.CurrentAccessLevel, - Entries = new List(), }; // Read the documentation comment @@ -978,10 +969,7 @@ namespace Flax.Build.Bindings { var desc = new StructureInfo { - Children = new List(), Access = context.CurrentAccessLevel, - Fields = new List(), - Functions = new List(), }; // Read the documentation comment @@ -1200,7 +1188,6 @@ namespace Flax.Build.Bindings context.Tokenizer.ExpectToken(TokenType.LeftParent); var desc = new InjectCppCodeInfo { - Children = new List(), Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\""), }; desc.Code = desc.Code.Substring(1, desc.Code.Length - 2); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 1b12cb838..0135f1130 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -100,6 +100,26 @@ namespace Flax.Build.Bindings } } + // Sort file paths to have stable results + headerFiles.Sort(); + + // Load cache + using (new ProfileEventScope("LoadCache")) + { + if (LoadCache(ref moduleInfo, moduleOptions, headerFiles)) + { + buildData.ModulesInfo[module] = moduleInfo; + + // Initialize API + using (new ProfileEventScope("Init")) + { + moduleInfo.Init(buildData); + } + + return moduleInfo; + } + } + // Parse bindings Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); int concurrency = Math.Min(Math.Max(1, (int)(Environment.ProcessorCount * Configuration.ConcurrencyProcessorScale)), Configuration.MaxConcurrency); @@ -117,9 +137,6 @@ namespace Flax.Build.Bindings } else { - // Sort by file size to improve performance (parse larger files first) - headerFiles.Sort((a, b) => new System.IO.FileInfo(b).Length.CompareTo(new System.IO.FileInfo(a).Length)); - // Multi-threaded ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); if (workerThreads != concurrency) @@ -133,6 +150,12 @@ namespace Flax.Build.Bindings }); } + // Save cache + using (new ProfileEventScope("SaveCache")) + { + SaveCache(moduleInfo, moduleOptions, headerFiles); + } + // Initialize API using (new ProfileEventScope("Init")) { @@ -162,7 +185,6 @@ namespace Flax.Build.Bindings var fileInfo = new FileInfo { Parent = null, - Children = new List(), Name = headerFiles[workIndex], Namespace = moduleInfo.Name, }; diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index a83ac2b55..c68ff1a59 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; using System.Linq; namespace Flax.Build.Bindings @@ -29,10 +30,10 @@ namespace Flax.Build.Bindings public bool IsAutoSerialization; public bool NoSpawn; public bool NoConstructor; - public List Functions; - public List Properties; - public List Fields; - public List Events; + public List Functions = new List(); + public List Properties = new List(); + public List Fields = new List(); + public List Events = new List(); internal HashSet UniqueFunctionNames; @@ -74,6 +75,40 @@ namespace Flax.Build.Bindings } } + public override void Write(BinaryWriter writer) + { + // TODO: convert into flags + writer.Write(IsStatic); + writer.Write(IsSealed); + writer.Write(IsAbstract); + writer.Write(IsAutoSerialization); + writer.Write(NoSpawn); + writer.Write(NoConstructor); + BindingsGenerator.Write(writer, Functions); + BindingsGenerator.Write(writer, Properties); + BindingsGenerator.Write(writer, Fields); + BindingsGenerator.Write(writer, Events); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + // TODO: convert into flags + IsStatic = reader.ReadBoolean(); + IsSealed = reader.ReadBoolean(); + IsAbstract = reader.ReadBoolean(); + IsAutoSerialization = reader.ReadBoolean(); + NoSpawn = reader.ReadBoolean(); + NoConstructor = reader.ReadBoolean(); + Functions = BindingsGenerator.Read(reader, Functions); + Properties = BindingsGenerator.Read(reader, Properties); + Fields = BindingsGenerator.Read(reader, Fields); + Events = BindingsGenerator.Read(reader, Events); + + base.Read(reader); + } + public int GetScriptVTableSize(Builder.BuildData buildData, out int offset) { if (_scriptVTableSize == -1) diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs index dcb2d2e9e..2f0dad003 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -36,5 +36,25 @@ namespace Flax.Build.Bindings Interfaces = null; } } + + public override void Write(BinaryWriter writer) + { + writer.Write((byte)Access); + writer.Write((byte)BaseTypeInheritance); + BindingsGenerator.Write(writer, BaseType); + BindingsGenerator.Write(writer, InterfaceNames); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Access = (AccessLevel)reader.ReadByte(); + BaseTypeInheritance = (AccessLevel)reader.ReadByte(); + BaseType = BindingsGenerator.Read(reader, BaseType); + InterfaceNames = BindingsGenerator.Read(reader, InterfaceNames); + + base.Read(reader); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs index bf9fc8132..ddfcb3a38 100644 --- a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -10,7 +11,7 @@ namespace Flax.Build.Bindings /// public class EnumInfo : ApiTypeInfo { - public struct EntryInfo + public struct EntryInfo : IBindingsCache { public string Name; public string[] Comment; @@ -21,11 +22,27 @@ namespace Flax.Build.Bindings { return Name + (string.IsNullOrEmpty(Value) ? string.Empty : " = " + Value); } + + public void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Comment); + BindingsGenerator.Write(writer, Value); + BindingsGenerator.Write(writer, Attributes); + } + + public void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Comment = BindingsGenerator.Read(reader, Comment); + Value = BindingsGenerator.Read(reader, Value); + Attributes = BindingsGenerator.Read(reader, Attributes); + } } public AccessLevel Access; public TypeInfo UnderlyingType; - public List Entries; + public List Entries = new List(); public override bool IsValueType => true; public override bool IsEnum => true; @@ -36,6 +53,24 @@ namespace Flax.Build.Bindings throw new NotSupportedException("API enums cannot have sub-types."); } + public override void Write(BinaryWriter writer) + { + writer.Write((byte)Access); + BindingsGenerator.Write(writer, UnderlyingType); + BindingsGenerator.Write(writer, Entries); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Access = (AccessLevel)reader.ReadByte(); + UnderlyingType = BindingsGenerator.Read(reader, UnderlyingType); + Entries = BindingsGenerator.Read(reader, Entries); + + base.Read(reader); + } + public override string ToString() { return "enum " + Name; diff --git a/Source/Tools/Flax.Build/Bindings/EventInfo.cs b/Source/Tools/Flax.Build/Bindings/EventInfo.cs index de93332ac..905bee803 100644 --- a/Source/Tools/Flax.Build/Bindings/EventInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EventInfo.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.IO; + namespace Flax.Build.Bindings { /// @@ -9,6 +11,20 @@ namespace Flax.Build.Bindings { public TypeInfo Type; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs index b1488ed09..d01cde5e7 100644 --- a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.IO; + namespace Flax.Build.Bindings { /// @@ -16,6 +18,28 @@ namespace Flax.Build.Bindings public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue); + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + // TODO: convert into flags + writer.Write(IsReadOnly); + writer.Write(NoArray); + BindingsGenerator.Write(writer, DefaultValue); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + // TODO: convert into flags + IsReadOnly = reader.ReadBoolean(); + NoArray = reader.ReadBoolean(); + DefaultValue = BindingsGenerator.Read(reader, DefaultValue); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs index 953fd7ed4..a78c9242a 100644 --- a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -9,7 +10,7 @@ namespace Flax.Build.Bindings /// public class FunctionInfo : MemberInfo { - public struct ParameterInfo + public struct ParameterInfo : IBindingsCache { public string Name; public TypeInfo Type; @@ -25,6 +26,28 @@ namespace Flax.Build.Bindings return Attributes != null && Attributes.Contains(name); } + public void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Type); + BindingsGenerator.Write(writer, DefaultValue); + BindingsGenerator.Write(writer, Attributes); + // TODO: convert into flags + writer.Write(IsRef); + writer.Write(IsOut); + } + + public void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Type = BindingsGenerator.Read(reader, Type); + DefaultValue = BindingsGenerator.Read(reader, DefaultValue); + Attributes = BindingsGenerator.Read(reader, Attributes); + // TODO: convert into flags + IsRef = reader.ReadBoolean(); + IsOut = reader.ReadBoolean(); + } + public override string ToString() { var result = Type + " " + Name; @@ -42,12 +65,36 @@ namespace Flax.Build.Bindings public string UniqueName; public TypeInfo ReturnType; - public List Parameters; + public List Parameters = new List(); public bool IsVirtual; public bool IsConst; public bool NoProxy; public GlueInfo Glue; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, ReturnType); + BindingsGenerator.Write(writer, Parameters); + // TODO: convert into flags + writer.Write(IsVirtual); + writer.Write(IsConst); + writer.Write(NoProxy); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + ReturnType = BindingsGenerator.Read(reader, ReturnType); + Parameters = BindingsGenerator.Read(reader, Parameters); + // TODO: convert into flags + IsVirtual = reader.ReadBoolean(); + IsConst = reader.ReadBoolean(); + NoProxy = reader.ReadBoolean(); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs index dfdb551f6..0bb53fc99 100644 --- a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.IO; + namespace Flax.Build.Bindings { /// @@ -9,6 +11,20 @@ namespace Flax.Build.Bindings { public string Code; + public override void Write(BinaryWriter writer) + { + writer.Write(Code); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Code = reader.ReadString(); + + base.Read(reader); + } + /// public override string ToString() { diff --git a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs index 3e09b9f0b..49187c101 100644 --- a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs @@ -1,11 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.IO; + namespace Flax.Build.Bindings { /// /// The native member information for bindings generator. /// - public class MemberInfo + public class MemberInfo : IBindingsCache { public string Name; public string[] Comment; @@ -17,5 +19,23 @@ namespace Flax.Build.Bindings { return Attributes != null && Attributes.Contains(name); } + + public virtual void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Comment); + writer.Write(IsStatic); + writer.Write((byte)Access); + BindingsGenerator.Write(writer, Attributes); + } + + public virtual void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Comment = BindingsGenerator.Read(reader, Comment); + IsStatic = reader.ReadBoolean(); + Access = (AccessLevel)reader.ReadByte(); + Attributes = BindingsGenerator.Read(reader, Attributes); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 7bb6d1608..dd83af9d9 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -1,5 +1,8 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; +using System.IO; + namespace Flax.Build.Bindings { /// @@ -22,5 +25,26 @@ namespace Flax.Build.Bindings // Sort module files to prevent bindings rebuild due to order changes (list might be created in async) Children.Sort(); } + + public override void Write(BinaryWriter writer) + { + writer.Write(Module.Name); + writer.Write(Module.FilePath); + BindingsGenerator.Write(writer, Module.BinaryModuleName); + writer.Write(Module.BuildNativeCode); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + if (reader.ReadString() != Module.Name || + reader.ReadString() != Module.FilePath || + BindingsGenerator.Read(reader, Module.BinaryModuleName) != Module.BinaryModuleName || + reader.ReadBoolean() != Module.BuildNativeCode) + throw new Exception(); + + base.Read(reader); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs index f53578b4e..96b0f8b93 100644 --- a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.IO; + namespace Flax.Build.Bindings { /// @@ -11,6 +13,24 @@ namespace Flax.Build.Bindings public FunctionInfo Getter; public FunctionInfo Setter; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + BindingsGenerator.Write(writer, Getter); + BindingsGenerator.Write(writer, Setter); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + Getter = BindingsGenerator.Read(reader, Getter); + Setter = BindingsGenerator.Read(reader, Setter); + + base.Read(reader); + } + public override string ToString() { return Type + " " + Name; diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index be24bb1f0..882ed6295 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -10,8 +11,8 @@ namespace Flax.Build.Bindings /// public class StructureInfo : ClassStructInfo { - public List Fields; - public List Functions; + public List Fields = new List(); + public List Functions = new List(); public bool IsAutoSerialization; public bool ForceNoPod; @@ -43,6 +44,26 @@ namespace Flax.Build.Bindings } } + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Fields); + BindingsGenerator.Write(writer, Functions); + writer.Write(IsAutoSerialization); + writer.Write(ForceNoPod); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Fields = BindingsGenerator.Read(reader, Fields); + Functions = BindingsGenerator.Read(reader, Functions); + IsAutoSerialization = reader.ReadBoolean(); + ForceNoPod = reader.ReadBoolean(); + + base.Read(reader); + } + public override void AddChild(ApiTypeInfo apiTypeInfo) { if (apiTypeInfo is EnumInfo) diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index f2d7ff387..2d15f6651 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -10,7 +11,7 @@ namespace Flax.Build.Bindings /// /// The native type information for bindings generator. /// - public class TypeInfo : IEquatable + public class TypeInfo : IEquatable, IBindingsCache { public string Type; public bool IsConst; @@ -51,6 +52,34 @@ namespace Flax.Build.Bindings return true; } + public void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + // TODO: pack as flags + writer.Write(IsConst); + writer.Write(IsRef); + writer.Write(IsPtr); + writer.Write(IsArray); + writer.Write(IsBitField); + writer.Write(ArraySize); + writer.Write(BitSize); + BindingsGenerator.Write(writer, GenericArgs); + } + + public void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + // TODO: convert into flags + IsConst = reader.ReadBoolean(); + IsRef = reader.ReadBoolean(); + IsPtr = reader.ReadBoolean(); + IsArray = reader.ReadBoolean(); + IsBitField = reader.ReadBoolean(); + ArraySize = reader.ReadInt32(); + BitSize = reader.ReadInt32(); + GenericArgs = BindingsGenerator.Read(reader, GenericArgs); + } + public override string ToString() { var sb = new StringBuilder(64); diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index 9d415beed..236348032 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -64,6 +64,7 @@ + diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index c37faa678..accf86209 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -14,6 +14,16 @@ namespace Flax.Build /// public static class Utilities { + /// + /// Gets the empty array of the given type (shared one). + /// + /// The type. + /// The empty array object. + public static T[] GetEmptyArray() + { + return Enumerable.Empty() as T[]; + } + /// /// Gets the size of the file as a readable string. /// From fa332a3785b419c3510fe1bb2687eba97f3d0f53 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 10:42:56 +0100 Subject: [PATCH 209/222] Optimize scripting API bindings generation if loaded API from valid cache --- .../Bindings/BindingsGenerator.CSharp.cs | 9 --- .../Bindings/BindingsGenerator.Cache.cs | 2 +- .../Bindings/BindingsGenerator.Cpp.cs | 17 +---- .../Flax.Build/Bindings/BindingsGenerator.cs | 73 +++++++++++++++---- .../Tools/Flax.Build/Bindings/ModuleInfo.cs | 1 + 5 files changed, 63 insertions(+), 39 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 2fd6a6edd..d09d21bbd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1028,9 +1028,6 @@ namespace Flax.Build.Bindings private static void GenerateCSharp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings) { - if (!bindings.UseBindings) - return; - var contents = new StringBuilder(); buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo); @@ -1081,12 +1078,6 @@ namespace Flax.Build.Bindings // Save generated file contents.AppendLine("#endif"); Utilities.WriteFileIfChanged(bindings.GeneratedCSharpFilePath, contents.ToString()); - - // Ensure that generated file is included into build - if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) - { - moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath); - } } internal struct GuidInterop diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index bc141759c..bd3cd2c2e 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -245,7 +245,7 @@ namespace Flax.Build.Bindings Module = moduleInfo.Module, Name = moduleInfo.Name, Namespace = moduleInfo.Namespace, - Children = new List(), + IsFromCache = true, }; try { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index d62a53144..58f1edaae 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1656,10 +1656,10 @@ namespace Flax.Build.Bindings } } - private static bool GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) + private static void GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) { if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.IsInBuild) - return false; + return; try { @@ -1671,16 +1671,12 @@ namespace Flax.Build.Bindings GenerateCppInterface(buildData, contents, moduleInfo, interfaceInfo); else if (type is InjectCppCodeInfo injectCppCodeInfo) contents.AppendLine(injectCppCodeInfo.Code); - else - return false; } catch { Log.Error($"Failed to generate C++ bindings for {type}."); throw; } - - return true; } private static void GenerateCppCppUsedNonPodTypes(BuildData buildData, ApiTypeInfo apiType) @@ -1728,21 +1724,16 @@ namespace Flax.Build.Bindings CppReferencesFiles.Add(fileInfo); } } - var headerPos = contents.Length; foreach (var child in moduleInfo.Children) { foreach (var apiTypeInfo in child.Children) { - if (GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo)) - bindings.UseBindings = true; + GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } - if (!bindings.UseBindings) - return; - GenerateCppModuleSource?.Invoke(buildData, moduleInfo, contents); { @@ -2036,7 +2027,7 @@ namespace Flax.Build.Bindings var binaryModuleSourcePath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.cpp"); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); - contents.AppendLine($"#include \"Engine/Scripting/BinaryModule.h\""); + contents.AppendLine("#include \"Engine/Scripting/BinaryModule.h\""); contents.AppendLine($"#include \"{binaryModuleName}.Gen.h\""); contents.AppendLine(); contents.AppendLine($"StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModule{binaryModuleName}(GetBinaryModule{binaryModuleName});"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 0135f1130..a7e1508eb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Flax.Build.NativeCpp; @@ -65,7 +66,6 @@ namespace Flax.Build.Bindings Module = module, Name = module.BinaryModuleName, Namespace = string.Empty, - Children = new List(), }; if (string.IsNullOrEmpty(moduleInfo.Name)) throw new Exception("Module name cannot be empty."); @@ -479,41 +479,82 @@ namespace Flax.Build.Bindings throw new Exception($"Invalid #if/endif pairing in file '{fileInfo.Name}'. Failed to generate API bindings for it."); } + private static bool UseBindings(object type) + { + var apiTypeInfo = type as ApiTypeInfo; + if (apiTypeInfo != null && apiTypeInfo.IsInBuild) + return false; + if ((type is ModuleInfo || type is FileInfo) && apiTypeInfo != null) + { + foreach (var child in apiTypeInfo.Children) + { + if (UseBindings(child)) + return true; + } + } + return type is ClassInfo || + type is StructureInfo || + type is InterfaceInfo || + type is InjectCppCodeInfo; + } + /// /// The API bindings generation utility that can produce scripting bindings for another languages to the native code. /// public static void GenerateBindings(BuildData buildData, Module module, ref BuildOptions moduleOptions, out BindingsResult bindings) { // Parse module (or load from cache) + var moduleInfo = ParseModule(buildData, module, moduleOptions); bindings = new BindingsResult { + UseBindings = UseBindings(moduleInfo), GeneratedCppFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"), GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"), }; - var moduleInfo = ParseModule(buildData, module, moduleOptions); + + if (bindings.UseBindings) + { + buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo); + + // Ensure that generated files are included into build + if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) + moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath); + } + + // Skip if module is cached (no scripting API changed) + if (moduleInfo.IsFromCache) + return; // Process parsed API - foreach (var child in moduleInfo.Children) + using (new ProfileEventScope("Process")) { - try + foreach (var child in moduleInfo.Children) { - foreach (var apiTypeInfo in child.Children) - ProcessAndValidate(buildData, apiTypeInfo); - } - catch (Exception) - { - if (child is FileInfo fileInfo) - Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings."); - throw; + try + { + foreach (var apiTypeInfo in child.Children) + ProcessAndValidate(buildData, apiTypeInfo); + } + catch (Exception) + { + if (child is FileInfo fileInfo) + Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings."); + throw; + } } } // Generate bindings for scripting - Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})"); - GenerateCpp(buildData, moduleInfo, ref bindings); - GenerateCSharp(buildData, moduleInfo, ref bindings); + if (bindings.UseBindings) + { + Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})"); + using (new ProfileEventScope("Cpp")) + GenerateCpp(buildData, moduleInfo, ref bindings); + using (new ProfileEventScope("CSharp")) + GenerateCSharp(buildData, moduleInfo, ref bindings); - // TODO: add support for extending this code and support generating bindings for other scripting languages + // TODO: add support for extending this code and support generating bindings for other scripting languages + } } /// diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index dd83af9d9..68742b554 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -11,6 +11,7 @@ namespace Flax.Build.Bindings public class ModuleInfo : ApiTypeInfo { public Module Module; + public bool IsFromCache; public override string ToString() { From 7221f7167fa00386a505470b22d3143e18733b6f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 10:59:17 +0100 Subject: [PATCH 210/222] Add profiler events to LocalExecutor thread workers --- Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs index 9b4a571f1..391b6952f 100644 --- a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs +++ b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Threading; using Flax.Build.Graph; @@ -178,7 +179,12 @@ namespace Flax.Build.BuildSystem.Graph private int ExecuteTask(Task task) { - ProcessStartInfo startInfo = new ProcessStartInfo + string name = "Task"; + if (task.ProducedFiles != null && task.ProducedFiles.Count != 0) + name = Path.GetFileName(task.ProducedFiles[0]); + var profilerEvent = Profiling.Begin(name); + + var startInfo = new ProcessStartInfo { WorkingDirectory = task.WorkingDirectory, FileName = task.CommandPath, @@ -230,10 +236,13 @@ namespace Flax.Build.BuildSystem.Graph // Hang until process end process.WaitForExit(); + Profiling.End(profilerEvent); return process.ExitCode; } finally { + Profiling.End(profilerEvent); + // Ensure to cleanup data process?.Close(); } From 537dde594df682a6a41f364c56ede3a73e149773 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 11:00:08 +0100 Subject: [PATCH 211/222] Update Actor docs for GetScripts to be more precise about search method --- Source/Engine/Level/Actor.cs | 6 +++--- Source/Engine/Level/Actor.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index cfb454354..2d9c27b9a 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -203,7 +203,7 @@ namespace FlaxEngine } /// - /// Finds the script of the given type. + /// Finds the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null if failed to find. @@ -213,7 +213,7 @@ namespace FlaxEngine } /// - /// Tries to find the script of the given type. + /// Tries to find the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The returned script, valid only if method returns true. @@ -240,7 +240,7 @@ namespace FlaxEngine } /// - /// Searches for all scripts of a specific type. + /// Searches for all scripts of a specific type from this actor. /// /// Type of the scripts to search for. Includes any scripts derived from the type. /// All scripts matching the specified type. diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index dea13cd4f..04377fd4f 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -272,14 +272,14 @@ public: } /// - /// Gets the script of the given type. + /// Gets the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null. API_FUNCTION() Script* GetScript(const MClass* type) const; /// - /// Gets the script of the given type. + /// Gets the script of the given type from this actor. /// /// The script or null. template @@ -289,14 +289,14 @@ public: } /// - /// Gets the scripts of the given type. + /// Gets the scripts of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The scripts. API_FUNCTION() Array GetScripts(const MClass* type) const; /// - /// Gets the scripts of the given type. + /// Gets the scripts of the given type from this actor. /// /// The scripts. template From 3658611e0fdd8d5731ece4ceb99fbb4782b119d8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 11:23:47 +0100 Subject: [PATCH 212/222] Fix compilation --- .../Engine/Physics/Colliders/SplineCollider.cpp | 17 ++--------------- .../Engine/Physics/Colliders/SplineCollider.h | 4 ---- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index f003971ae..e44d986cc 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -35,16 +35,12 @@ void SplineCollider::SetPreTransform(const Transform& value) UpdateGeometry(); } -#if USE_EDITOR - void SplineCollider::ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const { vertexBuffer.Add(_vertexBuffer); indexBuffer.Add(_indexBuffer); } -#endif - void SplineCollider::OnCollisionDataChanged() { // This should not be called during physics simulation, if it happened use write lock on physx scene @@ -316,11 +312,9 @@ void SplineCollider::GetGeometry(PxGeometryHolder& geometry) return; } -#if USE_EDITOR - // Transform vertices back to world space for debug shapes drawing + // Transform vertices back to world space for debug shapes drawing and navmesh building for (int32 i = 0; i < _vertexBuffer.Count(); i++) _vertexBuffer[i] = colliderTransform.LocalToWorld(_vertexBuffer[i]); -#endif // Update bounds _box = P2C(_triangleMesh->getLocalBounds()); @@ -335,14 +329,7 @@ void SplineCollider::GetGeometry(PxGeometryHolder& geometry) triangleMesh.triangleMesh = _triangleMesh; geometry.storeAny(triangleMesh); -#if !USE_EDITOR - // Free memory for static splines (if editor collision preview is not needed) - if (IsTransformStatic()) - { - _vertexBuffer.Resize(0); - _indexBuffer.Resize(0); - } -#endif + // TODO: find a way of releasing _vertexBuffer and _indexBuffer for static colliders (note: ExtractGeometry usage for navmesh generation at runtime) return; } diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h index 42cc25e09..c1cdab7b7 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.h +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -42,8 +42,6 @@ public: /// API_PROPERTY() void SetPreTransform(const Transform& value); -#if USE_EDITOR - /// /// Extracts the collision data geometry into list of triangles. /// @@ -51,8 +49,6 @@ public: /// The output index buffer. void ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const; -#endif - private: void OnCollisionDataChanged(); From a98f867bd8cbae93b66e22b2abb95077f0560650 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 11:47:39 +0100 Subject: [PATCH 213/222] Fix compiler warning --- Source/Engine/Platform/Windows/WindowsWindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 61c8737b8..2d60cc40a 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -426,8 +426,8 @@ void WindowsWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& heigh // Calculate result x = monitorInfo.rcMonitor.left; y = monitorInfo.rcMonitor.top; - width = monitorInfo.rcMonitor.right - x; - height = monitorInfo.rcMonitor.bottom - y; + width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; + height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; } float WindowsWindow::GetOpacity() const @@ -481,7 +481,7 @@ void WindowsWindow::StartTrackingMouse(bool useMouseScreenOffset) int32 x = 0 , y = 0, width = 0, height = 0; GetScreenInfo(x, y, width, height); - _mouseOffsetScreenSize = Rectangle(x, y, width, height); + _mouseOffsetScreenSize = Rectangle((float)x, (float)y, (float)width, (float)height); SetCapture(_handle); } From b742d0c326a23dfe3df1624251ec78c2748634f2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 13:09:50 +0100 Subject: [PATCH 214/222] Add support for custom defines for build system via `Flax.Build` command line --- Source/Tools/Flax.Build/Configuration.cs | 7 +++++++ Source/Tools/Flax.Build/Program.cs | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index baa4e30b1..28970f289 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Collections.Generic; + namespace Flax.Build { /// @@ -198,5 +200,10 @@ namespace Flax.Build /// [CommandLine("customProjectFormat", "", "Generates code project files for a custom project format type. Valid only with -genproject option.")] public static string ProjectFormatCustom = null; + + /// + /// Custom configuration defines provided via command line for the build tool. + /// + public static List CustomDefines = new List(); } } diff --git a/Source/Tools/Flax.Build/Program.cs b/Source/Tools/Flax.Build/Program.cs index fc6d86b3b..89e71ace0 100644 --- a/Source/Tools/Flax.Build/Program.cs +++ b/Source/Tools/Flax.Build/Program.cs @@ -32,6 +32,16 @@ namespace Flax.Build { // Setup CommandLine.Configure(typeof(Configuration)); + foreach (var option in CommandLine.GetOptions()) + { + if (option.Name.Length > 1 && option.Name[0] == 'D') + { + var define = option.Name.Substring(1); + if (!string.IsNullOrEmpty(option.Value)) + define += "=" + option.Value; + Configuration.CustomDefines.Add(define); + } + } if (Configuration.CurrentDirectory != null) Environment.CurrentDirectory = Configuration.CurrentDirectory; Globals.Root = Directory.GetCurrentDirectory(); From f66b7ff785d69633e4d1abaf97215d3c8aaf704f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 13:54:57 +0100 Subject: [PATCH 215/222] Add support for Custom Define in Game Cooker for build scripts configuration --- Source/Editor/Content/Settings/BuildSettings.cs | 16 +--------------- Source/Editor/Content/Settings/BuildTarget.cs | 12 ++++++------ Source/Editor/Cooker/CookingData.h | 5 +++++ Source/Editor/Cooker/GameCooker.cpp | 3 ++- Source/Editor/Cooker/GameCooker.h | 3 ++- .../Editor/Cooker/Steps/CompileScriptsStep.cpp | 5 +++++ Source/Editor/Windows/GameCookerWindow.cs | 7 +++++-- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Content/Settings/BuildSettings.cs b/Source/Editor/Content/Settings/BuildSettings.cs index 87530f1fb..5dc6310c3 100644 --- a/Source/Editor/Content/Settings/BuildSettings.cs +++ b/Source/Editor/Content/Settings/BuildSettings.cs @@ -25,13 +25,6 @@ namespace FlaxEditor.Content.Settings Platform = BuildPlatform.Windows64, Mode = BuildConfiguration.Development, }, - new BuildTarget - { - Name = "Windows 32bit", - Output = "Output\\Win32", - Platform = BuildPlatform.Windows32, - Mode = BuildConfiguration.Development, - }, } }, new BuildPreset @@ -44,14 +37,7 @@ namespace FlaxEditor.Content.Settings Name = "Windows 64bit", Output = "Output\\Win64", Platform = BuildPlatform.Windows64, - Mode = BuildConfiguration.Development, - }, - new BuildTarget - { - Name = "Windows 32bit", - Output = "Output\\Win32", - Platform = BuildPlatform.Windows32, - Mode = BuildConfiguration.Development, + Mode = BuildConfiguration.Release, }, } }, diff --git a/Source/Editor/Content/Settings/BuildTarget.cs b/Source/Editor/Content/Settings/BuildTarget.cs index 7cdc500f6..97c0b8c3d 100644 --- a/Source/Editor/Content/Settings/BuildTarget.cs +++ b/Source/Editor/Content/Settings/BuildTarget.cs @@ -35,6 +35,12 @@ namespace FlaxEditor.Content.Settings [EditorOrder(30), Tooltip("Configuration build mode")] public BuildConfiguration Mode; + /// + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + /// + [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] + public string[] CustomDefines; + /// /// The pre-build action command line. /// @@ -46,11 +52,5 @@ namespace FlaxEditor.Content.Settings /// [EditorOrder(110)] public string PostBuildAction; - - /// - /// Gets the build options computed from the target configuration. - /// - [HideInEditor, NoSerialize] - public virtual BuildOptions Options => BuildOptions.None; } } diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index b46b8ff72..56b15af23 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -174,6 +174,11 @@ struct FLAXENGINE_API CookingData /// BuildOptions Options; + /// + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + /// + Array CustomDefines; + /// /// The original output path (actual OutputPath could be modified by the Platform Tools or a plugin for additional layout customizations or packaging). This path is preserved. /// diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index d68ca587a..efd6763d8 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -257,7 +257,7 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform) return result; } -void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options) +void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options, const Array& customDefines) { if (IsRunning()) { @@ -281,6 +281,7 @@ void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, data.Platform = platform; data.Configuration = configuration; data.Options = options; + data.CustomDefines = customDefines; data.OutputPath = outputPath; FileSystem::NormalizePath(data.OutputPath); data.OutputPath = data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OutputPath); diff --git a/Source/Editor/Cooker/GameCooker.h b/Source/Editor/Cooker/GameCooker.h index 3defe51ff..f1bfc4ad3 100644 --- a/Source/Editor/Cooker/GameCooker.h +++ b/Source/Editor/Cooker/GameCooker.h @@ -84,7 +84,8 @@ public: /// The build configuration. /// The output path (output directory). /// The build options. - API_FUNCTION() static void Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options); + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + API_FUNCTION() static void Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options, const Array& customDefines); /// /// Sends a cancel event to the game building service. diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp index 3bb2e1eb6..dd7c77a94 100644 --- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp +++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp @@ -217,6 +217,11 @@ bool CompileScriptsStep::Perform(CookingData& data) args += TEXT(" -SkipTargets=FlaxGame"); } #endif + for (auto& define : data.CustomDefines) + { + args += TEXT(" -D"); + args += define; + } if (ScriptsBuilder::RunBuildTool(args)) { data.Error(TEXT("Failed to compile game scripts.")); diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 442cc4c34..278ce69d3 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -73,6 +73,9 @@ namespace FlaxEditor.Windows [EditorOrder(20), Tooltip("Configuration build mode")] public BuildConfiguration ConfigurationMode = BuildConfiguration.Development; + [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] + public string[] CustomDefines; + protected abstract BuildPlatform BuildPlatform { get; } protected virtual BuildOptions Options @@ -126,7 +129,7 @@ namespace FlaxEditor.Windows public virtual void Build() { var output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output)); - GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options); + GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options, CustomDefines); } } @@ -793,7 +796,7 @@ namespace FlaxEditor.Windows _preBuildAction = target.PreBuildAction; _postBuildAction = target.PostBuildAction; - GameCooker.Build(target.Platform, target.Mode, target.Output, target.Options); + GameCooker.Build(target.Platform, target.Mode, target.Output, BuildOptions.None, target.CustomDefines); } else if (_exitOnBuildEnd) { From 28a67f65b4b2390c6e38ee35f162d9ac727b0647 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 13:55:19 +0100 Subject: [PATCH 216/222] Fix build tool log file access --- Source/Tools/Flax.Build/Log.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Log.cs b/Source/Tools/Flax.Build/Log.cs index ba948d7ca..3bd972f42 100644 --- a/Source/Tools/Flax.Build/Log.cs +++ b/Source/Tools/Flax.Build/Log.cs @@ -36,7 +36,7 @@ namespace Flax.Build var path = Path.GetDirectoryName(Configuration.LogFile); if (!string.IsNullOrEmpty(path) && !Directory.Exists(path)) Directory.CreateDirectory(path); - _logFile = new FileStream(Configuration.LogFile, FileMode.Create); + _logFile = new FileStream(Configuration.LogFile, FileMode.Create, FileAccess.Write, FileShare.Read); _logFileWriter = new StreamWriter(_logFile); } } From c902e33d8af955aff7321e629e63e832bb140b57 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 15:54:22 +0100 Subject: [PATCH 217/222] Add Build Actions option to editor settings for build button configuration --- Source/Editor/Editor.cs | 17 -- Source/Editor/Modules/UIModule.cs | 2 +- Source/Editor/Options/GeneralOptions.cs | 49 ++++++ Source/Editor/States/BuildingScenesState.cs | 159 +++++++++++++++--- Source/Editor/States/EditingSceneState.cs | 22 +++ Source/Editor/States/ReloadingScriptsState.cs | 22 +++ Source/Engine/Utilities/State.cs | 3 - Source/Engine/Utilities/StateMachine.cs | 5 + 8 files changed, 233 insertions(+), 46 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index f81f81ddd..3aa8346c8 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -242,8 +242,6 @@ namespace FlaxEditor StateMachine = new EditorStateMachine(this); Undo = new EditorUndo(this); - ScriptsBuilder.ScriptsReloadBegin += ScriptsBuilder_ScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd += ScriptsBuilder_ScriptsReloadEnd; UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet; } @@ -260,18 +258,6 @@ namespace FlaxEditor return null; } - private void ScriptsBuilder_ScriptsReloadBegin() - { - EnsureState(); - StateMachine.GoToState(); - } - - private void ScriptsBuilder_ScriptsReloadEnd() - { - EnsureState(); - StateMachine.GoToState(); - } - internal void RegisterModule(EditorModule module) { Log("Register Editor module " + module); @@ -497,9 +483,6 @@ namespace FlaxEditor Surface.VisualScriptSurface.NodesCache.Clear(); Instance = null; - ScriptsBuilder.ScriptsReloadBegin -= ScriptsBuilder_ScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd -= ScriptsBuilder_ScriptsReloadEnd; - // Invoke new instance if need to open a project if (!string.IsNullOrEmpty(_projectToOpen)) { diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 6b01986a9..99d8f6e2a 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -507,7 +507,7 @@ namespace FlaxEditor.Modules _toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); ToolStrip.AddSeparator(); - _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes (Ctrl+F10)"); + _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); ToolStrip.AddSeparator(); _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play32, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)"); _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause32, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)"); diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 9b5450f45..85174030f 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -32,6 +32,42 @@ namespace FlaxEditor.Options LastOpened, } + /// + /// The build actions. + /// + public enum BuildAction + { + /// + /// Builds Constructive Solid Geometry brushes into meshes. + /// + [Tooltip("Builds Constructive Solid Geometry brushes into meshes.")] + CSG, + + /// + /// Builds Env Probes and Sky Lights to prerendered cube textures. + /// + [Tooltip("Builds Env Probes and Sky Lights to prerendered cube textures.")] + EnvProbes, + + /// + /// Builds static lighting into lightmaps. + /// + [Tooltip("Builds static lighting into lightmaps.")] + StaticLighting, + + /// + /// Builds navigation meshes. + /// + [Tooltip("Builds navigation meshes.")] + NavMesh, + + /// + /// Compiles the scripts. + /// + [Tooltip("Compiles the scripts.")] + CompileScripts, + } + /// /// Gets or sets the scene to load on editor startup. /// @@ -53,6 +89,19 @@ namespace FlaxEditor.Options [EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")] public float EditorFPS { get; set; } = 60.0f; + /// + /// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh). + /// + [EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")] + public BuildAction[] BuildActions { get; set; } = + { + BuildAction.CSG, + BuildAction.EnvProbes, + BuildAction.StaticLighting, + BuildAction.EnvProbes, + BuildAction.NavMesh, + }; + /// /// Gets or sets a value indicating whether perform automatic scripts reload on main window focus. /// diff --git a/Source/Editor/States/BuildingScenesState.cs b/Source/Editor/States/BuildingScenesState.cs index 998292626..40ccff829 100644 --- a/Source/Editor/States/BuildingScenesState.cs +++ b/Source/Editor/States/BuildingScenesState.cs @@ -1,6 +1,9 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using System.Linq; +using FlaxEditor.Options; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; using FlaxEngine.Utilities; @@ -16,6 +19,9 @@ namespace FlaxEditor.States { private sealed class SubStateMachine : StateMachine { + public int ActionIndex = -1; + public readonly List Actions = new List(); + protected override void SwitchState(State nextState) { if (CurrentState != null && nextState != null) @@ -27,10 +33,40 @@ namespace FlaxEditor.States private abstract class SubState : State { + public virtual bool DirtyScenes => true; + + public virtual bool CanReloadScripts => false; + + public virtual void Before() + { + } + public virtual void Update() { } + public virtual void Done() + { + var stateMachine = (SubStateMachine)StateMachine; + stateMachine.ActionIndex++; + if (stateMachine.ActionIndex < stateMachine.Actions.Count) + { + var action = stateMachine.Actions[stateMachine.ActionIndex]; + var state = stateMachine.States.FirstOrDefault(x => x is ActionState a && a.Action == action); + if (state != null) + { + StateMachine.GoToState(state); + } + else + { + Editor.LogError($"Missing or invalid build scene action {action}."); + } + return; + } + + StateMachine.GoToState(); + } + public virtual void Cancel() { StateMachine.GoToState(); @@ -45,18 +81,31 @@ namespace FlaxEditor.States { public override void OnEnter() { - var editor = Editor.Instance; - foreach (var scene in Level.Scenes) + var stateMachine = (SubStateMachine)StateMachine; + var scenesDirty = false; + foreach (var state in stateMachine.States) { - scene.ClearLightmaps(); - editor.Scene.MarkSceneEdited(scene); + ((SubState)state).Before(); + scenesDirty |= ((SubState)state).DirtyScenes; } - StateMachine.GoToState(); + if (scenesDirty) + { + foreach (var scene in Level.Scenes) + Editor.Instance.Scene.MarkSceneEdited(scene); + } + Done(); } } - private sealed class CSGState : SubState + private abstract class ActionState : SubState { + public abstract GeneralOptions.BuildAction Action { get; } + } + + private sealed class CSGState : ActionState + { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.CSG; + public override void OnEnter() { foreach (var scene in Level.Scenes) @@ -68,13 +117,14 @@ namespace FlaxEditor.States public override void Update() { if (!Editor.Internal_GetIsCSGActive()) - StateMachine.GoToState(); + Done(); } } - - private class EnvProbesNoGIState : SubState + private class EnvProbesState : ActionState { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.EnvProbes; + public override void OnEnter() { Editor.Instance.Scene.ExecuteOnGraph(node => @@ -94,12 +144,20 @@ namespace FlaxEditor.States public override void Update() { if (!Editor.Instance.ProgressReporting.BakeEnvProbes.IsActive) - StateMachine.GoToState(); + Done(); } } - private sealed class StaticLightingState : SubState + private sealed class StaticLightingState : ActionState { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.StaticLighting; + + public override void Before() + { + foreach (var scene in Level.Scenes) + scene.ClearLightmaps(); + } + public override void OnEnter() { Editor.LightmapsBakeEnd += OnLightmapsBakeEnd; @@ -110,7 +168,6 @@ namespace FlaxEditor.States OnLightmapsBakeEnd(false); } - /// public override void Cancel() { Editor.Internal_BakeLightmaps(true); @@ -125,21 +182,14 @@ namespace FlaxEditor.States private void OnLightmapsBakeEnd(bool failed) { - StateMachine.GoToState(); + Done(); } } - private sealed class EnvProbesWithGIState : EnvProbesNoGIState + private sealed class NavMeshState : ActionState { - public override void Update() - { - if (!Editor.Instance.ProgressReporting.BakeEnvProbes.IsActive) - StateMachine.GoToState(); - } - } + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.NavMesh; - private sealed class NavMeshState : SubState - { public override void OnEnter() { foreach (var scene in Level.Scenes) @@ -151,7 +201,58 @@ namespace FlaxEditor.States public override void Update() { if (!Navigation.IsBuildingNavMesh) - StateMachine.GoToState(); + Done(); + } + } + + private sealed class CompileScriptsState : ActionState + { + private bool _compiled, _reloaded; + + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.CompileScripts; + + public override bool DirtyScenes => false; + + public override bool CanReloadScripts => true; + + public override void OnEnter() + { + _compiled = _reloaded = false; + ScriptsBuilder.Compile(); + + ScriptsBuilder.CompilationSuccess += OnCompilationSuccess; + ScriptsBuilder.CompilationFailed += OnCompilationFailed; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + public override void OnExit(State nextState) + { + ScriptsBuilder.CompilationSuccess -= OnCompilationSuccess; + ScriptsBuilder.CompilationFailed -= OnCompilationFailed; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + + base.OnExit(nextState); + } + + private void OnCompilationSuccess() + { + _compiled = true; + } + + private void OnCompilationFailed() + { + Cancel(); + } + + private void OnScriptsReloadEnd() + { + _reloaded = true; + } + + public override void Update() + { + if (_compiled && _reloaded) + Done(); } } @@ -173,10 +274,10 @@ namespace FlaxEditor.States _stateMachine.AddState(new BeginState()); _stateMachine.AddState(new SetupState()); _stateMachine.AddState(new CSGState()); - _stateMachine.AddState(new EnvProbesNoGIState()); + _stateMachine.AddState(new EnvProbesState()); _stateMachine.AddState(new StaticLightingState()); - _stateMachine.AddState(new EnvProbesWithGIState()); _stateMachine.AddState(new NavMeshState()); + _stateMachine.AddState(new CompileScriptsState()); _stateMachine.AddState(new EndState()); _stateMachine.GoToState(); } @@ -192,6 +293,9 @@ namespace FlaxEditor.States /// public override bool CanEditContent => false; + /// + public override bool CanReloadScripts => ((SubState)_stateMachine.CurrentState).CanReloadScripts; + /// public override bool IsPerformanceHeavy => true; @@ -215,6 +319,11 @@ namespace FlaxEditor.States { Editor.Log("Starting scenes build..."); _startTime = DateTime.Now; + _stateMachine.ActionIndex = -1; + _stateMachine.Actions.Clear(); + var actions = (GeneralOptions.BuildAction[])Editor.Options.Options.General.BuildActions?.Clone(); + if (actions != null) + _stateMachine.Actions.AddRange(actions); _stateMachine.GoToState(); } diff --git a/Source/Editor/States/EditingSceneState.cs b/Source/Editor/States/EditingSceneState.cs index 7aa9ce13d..176039948 100644 --- a/Source/Editor/States/EditingSceneState.cs +++ b/Source/Editor/States/EditingSceneState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.States { @@ -39,5 +40,26 @@ namespace FlaxEditor.States { UpdateFPS(); } + + /// + public override void OnEnter() + { + base.OnEnter(); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + /// + public override void OnExit(State nextState) + { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + + base.OnExit(nextState); + } + + private void OnScriptsReloadBegin() + { + StateMachine.GoToState(); + } } } diff --git a/Source/Editor/States/ReloadingScriptsState.cs b/Source/Editor/States/ReloadingScriptsState.cs index 17f1abdab..1fb34317d 100644 --- a/Source/Editor/States/ReloadingScriptsState.cs +++ b/Source/Editor/States/ReloadingScriptsState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.States { @@ -18,5 +19,26 @@ namespace FlaxEditor.States : base(editor) { } + + /// + public override void OnEnter() + { + base.OnEnter(); + + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + /// + public override void OnExit(State nextState) + { + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + + base.OnExit(nextState); + } + + private void OnScriptsReloadEnd() + { + StateMachine.GoToState(); + } } } diff --git a/Source/Engine/Utilities/State.cs b/Source/Engine/Utilities/State.cs index f77308878..a0797ec0e 100644 --- a/Source/Engine/Utilities/State.cs +++ b/Source/Engine/Utilities/State.cs @@ -12,9 +12,6 @@ namespace FlaxEngine.Utilities /// /// Gets the state machine. /// - /// - /// The state machine. - /// public StateMachine StateMachine => owner; /// diff --git a/Source/Engine/Utilities/StateMachine.cs b/Source/Engine/Utilities/StateMachine.cs index 9966e3378..250d2fbe6 100644 --- a/Source/Engine/Utilities/StateMachine.cs +++ b/Source/Engine/Utilities/StateMachine.cs @@ -35,6 +35,11 @@ namespace FlaxEngine.Utilities /// public event Action StateChanged; + /// + /// Gets the states (read-only). + /// + public IReadOnlyList States => states; + /// /// Gets state of given type. /// From d05204cb9153850be6e7985d3d4129e38bae5fd8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 15:55:10 +0100 Subject: [PATCH 218/222] Fix missing lightmap uvs calc in vertex shader --- Content/Editor/MaterialTemplates/Features/Lightmap.hlsl | 1 + Content/Editor/MaterialTemplates/Surface.shader | 2 +- Source/Engine/Graphics/Materials/MaterialShader.h | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl index 33ef567ca..f4583fa7f 100644 --- a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl +++ b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. @0// Lightmap: Defines +#define CAN_USE_LIGHTMAP 1 @1// Lightmap: Includes @2// Lightmap: Constants float4 LightmapArea; diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index 1038a73a3..4eee27691 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -350,7 +350,7 @@ VertexOutput VS(ModelInput input) output.Geometry.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); #else -#if USE_LIGHTMAP +#if CAN_USE_LIGHTMAP output.Geometry.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; #else output.Geometry.LightmapUV = input.LightmapUV; diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 102ac4d41..8c49389a7 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -9,7 +9,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 147 +#define MATERIAL_GRAPH_VERSION 148 class Material; class GPUShader; From 1530f91944256798021e85a5195ce87bacc8bf53 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 16 Feb 2021 15:55:18 +0100 Subject: [PATCH 219/222] Update material assets --- Content/Editor/Camera/M_Camera.flax | 4 ++-- Content/Editor/CubeTexturePreviewMaterial.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 | 2 +- Content/Editor/Particles/Particle Material Color.flax | 2 +- Content/Editor/Particles/Smoke Material.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 | 4 ++-- Content/Engine/DefaultDeformableMaterial.flax | 2 +- Content/Engine/DefaultMaterial.flax | 4 ++-- Content/Engine/DefaultTerrainMaterial.flax | 4 ++-- Content/Engine/SingleColorMaterial.flax | 4 ++-- Content/Engine/SkyboxMaterial.flax | 4 ++-- 22 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index 768cf1986..94890c96c 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:2d62539b39925798a3d526d20b3fc4e30c268629427c3920e1ece9f4c446ec45 -size 29995 +oid sha256:7b4752ca9986784f54afbad450edacdb3f87ed93d6e904c76ba40bb467e58d25 +size 30027 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index f4047b652..9825995fb 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:33afbfa5c2b3e24499de14fdabf87bdc809ce703f859b718241e203115db3961 -size 31552 +oid sha256:6740a5d3ecc88c95f296dc0b77d8e177fcf3cc303dbaf9d1602aaf7008eb8b65 +size 31584 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index 3cb4ff8dd..f464ad768 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:bdca03c927766e9d4bb5409e2507b3a9d22f13342fad159182fdd8da122312cb -size 30076 +oid sha256:c9c2e7cc6393d6f2079d4d064daa0a2663ec84c25be7778b73da22e9c66c226e +size 30108 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index 8c87ae3b3..af6e065a2 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:5129d65fa3a24af617b25b9058294b89e65290581b2992e77ba46a12ade7a8ee -size 34537 +oid sha256:15e7562cb1f6b58f9030fbd66477b131b577f9d7c08f0d6b67078f02d05e6015 +size 34541 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index 6bc4e8f93..64145ecec 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:1e470f838ed5ff78b311169f974a3832b387730be2030dc67e3ddf0d601dc3c0 -size 31063 +oid sha256:26a9333914f7ddf750e8d33be03c739bd3cc4fb455ebecbe83018b63ab8277b2 +size 31067 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 2b85a3941..4e85879a0 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:083633783ea5733720803b285a2378a6130fec506b9a2abe93b146cdf736d0ed -size 30276 +oid sha256:405fdb8843f7836807a0efc40564c0ab46eab62072d04b6e209d140e4bedab66 +size 30280 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 6a082e985..f3b1474d9 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:ae4775c4ade6c4e50f81b68a8a8546696f58bb1d701f2d8397a47d757707782f +oid sha256:74ab675aa70e0bfa43e7eedf8b813c8e0a1c792fa59289ef50cfe2cf2505b900 size 15863 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index 1314e5596..574773030 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:770a2498f0a712ae1705fd74204c62ded862b73613ed8cec70974bee0802350a -size 31010 +oid sha256:4e79a6d2c67d7dab5a6ef26e433c21db3533c54605627baf16d17cd33e998bb3 +size 31042 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 824fbf090..6fb6aa10d 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:50fcefc75caa48fe73ed23bf3fe2c023f9a6ac146d3a3bc16b4386a39aaa56f7 -size 28871 +oid sha256:f1a371e03bfc916068cbc2863f3c92dcab3ac0ba3a4839ac0efa5e10e43807b2 +size 28875 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index e7b91ff38..2cf1d5cb7 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:aae6e991a3623c9c84165c30fceab674d4173bb27303a164bd3dcef61f30654d -size 28803 +oid sha256:df1097033d79b531c8f09c2cb95bbb85a8190c6cd0649b77f05dd617410a0b8e +size 28807 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index 7dbe080c9..5466742ea 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:58bba43288fb4b2dbcc8a6401ac765fd269bb231c1b19c64aa2bb9ca72e9c106 +oid sha256:b02d31b97be388836c675b7d5814fc5f46623446c9ffd7cff837901694141ad8 size 18415 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 81fcbea72..30b136e26 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:e44da38b26f222f7081278dc64b7fa932c6f9bfd0b79694da784bd4d944e838a +oid sha256:de2cfa951e89d51dbe8453525c94a527cb9d1ad9fbd77166b56e6e4eb35f2806 size 29232 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index 66c3466e8..a0c1124e0 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:586e1e4620df43c638ca5a059dc1b705a82b47f398c973dd526e3f58570cf645 +oid sha256:d55de18eb0aba44d084546b4a5aabf3482219c9b2c14930e9446fe86b711e61c size 35331 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index 30093ee5e..1a9236d73 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:2ce01e5aeaf46ed0ed2285b2b6c5b0d8f0bebdb0a7f5fbd246eef22a8ead21fa -size 27520 +oid sha256:b6cd8240338f9b506ebb6ef42646958d72f6ed10ceae1ec1fa49291e2f4deeb4 +size 27548 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index a942d7848..34de2594f 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:3f8b40c2622b1422ae062bafec002cd120e160f2ed56aab10e3390ba3dbd3a55 -size 21199 +oid sha256:ec51e766fb690f614663e39dc466c38d23b44ed08eec927c49f49cd46795e07e +size 21227 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index cc3b32eb9..7a6c1e659 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:f92a749ea24fc5d7b45e0e4f2be5619402163f4292d565e5fe014268a46a1275 +oid sha256:d36c699f69d20d9d9aaf03011bdcaa2cbdfd42e6ceed7defe3dd70fc8b9e9a78 size 10653 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 0b90a7474..b1af939b3 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:9563e2a8c60bac96093c614dde7a581bc5eb1ce6f7da00339f4b8f2f1607c2a4 -size 28871 +oid sha256:da9378517b42e1c7c4e9c7a79d1f86d41f979655d6f8d2900708ae668517e486 +size 28875 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax index 3b2e45a87..197d82b3f 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:dab7eb4f71a407eae7d6b051cbad701f234742b0d96646811c8a615de80e94eb +oid sha256:d9835db8c0994006b43042b26164ace74c8a2027a9586edc3ec80d16ca75f680 size 18800 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index a746a0e4e..4212fadbd 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:77076bc1e501bc2f630f559dfb699c965d2d95c5a611b4619a2819bc1f6eb8ca -size 31774 +oid sha256:757da751c813fd60224d2000faa2de848f36b8555163791b9c852ac7e1c6b59a +size 31806 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index c7fbfec0f..2a98d9c36 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:909a9b45c735f3356d68885f6d294cb0627f5107316edef32b4b56328e8a0353 -size 23279 +oid sha256:c0a0cd43b9fb5a84e85c4376049db1ef3d2c64dd0cca338ff8f790e6aa1f86a9 +size 23307 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index 2aceb44be..a22fabbb8 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:075200d4b5a1353b463aff9c019212bea19aaee6ff38e34a3ddefceb816c00ec -size 30075 +oid sha256:889439f17a4523cf18a17c46f4348654cce5bc25c1ab1928e411405d8eacfd99 +size 30130 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 7a7822ac7..46e0c9304 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:b58ddef0b73414d33f88a4e15df151b0a95a1093116b3d1e194d9b781ff09051 -size 31296 +oid sha256:1a5bfb7e11eeccf5c44ee2a284b3d0479a752cb8af470b5b93fd8fe8213947be +size 31328 From 25f61e931e9ee0d729338814a5d01f63bca9876f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 17 Feb 2021 10:58:58 +0100 Subject: [PATCH 220/222] Add support for using other VC++ toolset for Window and add cmd arg for selecting compiler manually --- Source/Tools/Flax.Build/Configuration.cs | 6 ++++++ .../Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs | 2 +- .../Flax.Build/Platforms/Windows/WindowsToolchainBase.cs | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 28970f289..bd9785d35 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -201,6 +201,12 @@ namespace Flax.Build [CommandLine("customProjectFormat", "", "Generates code project files for a custom project format type. Valid only with -genproject option.")] public static string ProjectFormatCustom = null; + /// + /// Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows. + /// + [CommandLine("compiler", "", "Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows.")] + public static string Compiler = null; + /// /// Custom configuration defines provided via command line for the build tool. /// diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index 8f247a189..40f338a65 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -20,7 +20,7 @@ namespace Flax.Build.Platforms /// The platform. /// The target architecture. public WindowsToolchain(WindowsPlatform platform, TargetArchitecture architecture) - : base(platform, architecture, WindowsPlatformToolset.v140, WindowsPlatformSDK.Latest) + : base(platform, architecture, WindowsPlatformToolset.Latest, WindowsPlatformSDK.Latest) { } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 4d3289a86..919971f68 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -78,6 +78,13 @@ namespace Flax.Build.Platforms var toolsets = WindowsPlatformBase.GetToolsets(); var sdks = WindowsPlatformBase.GetSDKs(); + // Pick the overriden toolset + if (Configuration.Compiler != null) + { + if (Enum.TryParse(Configuration.Compiler, out WindowsPlatformToolset compiler)) + toolsetVer = compiler; + } + // Pick the newest installed Visual Studio version if using the default toolset if (toolsetVer == WindowsPlatformToolset.Default) { From 7cc5560adfe5d4f302ee74e6e9463359c262f49e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 17 Feb 2021 10:59:08 +0100 Subject: [PATCH 221/222] Fix compilation warnings --- Source/Engine/Core/Utilities.h | 6 ++--- Source/Engine/Debug/DebugDraw.cpp | 28 ++++++++++++------------ Source/Engine/Level/Actors/SpotLight.cpp | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h index 8d77c508d..531509d61 100644 --- a/Source/Engine/Core/Utilities.h +++ b/Source/Engine/Core/Utilities.h @@ -11,21 +11,21 @@ namespace Utilities template FORCE_INLINE T RoundTo1DecimalPlace(T value) { - return round(value * 10) / 10; + return (T)round((double)value * 10) / (T)10; } // Round floating point value up to 2 decimal places template FORCE_INLINE T RoundTo2DecimalPlaces(T value) { - return round(value * 100) / 100; + return (T)round((double)value * 100.0) / (T)100; } // Round floating point value up to 3 decimal places template FORCE_INLINE T RoundTo3DecimalPlaces(T value) { - return round(value * 1000) / 1000; + return (T)round((double)value * 1000.0) / (T)1000; } // Converts size of the file (in bytes) to the best fitting string diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index e394b31f4..bae424c4e 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -313,10 +313,10 @@ bool DebugDrawService::Init() for (float a = 0.0f; a < TWO_PI; a += step) { // Calculate sines and cosines - float sinA = sin(a); - float cosA = cos(a); - float sinB = sin(a + step); - float cosB = cos(a + step); + float sinA = Math::Sin(a); + float cosA = Math::Cos(a); + float sinB = Math::Sin(a + step); + float cosB = Math::Cos(a + step); // XY loop SphereCache[index++] = Vector3(cosA, sinA, 0.0f); @@ -337,10 +337,10 @@ bool DebugDrawService::Init() for (float a = 0.0f; a < TWO_PI; a += step) { // Calculate sines and cosines - float sinA = sin(a); - float cosA = cos(a); - float sinB = sin(a + step); - float cosB = cos(a + step); + float sinA = Math::Sin(a); + float cosA = Math::Cos(a); + float sinB = Math::Sin(a + step); + float cosB = Math::Cos(a + step); CircleCache[index++] = Vector3(cosA, sinA, 0.0f); CircleCache[index++] = Vector3(cosB, sinB, 0.0f); @@ -1049,10 +1049,10 @@ void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientat { // Calculate sines and cosines // TODO: optimize this stuff - float sinA = sin(a) * radius; - float cosA = cos(a) * radius; - float sinB = sin(a + step) * radius; - float cosB = cos(a + step) * radius; + float sinA = Math::Sin(a) * radius; + float cosA = Math::Cos(a) * radius; + float sinB = Math::Sin(a + step) * radius; + float cosB = Math::Cos(a + step) * radius; // First XY loop DRAW_WIRE_BOX_LINE(cosA, sinA, -halfLength, cosB, sinB, -halfLength); @@ -1098,8 +1098,8 @@ void DebugDraw::DrawWireCylinder(const Vector3& position, const Quaternion& orie { // Cache data float theta = i * angleBetweenFacets; - float x = cos(theta) * radius; - float z = sin(theta) * radius; + float x = Math::Cos(theta) * radius; + float z = Math::Sin(theta) * radius; // Top cap CylinderCache[index++] = Vector3(x, verticalOffset, z); diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 57d4d97f3..c601506c4 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -198,8 +198,8 @@ void SpotLight::OnDebugDrawSelected() Vector3 up = _transform.GetUp(); Vector3 forward = GetDirection(); float radius = GetScaledRadius(); - float discRadius = radius * tan(_outerConeAngle * DegreesToRadians); - float falloffDiscRadius = radius * tan(_innerConeAngle * DegreesToRadians); + float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians); + float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians); Vector3 position = GetPosition(); DEBUG_DRAW_LINE(position, position + forward * radius + up * discRadius, color, 0, true); From d6546fb41c58c58854d2af6adbfd07925c6e9621 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 17 Feb 2021 11:00:48 +0100 Subject: [PATCH 222/222] Update VulkanMemoryAllocator to `3.0.0-development` (2021-02-16) --- .../GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 2 + .../VulkanMemoryAllocator/LICENSE.txt | 2 +- .../VulkanMemoryAllocator/vk_mem_alloc.h | 4864 ++++++++++++----- 3 files changed, 3612 insertions(+), 1256 deletions(-) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 5457872d5..1205cde98 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1825,7 +1825,9 @@ bool GPUDeviceVulkan::Init() #endif #undef INIT_FUNC VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0; allocatorInfo.physicalDevice = gpu; + allocatorInfo.instance = Instance; allocatorInfo.device = Device; allocatorInfo.pVulkanFunctions = &vulkanFunctions; VALIDATE_VULKAN_RESULT(vmaCreateAllocator(&allocatorInfo, &Allocator)); diff --git a/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt b/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt index 67b0d01dc..71e824f80 100644 --- a/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt +++ b/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2017-2021 Advanced Micro Devices, Inc. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h b/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h index 12256dee6..a410c63e4 100644 --- a/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h +++ b/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. +// Copyright (c) 2017-2021 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,11 @@ #ifndef AMD_VULKAN_MEMORY_ALLOCATOR_H #define AMD_VULKAN_MEMORY_ALLOCATOR_H -#ifdef __cplusplus -extern "C" { -#endif - /** \mainpage Vulkan Memory Allocator -Version 2.2.1-development (2018-12-14) +Version 3.0.0-development (2021-02-16) -Copyright (c) 2017-2018 Advanced Micro Devices, Inc. All rights reserved. \n +Copyright (c) 2017-2021 Advanced Micro Devices, Inc. All rights reserved. \n License: MIT Documentation of all members: vk_mem_alloc.h @@ -52,8 +48,12 @@ Documentation of all members: vk_mem_alloc.h - \subpage memory_mapping - [Mapping functions](@ref memory_mapping_mapping_functions) - [Persistently mapped memory](@ref memory_mapping_persistently_mapped_memory) - - [Cache control](@ref memory_mapping_cache_control) + - [Cache flush and invalidate](@ref memory_mapping_cache_control) - [Finding out if memory is mappable](@ref memory_mapping_finding_if_memory_mappable) + - \subpage staying_within_budget + - [Querying for budget](@ref staying_within_budget_querying_for_budget) + - [Controlling memory usage](@ref staying_within_budget_controlling_memory_usage) + - \subpage resource_aliasing - \subpage custom_memory_pools - [Choosing memory type index](@ref custom_memory_pools_MemTypeIndex) - [Linear allocation algorithm](@ref linear_algorithm) @@ -63,10 +63,10 @@ Documentation of all members: vk_mem_alloc.h - [Ring buffer](@ref linear_algorithm_ring_buffer) - [Buddy allocation algorithm](@ref buddy_algorithm) - \subpage defragmentation - - [Defragmenting CPU memory](@ref defragmentation_cpu) - - [Defragmenting GPU memory](@ref defragmentation_gpu) - - [Additional notes](@ref defragmentation_additional_notes) - - [Writing custom allocation algorithm](@ref defragmentation_custom_algorithm) + - [Defragmenting CPU memory](@ref defragmentation_cpu) + - [Defragmenting GPU memory](@ref defragmentation_gpu) + - [Additional notes](@ref defragmentation_additional_notes) + - [Writing custom allocation algorithm](@ref defragmentation_custom_algorithm) - \subpage lost_allocations - \subpage statistics - [Numeric statistics](@ref statistics_numeric_statistics) @@ -80,6 +80,7 @@ Documentation of all members: vk_mem_alloc.h - [Corruption detection](@ref debugging_memory_usage_corruption_detection) - \subpage record_and_replay - \subpage usage_patterns + - [Common mistakes](@ref usage_patterns_common_mistakes) - [Simple patterns](@ref usage_patterns_simple) - [Advanced patterns](@ref usage_patterns_advanced) - \subpage configuration @@ -88,6 +89,8 @@ Documentation of all members: vk_mem_alloc.h - [Device memory allocation callbacks](@ref allocation_callbacks) - [Device heap memory limit](@ref heap_memory_limit) - \subpage vk_khr_dedicated_allocation + - \subpage enabling_buffer_device_address + - \subpage vk_amd_device_coherent_memory - \subpage general_considerations - [Thread safety](@ref general_considerations_thread_safety) - [Validation layer warnings](@ref general_considerations_validation_layer_warnings) @@ -139,24 +142,42 @@ before including these headers (like `WIN32_LEAN_AND_MEAN` or `WINVER` for Windows, `VK_USE_PLATFORM_WIN32_KHR` for Vulkan), you must define them before every `#include` of this library. +You may need to configure the way you import Vulkan functions. + +- By default, VMA assumes you you link statically with Vulkan API. If this is not the case, + `#define VMA_STATIC_VULKAN_FUNCTIONS 0` before `#include` of the VMA implementation and use another way. +- You can `#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1` and make sure `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` globals are defined. + All the remaining Vulkan functions will be fetched automatically. +- Finally, you can provide your own pointers to all Vulkan functions needed by VMA using structure member + VmaAllocatorCreateInfo::pVulkanFunctions, if you fetched them in some custom way e.g. using some loader like [Volk](https://github.com/zeux/volk). + \section quick_start_initialization Initialization At program startup: --# Initialize Vulkan to have `VkPhysicalDevice` and `VkDevice` object. +-# Initialize Vulkan to have `VkPhysicalDevice`, `VkDevice` and `VkInstance` object. -# Fill VmaAllocatorCreateInfo structure and create #VmaAllocator object by calling vmaCreateAllocator(). \code VmaAllocatorCreateInfo allocatorInfo = {}; +allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_2; allocatorInfo.physicalDevice = physicalDevice; allocatorInfo.device = device; +allocatorInfo.instance = instance; VmaAllocator allocator; vmaCreateAllocator(&allocatorInfo, &allocator); \endcode +Only members `physicalDevice`, `device`, `instance` are required. +However, you should inform the library which Vulkan version do you use by setting +VmaAllocatorCreateInfo::vulkanApiVersion and which extensions did you enable +by setting VmaAllocatorCreateInfo::flags (like #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT for VK_KHR_buffer_device_address). +Otherwise, VMA would use only features of Vulkan 1.0 core with no extensions. + + \section quick_start_resource_allocation Resource allocation When you want to create a buffer or image: @@ -206,7 +227,8 @@ You can also combine multiple methods. -# If you already have a buffer or an image created, you want to allocate memory for it and then you will bind it yourself, you can use function vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(). - For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory(). + For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory() + or their extended versions: vmaBindBufferMemory2(), vmaBindImageMemory2(). -# If you want to create a buffer or an image, allocate memory for it and bind them together, all in one call, you can use function vmaCreateBuffer(), vmaCreateImage(). This is the easiest and recommended way to use this library. @@ -297,6 +319,7 @@ VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); \endcode + \section choosing_memory_type_custom_memory_pools Custom memory pools If you allocate from custom memory pool, all the ways of specifying memory @@ -421,16 +444,18 @@ There are some exceptions though, when you should consider mapping memory only f which requires unmapping before GPU can see updated texture. - Keeping many large memory blocks mapped may impact performance or stability of some debugging tools. -\section memory_mapping_cache_control Cache control - +\section memory_mapping_cache_control Cache flush and invalidate + Memory in Vulkan doesn't need to be unmapped before using it on GPU, but unless a memory types has `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set, -you need to manually invalidate cache before reading of mapped pointer -and flush cache after writing to mapped pointer. +you need to manually **invalidate** cache before reading of mapped pointer +and **flush** cache after writing to mapped pointer. +Map/unmap operations don't do that automatically. Vulkan provides following functions for this purpose `vkFlushMappedMemoryRanges()`, `vkInvalidateMappedMemoryRanges()`, but this library provides more convenient functions that refer to given allocation object: vmaFlushAllocation(), -vmaInvalidateAllocation(). +vmaInvalidateAllocation(), +or multiple objects at once: vmaFlushAllocations(), vmaInvalidateAllocations(). Regions of memory specified for flush/invalidate must be aligned to `VkPhysicalDeviceLimits::nonCoherentAtomSize`. This is automatically ensured by the library. @@ -440,7 +465,7 @@ within blocks are aligned to this value, so their offsets are always multiply of Please note that memory allocated with #VMA_MEMORY_USAGE_CPU_ONLY is guaranteed to be `HOST_COHERENT`. -Also, Windows drivers from all 3 PC GPU vendors (AMD, Intel, NVIDIA) +Also, Windows drivers from all 3 **PC** GPU vendors (AMD, Intel, NVIDIA) currently provide `HOST_COHERENT` flag on all memory types that are `HOST_VISIBLE`, so on this platform you may not need to bother. @@ -472,7 +497,7 @@ vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allo VkMemoryPropertyFlags memFlags; vmaGetMemoryTypeProperties(allocator, allocInfo.memoryType, &memFlags); -if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) +if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) { // Allocation ended up in mappable memory. You can map it and access it directly. void* mappedData; @@ -507,7 +532,7 @@ VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); -if(allocInfo.pUserData != nullptr) +if(allocInfo.pMappedData != nullptr) { // Allocation ended up in mappable memory. // It's persistently mapped. You can access it directly. @@ -521,6 +546,186 @@ else \endcode +\page staying_within_budget Staying within budget + +When developing a graphics-intensive game or program, it is important to avoid allocating +more GPU memory than it's physically available. When the memory is over-committed, +various bad things can happen, depending on the specific GPU, graphics driver, and +operating system: + +- It may just work without any problems. +- The application may slow down because some memory blocks are moved to system RAM + and the GPU has to access them through PCI Express bus. +- A new allocation may take very long time to complete, even few seconds, and possibly + freeze entire system. +- The new allocation may fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +- It may even result in GPU crash (TDR), observed as `VK_ERROR_DEVICE_LOST` + returned somewhere later. + +\section staying_within_budget_querying_for_budget Querying for budget + +To query for current memory usage and available budget, use function vmaGetBudget(). +Returned structure #VmaBudget contains quantities expressed in bytes, per Vulkan memory heap. + +Please note that this function returns different information and works faster than +vmaCalculateStats(). vmaGetBudget() can be called every frame or even before every +allocation, while vmaCalculateStats() is intended to be used rarely, +only to obtain statistical information, e.g. for debugging purposes. + +It is recommended to use VK_EXT_memory_budget device extension to obtain information +about the budget from Vulkan device. VMA is able to use this extension automatically. +When not enabled, the allocator behaves same way, but then it estimates current usage +and available budget based on its internal information and Vulkan memory heap sizes, +which may be less precise. In order to use this extension: + +1. Make sure extensions VK_EXT_memory_budget and VK_KHR_get_physical_device_properties2 + required by it are available and enable them. Please note that the first is a device + extension and the second is instance extension! +2. Use flag #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT when creating #VmaAllocator object. +3. Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from + Vulkan inside of it to avoid overhead of querying it with every allocation. + +\section staying_within_budget_controlling_memory_usage Controlling memory usage + +There are many ways in which you can try to stay within the budget. + +First, when making new allocation requires allocating a new memory block, the library +tries not to exceed the budget automatically. If a block with default recommended size +(e.g. 256 MB) would go over budget, a smaller block is allocated, possibly even +dedicated memory for just this resource. + +If the size of the requested resource plus current memory usage is more than the +budget, by default the library still tries to create it, leaving it to the Vulkan +implementation whether the allocation succeeds or fails. You can change this behavior +by using #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag. With it, the allocation is +not made if it would exceed the budget or if the budget is already exceeded. +Some other allocations become lost instead to make room for it, if the mechanism of +[lost allocations](@ref lost_allocations) is used. +If that is not possible, the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +Example usage pattern may be to pass the #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag +when creating resources that are not essential for the application (e.g. the texture +of a specific object) and not to pass it when creating critically important resources +(e.g. render targets). + +Finally, you can also use #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT flag to make sure +a new allocation is created only when it fits inside one of the existing memory blocks. +If it would require to allocate a new block, if fails instead with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +This also ensures that the function call is very fast because it never goes to Vulkan +to obtain a new block. + +Please note that creating \ref custom_memory_pools with VmaPoolCreateInfo::minBlockCount +set to more than 0 will try to allocate memory blocks without checking whether they +fit within budget. + + +\page resource_aliasing Resource aliasing (overlap) + +New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory +management, give an opportunity to alias (overlap) multiple resources in the +same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). +It can be useful to save video memory, but it must be used with caution. + +For example, if you know the flow of your whole render frame in advance, you +are going to use some intermediate textures or buffers only during a small range of render passes, +and you know these ranges don't overlap in time, you can bind these resources to +the same place in memory, even if they have completely different parameters (width, height, format etc.). + +![Resource aliasing (overlap)](../gfx/Aliasing.png) + +Such scenario is possible using VMA, but you need to create your images manually. +Then you need to calculate parameters of an allocation to be made using formula: + +- allocation size = max(size of each image) +- allocation alignment = max(alignment of each image) +- allocation memoryTypeBits = bitwise AND(memoryTypeBits of each image) + +Following example shows two different images bound to the same place in memory, +allocated to fit largest of them. + +\code +// A 512x512 texture to be sampled. +VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; +img1CreateInfo.imageType = VK_IMAGE_TYPE_2D; +img1CreateInfo.extent.width = 512; +img1CreateInfo.extent.height = 512; +img1CreateInfo.extent.depth = 1; +img1CreateInfo.mipLevels = 10; +img1CreateInfo.arrayLayers = 1; +img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; +img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + +// A full screen texture to be used as color attachment. +VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; +img2CreateInfo.imageType = VK_IMAGE_TYPE_2D; +img2CreateInfo.extent.width = 1920; +img2CreateInfo.extent.height = 1080; +img2CreateInfo.extent.depth = 1; +img2CreateInfo.mipLevels = 1; +img2CreateInfo.arrayLayers = 1; +img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; +img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + +VkImage img1; +res = vkCreateImage(device, &img1CreateInfo, nullptr, &img1); +VkImage img2; +res = vkCreateImage(device, &img2CreateInfo, nullptr, &img2); + +VkMemoryRequirements img1MemReq; +vkGetImageMemoryRequirements(device, img1, &img1MemReq); +VkMemoryRequirements img2MemReq; +vkGetImageMemoryRequirements(device, img2, &img2MemReq); + +VkMemoryRequirements finalMemReq = {}; +finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size); +finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment); +finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits; +// Validate if(finalMemReq.memoryTypeBits != 0) + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + +VmaAllocation alloc; +res = vmaAllocateMemory(allocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr); + +res = vmaBindImageMemory(allocator, alloc, img1); +res = vmaBindImageMemory(allocator, alloc, img2); + +// You can use img1, img2 here, but not at the same time! + +vmaFreeMemory(allocator, alloc); +vkDestroyImage(allocator, img2, nullptr); +vkDestroyImage(allocator, img1, nullptr); +\endcode + +Remember that using resouces that alias in memory requires proper synchronization. +You need to issue a memory barrier to make sure commands that use `img1` and `img2` +don't overlap on GPU timeline. +You also need to treat a resource after aliasing as uninitialized - containing garbage data. +For example, if you use `img1` and then want to use `img2`, you need to issue +an image memory barrier for `img2` with `oldLayout` = `VK_IMAGE_LAYOUT_UNDEFINED`. + +Additional considerations: + +- Vulkan also allows to interpret contents of memory between aliasing resources consistently in some cases. +See chapter 11.8. "Memory Aliasing" of Vulkan specification or `VK_IMAGE_CREATE_ALIAS_BIT` flag. +- You can create more complex layout where different images and buffers are bound +at different offsets inside one large allocation. For example, one can imagine +a big texture used in some render passes, aliasing with a set of many small buffers +used between in some further passes. To bind a resource at non-zero offset of an allocation, +use vmaBindBufferMemory2() / vmaBindImageMemory2(). +- Before allocating memory for the resources you want to alias, check `memoryTypeBits` +returned in memory requirements of each resource to make sure the bits overlap. +Some GPUs may expose multiple memory types suitable e.g. only for buffers or +images with `COLOR_ATTACHMENT` usage, so the sets of memory types supported by your +resources may be disjoint. Aliasing them is not possible in that case. + + \page custom_memory_pools Custom memory pools A memory pool contains a number of `VkDeviceMemory` blocks. @@ -744,7 +949,7 @@ allocations. To mitigate this problem, you can use defragmentation feature: structure #VmaDefragmentationInfo2, function vmaDefragmentationBegin(), vmaDefragmentationEnd(). -Given set of allocations, +Given set of allocations, this function can move them to compact used memory, ensure more continuous free space and possibly also free some `VkDeviceMemory` blocks. @@ -761,7 +966,8 @@ What it doesn't do, so you need to do it yourself: - Recreate buffers and images that were bound to allocations that were defragmented and bind them with their new places in memory. You must use `vkDestroyBuffer()`, `vkDestroyImage()`, - `vkCreateBuffer()`, `vkCreateImage()` for that purpose and NOT vmaDestroyBuffer(), + `vkCreateBuffer()`, `vkCreateImage()`, vmaBindBufferMemory(), vmaBindImageMemory() + for that purpose and NOT vmaDestroyBuffer(), vmaDestroyImage(), vmaCreateBuffer(), vmaCreateImage(), because you don't need to destroy or create allocation objects! - Recreate views and update descriptors that point to these buffers and images. @@ -809,13 +1015,13 @@ for(uint32_t i = 0; i < allocCount; ++i) // Create new buffer with same parameters. VkBufferCreateInfo bufferInfo = ...; vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]); - + // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning. - + // Bind new buffer to new memory region. Data contained in it is already moved. VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocations[i], &allocInfo); - vkBindBufferMemory(device, buffers[i], allocInfo.deviceMemory, allocInfo.offset); + vmaBindBufferMemory(allocator, allocations[i], buffers[i]); } } \endcode @@ -887,13 +1093,13 @@ for(uint32_t i = 0; i < allocCount; ++i) // Create new buffer with same parameters. VkBufferCreateInfo bufferInfo = ...; vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]); - + // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning. - + // Bind new buffer to new memory region. Data contained in it is already moved. VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocations[i], &allocInfo); - vkBindBufferMemory(device, buffers[i], allocInfo.deviceMemory, allocInfo.offset); + vmaBindBufferMemory(allocator, allocations[i], buffers[i]); } } \endcode @@ -907,19 +1113,22 @@ in function vmaDefragmentationBegin(). \section defragmentation_additional_notes Additional notes -While using defragmentation, you may experience validation layer warnings, which you just need to ignore. -See [Validation layer warnings](@ref general_considerations_validation_layer_warnings). +It is only legal to defragment allocations bound to: -If you defragment allocations bound to images, these images should be created with -`VK_IMAGE_CREATE_ALIAS_BIT` flag, to make sure that new image created with same -parameters and pointing to data copied to another memory region will interpret -its contents consistently. Otherwise you may experience corrupted data on some -implementations, e.g. due to different pixel swizzling used internally by the graphics driver. +- buffers +- images created with `VK_IMAGE_CREATE_ALIAS_BIT`, `VK_IMAGE_TILING_LINEAR`, and + being currently in `VK_IMAGE_LAYOUT_GENERAL` or `VK_IMAGE_LAYOUT_PREINITIALIZED`. + +Defragmentation of images created with `VK_IMAGE_TILING_OPTIMAL` or in any other +layout may give undefined results. If you defragment allocations bound to images, new images to be bound to new memory region after defragmentation should be created with `VK_IMAGE_LAYOUT_PREINITIALIZED` -and then transitioned to their original layout from before defragmentation using -an image memory barrier. +and then transitioned to their original layout from before defragmentation if +needed using an image memory barrier. + +While using defragmentation, you may experience validation layer warnings, which you just need to ignore. +See [Validation layer warnings](@ref general_considerations_validation_layer_warnings). Please don't expect memory to be fully compacted after defragmentation. Algorithms inside are based on some heuristics that try to maximize number of Vulkan @@ -1186,6 +1395,9 @@ printf("Image name: %s\n", imageName); That string is also printed in JSON report created by vmaBuildStatsString(). +\note Passing string name to VMA allocation doesn't automatically set it to the Vulkan buffer or image created with it. +You must do it manually using an extension like VK_EXT_debug_utils, which is independent of this library. + \page debugging_memory_usage Debugging incorrect memory usage @@ -1277,7 +1489,7 @@ which indicates a serious bug. You can also explicitly request checking margins of all allocations in all memory blocks that belong to specified memory types by using function vmaCheckCorruption(), -or in memory blocks that belong to specified custom pool, by using function +or in memory blocks that belong to specified custom pool, by using function vmaCheckPoolCorruption(). Margin validation (corruption detection) works only for memory types that are @@ -1301,6 +1513,13 @@ application. It can be useful to: \section record_and_replay_usage Usage +Recording functionality is disabled by default. +To enable it, define following macro before every include of this library: + +\code +#define VMA_RECORDING_ENABLED 1 +\endcode + To record sequence of calls to a file: Fill in VmaAllocatorCreateInfo::pRecordSettings member while creating #VmaAllocator object. File is opened and written during whole lifetime of the allocator. @@ -1327,7 +1546,6 @@ It's a human-readable, text file in CSV format (Comma Separated Values). coded and tested only on Windows. Inclusion of recording code is driven by `VMA_RECORDING_ENABLED` macro. Support for other platforms should be easy to add. Contributions are welcomed. -- Currently calls to vmaDefragment() function are not recorded. \page usage_patterns Recommended usage patterns @@ -1336,6 +1554,27 @@ See also slides from talk: [Sawicki, Adam. Advanced Graphics Techniques Tutorial: Memory management in Vulkan and DX12. Game Developers Conference, 2018](https://www.gdcvault.com/play/1025458/Advanced-Graphics-Techniques-Tutorial-New) +\section usage_patterns_common_mistakes Common mistakes + +Use of CPU_TO_GPU instead of CPU_ONLY memory + +#VMA_MEMORY_USAGE_CPU_TO_GPU is recommended only for resources that will be +mapped and written by the CPU, as well as read directly by the GPU - like some +buffers or textures updated every frame (dynamic). If you create a staging copy +of a resource to be written by CPU and then used as a source of transfer to +another resource placed in the GPU memory, that staging resource should be +created with #VMA_MEMORY_USAGE_CPU_ONLY. Please read the descriptions of these +enums carefully for details. + +Unnecessary use of custom pools + +\ref custom_memory_pools may be useful for special purposes - when you want to +keep certain type of resources separate e.g. to reserve minimum amount of memory +for them, limit maximum amount of memory they can occupy, or make some of them +push out the other through the mechanism of \ref lost_allocations. For most +resources this is not needed and so it is not recommended to create #VmaPool +objects and allocations out of them. Allocating from the default pool is sufficient. + \section usage_patterns_simple Simple patterns \subsection usage_patterns_simple_render_targets Render targets @@ -1391,6 +1630,7 @@ This is a more complex situation. Different solutions are possible, and the best one depends on specific GPU type, but you can use this simple approach for the start. Prefer to write to such resource sequentially (e.g. using `memcpy`). Don't perform random access or any reads from it on CPU, as it may be very slow. +Also note that textures written directly from the host through a mapped pointer need to be in LINEAR not OPTIMAL layout. \subsection usage_patterns_readback Readback @@ -1423,10 +1663,10 @@ directly instead of submitting explicit transfer (see below). For resources that you frequently write on CPU and read on GPU, many solutions are possible: -# Create one copy in video memory using #VMA_MEMORY_USAGE_GPU_ONLY, - second copy in system memory using #VMA_MEMORY_USAGE_CPU_ONLY and submit explicit tranfer each time. --# Create just single copy using #VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU, + second copy in system memory using #VMA_MEMORY_USAGE_CPU_ONLY and submit explicit transfer each time. +-# Create just a single copy using #VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU, read it directly on GPU. --# Create just single copy using #VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU, +-# Create just a single copy using #VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU, read it directly on GPU. Which solution is the most efficient depends on your resource and especially on the GPU. @@ -1454,6 +1694,10 @@ solutions are possible: You should take some measurements to decide which option is faster in case of your specific resource. +Note that textures accessed directly from the host through a mapped pointer need to be in LINEAR layout, +which may slow down their usage on the device. +Textures accessed only by the device and transfer operations can use OPTIMAL layout. + If you don't want to specialize your code for specific types of GPUs, you can still make an simple optimization for cases when your resource ends up in mappable memory to use it directly in this case instead of creating CPU-side staging copy. @@ -1469,14 +1713,38 @@ mutex, atomic etc. The library uses its own implementation of containers by default, but you can switch to using STL containers instead. +For example, define `VMA_ASSERT(expr)` before including the library to provide +custom implementation of the assertion, compatible with your project. +By default it is defined to standard C `assert(expr)` in `_DEBUG` configuration +and empty otherwise. + \section config_Vulkan_functions Pointers to Vulkan functions -The library uses Vulkan functions straight from the `vulkan.h` header by default. -If you want to provide your own pointers to these functions, e.g. fetched using -`vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`: +There are multiple ways to import pointers to Vulkan functions in the library. +In the simplest case you don't need to do anything. +If the compilation or linking of your program or the initialization of the #VmaAllocator +doesn't work for you, you can try to reconfigure it. + +First, the allocator tries to fetch pointers to Vulkan functions linked statically, +like this: + +\code +m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; +\endcode + +If you want to disable this feature, set configuration macro: `#define VMA_STATIC_VULKAN_FUNCTIONS 0`. + +Second, you can provide the pointers yourself by setting member VmaAllocatorCreateInfo::pVulkanFunctions. +You can fetch them e.g. using functions `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` or +by using a helper library like [volk](https://github.com/zeux/volk). + +Third, VMA tries to fetch remaining pointers that are still null by calling +`vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` on its own. +If you want to disable this feature, set configuration macro: `#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0`. + +Finally, all the function pointers required by the library (considering selected +Vulkan version and enabled extensions) are checked with `VMA_ASSERT` if they are not null. --# Define `VMA_STATIC_VULKAN_FUNCTIONS 0`. --# Provide valid pointers through VmaAllocatorCreateInfo::pVulkanFunctions. \section custom_memory_allocator Custom host memory allocator @@ -1498,11 +1766,11 @@ VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. When device memory of certain heap runs out of free space, new allocations may fail (returning error code) or they may succeed, silently pushing some existing memory blocks from GPU VRAM to system RAM (which degrades performance). This -behavior is implementation-dependant - it depends on GPU vendor and graphics +behavior is implementation-dependent - it depends on GPU vendor and graphics driver. On AMD cards it can be controlled while creating Vulkan device object by using -VK_AMD_memory_allocation_behavior extension, if available. +VK_AMD_memory_overallocation_behavior extension, if available. Alternatively, if you want to test how your program behaves with limited amount of Vulkan device memory available without switching your graphics card to one that really has @@ -1556,11 +1824,115 @@ unaware of it. To learn more about this extension, see: -- [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html#VK_KHR_dedicated_allocation) +- [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap44.html#VK_KHR_dedicated_allocation) - [VK_KHR_dedicated_allocation unofficial manual](http://asawicki.info/articles/VK_KHR_dedicated_allocation.php5) +\page vk_amd_device_coherent_memory VK_AMD_device_coherent_memory + +VK_AMD_device_coherent_memory is a device extension that enables access to +additional memory types with `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and +`VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flag. It is useful mostly for +allocation of buffers intended for writing "breadcrumb markers" in between passes +or draw calls, which in turn are useful for debugging GPU crash/hang/TDR cases. + +When the extension is available but has not been enabled, Vulkan physical device +still exposes those memory types, but their usage is forbidden. VMA automatically +takes care of that - it returns `VK_ERROR_FEATURE_NOT_PRESENT` when an attempt +to allocate memory of such type is made. + +If you want to use this extension in connection with VMA, follow these steps: + +\section vk_amd_device_coherent_memory_initialization Initialization + +1) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains "VK_AMD_device_coherent_memory". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true. + +3) While creating device with `vkCreateDevice`, enable this extension - add "VK_AMD_device_coherent_memory" +to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to +`VkPhysicalDeviceFeatures2::pNext` and set its member `deviceCoherentMemory` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this extension and feature - add #VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT +to VmaAllocatorCreateInfo::flags. + +\section vk_amd_device_coherent_memory_usage Usage + +After following steps described above, you can create VMA allocations and custom pools +out of the special `DEVICE_COHERENT` and `DEVICE_UNCACHED` memory types on eligible +devices. There are multiple ways to do it, for example: + +- You can request or prefer to allocate out of such memory types by adding + `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` to VmaAllocationCreateInfo::requiredFlags + or VmaAllocationCreateInfo::preferredFlags. Those flags can be freely mixed with + other ways of \ref choosing_memory_type, like setting VmaAllocationCreateInfo::usage. +- If you manually found memory type index to use for this purpose, force allocation + from this specific index by setting VmaAllocationCreateInfo::memoryTypeBits `= 1u << index`. + +\section vk_amd_device_coherent_memory_more_information More information + +To learn more about this extension, see [VK_AMD_device_coherent_memory in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap44.html#VK_AMD_device_coherent_memory) + +Example use of this extension can be found in the code of the sample and test suite +accompanying this library. + + +\page enabling_buffer_device_address Enabling buffer device address + +Device extension VK_KHR_buffer_device_address +allow to fetch raw GPU pointer to a buffer and pass it for usage in a shader code. +It is promoted to core Vulkan 1.2. + +If you want to use this feature in connection with VMA, follow these steps: + +\section enabling_buffer_device_address_initialization Initialization + +1) (For Vulkan version < 1.2) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains +"VK_KHR_buffer_device_address". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceBufferDeviceAddressFeatures*::bufferDeviceAddress` is true. + +3) (For Vulkan version < 1.2) While creating device with `vkCreateDevice`, enable this extension - add +"VK_KHR_buffer_device_address" to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to +`VkPhysicalDeviceFeatures2::pNext` and set its member `bufferDeviceAddress` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this feature - add #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT +to VmaAllocatorCreateInfo::flags. + +\section enabling_buffer_device_address_usage Usage + +After following steps described above, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*` using VMA. +The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT*` to +allocated memory blocks wherever it might be needed. + +Please note that the library supports only `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*`. +The second part of this functionality related to "capture and replay" is not supported, +as it is intended for usage in debugging tools like RenderDoc, not in everyday Vulkan usage. + +\section enabling_buffer_device_address_more_information More information + +To learn more about this extension, see [VK_KHR_buffer_device_address in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap46.html#VK_KHR_buffer_device_address) + +Example use of this extension can be found in the code of the sample and test suite +accompanying this library. + \page general_considerations General considerations \section general_considerations_thread_safety Thread safety @@ -1619,10 +1991,15 @@ Features deliberately excluded from the scope of this library: - Data transfer. Uploading (straming) and downloading data of buffers and images between CPU and GPU memory and related synchronization is responsibility of the user. + Defining some "texture" object that would automatically stream its data from a + staging copy in CPU memory to GPU memory would rather be a feature of another, + higher-level library implemented on top of VMA. - Allocations for imported/exported external memory. They tend to require explicit memory type index and dedicated allocation anyway, so they don't interact with main features of this library. Such special purpose allocations should be made manually, using `vkCreateBuffer()` and `vkAllocateMemory()`. +- Sub-allocation of parts of one large buffer. Although recommended as a good practice, + it is the user's responsibility to implement such logic on top of VMA. - Recreation of buffers and images. Although the library has functions for buffer and image creation (vmaCreateBuffer(), vmaCreateImage()), you need to recreate these objects yourself after defragmentation. That's because the big @@ -1632,8 +2009,9 @@ Features deliberately excluded from the scope of this library: objects in CPU memory (not Vulkan memory), allocation failures are not checked and handled gracefully, because that would complicate code significantly and is usually not needed in desktop PC applications anyway. + Success of an allocation is just checked with an assert. - Code free of any compiler warnings. Maintaining the library to compile and - work correctly on so many different platforms is hard enough. Being free of + work correctly on so many different platforms is hard enough. Being free of any warnings, on any version of any compiler, is simply not feasible. - This is a C++ library with C interface. Bindings or ports to any other programming languages are welcomed as external projects and @@ -1641,28 +2019,66 @@ Features deliberately excluded from the scope of this library: */ +#ifdef __cplusplus +extern "C" { +#endif + /* Define this macro to 0/1 to disable/enable support for recording functionality, available through VmaAllocatorCreateInfo::pRecordSettings. */ #ifndef VMA_RECORDING_ENABLED - #ifdef _WIN32 - #define VMA_RECORDING_ENABLED 1 - #else - #define VMA_RECORDING_ENABLED 0 - #endif + #define VMA_RECORDING_ENABLED 0 #endif -#ifndef NOMINMAX +#if !defined(NOMINMAX) && defined(VMA_IMPLEMENTATION) #define NOMINMAX // For windows.h #endif +#if defined(__ANDROID__) && defined(VK_NO_PROTOTYPES) && VMA_STATIC_VULKAN_FUNCTIONS + extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; + extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; + extern PFN_vkAllocateMemory vkAllocateMemory; + extern PFN_vkFreeMemory vkFreeMemory; + extern PFN_vkMapMemory vkMapMemory; + extern PFN_vkUnmapMemory vkUnmapMemory; + extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; + extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; + extern PFN_vkBindBufferMemory vkBindBufferMemory; + extern PFN_vkBindImageMemory vkBindImageMemory; + extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; + extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; + extern PFN_vkCreateBuffer vkCreateBuffer; + extern PFN_vkDestroyBuffer vkDestroyBuffer; + extern PFN_vkCreateImage vkCreateImage; + extern PFN_vkDestroyImage vkDestroyImage; + extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer; + #if VMA_VULKAN_VERSION >= 1001000 + extern PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2; + extern PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2; + extern PFN_vkBindBufferMemory2 vkBindBufferMemory2; + extern PFN_vkBindImageMemory2 vkBindImageMemory2; + extern PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2; + #endif // #if VMA_VULKAN_VERSION >= 1001000 +#endif // #if defined(__ANDROID__) && VMA_STATIC_VULKAN_FUNCTIONS && VK_NO_PROTOTYPES + #ifndef VULKAN_H_ #include #endif -#if VMA_RECORDING_ENABLED - #include +// Define this macro to declare maximum supported Vulkan version in format AAABBBCCC, +// where AAA = major, BBB = minor, CCC = patch. +// If you want to use version > 1.0, it still needs to be enabled via VmaAllocatorCreateInfo::vulkanApiVersion. +#if !defined(VMA_VULKAN_VERSION) + #if defined(VK_VERSION_1_2) + #define VMA_VULKAN_VERSION 1002000 + #elif defined(VK_VERSION_1_1) + #define VMA_VULKAN_VERSION 1001000 + #else + #define VMA_VULKAN_VERSION 1000000 + #endif #endif #if !defined(VMA_DEDICATED_ALLOCATION) @@ -1673,6 +2089,105 @@ available through VmaAllocatorCreateInfo::pRecordSettings. #endif #endif +#if !defined(VMA_BIND_MEMORY2) + #if VK_KHR_bind_memory2 + #define VMA_BIND_MEMORY2 1 + #else + #define VMA_BIND_MEMORY2 0 + #endif +#endif + +#if !defined(VMA_MEMORY_BUDGET) + #if VK_EXT_memory_budget && (VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000) + #define VMA_MEMORY_BUDGET 1 + #else + #define VMA_MEMORY_BUDGET 0 + #endif +#endif + +// Defined to 1 when VK_KHR_buffer_device_address device extension or equivalent core Vulkan 1.2 feature is defined in its headers. +#if !defined(VMA_BUFFER_DEVICE_ADDRESS) + #if VK_KHR_buffer_device_address || VMA_VULKAN_VERSION >= 1002000 + #define VMA_BUFFER_DEVICE_ADDRESS 1 + #else + #define VMA_BUFFER_DEVICE_ADDRESS 0 + #endif +#endif + +// Defined to 1 when VK_EXT_memory_priority device extension is defined in Vulkan headers. +#if !defined(VMA_MEMORY_PRIORITY) + #if VK_EXT_memory_priority + #define VMA_MEMORY_PRIORITY 1 + #else + #define VMA_MEMORY_PRIORITY 0 + #endif +#endif + +// Define these macros to decorate all public functions with additional code, +// before and after returned type, appropriately. This may be useful for +// exporting the functions when compiling VMA as a separate library. Example: +// #define VMA_CALL_PRE __declspec(dllexport) +// #define VMA_CALL_POST __cdecl +#ifndef VMA_CALL_PRE + #define VMA_CALL_PRE +#endif +#ifndef VMA_CALL_POST + #define VMA_CALL_POST +#endif + +// Define this macro to decorate pointers with an attribute specifying the +// length of the array they point to if they are not null. +// +// The length may be one of +// - The name of another parameter in the argument list where the pointer is declared +// - The name of another member in the struct where the pointer is declared +// - The name of a member of a struct type, meaning the value of that member in +// the context of the call. For example +// VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount"), +// this means the number of memory heaps available in the device associated +// with the VmaAllocator being dealt with. +#ifndef VMA_LEN_IF_NOT_NULL + #define VMA_LEN_IF_NOT_NULL(len) +#endif + +// The VMA_NULLABLE macro is defined to be _Nullable when compiling with Clang. +// see: https://clang.llvm.org/docs/AttributeReference.html#nullable +#ifndef VMA_NULLABLE + #ifdef __clang__ + #define VMA_NULLABLE _Nullable + #else + #define VMA_NULLABLE + #endif +#endif + +// The VMA_NOT_NULL macro is defined to be _Nonnull when compiling with Clang. +// see: https://clang.llvm.org/docs/AttributeReference.html#nonnull +#ifndef VMA_NOT_NULL + #ifdef __clang__ + #define VMA_NOT_NULL _Nonnull + #else + #define VMA_NOT_NULL + #endif +#endif + +// If non-dispatchable handles are represented as pointers then we can give +// then nullability annotations +#ifndef VMA_NOT_NULL_NON_DISPATCHABLE + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VMA_NOT_NULL_NON_DISPATCHABLE VMA_NOT_NULL + #else + #define VMA_NOT_NULL_NON_DISPATCHABLE + #endif +#endif + +#ifndef VMA_NULLABLE_NON_DISPATCHABLE + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VMA_NULLABLE_NON_DISPATCHABLE VMA_NULLABLE + #else + #define VMA_NULLABLE_NON_DISPATCHABLE + #endif +#endif + /** \struct VmaAllocator \brief Represents main object of this library initialized. @@ -1686,16 +2201,18 @@ VK_DEFINE_HANDLE(VmaAllocator) /// Callback function called after successful vkAllocateMemory. typedef void (VKAPI_PTR *PFN_vmaAllocateDeviceMemoryFunction)( - VmaAllocator allocator, - uint32_t memoryType, - VkDeviceMemory memory, - VkDeviceSize size); + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryType, + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory, + VkDeviceSize size, + void* VMA_NULLABLE pUserData); /// Callback function called before vkFreeMemory. typedef void (VKAPI_PTR *PFN_vmaFreeDeviceMemoryFunction)( - VmaAllocator allocator, - uint32_t memoryType, - VkDeviceMemory memory, - VkDeviceSize size); + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryType, + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory, + VkDeviceSize size, + void* VMA_NULLABLE pUserData); /** \brief Set of callbacks that the library will call for `vkAllocateMemory` and `vkFreeMemory`. @@ -1706,9 +2223,11 @@ Used in VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. */ typedef struct VmaDeviceMemoryCallbacks { /// Optional, can be null. - PFN_vmaAllocateDeviceMemoryFunction pfnAllocate; + PFN_vmaAllocateDeviceMemoryFunction VMA_NULLABLE pfnAllocate; /// Optional, can be null. - PFN_vmaFreeDeviceMemoryFunction pfnFree; + PFN_vmaFreeDeviceMemoryFunction VMA_NULLABLE pfnFree; + /// Optional, can be null. + void* VMA_NULLABLE pUserData; } VmaDeviceMemoryCallbacks; /// Flags for created #VmaAllocator. @@ -1720,6 +2239,9 @@ typedef enum VmaAllocatorCreateFlagBits { VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT = 0x00000001, /** \brief Enables usage of VK_KHR_dedicated_allocation extension. + The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. + When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. + Using this extenion will automatically allocate dedicated blocks of memory for some buffers and images instead of suballocating place for them out of bigger memory blocks (as if you explicitly used #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT @@ -1731,15 +2253,95 @@ typedef enum VmaAllocatorCreateFlagBits { VmaAllocatorCreateInfo::device, and you want them to be used internally by this library: - - VK_KHR_get_memory_requirements2 - - VK_KHR_dedicated_allocation + - VK_KHR_get_memory_requirements2 (device extension) + - VK_KHR_dedicated_allocation (device extension) -When this flag is set, you can experience following warnings reported by Vulkan -validation layer. You can ignore them. + When this flag is set, you can experience following warnings reported by Vulkan + validation layer. You can ignore them. -> vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer. + > vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer. */ VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT = 0x00000002, + /** + Enables usage of VK_KHR_bind_memory2 extension. + + The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. + When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. + + You may set this flag only if you found out that this device extension is supported, + you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + and you want it to be used internally by this library. + + The extension provides functions `vkBindBufferMemory2KHR` and `vkBindImageMemory2KHR`, + which allow to pass a chain of `pNext` structures while binding. + This flag is required if you use `pNext` parameter in vmaBindBufferMemory2() or vmaBindImageMemory2(). + */ + VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT = 0x00000004, + /** + Enables usage of VK_EXT_memory_budget extension. + + You may set this flag only if you found out that this device extension is supported, + you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + and you want it to be used internally by this library, along with another instance extension + VK_KHR_get_physical_device_properties2, which is required by it (or Vulkan 1.1, where this extension is promoted). + + The extension provides query for current memory usage and budget, which will probably + be more accurate than an estimation used by the library otherwise. + */ + VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT = 0x00000008, + /** + Enables usage of VK_AMD_device_coherent_memory extension. + + You may set this flag only if you: + + - found out that this device extension is supported and enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + - checked that `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true and set it while creating the Vulkan device, + - want it to be used internally by this library. + + The extension and accompanying device feature provide access to memory types with + `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and `VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flags. + They are useful mostly for writing breadcrumb markers - a common method for debugging GPU crash/hang/TDR. + + When the extension is not enabled, such memory types are still enumerated, but their usage is illegal. + To protect from this error, if you don't create the allocator with this flag, it will refuse to allocate any memory or create a custom pool in such memory type, + returning `VK_ERROR_FEATURE_NOT_PRESENT`. + */ + VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT = 0x00000010, + /** + Enables usage of "buffer device address" feature, which allows you to use function + `vkGetBufferDeviceAddress*` to get raw GPU pointer to a buffer and pass it for usage inside a shader. + + You may set this flag only if you: + + 1. (For Vulkan version < 1.2) Found as available and enabled device extension + VK_KHR_buffer_device_address. + This extension is promoted to core Vulkan 1.2. + 2. Found as available and enabled device feature `VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress`. + + When this flag is set, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` using VMA. + The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT` to + allocated memory blocks wherever it might be needed. + + For more information, see documentation chapter \ref enabling_buffer_device_address. + */ + VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT = 0x00000020, + /** + Enables usage of VK_EXT_memory_priority extension in the library. + + You may set this flag only if you found available and enabled this device extension, + along with `VkPhysicalDeviceMemoryPriorityFeaturesEXT::memoryPriority == VK_TRUE`, + while creating Vulkan device passed as VmaAllocatorCreateInfo::device. + + When this flag is used, VmaAllocationCreateInfo::priority and VmaPoolCreateInfo::priority + are used to set priorities of allocated Vulkan memory. Without it, these variables are ignored. + + A priority must be a floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations. + Larger values are higher priority. The granularity of the priorities is implementation-dependent. + It is automatically passed to every call to `vkAllocateMemory` done by the library using structure `VkMemoryPriorityAllocateInfoEXT`. + The value to be used for default priority is 0.5. + For more details, see the documentation of the VK_EXT_memory_priority extension. + */ + VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT = 0x00000040, VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaAllocatorCreateFlagBits; @@ -1750,26 +2352,33 @@ typedef VkFlags VmaAllocatorCreateFlags; Used in VmaAllocatorCreateInfo::pVulkanFunctions. */ typedef struct VmaVulkanFunctions { - PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; - PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; - PFN_vkAllocateMemory vkAllocateMemory; - PFN_vkFreeMemory vkFreeMemory; - PFN_vkMapMemory vkMapMemory; - PFN_vkUnmapMemory vkUnmapMemory; - PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; - PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; - PFN_vkBindBufferMemory vkBindBufferMemory; - PFN_vkBindImageMemory vkBindImageMemory; - PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; - PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; - PFN_vkCreateBuffer vkCreateBuffer; - PFN_vkDestroyBuffer vkDestroyBuffer; - PFN_vkCreateImage vkCreateImage; - PFN_vkDestroyImage vkDestroyImage; - PFN_vkCmdCopyBuffer vkCmdCopyBuffer; -#if VMA_DEDICATED_ALLOCATION - PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR; - PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR; + PFN_vkGetPhysicalDeviceProperties VMA_NULLABLE vkGetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceMemoryProperties VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties; + PFN_vkAllocateMemory VMA_NULLABLE vkAllocateMemory; + PFN_vkFreeMemory VMA_NULLABLE vkFreeMemory; + PFN_vkMapMemory VMA_NULLABLE vkMapMemory; + PFN_vkUnmapMemory VMA_NULLABLE vkUnmapMemory; + PFN_vkFlushMappedMemoryRanges VMA_NULLABLE vkFlushMappedMemoryRanges; + PFN_vkInvalidateMappedMemoryRanges VMA_NULLABLE vkInvalidateMappedMemoryRanges; + PFN_vkBindBufferMemory VMA_NULLABLE vkBindBufferMemory; + PFN_vkBindImageMemory VMA_NULLABLE vkBindImageMemory; + PFN_vkGetBufferMemoryRequirements VMA_NULLABLE vkGetBufferMemoryRequirements; + PFN_vkGetImageMemoryRequirements VMA_NULLABLE vkGetImageMemoryRequirements; + PFN_vkCreateBuffer VMA_NULLABLE vkCreateBuffer; + PFN_vkDestroyBuffer VMA_NULLABLE vkDestroyBuffer; + PFN_vkCreateImage VMA_NULLABLE vkCreateImage; + PFN_vkDestroyImage VMA_NULLABLE vkDestroyImage; + PFN_vkCmdCopyBuffer VMA_NULLABLE vkCmdCopyBuffer; +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + PFN_vkGetBufferMemoryRequirements2KHR VMA_NULLABLE vkGetBufferMemoryRequirements2KHR; + PFN_vkGetImageMemoryRequirements2KHR VMA_NULLABLE vkGetImageMemoryRequirements2KHR; +#endif +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + PFN_vkBindBufferMemory2KHR VMA_NULLABLE vkBindBufferMemory2KHR; + PFN_vkBindImageMemory2KHR VMA_NULLABLE vkBindImageMemory2KHR; +#endif +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + PFN_vkGetPhysicalDeviceMemoryProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties2KHR; #endif } VmaVulkanFunctions; @@ -1781,7 +2390,7 @@ typedef enum VmaRecordFlagBits { It may degrade performance though. */ VMA_RECORD_FLUSH_AFTER_CALL_BIT = 0x00000001, - + VMA_RECORD_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaRecordFlagBits; typedef VkFlags VmaRecordFlags; @@ -1798,7 +2407,7 @@ typedef struct VmaRecordSettings It will be opened for the whole time #VmaAllocator object is alive. If opening this file fails, creation of the whole allocator object fails. */ - const char* pFilePath; + const char* VMA_NOT_NULL pFilePath; } VmaRecordSettings; /// Description of a Allocator to be created. @@ -1808,19 +2417,19 @@ typedef struct VmaAllocatorCreateInfo VmaAllocatorCreateFlags flags; /// Vulkan physical device. /** It must be valid throughout whole lifetime of created allocator. */ - VkPhysicalDevice physicalDevice; + VkPhysicalDevice VMA_NOT_NULL physicalDevice; /// Vulkan device. /** It must be valid throughout whole lifetime of created allocator. */ - VkDevice device; + VkDevice VMA_NOT_NULL device; /// Preferred size of a single `VkDeviceMemory` block to be allocated from large heaps > 1 GiB. Optional. /** Set to 0 to use default, which is currently 256 MiB. */ VkDeviceSize preferredLargeHeapBlockSize; /// Custom CPU memory allocation callbacks. Optional. /** Optional, can be null. When specified, will also be used for all CPU-side memory allocations. */ - const VkAllocationCallbacks* pAllocationCallbacks; + const VkAllocationCallbacks* VMA_NULLABLE pAllocationCallbacks; /// Informative callbacks for `vkAllocateMemory`, `vkFreeMemory`. Optional. /** Optional, can be null. */ - const VmaDeviceMemoryCallbacks* pDeviceMemoryCallbacks; + const VmaDeviceMemoryCallbacks* VMA_NULLABLE pDeviceMemoryCallbacks; /** \brief Maximum number of additional frames that are in use at the same time as current frame. This value is used only when you make allocations with @@ -1859,52 +2468,88 @@ typedef struct VmaAllocatorCreateInfo blocks to system RAM. This driver behavior can also be controlled using VK_AMD_memory_overallocation_behavior extension. */ - const VkDeviceSize* pHeapSizeLimit; - /** \brief Pointers to Vulkan functions. Can be null if you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1`. + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount") pHeapSizeLimit; - If you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1` in configuration section, - you can pass null as this member, because the library will fetch pointers to - Vulkan functions internally in a static way, like: + /** \brief Pointers to Vulkan functions. Can be null. - vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; - - Fill this member if you want to provide your own pointers to Vulkan functions, - e.g. fetched using `vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`. + For details see [Pointers to Vulkan functions](@ref config_Vulkan_functions). */ - const VmaVulkanFunctions* pVulkanFunctions; + const VmaVulkanFunctions* VMA_NULLABLE pVulkanFunctions; /** \brief Parameters for recording of VMA calls. Can be null. If not null, it enables recording of calls to VMA functions to a file. If support for recording is not enabled using `VMA_RECORDING_ENABLED` macro, creation of the allocator object fails with `VK_ERROR_FEATURE_NOT_PRESENT`. */ - const VmaRecordSettings* pRecordSettings; + const VmaRecordSettings* VMA_NULLABLE pRecordSettings; + /** \brief Handle to Vulkan instance object. + + Starting from version 3.0.0 this member is no longer optional, it must be set! + */ + VkInstance VMA_NOT_NULL instance; + /** \brief Optional. The highest version of Vulkan that the application is designed to use. + + It must be a value in the format as created by macro `VK_MAKE_VERSION` or a constant like: `VK_API_VERSION_1_1`, `VK_API_VERSION_1_0`. + The patch version number specified is ignored. Only the major and minor versions are considered. + It must be less or equal (preferably equal) to value as passed to `vkCreateInstance` as `VkApplicationInfo::apiVersion`. + Only versions 1.0, 1.1, 1.2 are supported by the current implementation. + Leaving it initialized to zero is equivalent to `VK_API_VERSION_1_0`. + */ + uint32_t vulkanApiVersion; } VmaAllocatorCreateInfo; /// Creates Allocator object. -VkResult vmaCreateAllocator( - const VmaAllocatorCreateInfo* pCreateInfo, - VmaAllocator* pAllocator); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( + const VmaAllocatorCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocator VMA_NULLABLE * VMA_NOT_NULL pAllocator); /// Destroys allocator object. -void vmaDestroyAllocator( - VmaAllocator allocator); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( + VmaAllocator VMA_NULLABLE allocator); + +/** \brief Information about existing #VmaAllocator object. +*/ +typedef struct VmaAllocatorInfo +{ + /** \brief Handle to Vulkan instance object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::instance. + */ + VkInstance VMA_NOT_NULL instance; + /** \brief Handle to Vulkan physical device object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::physicalDevice. + */ + VkPhysicalDevice VMA_NOT_NULL physicalDevice; + /** \brief Handle to Vulkan device object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::device. + */ + VkDevice VMA_NOT_NULL device; +} VmaAllocatorInfo; + +/** \brief Returns information about existing #VmaAllocator object - handle to Vulkan device etc. + +It might be useful if you want to keep just the #VmaAllocator handle and fetch other required handles to +`VkPhysicalDevice`, `VkDevice` etc. every time using this function. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(VmaAllocator VMA_NOT_NULL allocator, VmaAllocatorInfo* VMA_NOT_NULL pAllocatorInfo); /** PhysicalDeviceProperties are fetched from physicalDevice by the allocator. You can access it here, without fetching it again on your own. */ -void vmaGetPhysicalDeviceProperties( - VmaAllocator allocator, - const VkPhysicalDeviceProperties** ppPhysicalDeviceProperties); +VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( + VmaAllocator VMA_NOT_NULL allocator, + const VkPhysicalDeviceProperties* VMA_NULLABLE * VMA_NOT_NULL ppPhysicalDeviceProperties); /** PhysicalDeviceMemoryProperties are fetched from physicalDevice by the allocator. You can access it here, without fetching it again on your own. */ -void vmaGetMemoryProperties( - VmaAllocator allocator, - const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties); +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( + VmaAllocator VMA_NOT_NULL allocator, + const VkPhysicalDeviceMemoryProperties* VMA_NULLABLE * VMA_NOT_NULL ppPhysicalDeviceMemoryProperties); /** \brief Given Memory Type Index, returns Property Flags of this memory type. @@ -1912,10 +2557,10 @@ void vmaGetMemoryProperties( This is just a convenience function. Same information can be obtained using vmaGetMemoryProperties(). */ -void vmaGetMemoryTypeProperties( - VmaAllocator allocator, +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( + VmaAllocator VMA_NOT_NULL allocator, uint32_t memoryTypeIndex, - VkMemoryPropertyFlags* pFlags); + VkMemoryPropertyFlags* VMA_NOT_NULL pFlags); /** \brief Sets index of the current frame. @@ -1925,8 +2570,8 @@ This function must be used if you make allocations with when a new frame begins. Allocations queried using vmaGetAllocationInfo() cannot become lost in the current frame. */ -void vmaSetCurrentFrameIndex( - VmaAllocator allocator, +VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( + VmaAllocator VMA_NOT_NULL allocator, uint32_t frameIndex); /** \brief Calculated statistics of memory usage in entire allocator. @@ -1955,26 +2600,91 @@ typedef struct VmaStats VmaStatInfo total; } VmaStats; -/// Retrieves statistics from current state of the Allocator. -void vmaCalculateStats( - VmaAllocator allocator, - VmaStats* pStats); +/** \brief Retrieves statistics from current state of the Allocator. +This function is called "calculate" not "get" because it has to traverse all +internal data structures, so it may be quite slow. For faster but more brief statistics +suitable to be called every frame or every allocation, use vmaGetBudget(). + +Note that when using allocator from multiple threads, returned information may immediately +become outdated. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats( + VmaAllocator VMA_NOT_NULL allocator, + VmaStats* VMA_NOT_NULL pStats); + +/** \brief Statistics of current memory usage and available budget, in bytes, for specific memory heap. +*/ +typedef struct VmaBudget +{ + /** \brief Sum size of all `VkDeviceMemory` blocks allocated from particular heap, in bytes. + */ + VkDeviceSize blockBytes; + + /** \brief Sum size of all allocations created in particular heap, in bytes. + + Usually less or equal than `blockBytes`. + Difference `blockBytes - allocationBytes` is the amount of memory allocated but unused - + available for new allocations or wasted due to fragmentation. + + It might be greater than `blockBytes` if there are some allocations in lost state, as they account + to this value as well. + */ + VkDeviceSize allocationBytes; + + /** \brief Estimated current memory usage of the program, in bytes. + + Fetched from system using `VK_EXT_memory_budget` extension if enabled. + + It might be different than `blockBytes` (usually higher) due to additional implicit objects + also occupying the memory, like swapchain, pipelines, descriptor heaps, command buffers, or + `VkDeviceMemory` blocks allocated outside of this library, if any. + */ + VkDeviceSize usage; + + /** \brief Estimated amount of memory available to the program, in bytes. + + Fetched from system using `VK_EXT_memory_budget` extension if enabled. + + It might be different (most probably smaller) than `VkMemoryHeap::size[heapIndex]` due to factors + external to the program, like other programs also consuming system resources. + Difference `budget - usage` is the amount of additional memory that can probably + be allocated without problems. Exceeding the budget may result in various problems. + */ + VkDeviceSize budget; +} VmaBudget; + +/** \brief Retrieves information about current memory budget for all memory heaps. + +\param[out] pBudget Must point to array with number of elements at least equal to number of memory heaps in physical device used. + +This function is called "get" not "calculate" because it is very fast, suitable to be called +every frame or every allocation. For more detailed statistics use vmaCalculateStats(). + +Note that when using allocator from multiple threads, returned information may immediately +become outdated. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget( + VmaAllocator VMA_NOT_NULL allocator, + VmaBudget* VMA_NOT_NULL pBudget); + +#ifndef VMA_STATS_STRING_ENABLED #define VMA_STATS_STRING_ENABLED 1 +#endif #if VMA_STATS_STRING_ENABLED /// Builds and returns statistics as string in JSON format. /** @param[out] ppStatsString Must be freed using vmaFreeStatsString() function. */ -void vmaBuildStatsString( - VmaAllocator allocator, - char** ppStatsString, +VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( + VmaAllocator VMA_NOT_NULL allocator, + char* VMA_NULLABLE * VMA_NOT_NULL ppStatsString, VkBool32 detailedMap); -void vmaFreeStatsString( - VmaAllocator allocator, - char* pStatsString); +VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( + VmaAllocator VMA_NOT_NULL allocator, + char* VMA_NULLABLE pStatsString); #endif // #if VMA_STATS_STRING_ENABLED @@ -2000,7 +2710,7 @@ typedef enum VmaMemoryUsage It is roughly equivalent of `D3D12_HEAP_TYPE_DEFAULT`. Usage: - + - Resources written and read by device, e.g. images used as attachments. - Resources transferred from host once (immutable) or infrequently and read by device multiple times, e.g. textures to be sampled, vertex buffers, uniform @@ -2025,7 +2735,7 @@ typedef enum VmaMemoryUsage Memory that is both mappable on host (guarantees to be `HOST_VISIBLE`) and preferably fast to access by GPU. CPU access is typically uncached. Writes may be write-combined. - Usage: Resources written frequently by host (dynamic), read by device. E.g. textures, vertex buffers, uniform buffers updated every frame or every draw call. + Usage: Resources written frequently by host (dynamic), read by device. E.g. textures (with LINEAR layout), vertex buffers, uniform buffers updated every frame or every draw call. */ VMA_MEMORY_USAGE_CPU_TO_GPU = 3, /** Memory mappable on host (guarantees to be `HOST_VISIBLE`) and cached. @@ -2037,34 +2747,49 @@ typedef enum VmaMemoryUsage - Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection. */ VMA_MEMORY_USAGE_GPU_TO_CPU = 4, + /** CPU memory - memory that is preferably not `DEVICE_LOCAL`, but also not guaranteed to be `HOST_VISIBLE`. + + Usage: Staging copy of resources moved from GPU memory to CPU memory as part + of custom paging/residency mechanism, to be moved back to GPU memory when needed. + */ + VMA_MEMORY_USAGE_CPU_COPY = 5, + /** Lazily allocated GPU memory having `VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT`. + Exists mostly on mobile platforms. Using it on desktop PC or other GPUs with no such memory type present will fail the allocation. + + Usage: Memory for transient attachment images (color attachments, depth attachments etc.), created with `VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`. + + Allocations with this usage are always created as dedicated - it implies #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + */ + VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED = 6, + VMA_MEMORY_USAGE_MAX_ENUM = 0x7FFFFFFF } VmaMemoryUsage; /// Flags to be passed as VmaAllocationCreateInfo::flags. typedef enum VmaAllocationCreateFlagBits { /** \brief Set this flag if the allocation should have its own memory block. - + Use it for special, big resources, like fullscreen images used as attachments. - + You should not use this flag if VmaAllocationCreateInfo::pool is not null. */ VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT = 0x00000001, /** \brief Set this flag to only try to allocate from existing `VkDeviceMemory` blocks and never create new such block. - + If new allocation cannot be placed in any of the existing blocks, allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY` error. - + You should not use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT and #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT at the same time. It makes no sense. - + If VmaAllocationCreateInfo::pool is not null, this flag is implied and ignored. */ VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT = 0x00000002, /** \brief Set this flag to use a memory that will be persistently mapped and retrieve pointer to it. - + Pointer to mapped memory will be returned through VmaAllocationInfo::pMappedData. - Is it valid to use this flag for allocation made from memory type that is not + It is valid to use this flag for allocation made from memory type that is not `HOST_VISIBLE`. This flag is then ignored and memory is not mapped. This is useful if you need an allocation that is efficient to use on GPU (`DEVICE_LOCAL`) and still want to map it directly if possible on platforms that @@ -2104,6 +2829,16 @@ typedef enum VmaAllocationCreateFlagBits { This flag is only allowed for custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT flag. */ VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = 0x00000040, + /** Create both buffer/image and allocation, but don't bind them together. + It is useful when you want to bind yourself to do some more advanced binding, e.g. using some extensions. + The flag is meaningful only with functions that bind by default: vmaCreateBuffer(), vmaCreateImage(). + Otherwise it is ignored. + */ + VMA_ALLOCATION_CREATE_DONT_BIND_BIT = 0x00000080, + /** Create allocation only if additional device memory required for it, if any, won't exceed + memory budget. Otherwise return `VK_ERROR_OUT_OF_DEVICE_MEMORY`. + */ + VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT = 0x00000100, /** Allocation strategy that chooses smallest possible free range for the allocation. @@ -2147,19 +2882,19 @@ typedef struct VmaAllocationCreateInfo /// Use #VmaAllocationCreateFlagBits enum. VmaAllocationCreateFlags flags; /** \brief Intended usage of memory. - + You can leave #VMA_MEMORY_USAGE_UNKNOWN if you specify memory requirements in other way. \n If `pool` is not null, this member is ignored. */ VmaMemoryUsage usage; /** \brief Flags that must be set in a Memory Type chosen for an allocation. - + Leave 0 if you specify memory requirements in other way. \n If `pool` is not null, this member is ignored.*/ VkMemoryPropertyFlags requiredFlags; /** \brief Flags that preferably should be set in a memory type chosen for an allocation. - - Set to 0 if no additional flags are prefered. \n + + Set to 0 if no additional flags are preferred. \n If `pool` is not null, this member is ignored. */ VkMemoryPropertyFlags preferredFlags; /** \brief Bitmask containing one bit set for every memory type acceptable for this allocation. @@ -2175,14 +2910,21 @@ typedef struct VmaAllocationCreateInfo Leave `VK_NULL_HANDLE` to allocate from default pool. If not null, members: `usage`, `requiredFlags`, `preferredFlags`, `memoryTypeBits` are ignored. */ - VmaPool pool; + VmaPool VMA_NULLABLE pool; /** \brief Custom general-purpose pointer that will be stored in #VmaAllocation, can be read as VmaAllocationInfo::pUserData and changed using vmaSetAllocationUserData(). - + If #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is used, it must be either null or pointer to a null-terminated string. The string will be then copied to internal buffer, so it doesn't need to be valid after allocation call. */ - void* pUserData; + void* VMA_NULLABLE pUserData; + /** \brief A floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations. + + It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object + and this allocation ends up as dedicated or is explicitly forced as dedicated using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + Otherwise, it has the priority of a memory block where it is placed and this variable is ignored. + */ + float priority; } VmaAllocationCreateInfo; /** @@ -2201,11 +2943,11 @@ device doesn't support any memory type with requested features for the specific type of resource you want to use it for. Please check parameters of your resource, like image layout (OPTIMAL versus LINEAR) or mip level count. */ -VkResult vmaFindMemoryTypeIndex( - VmaAllocator allocator, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( + VmaAllocator VMA_NOT_NULL allocator, uint32_t memoryTypeBits, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - uint32_t* pMemoryTypeIndex); + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); /** \brief Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo. @@ -2219,11 +2961,11 @@ It is just a convenience function, equivalent to calling: - `vmaFindMemoryTypeIndex` - `vkDestroyBuffer` */ -VkResult vmaFindMemoryTypeIndexForBufferInfo( - VmaAllocator allocator, - const VkBufferCreateInfo* pBufferCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - uint32_t* pMemoryTypeIndex); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); /** \brief Helps to find memoryTypeIndex, given VkImageCreateInfo and VmaAllocationCreateInfo. @@ -2237,11 +2979,11 @@ It is just a convenience function, equivalent to calling: - `vmaFindMemoryTypeIndex` - `vkDestroyImage` */ -VkResult vmaFindMemoryTypeIndexForImageInfo( - VmaAllocator allocator, - const VkImageCreateInfo* pImageCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - uint32_t* pMemoryTypeIndex); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( + VmaAllocator VMA_NOT_NULL allocator, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); /// Flags to be passed as VmaPoolCreateInfo::flags. typedef enum VmaPoolCreateFlagBits { @@ -2328,7 +3070,7 @@ typedef struct VmaPoolCreateInfo { /** \brief Maximum number of blocks that can be allocated in this pool. Optional. Set to 0 to use default, which is `SIZE_MAX`, which means no limit. - + Set to same value as VmaPoolCreateInfo::minBlockCount to have fixed amount of memory allocated throughout whole lifetime of this pool. */ @@ -2347,6 +3089,12 @@ typedef struct VmaPoolCreateInfo { become lost, set this value to 0. */ uint32_t frameInUseCount; + /** \brief A floating-point value between 0 and 1, indicating the priority of the allocations in this pool relative to other memory allocations. + + It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object. + Otherwise, this variable is ignored. + */ + float priority; } VmaPoolCreateInfo; /** \brief Describes parameter of existing #VmaPool. @@ -2382,16 +3130,16 @@ typedef struct VmaPoolStats { @param pCreateInfo Parameters of pool to create. @param[out] pPool Handle to created pool. */ -VkResult vmaCreatePool( - VmaAllocator allocator, - const VmaPoolCreateInfo* pCreateInfo, - VmaPool* pPool); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( + VmaAllocator VMA_NOT_NULL allocator, + const VmaPoolCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaPool VMA_NULLABLE * VMA_NOT_NULL pPool); /** \brief Destroys #VmaPool object and frees Vulkan device memory. */ -void vmaDestroyPool( - VmaAllocator allocator, - VmaPool pool); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NULLABLE pool); /** \brief Retrieves statistics of existing #VmaPool object. @@ -2399,10 +3147,10 @@ void vmaDestroyPool( @param pool Pool object. @param[out] pPoolStats Statistics of specified pool. */ -void vmaGetPoolStats( - VmaAllocator allocator, - VmaPool pool, - VmaPoolStats* pPoolStats); +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + VmaPoolStats* VMA_NOT_NULL pPoolStats); /** \brief Marks all allocations in given pool as lost if they are not used in current frame or VmaPoolCreateInfo::frameInUseCount back from now. @@ -2410,10 +3158,10 @@ void vmaGetPoolStats( @param pool Pool. @param[out] pLostAllocationCount Number of allocations marked as lost. Optional - pass null if you don't need this information. */ -void vmaMakePoolAllocationsLost( - VmaAllocator allocator, - VmaPool pool, - size_t* pLostAllocationCount); +VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + size_t* VMA_NULLABLE pLostAllocationCount); /** \brief Checks magic number in margins around all allocations in given memory pool in search for corruptions. @@ -2429,7 +3177,28 @@ Possible return values: `VMA_ASSERT` is also fired in that case. - Other value: Error returned by Vulkan, e.g. memory mapping failure. */ -VkResult vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator VMA_NOT_NULL allocator, VmaPool VMA_NOT_NULL pool); + +/** \brief Retrieves name of a custom pool. + +After the call `ppName` is either null or points to an internally-owned null-terminated string +containing name of the pool that was previously set. The pointer becomes invalid when the pool is +destroyed or its name is changed using vmaSetPoolName(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + const char* VMA_NULLABLE * VMA_NOT_NULL ppName); + +/** \brief Sets name of a custom pool. + +`pName` can be either null or pointer to a null-terminated string with new name for the pool. +Function makes internal copy of the string, so it can be changed or freed immediately after this call. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + const char* VMA_NULLABLE pName); /** \struct VmaAllocation \brief Represents single memory allocation. @@ -2461,20 +3230,25 @@ VK_DEFINE_HANDLE(VmaAllocation) */ typedef struct VmaAllocationInfo { /** \brief Memory type index that this allocation was allocated from. - + It never changes. */ uint32_t memoryType; /** \brief Handle to Vulkan memory object. Same memory object can be shared by multiple allocations. - + It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. If the allocation is lost, it is equal to `VK_NULL_HANDLE`. */ - VkDeviceMemory deviceMemory; - /** \brief Offset into deviceMemory object to the beginning of this allocation, in bytes. (deviceMemory, offset) pair is unique to this allocation. + VkDeviceMemory VMA_NULLABLE_NON_DISPATCHABLE deviceMemory; + /** \brief Offset in `VkDeviceMemory` object to the beginning of this allocation, in bytes. `(deviceMemory, offset)` pair is unique to this allocation. + + You usually don't need to use this offset. If you create a buffer or an image together with the allocation using e.g. function + vmaCreateBuffer(), vmaCreateImage(), functions that operate on these resources refer to the beginning of the buffer or image, + not entire device memory block. Functions like vmaMapMemory(), vmaBindBufferMemory() also refer to the beginning of the allocation + and apply this offset automatically. It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. */ @@ -2482,22 +3256,28 @@ typedef struct VmaAllocationInfo { /** \brief Size of this allocation, in bytes. It never changes, unless allocation is lost. + + \note Allocation size returned in this variable may be greater than the size + requested for the resource e.g. as `VkBufferCreateInfo::size`. Whole size of the + allocation is accessible for operations on memory e.g. using a pointer after + mapping with vmaMapMemory(), but operations on the resource e.g. using + `vkCmdCopyBuffer` must be limited to the size of the resource. */ VkDeviceSize size; /** \brief Pointer to the beginning of this allocation as mapped data. If the allocation hasn't been mapped using vmaMapMemory() and hasn't been - created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value null. + created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value is null. It can change after call to vmaMapMemory(), vmaUnmapMemory(). It can also change after call to vmaDefragment() if this allocation is passed to the function. */ - void* pMappedData; + void* VMA_NULLABLE pMappedData; /** \brief Custom general-purpose pointer that was passed as VmaAllocationCreateInfo::pUserData or set using vmaSetAllocationUserData(). It can change after call to vmaSetAllocationUserData() for this allocation. */ - void* pUserData; + void* VMA_NULLABLE pUserData; } VmaAllocationInfo; /** \brief General purpose memory allocation. @@ -2510,12 +3290,12 @@ You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(), vmaCreateBuffer(), vmaCreateImage() instead whenever possible. */ -VkResult vmaAllocateMemory( - VmaAllocator allocator, - const VkMemoryRequirements* pVkMemoryRequirements, - const VmaAllocationCreateInfo* pCreateInfo, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( + VmaAllocator VMA_NOT_NULL allocator, + const VkMemoryRequirements* VMA_NOT_NULL pVkMemoryRequirements, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief General purpose memory allocation for multiple allocation objects at once. @@ -2536,13 +3316,13 @@ All allocations are made using same parameters. All of them are created out of t If any allocation fails, all allocations already made within this function call are also freed, so that when returned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`. */ -VkResult vmaAllocateMemoryPages( - VmaAllocator allocator, - const VkMemoryRequirements* pVkMemoryRequirements, - const VmaAllocationCreateInfo* pCreateInfo, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( + VmaAllocator VMA_NOT_NULL allocator, + const VkMemoryRequirements* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pVkMemoryRequirements, + const VmaAllocationCreateInfo* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pCreateInfo, size_t allocationCount, - VmaAllocation* pAllocations, - VmaAllocationInfo* pAllocationInfo); + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations, + VmaAllocationInfo* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationInfo); /** @param[out] pAllocation Handle to allocated memory. @@ -2550,28 +3330,28 @@ VkResult vmaAllocateMemoryPages( You should free the memory using vmaFreeMemory(). */ -VkResult vmaAllocateMemoryForBuffer( - VmaAllocator allocator, - VkBuffer buffer, - const VmaAllocationCreateInfo* pCreateInfo, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /// Function similar to vmaAllocateMemoryForBuffer(). -VkResult vmaAllocateMemoryForImage( - VmaAllocator allocator, - VkImage image, - const VmaAllocationCreateInfo* pCreateInfo, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). Passing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped. */ -void vmaFreeMemory( - VmaAllocator allocator, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( + VmaAllocator VMA_NOT_NULL allocator, + const VmaAllocation VMA_NULLABLE allocation); /** \brief Frees memory and destroys multiple allocations. @@ -2583,39 +3363,26 @@ It may be internally optimized to be more efficient than calling vmaFreeMemory() Allocations in `pAllocations` array can come from any memory pools and types. Passing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped. */ -void vmaFreeMemoryPages( - VmaAllocator allocator, +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( + VmaAllocator VMA_NOT_NULL allocator, size_t allocationCount, - VmaAllocation* pAllocations); + const VmaAllocation VMA_NULLABLE * VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations); -/** \brief Tries to resize an allocation in place, if there is enough free memory after it. +/** \brief Deprecated. -Tries to change allocation's size without moving or reallocating it. -You can both shrink and grow allocation size. -When growing, it succeeds only when the allocation belongs to a memory block with enough -free space after it. - -Returns `VK_SUCCESS` if allocation's size has been successfully changed. -Returns `VK_ERROR_OUT_OF_POOL_MEMORY` if allocation's size could not be changed. - -After successful call to this function, VmaAllocationInfo::size of this allocation changes. -All other parameters stay the same: memory pool and type, alignment, offset, mapped pointer. - -- Calling this function on allocation that is in lost state fails with result `VK_ERROR_VALIDATION_FAILED_EXT`. -- Calling this function with `newSize` same as current allocation size does nothing and returns `VK_SUCCESS`. -- Resizing dedicated allocations, as well as allocations created in pools that use linear - or buddy algorithm, is not supported. - The function returns `VK_ERROR_FEATURE_NOT_PRESENT` in such cases. - Support may be added in the future. +\deprecated +In version 2.2.0 it used to try to change allocation's size without moving or reallocating it. +In current version it returns `VK_SUCCESS` only if `newSize` equals current allocation's size. +Otherwise returns `VK_ERROR_OUT_OF_POOL_MEMORY`, indicating that allocation's size could not be changed. */ -VkResult vmaResizeAllocation( - VmaAllocator allocator, - VmaAllocation allocation, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, VkDeviceSize newSize); /** \brief Returns current information about specified allocation and atomically marks it as used in current frame. -Current paramters of given allocation are returned in `pAllocationInfo`. +Current paramteres of given allocation are returned in `pAllocationInfo`. This function also atomically "touches" allocation - marks it as used in current frame, just like vmaTouchAllocation(). @@ -2629,10 +3396,10 @@ you can avoid calling it too often. (e.g. due to defragmentation or allocation becoming lost). - If you just want to check if allocation is not lost, vmaTouchAllocation() will work faster. */ -void vmaGetAllocationInfo( - VmaAllocator allocator, - VmaAllocation allocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VmaAllocationInfo* VMA_NOT_NULL pAllocationInfo); /** \brief Returns `VK_TRUE` if allocation is not lost and atomically marks it as used in current frame. @@ -2648,9 +3415,9 @@ Lost allocation and the buffer/image still need to be destroyed. If the allocation has been created without #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function always returns `VK_TRUE`. */ -VkBool32 vmaTouchAllocation( - VmaAllocator allocator, - VmaAllocation allocation); +VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation); /** \brief Sets pUserData in given allocation to new value. @@ -2665,10 +3432,10 @@ If the flag was not used, the value of pointer `pUserData` is just copied to allocation's `pUserData`. It is opaque, so you can use it however you want - e.g. as a pointer, ordinal number or some handle to you own data. */ -void vmaSetAllocationUserData( - VmaAllocator allocator, - VmaAllocation allocation, - void* pUserData); +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + void* VMA_NULLABLE pUserData); /** \brief Creates new allocation that is in lost state from the beginning. @@ -2680,9 +3447,9 @@ Returned allocation is not tied to any specific memory pool or memory type and not bound to any image or buffer. It has size = 0. It cannot be turned into a real, non-empty allocation. */ -void vmaCreateLostAllocation( - VmaAllocator allocator, - VmaAllocation* pAllocation); +VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation); /** \brief Maps memory represented by given allocation and returns pointer to it. @@ -2717,23 +3484,33 @@ This function fails when used on allocation made in memory type that is not This function always fails when called for allocation that was created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocations cannot be mapped. + +This function doesn't automatically flush or invalidate caches. +If the allocation is made from a memory types that is not `HOST_COHERENT`, +you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. */ -VkResult vmaMapMemory( - VmaAllocator allocator, - VmaAllocation allocation, - void** ppData); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + void* VMA_NULLABLE * VMA_NOT_NULL ppData); /** \brief Unmaps memory represented by given allocation, mapped previously using vmaMapMemory(). For details, see description of vmaMapMemory(). + +This function doesn't automatically flush or invalidate caches. +If the allocation is made from a memory types that is not `HOST_COHERENT`, +you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. */ -void vmaUnmapMemory( - VmaAllocator allocator, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation); /** \brief Flushes memory of given allocation. Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of given allocation. +It needs to be called after writing to a mapped memory for memory types that are not `HOST_COHERENT`. +Unmap operation doesn't do that automatically. - `offset` must be relative to the beginning of allocation. - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. @@ -2742,12 +3519,25 @@ Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of gi - If `size` is 0, this call is ignored. - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, this call is ignored. + +Warning! `offset` and `size` are relative to the contents of given `allocation`. +If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. +Do not pass allocation's offset as `offset`!!! + +This function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. */ -void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize offset, + VkDeviceSize size); /** \brief Invalidates memory of given allocation. Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range of given allocation. +It needs to be called before reading from a mapped memory for memory types that are not `HOST_COHERENT`. +Map operation doesn't do that automatically. - `offset` must be relative to the beginning of allocation. - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. @@ -2756,8 +3546,61 @@ Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range - If `size` is 0, this call is ignored. - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, this call is ignored. + +Warning! `offset` and `size` are relative to the contents of given `allocation`. +If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. +Do not pass allocation's offset as `offset`!!! + +This function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if +it is called, otherwise `VK_SUCCESS`. */ -void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize offset, + VkDeviceSize size); + +/** \brief Flushes memory of given set of allocations. + +Calls `vkFlushMappedMemoryRanges()` for memory associated with given ranges of given allocations. +For more information, see documentation of vmaFlushAllocation(). + +\param allocator +\param allocationCount +\param allocations +\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all ofsets are zero. +\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations. + +This function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t allocationCount, + const VmaAllocation VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes); + +/** \brief Invalidates memory of given set of allocations. + +Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given ranges of given allocations. +For more information, see documentation of vmaInvalidateAllocation(). + +\param allocator +\param allocationCount +\param allocations +\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all ofsets are zero. +\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations. + +This function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t allocationCount, + const VmaAllocation VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes); /** \brief Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions. @@ -2775,7 +3618,7 @@ Possible return values: `VMA_ASSERT` is also fired in that case. - Other value: Error returned by Vulkan, e.g. memory mapping failure. */ -VkResult vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator VMA_NOT_NULL allocator, uint32_t memoryTypeBits); /** \struct VmaDefragmentationContext \brief Represents Opaque object that represents started defragmentation process. @@ -2787,6 +3630,7 @@ VK_DEFINE_HANDLE(VmaDefragmentationContext) /// Flags to be used in vmaDefragmentationBegin(). None at the moment. Reserved for future use. typedef enum VmaDefragmentationFlagBits { + VMA_DEFRAGMENTATION_FLAG_INCREMENTAL = 0x1, VMA_DEFRAGMENTATION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaDefragmentationFlagBits; typedef VkFlags VmaDefragmentationFlags; @@ -2810,13 +3654,13 @@ typedef struct VmaDefragmentationInfo2 { It is safe to pass allocations that are in the lost state - they are ignored. All allocations not present in this array are considered non-moveable during this defragmentation. */ - VmaAllocation* pAllocations; + const VmaAllocation VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations; /** \brief Optional, output. Pointer to array that will be filled with information whether the allocation at certain index has been changed during defragmentation. The array should have `allocationCount` elements. You can pass null if you are not interested in this information. */ - VkBool32* pAllocationsChanged; + VkBool32* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationsChanged; /** \brief Numer of pools in `pPools` array. */ uint32_t poolCount; @@ -2835,9 +3679,9 @@ typedef struct VmaDefragmentationInfo2 { Using this array is equivalent to specifying all allocations from the pools in `pAllocations`. It might be more efficient. */ - VmaPool* pPools; + const VmaPool VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(poolCount) pPools; /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on CPU side, like `memcpy()`, `memmove()`. - + `VK_WHOLE_SIZE` means no limit. */ VkDeviceSize maxCpuBytesToMove; @@ -2847,7 +3691,7 @@ typedef struct VmaDefragmentationInfo2 { */ uint32_t maxCpuAllocationsToMove; /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on GPU side, posted to `commandBuffer`. - + `VK_WHOLE_SIZE` means no limit. */ VkDeviceSize maxGpuBytesToMove; @@ -2864,16 +3708,31 @@ typedef struct VmaDefragmentationInfo2 { Passing null means that only CPU defragmentation will be performed. */ - VkCommandBuffer commandBuffer; + VkCommandBuffer VMA_NULLABLE commandBuffer; } VmaDefragmentationInfo2; +typedef struct VmaDefragmentationPassMoveInfo { + VmaAllocation VMA_NOT_NULL allocation; + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory; + VkDeviceSize offset; +} VmaDefragmentationPassMoveInfo; + +/** \brief Parameters for incremental defragmentation steps. + +To be used with function vmaBeginDefragmentationPass(). +*/ +typedef struct VmaDefragmentationPassInfo { + uint32_t moveCount; + VmaDefragmentationPassMoveInfo* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(moveCount) pMoves; +} VmaDefragmentationPassInfo; + /** \brief Deprecated. Optional configuration parameters to be passed to function vmaDefragment(). \deprecated This is a part of the old interface. It is recommended to use structure #VmaDefragmentationInfo2 and function vmaDefragmentationBegin() instead. */ typedef struct VmaDefragmentationInfo { /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places. - + Default is `VK_WHOLE_SIZE`, which means no limit. */ VkDeviceSize maxBytesToMove; @@ -2921,21 +3780,34 @@ Warning! Between the call to vmaDefragmentationBegin() and vmaDefragmentationEnd They become valid after call to vmaDefragmentationEnd(). - If `pInfo->commandBuffer` is not null, you must submit that command buffer and make sure it finished execution before calling vmaDefragmentationEnd(). + +For more information and important limitations regarding defragmentation, see documentation chapter: +[Defragmentation](@ref defragmentation). */ -VkResult vmaDefragmentationBegin( - VmaAllocator allocator, - const VmaDefragmentationInfo2* pInfo, - VmaDefragmentationStats* pStats, - VmaDefragmentationContext *pContext); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin( + VmaAllocator VMA_NOT_NULL allocator, + const VmaDefragmentationInfo2* VMA_NOT_NULL pInfo, + VmaDefragmentationStats* VMA_NULLABLE pStats, + VmaDefragmentationContext VMA_NULLABLE * VMA_NOT_NULL pContext); /** \brief Ends defragmentation process. Use this function to finish defragmentation started by vmaDefragmentationBegin(). It is safe to pass `context == null`. The function then does nothing. */ -VkResult vmaDefragmentationEnd( - VmaAllocator allocator, - VmaDefragmentationContext context); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NULLABLE context); + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NULLABLE context, + VmaDefragmentationPassInfo* VMA_NOT_NULL pInfo +); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NULLABLE context +); /** \brief Deprecated. Compacts memory by moving allocations. @@ -2977,13 +3849,13 @@ you should measure that on your platform. For more information, see [Defragmentation](@ref defragmentation) chapter. */ -VkResult vmaDefragment( - VmaAllocator allocator, - VmaAllocation* pAllocations, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment( + VmaAllocator VMA_NOT_NULL allocator, + const VmaAllocation VMA_NOT_NULL * VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations, size_t allocationCount, - VkBool32* pAllocationsChanged, - const VmaDefragmentationInfo *pDefragmentationInfo, - VmaDefragmentationStats* pDefragmentationStats); + VkBool32* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationsChanged, + const VmaDefragmentationInfo* VMA_NULLABLE pDefragmentationInfo, + VmaDefragmentationStats* VMA_NULLABLE pDefragmentationStats); /** \brief Binds buffer to allocation. @@ -2997,10 +3869,27 @@ allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from mul It is recommended to use function vmaCreateBuffer() instead of this one. */ -VkResult vmaBindBufferMemory( - VmaAllocator allocator, - VmaAllocation allocation, - VkBuffer buffer); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer); + +/** \brief Binds buffer to allocation with additional parameters. + +@param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0. +@param pNext A chain of structures to be attached to `VkBindBufferMemoryInfoKHR` structure used internally. Normally it should be null. + +This function is similar to vmaBindBufferMemory(), but it provides additional parameters. + +If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag +or with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer, + const void* VMA_NULLABLE pNext); /** \brief Binds image to allocation. @@ -3014,10 +3903,27 @@ allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from mul It is recommended to use function vmaCreateImage() instead of this one. */ -VkResult vmaBindImageMemory( - VmaAllocator allocator, - VmaAllocation allocation, - VkImage image); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image); + +/** \brief Binds image to allocation with additional parameters. + +@param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0. +@param pNext A chain of structures to be attached to `VkBindImageMemoryInfoKHR` structure used internally. Normally it should be null. + +This function is similar to vmaBindImageMemory(), but it provides additional parameters. + +If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag +or with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image, + const void* VMA_NULLABLE pNext); /** @param[out] pBuffer Buffer that was created. @@ -3037,21 +3943,25 @@ If the function succeeded, you must destroy both buffer and allocation when you no longer need them using either convenience function vmaDestroyBuffer() or separately, using `vkDestroyBuffer()` and vmaFreeMemory(). -If VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used, +If #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used, VK_KHR_dedicated_allocation extension is used internally to query driver whether it requires or prefers the new buffer to have dedicated allocation. If yes, and if dedicated allocation is possible (VmaAllocationCreateInfo::pool is null -and VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated +and #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated allocation for this buffer, just like when using -VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. +#VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + +\note This function creates a new `VkBuffer`. Sub-allocation of parts of one large buffer, +although recommended as a good practice, is out of scope of this library and could be implemented +by the user as a higher-level logic on top of VMA. */ -VkResult vmaCreateBuffer( - VmaAllocator allocator, - const VkBufferCreateInfo* pBufferCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - VkBuffer* pBuffer, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE * VMA_NOT_NULL pBuffer, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief Destroys Vulkan buffer and frees allocated memory. @@ -3064,19 +3974,19 @@ vmaFreeMemory(allocator, allocation); It it safe to pass null as buffer and/or allocation. */ -void vmaDestroyBuffer( - VmaAllocator allocator, - VkBuffer buffer, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE buffer, + VmaAllocation VMA_NULLABLE allocation); /// Function similar to vmaCreateBuffer(). -VkResult vmaCreateImage( - VmaAllocator allocator, - const VkImageCreateInfo* pImageCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - VkImage* pImage, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( + VmaAllocator VMA_NOT_NULL allocator, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE * VMA_NOT_NULL pImage, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief Destroys Vulkan image and frees allocated memory. @@ -3089,10 +3999,10 @@ vmaFreeMemory(allocator, allocation); It it safe to pass null as image and/or allocation. */ -void vmaDestroyImage( - VmaAllocator allocator, - VkImage image, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NULLABLE_NON_DISPATCHABLE image, + VmaAllocation VMA_NULLABLE allocation); #ifdef __cplusplus } @@ -3111,6 +4021,17 @@ void vmaDestroyImage( #include #include #include +#include + +#if VMA_RECORDING_ENABLED + #include + #if defined(_WIN32) + #include + #else + #include + #include + #endif +#endif /******************************************************************************* CONFIGURATION SECTION @@ -3124,12 +4045,23 @@ Define this macro to 1 to make the library fetch pointers to Vulkan functions internally, like: vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; - -Define to 0 if you are going to provide you own pointers to Vulkan functions via -VmaAllocatorCreateInfo::pVulkanFunctions. */ #if !defined(VMA_STATIC_VULKAN_FUNCTIONS) && !defined(VK_NO_PROTOTYPES) -#define VMA_STATIC_VULKAN_FUNCTIONS 1 + #define VMA_STATIC_VULKAN_FUNCTIONS 1 +#endif + +/* +Define this macro to 1 to make the library fetch pointers to Vulkan functions +internally, like: + + vulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkGetDeviceProcAddr(m_hDevice, vkAllocateMemory); +*/ +#if !defined(VMA_DYNAMIC_VULKAN_FUNCTIONS) + #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 + #if defined(VK_NO_PROTOTYPES) + extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + #endif #endif // Define this macro to 1 to make the library use STL containers instead of its own implementation. @@ -3161,6 +4093,10 @@ the containers. #endif #endif +/* +THESE INCLUDES ARE NOT ENABLED BY DEFAULT. +Library has its own container implementation. +*/ #if VMA_USE_STL_VECTOR #include #endif @@ -3180,7 +4116,6 @@ remove them if not needed. #include // for assert #include // for min, max #include -#include // for std::atomic #ifndef VMA_NULL // Value used as null pointer. Define it to e.g.: nullptr, NULL, 0, (void*)0. @@ -3189,7 +4124,7 @@ remove them if not needed. #if defined(__ANDROID_API__) && (__ANDROID_API__ < 16) #include -void *aligned_alloc(size_t alignment, size_t size) +static void* vma_aligned_alloc(size_t alignment, size_t size) { // alignment must be >= sizeof(void*) if(alignment < sizeof(void*)) @@ -3199,10 +4134,27 @@ void *aligned_alloc(size_t alignment, size_t size) return memalign(alignment, size); } -#elif defined(__APPLE__) || defined(__ANDROID__) +#elif defined(__APPLE__) || defined(__ANDROID__) || (defined(__linux__) && defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC)) #include -void *aligned_alloc(size_t alignment, size_t size) + +#if defined(__APPLE__) +#include +#endif + +static void* vma_aligned_alloc(size_t alignment, size_t size) { +#if defined(__APPLE__) && (defined(MAC_OS_X_VERSION_10_16) || defined(__IPHONE_14_0)) +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_16 || __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + // For C++14, usr/include/malloc/_malloc.h declares aligned_alloc()) only + // with the MacOSX11.0 SDK in Xcode 12 (which is what adds + // MAC_OS_X_VERSION_10_16), even though the function is marked + // availabe for 10.15. That's why the preprocessor checks for 10.16 but + // the __builtin_available checks for 10.15. + // People who use C++17 could call aligned_alloc with the 10.15 SDK already. + if (__builtin_available(macOS 10.15, iOS 13, *)) + return aligned_alloc(alignment, size); +#endif +#endif // alignment must be >= sizeof(void*) if(alignment < sizeof(void*)) { @@ -3214,6 +4166,28 @@ void *aligned_alloc(size_t alignment, size_t size) return pointer; return VMA_NULL; } +#elif defined(_WIN32) +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + return _aligned_malloc(size, alignment); +} +#else +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + return aligned_alloc(alignment, size); +} +#endif + +#if defined(_WIN32) +static void vma_aligned_free(void* ptr) +{ + _aligned_free(ptr); +} +#else +static void vma_aligned_free(void* ptr) +{ + free(ptr); +} #endif // If your compiler is not compatible with C++11 and definition of @@ -3223,20 +4197,20 @@ void *aligned_alloc(size_t alignment, size_t size) // Normal assert to check for programmer's errors, especially in Debug configuration. #ifndef VMA_ASSERT - #ifdef _DEBUG - #define VMA_ASSERT(expr) assert(expr) - #else + #ifdef NDEBUG #define VMA_ASSERT(expr) + #else + #define VMA_ASSERT(expr) assert(expr) #endif #endif // Assert that will be called very often, like inside data structures e.g. operator[]. // Making it non-empty can make program slow. #ifndef VMA_HEAVY_ASSERT - #ifdef _DEBUG - #define VMA_HEAVY_ASSERT(expr) //VMA_ASSERT(expr) - #else + #ifdef NDEBUG #define VMA_HEAVY_ASSERT(expr) + #else + #define VMA_HEAVY_ASSERT(expr) //VMA_ASSERT(expr) #endif #endif @@ -3245,19 +4219,16 @@ void *aligned_alloc(size_t alignment, size_t size) #endif #ifndef VMA_SYSTEM_ALIGNED_MALLOC - #if defined(_WIN32) - #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (_aligned_malloc((size), (alignment))) - #else - #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (aligned_alloc((alignment), (size) )) - #endif + #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) vma_aligned_alloc((alignment), (size)) #endif -#ifndef VMA_SYSTEM_FREE - #if defined(_WIN32) - #define VMA_SYSTEM_FREE(ptr) _aligned_free(ptr) +#ifndef VMA_SYSTEM_ALIGNED_FREE + // VMA_SYSTEM_FREE is the old name, but might have been defined by the user + #if defined(VMA_SYSTEM_FREE) + #define VMA_SYSTEM_ALIGNED_FREE(ptr) VMA_SYSTEM_FREE(ptr) #else - #define VMA_SYSTEM_FREE(ptr) free(ptr) - #endif + #define VMA_SYSTEM_ALIGNED_FREE(ptr) vma_aligned_free(ptr) + #endif #endif #ifndef VMA_MIN @@ -3308,6 +4279,7 @@ void *aligned_alloc(size_t alignment, size_t size) public: void Lock() { m_Mutex.lock(); } void Unlock() { m_Mutex.unlock(); } + bool TryLock() { return m_Mutex.try_lock(); } private: std::mutex m_Mutex; }; @@ -3324,22 +4296,27 @@ void *aligned_alloc(size_t alignment, size_t size) public: void LockRead() { m_Mutex.lock_shared(); } void UnlockRead() { m_Mutex.unlock_shared(); } + bool TryLockRead() { return m_Mutex.try_lock_shared(); } void LockWrite() { m_Mutex.lock(); } void UnlockWrite() { m_Mutex.unlock(); } + bool TryLockWrite() { return m_Mutex.try_lock(); } private: std::shared_mutex m_Mutex; }; #define VMA_RW_MUTEX VmaRWMutex - #elif defined(_WIN32) + #elif defined(_WIN32) && defined(WINVER) && WINVER >= 0x0600 // Use SRWLOCK from WinAPI. + // Minimum supported client = Windows Vista, server = Windows Server 2008. class VmaRWMutex { public: VmaRWMutex() { InitializeSRWLock(&m_Lock); } void LockRead() { AcquireSRWLockShared(&m_Lock); } void UnlockRead() { ReleaseSRWLockShared(&m_Lock); } + bool TryLockRead() { return TryAcquireSRWLockShared(&m_Lock) != FALSE; } void LockWrite() { AcquireSRWLockExclusive(&m_Lock); } void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); } + bool TryLockWrite() { return TryAcquireSRWLockExclusive(&m_Lock) != FALSE; } private: SRWLOCK m_Lock; }; @@ -3351,8 +4328,10 @@ void *aligned_alloc(size_t alignment, size_t size) public: void LockRead() { m_Mutex.Lock(); } void UnlockRead() { m_Mutex.Unlock(); } + bool TryLockRead() { return m_Mutex.TryLock(); } void LockWrite() { m_Mutex.Lock(); } void UnlockWrite() { m_Mutex.Unlock(); } + bool TryLockWrite() { return m_Mutex.TryLock(); } private: VMA_MUTEX m_Mutex; }; @@ -3361,15 +4340,16 @@ void *aligned_alloc(size_t alignment, size_t size) #endif // #ifndef VMA_RW_MUTEX /* -If providing your own implementation, you need to implement a subset of std::atomic: - -- Constructor(uint32_t desired) -- uint32_t load() const -- void store(uint32_t desired) -- bool compare_exchange_weak(uint32_t& expected, uint32_t desired) +If providing your own implementation, you need to implement a subset of std::atomic. */ #ifndef VMA_ATOMIC_UINT32 - #define VMA_ATOMIC_UINT32 std::atomic + #include + #define VMA_ATOMIC_UINT32 std::atomic +#endif + +#ifndef VMA_ATOMIC_UINT64 + #include + #define VMA_ATOMIC_UINT64 std::atomic #endif #ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY @@ -3458,6 +4438,12 @@ static const uint8_t VMA_ALLOCATION_FILL_PATTERN_DESTROYED = 0xEF; END OF CONFIGURATION */ +// # Copy of some Vulkan definitions so we don't need to check their existence just to handle few constants. + +static const uint32_t VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY = 0x00000040; +static const uint32_t VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY = 0x00000080; +static const uint32_t VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY = 0x00020000; + static const uint32_t VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET = 0x10000000u; static VkAllocationCallbacks VmaEmptyAllocationCallbacks = { @@ -3466,34 +4452,12 @@ static VkAllocationCallbacks VmaEmptyAllocationCallbacks = { // Returns number of bits set to 1 in (v). static inline uint32_t VmaCountBitsSet(uint32_t v) { - uint32_t c = v - ((v >> 1) & 0x55555555); - c = ((c >> 2) & 0x33333333) + (c & 0x33333333); - c = ((c >> 4) + c) & 0x0F0F0F0F; - c = ((c >> 8) + c) & 0x00FF00FF; - c = ((c >> 16) + c) & 0x0000FFFF; - return c; -} - -// Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16. -// Use types like uint32_t, uint64_t as T. -template -static inline T VmaAlignUp(T val, T align) -{ - return (val + align - 1) / align * align; -} -// Aligns given value down to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 8. -// Use types like uint32_t, uint64_t as T. -template -static inline T VmaAlignDown(T val, T align) -{ - return val / align * align; -} - -// Division with mathematical rounding to nearest number. -template -static inline T VmaRoundDiv(T x, T y) -{ - return (x + (y / (T)2)) / y; + uint32_t c = v - ((v >> 1) & 0x55555555); + c = ((c >> 2) & 0x33333333) + (c & 0x33333333); + c = ((c >> 4) + c) & 0x0F0F0F0F; + c = ((c >> 8) + c) & 0x00FF00FF; + c = ((c >> 16) + c) & 0x0000FFFF; + return c; } /* @@ -3507,10 +4471,34 @@ inline bool VmaIsPow2(T x) return (x & (x-1)) == 0; } +// Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16. +// Use types like uint32_t, uint64_t as T. +template +static inline T VmaAlignUp(T val, T alignment) +{ + VMA_HEAVY_ASSERT(VmaIsPow2(alignment)); + return (val + alignment - 1) & ~(alignment - 1); +} +// Aligns given value down to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 8. +// Use types like uint32_t, uint64_t as T. +template +static inline T VmaAlignDown(T val, T alignment) +{ + VMA_HEAVY_ASSERT(VmaIsPow2(alignment)); + return val & ~(alignment - 1); +} + +// Division with mathematical rounding to nearest number. +template +static inline T VmaRoundDiv(T x, T y) +{ + return (x + (y / (T)2)) / y; +} + // Returns smallest power of 2 greater or equal to v. static inline uint32_t VmaNextPow2(uint32_t v) { - v--; + v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; @@ -3521,7 +4509,7 @@ static inline uint32_t VmaNextPow2(uint32_t v) } static inline uint64_t VmaNextPow2(uint64_t v) { - v--; + v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; @@ -3560,6 +4548,8 @@ static inline bool VmaStrIsEmpty(const char* pStr) return pStr == VMA_NULL || *pStr == '\0'; } +#if VMA_STATS_STRING_ENABLED + static const char* VmaAlgorithmToStr(uint32_t algorithm) { switch(algorithm) @@ -3576,6 +4566,8 @@ static const char* VmaAlgorithmToStr(uint32_t algorithm) } } +#endif // #if VMA_STATS_STRING_ENABLED + #ifndef VMA_SORT template @@ -3662,7 +4654,7 @@ static inline bool VmaIsBufferImageGranularityConflict( { VMA_SWAP(suballocType1, suballocType2); } - + switch(suballocType1) { case VMA_SUBALLOCATION_TYPE_FREE: @@ -3691,16 +4683,21 @@ static inline bool VmaIsBufferImageGranularityConflict( static void VmaWriteMagicValue(void* pData, VkDeviceSize offset) { +#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION uint32_t* pDst = (uint32_t*)((char*)pData + offset); const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); for(size_t i = 0; i < numberCount; ++i, ++pDst) { *pDst = VMA_CORRUPTION_DETECTION_MAGIC_VALUE; } +#else + // no-op +#endif } static bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset) { +#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION const uint32_t* pSrc = (const uint32_t*)((const char*)pData + offset); const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); for(size_t i = 0; i < numberCount; ++i, ++pSrc) @@ -3710,9 +4707,22 @@ static bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset) return false; } } +#endif return true; } +/* +Fills structure with parameters of an example buffer to be used for transfers +during GPU memory defragmentation. +*/ +static void VmaFillGpuDefragmentationBufferCreateInfo(VkBufferCreateInfo& outBufCreateInfo) +{ + memset(&outBufCreateInfo, 0, sizeof(outBufCreateInfo)); + outBufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + outBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + outBufCreateInfo.size = (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE; // Example size. +} + // Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope). struct VmaMutexLock { @@ -3773,12 +4783,12 @@ Returned value is the found element, if present in the collection or place where new element with value (key) should be inserted. */ template -static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT &key, CmpLess cmp) +static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT &key, const CmpLess& cmp) { size_t down = 0, up = (end - beg); while(down < up) { - const size_t mid = (down + up) / 2; + const size_t mid = down + (up - down) / 2; // Overflow-safe midpoint calculation if(cmp(*(beg+mid), key)) { down = mid + 1; @@ -3791,6 +4801,19 @@ static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT &key, Cm return beg + down; } +template +IterT VmaBinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp) +{ + IterT it = VmaBinaryFindFirstNotLess( + beg, end, value, cmp); + if(it == end || + (!cmp(*it, value) && !cmp(value, *it))) + { + return it; + } + return end; +} + /* Returns true if all pointers in the array are not-null and unique. Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT. @@ -3817,15 +4840,23 @@ static bool VmaValidatePointerArray(uint32_t count, const T* arr) return true; } +template +static inline void VmaPnextChainPushFront(MainT* mainStruct, NewT* newStruct) +{ + newStruct->pNext = mainStruct->pNext; + mainStruct->pNext = newStruct; +} + //////////////////////////////////////////////////////////////////////////////// // Memory allocation static void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t size, size_t alignment) { + void* result = VMA_NULL; if((pAllocationCallbacks != VMA_NULL) && (pAllocationCallbacks->pfnAllocation != VMA_NULL)) { - return (*pAllocationCallbacks->pfnAllocation)( + result = (*pAllocationCallbacks->pfnAllocation)( pAllocationCallbacks->pUserData, size, alignment, @@ -3833,8 +4864,10 @@ static void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t } else { - return VMA_SYSTEM_ALIGNED_MALLOC(size, alignment); + result = VMA_SYSTEM_ALIGNED_MALLOC(size, alignment); } + VMA_ASSERT(result != VMA_NULL && "CPU memory allocation failed."); + return result; } static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr) @@ -3846,7 +4879,7 @@ static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr } else { - VMA_SYSTEM_FREE(ptr); + VMA_SYSTEM_ALIGNED_FREE(ptr); } } @@ -3886,6 +4919,30 @@ static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, } } +static char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr) +{ + if(srcStr != VMA_NULL) + { + const size_t len = strlen(srcStr); + char* const result = vma_new_array(allocs, char, len + 1); + memcpy(result, srcStr, len + 1); + return result; + } + else + { + return VMA_NULL; + } +} + +static void VmaFreeString(const VkAllocationCallbacks* allocs, char* str) +{ + if(str != VMA_NULL) + { + const size_t len = strlen(str); + vma_delete_array(allocs, str, len + 1); + } +} + // STL-compatible allocator. template class VmaStlAllocator @@ -3893,7 +4950,7 @@ class VmaStlAllocator public: const VkAllocationCallbacks* const m_pCallbacks; typedef T value_type; - + VmaStlAllocator(const VkAllocationCallbacks* pCallbacks) : m_pCallbacks(pCallbacks) { } template VmaStlAllocator(const VmaStlAllocator& src) : m_pCallbacks(src.m_pCallbacks) { } @@ -3956,7 +5013,12 @@ public: m_Capacity(count) { } - + + // This version of the constructor is here for compatibility with pre-C++14 std::vector. + // value is unused. + VmaVector(size_t count, const T& value, const AllocatorT& allocator) + : VmaVector(count, allocator) {} + VmaVector(const VmaVector& src) : m_Allocator(src.m_Allocator), m_pArray(src.m_Count ? (T*)VmaAllocateArray(src.m_Allocator.m_pCallbacks, src.m_Count) : VMA_NULL), @@ -3968,7 +5030,7 @@ public: memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T)); } } - + ~VmaVector() { VmaFree(m_Allocator.m_pCallbacks, m_pArray); @@ -3986,12 +5048,12 @@ public: } return *this; } - + bool empty() const { return m_Count == 0; } size_t size() const { return m_Count; } T* data() { return m_pArray; } const T* data() const { return m_pArray; } - + T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); @@ -4027,12 +5089,12 @@ public: void reserve(size_t newCapacity, bool freeMemory = false) { newCapacity = VMA_MAX(newCapacity, m_Count); - + if((newCapacity < m_Capacity) && !freeMemory) { newCapacity = m_Capacity; } - + if(newCapacity != m_Capacity) { T* const newArray = newCapacity ? VmaAllocateArray(m_Allocator, newCapacity) : VMA_NULL; @@ -4182,19 +5244,174 @@ bool VmaVectorRemoveSorted(VectorT& vector, const typename VectorT::value_type& return false; } -template -IterT VmaVectorFindSorted(const IterT& beg, const IterT& end, const KeyT& value) +//////////////////////////////////////////////////////////////////////////////// +// class VmaSmallVector + +/* +This is a vector (a variable-sized array), optimized for the case when the array is small. + +It contains some number of elements in-place, which allows it to avoid heap allocation +when the actual number of elements is below that threshold. This allows normal "small" +cases to be fast without losing generality for large inputs. +*/ + +template +class VmaSmallVector { - CmpLess comparator; - IterT it = VmaBinaryFindFirstNotLess( - beg, end, value, comparator); - if(it == end || - (!comparator(*it, value) && !comparator(value, *it))) +public: + typedef T value_type; + + VmaSmallVector(const AllocatorT& allocator) : + m_Count(0), + m_DynamicArray(allocator) { - return it; } - return end; -} + VmaSmallVector(size_t count, const AllocatorT& allocator) : + m_Count(count), + m_DynamicArray(count > N ? count : 0, allocator) + { + } + template + VmaSmallVector(const VmaSmallVector& src) = delete; + template + VmaSmallVector& operator=(const VmaSmallVector& rhs) = delete; + + bool empty() const { return m_Count == 0; } + size_t size() const { return m_Count; } + T* data() { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; } + const T* data() const { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; } + + T& operator[](size_t index) + { + VMA_HEAVY_ASSERT(index < m_Count); + return data()[index]; + } + const T& operator[](size_t index) const + { + VMA_HEAVY_ASSERT(index < m_Count); + return data()[index]; + } + + T& front() + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[0]; + } + const T& front() const + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[0]; + } + T& back() + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[m_Count - 1]; + } + const T& back() const + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[m_Count - 1]; + } + + void resize(size_t newCount, bool freeMemory = false) + { + if(newCount > N && m_Count > N) + { + // Any direction, staying in m_DynamicArray + m_DynamicArray.resize(newCount, freeMemory); + } + else if(newCount > N && m_Count <= N) + { + // Growing, moving from m_StaticArray to m_DynamicArray + m_DynamicArray.resize(newCount, freeMemory); + if(m_Count > 0) + { + memcpy(m_DynamicArray.data(), m_StaticArray, m_Count * sizeof(T)); + } + } + else if(newCount <= N && m_Count > N) + { + // Shrinking, moving from m_DynamicArray to m_StaticArray + if(newCount > 0) + { + memcpy(m_StaticArray, m_DynamicArray.data(), newCount * sizeof(T)); + } + m_DynamicArray.resize(0, freeMemory); + } + else + { + // Any direction, staying in m_StaticArray - nothing to do here + } + m_Count = newCount; + } + + void clear(bool freeMemory = false) + { + m_DynamicArray.clear(freeMemory); + m_Count = 0; + } + + void insert(size_t index, const T& src) + { + VMA_HEAVY_ASSERT(index <= m_Count); + const size_t oldCount = size(); + resize(oldCount + 1); + T* const dataPtr = data(); + if(index < oldCount) + { + // I know, this could be more optimal for case where memmove can be memcpy directly from m_StaticArray to m_DynamicArray. + memmove(dataPtr + (index + 1), dataPtr + index, (oldCount - index) * sizeof(T)); + } + dataPtr[index] = src; + } + + void remove(size_t index) + { + VMA_HEAVY_ASSERT(index < m_Count); + const size_t oldCount = size(); + if(index < oldCount - 1) + { + // I know, this could be more optimal for case where memmove can be memcpy directly from m_DynamicArray to m_StaticArray. + T* const dataPtr = data(); + memmove(dataPtr + index, dataPtr + (index + 1), (oldCount - index - 1) * sizeof(T)); + } + resize(oldCount - 1); + } + + void push_back(const T& src) + { + const size_t newIndex = size(); + resize(newIndex + 1); + data()[newIndex] = src; + } + + void pop_back() + { + VMA_HEAVY_ASSERT(m_Count > 0); + resize(size() - 1); + } + + void push_front(const T& src) + { + insert(0, src); + } + + void pop_front() + { + VMA_HEAVY_ASSERT(m_Count > 0); + remove(0); + } + + typedef T* iterator; + + iterator begin() { return data(); } + iterator end() { return data() + m_Count; } + +private: + size_t m_Count; + T m_StaticArray[N]; // Used when m_Size <= N + VmaVector m_DynamicArray; // Used when m_Size > N +}; //////////////////////////////////////////////////////////////////////////////// // class VmaPoolAllocator @@ -4211,15 +5428,14 @@ class VmaPoolAllocator public: VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity); ~VmaPoolAllocator(); - void Clear(); - T* Alloc(); + template T* Alloc(Types... args); void Free(T* ptr); private: union Item { uint32_t NextFreeIndex; - T Value; + alignas(T) char Value[sizeof(T)]; }; struct ItemBlock @@ -4228,7 +5444,7 @@ private: uint32_t Capacity; uint32_t FirstFreeIndex; }; - + const VkAllocationCallbacks* m_pAllocationCallbacks; const uint32_t m_FirstBlockCapacity; VmaVector< ItemBlock, VmaStlAllocator > m_ItemBlocks; @@ -4247,12 +5463,6 @@ VmaPoolAllocator::VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCa template VmaPoolAllocator::~VmaPoolAllocator() -{ - Clear(); -} - -template -void VmaPoolAllocator::Clear() { for(size_t i = m_ItemBlocks.size(); i--; ) vma_delete_array(m_pAllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity); @@ -4260,7 +5470,7 @@ void VmaPoolAllocator::Clear() } template -T* VmaPoolAllocator::Alloc() +template T* VmaPoolAllocator::Alloc(Types... args) { for(size_t i = m_ItemBlocks.size(); i--; ) { @@ -4270,7 +5480,9 @@ T* VmaPoolAllocator::Alloc() { Item* const pItem = &block.pItems[block.FirstFreeIndex]; block.FirstFreeIndex = pItem->NextFreeIndex; - return &pItem->Value; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; } } @@ -4278,7 +5490,9 @@ T* VmaPoolAllocator::Alloc() ItemBlock& newBlock = CreateNewBlock(); Item* const pItem = &newBlock.pItems[0]; newBlock.FirstFreeIndex = pItem->NextFreeIndex; - return &pItem->Value; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; } template @@ -4288,14 +5502,15 @@ void VmaPoolAllocator::Free(T* ptr) for(size_t i = m_ItemBlocks.size(); i--; ) { ItemBlock& block = m_ItemBlocks[i]; - + // Casting to union. Item* pItemPtr; memcpy(&pItemPtr, &ptr, sizeof(pItemPtr)); - + // Check if pItemPtr is in address range of this block. if((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) { + ptr->~T(); // Explicit destructor call. const uint32_t index = static_cast(pItemPtr - block.pItems); pItemPtr->NextFreeIndex = block.FirstFreeIndex; block.FirstFreeIndex = index; @@ -4368,7 +5583,7 @@ public: ItemType* PushFront(const T& value); void PopBack(); void PopFront(); - + // Item can be null - it means PushBack. ItemType* InsertBefore(ItemType* pItem); // Item can be null - it means PushFront. @@ -4678,7 +5893,7 @@ public: VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } - + private: VmaRawList* m_pList; VmaListItem* m_pItem; @@ -4706,7 +5921,7 @@ public: m_pItem(src.m_pItem) { } - + const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); @@ -4761,7 +5976,7 @@ public: VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } - + private: const_iterator(const VmaRawList* pList, const VmaListItem* pItem) : m_pList(pList), @@ -4840,7 +6055,7 @@ public: void insert(const PairType& pair); iterator find(const KeyT& key); void erase(iterator it); - + private: VmaVector< PairType, VmaStlAllocator > m_Vector; }; @@ -4924,28 +6139,27 @@ public: }; /* - This struct cannot have constructor or destructor. It must be POD because it is - allocated using VmaPoolAllocator. + This struct is allocated using VmaPoolAllocator. */ - void Ctor(uint32_t currentFrameIndex, bool userDataString) + VmaAllocation_T(uint32_t currentFrameIndex, bool userDataString) : + m_Alignment{1}, + m_Size{0}, + m_pUserData{VMA_NULL}, + m_LastUseFrameIndex{currentFrameIndex}, + m_MemoryTypeIndex{0}, + m_Type{(uint8_t)ALLOCATION_TYPE_NONE}, + m_SuballocationType{(uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN}, + m_MapCount{0}, + m_Flags{userDataString ? (uint8_t)FLAG_USER_DATA_STRING : (uint8_t)0} { - m_Alignment = 1; - m_Size = 0; - m_pUserData = VMA_NULL; - m_LastUseFrameIndex = currentFrameIndex; - m_Type = (uint8_t)ALLOCATION_TYPE_NONE; - m_SuballocationType = (uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN; - m_MapCount = 0; - m_Flags = userDataString ? (uint8_t)FLAG_USER_DATA_STRING : 0; - #if VMA_STATS_STRING_ENABLED m_CreationFrameIndex = currentFrameIndex; m_BufferImageUsage = 0; #endif } - void Dtor() + ~VmaAllocation_T() { VMA_ASSERT((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) == 0 && "Allocation was not unmapped before destruction."); @@ -4958,6 +6172,7 @@ public: VkDeviceSize offset, VkDeviceSize alignment, VkDeviceSize size, + uint32_t memoryTypeIndex, VmaSuballocationType suballocationType, bool mapped, bool canBecomeLost) @@ -4967,6 +6182,7 @@ public: m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; m_Alignment = alignment; m_Size = size; + m_MemoryTypeIndex = memoryTypeIndex; m_MapCount = mapped ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0; m_SuballocationType = (uint8_t)suballocationType; m_BlockAllocation.m_Block = block; @@ -4979,6 +6195,7 @@ public: VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); VMA_ASSERT(m_LastUseFrameIndex.load() == VMA_FRAME_INDEX_LOST); m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; + m_MemoryTypeIndex = 0; m_BlockAllocation.m_Block = VMA_NULL; m_BlockAllocation.m_Offset = 0; m_BlockAllocation.m_CanBecomeLost = true; @@ -4987,9 +6204,8 @@ public: void ChangeBlockAllocation( VmaAllocator hAllocator, VmaDeviceMemoryBlock* block, - VkDeviceSize offset); + VkDeviceSize offset); - void ChangeSize(VkDeviceSize newSize); void ChangeOffset(VkDeviceSize newOffset); // pMappedData not null means allocation is created with MAPPED flag. @@ -5005,9 +6221,9 @@ public: m_Type = (uint8_t)ALLOCATION_TYPE_DEDICATED; m_Alignment = 0; m_Size = size; + m_MemoryTypeIndex = memoryTypeIndex; m_SuballocationType = (uint8_t)suballocationType; m_MapCount = (pMappedData != VMA_NULL) ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0; - m_DedicatedAllocation.m_MemoryTypeIndex = memoryTypeIndex; m_DedicatedAllocation.m_hMemory = hMemory; m_DedicatedAllocation.m_pMappedData = pMappedData; } @@ -5027,11 +6243,11 @@ public: } VkDeviceSize GetOffset() const; VkDeviceMemory GetMemory() const; - uint32_t GetMemoryTypeIndex() const; + uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } bool IsPersistentMap() const { return (m_MapCount & MAP_COUNT_FLAG_PERSISTENT_MAP) != 0; } void* GetMappedData() const; bool CanBecomeLost() const; - + uint32_t GetLastUseFrameIndex() const { return m_LastUseFrameIndex.load(); @@ -5044,7 +6260,7 @@ public: - If hAllocation.LastUseFrameIndex + frameInUseCount < allocator.CurrentFrameIndex, makes it lost by setting LastUseFrameIndex = VMA_FRAME_INDEX_LOST and returns true. - Else, returns false. - + If hAllocation is already lost, assert - you should not call it then. If hAllocation was not created with CAN_BECOME_LOST_BIT, assert. */ @@ -5086,6 +6302,7 @@ private: VkDeviceSize m_Size; void* m_pUserData; VMA_ATOMIC_UINT32 m_LastUseFrameIndex; + uint32_t m_MemoryTypeIndex; uint8_t m_Type; // ALLOCATION_TYPE uint8_t m_SuballocationType; // VmaSuballocationType // Bit 0x80 is set when allocation was created with VMA_ALLOCATION_CREATE_MAPPED_BIT. @@ -5104,7 +6321,6 @@ private: // Allocation for an object that has its own private VkDeviceMemory. struct DedicatedAllocation { - uint32_t m_MemoryTypeIndex; VkDeviceMemory m_hMemory; void* m_pMappedData; // Not null means memory is mapped. }; @@ -5260,9 +6476,6 @@ public: virtual void Free(const VmaAllocation allocation) = 0; virtual void FreeAtOffset(VkDeviceSize offset) = 0; - // Tries to resize (grow or shrink) space for given allocation, in place. - virtual bool ResizeAllocation(const VmaAllocation alloc, VkDeviceSize newSize) { return false; } - protected: const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; } @@ -5341,11 +6554,9 @@ public: virtual void Free(const VmaAllocation allocation); virtual void FreeAtOffset(VkDeviceSize offset); - virtual bool ResizeAllocation(const VmaAllocation alloc, VkDeviceSize newSize); - //////////////////////////////////////////////////////////////////////////////// // For defragmentation - + bool IsBufferImageGranularityConflictPossible( VkDeviceSize bufferImageGranularity, VmaSuballocationType& inOutPrevSuballocType) const; @@ -5556,7 +6767,7 @@ private: SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } - + // Number of items in 1st vector with hAllocation = null at the beginning. size_t m_1stNullItemsBeginCount; // Number of other items in 1st vector with hAllocation = null somewhere in the middle. @@ -5767,7 +6978,7 @@ public: uint32_t algorithm); // Always call before destruction. void Destroy(VmaAllocator allocator); - + VmaPool GetParentPool() const { return m_hParentPool; } VkDeviceMemory GetDeviceMemory() const { return m_hMemory; } uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } @@ -5789,11 +7000,15 @@ public: VkResult BindBufferMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkBuffer hBuffer); + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext); VkResult BindImageMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkImage hImage); + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext); private: VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool. @@ -5826,6 +7041,9 @@ struct VmaDefragmentationMove VkDeviceSize srcOffset; VkDeviceSize dstOffset; VkDeviceSize size; + VmaAllocation hAllocation; + VmaDeviceMemoryBlock* pSrcBlock; + VmaDeviceMemoryBlock* pDstBlock; }; class VmaDefragmentationAlgorithm; @@ -5849,14 +7067,16 @@ public: size_t maxBlockCount, VkDeviceSize bufferImageGranularity, uint32_t frameInUseCount, - bool isCustomPool, bool explicitBlockSize, - uint32_t algorithm); + uint32_t algorithm, + float priority); ~VmaBlockVector(); VkResult CreateMinBlocks(); + VmaAllocator GetAllocator() const { return m_hAllocator; } VmaPool GetParentPool() const { return m_hParentPool; } + bool IsCustomPool() const { return m_hParentPool != VMA_NULL; } uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } VkDeviceSize GetPreferredBlockSize() const { return m_PreferredBlockSize; } VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; } @@ -5865,7 +7085,7 @@ public: void GetPoolStats(VmaPoolStats* pStats); - bool IsEmpty() const { return m_Blocks.empty(); } + bool IsEmpty(); bool IsCorruptionDetectionEnabled() const; VkResult Allocate( @@ -5877,8 +7097,7 @@ public: size_t allocationCount, VmaAllocation* pAllocations); - void Free( - VmaAllocation hAllocation); + void Free(const VmaAllocation hAllocation); // Adds statistics of this BlockVector to pStats. void AddStats(VmaStats* pStats); @@ -5895,12 +7114,21 @@ public: // Saves results in pCtx->res. void Defragment( class VmaBlockVectorDefragmentationContext* pCtx, - VmaDefragmentationStats* pStats, + VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags, VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove, VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove, VkCommandBuffer commandBuffer); void DefragmentationEnd( class VmaBlockVectorDefragmentationContext* pCtx, + uint32_t flags, + VmaDefragmentationStats* pStats); + + uint32_t ProcessDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, + VmaDefragmentationPassMoveInfo* pMove, uint32_t maxMoves); + + void CommitDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, VmaDefragmentationStats* pStats); //////////////////////////////////////////////////////////////////////////////// @@ -5922,14 +7150,14 @@ private: const size_t m_MaxBlockCount; const VkDeviceSize m_BufferImageGranularity; const uint32_t m_FrameInUseCount; - const bool m_IsCustomPool; const bool m_ExplicitBlockSize; const uint32_t m_Algorithm; - /* There can be at most one allocation that is completely empty - a - hysteresis to avoid pessimistic case of alternating creation and destruction - of a VkDeviceMemory. */ - bool m_HasEmptyBlock; + const float m_Priority; VMA_RW_MUTEX m_Mutex; + + /* There can be at most one allocation that is completely empty (except when minBlockCount > 0) - + a hysteresis to avoid pessimistic case of alternating creation and destruction of a VkDeviceMemory. */ + bool m_HasEmptyBlock; // Incrementally sorted by sumFreeSize, ascending. VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator > m_Blocks; uint32_t m_NextBlockId; @@ -5972,7 +7200,7 @@ private: // Saves result to pCtx->res. void ApplyDefragmentationMovesGpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, - const VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, + VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkCommandBuffer commandBuffer); /* @@ -5980,6 +7208,8 @@ private: - updated with new data. */ void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats); + + void UpdateHasEmptyBlock(); }; struct VmaPool_T @@ -5997,12 +7227,16 @@ public: uint32_t GetId() const { return m_Id; } void SetId(uint32_t id) { VMA_ASSERT(m_Id == 0); m_Id = id; } + const char* GetName() const { return m_Name; } + void SetName(const char* pName); + #if VMA_STATS_STRING_ENABLED //void PrintDetailedMap(class VmaStringBuilder& sb); #endif private: uint32_t m_Id; + char* m_Name; }; /* @@ -6035,7 +7269,8 @@ public: virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) = 0; + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags) = 0; virtual VkDeviceSize GetBytesMoved() const = 0; virtual uint32_t GetAllocationsMoved() const = 0; @@ -6080,7 +7315,8 @@ public: virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove); + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags); virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; } virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; } @@ -6181,7 +7417,8 @@ private: VkResult DefragmentRound( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove); + uint32_t maxAllocationsToMove, + bool freeOldAllocations); size_t CalcBlocksWithNonMovableCount() const; @@ -6207,7 +7444,8 @@ public: virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove); + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags); virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; } virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; } @@ -6287,7 +7525,7 @@ private: } } } - + if(bestIndex != SIZE_MAX) { outBlockInfoIndex = m_FreeSpaces[bestIndex].blockInfoIndex; @@ -6346,12 +7584,6 @@ struct VmaBlockDefragmentationContext }; uint32_t flags; VkBuffer hBuffer; - - VmaBlockDefragmentationContext() : - flags(0), - hBuffer(VK_NULL_HANDLE) - { - } }; class VmaBlockVectorDefragmentationContext @@ -6361,13 +7593,16 @@ public: VkResult res; bool mutexLocked; VmaVector< VmaBlockDefragmentationContext, VmaStlAllocator > blockContexts; + VmaVector< VmaDefragmentationMove, VmaStlAllocator > defragmentationMoves; + uint32_t defragmentationMovesProcessed; + uint32_t defragmentationMovesCommitted; + bool hasDefragmentationPlan; VmaBlockVectorDefragmentationContext( VmaAllocator hAllocator, VmaPool hCustomPool, // Optional. VmaBlockVector* pBlockVector, - uint32_t currFrameIndex, - uint32_t flags); + uint32_t currFrameIndex); ~VmaBlockVectorDefragmentationContext(); VmaPool GetCustomPool() const { return m_hCustomPool; } @@ -6377,7 +7612,7 @@ public: void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged); void AddAll() { m_AllAllocations = true; } - void Begin(bool overlappingMoveSupported); + void Begin(bool overlappingMoveSupported, VmaDefragmentationFlags flags); private: const VmaAllocator m_hAllocator; @@ -6386,7 +7621,6 @@ private: // Redundant, for convenience not to fetch from m_hCustomPool->m_BlockVector or m_hAllocator->m_pBlockVectors. VmaBlockVector* const m_pBlockVector; const uint32_t m_CurrFrameIndex; - const uint32_t m_AlgorithmFlags; // Owner of this object. VmaDefragmentationAlgorithm* m_pAlgorithm; @@ -6412,10 +7646,10 @@ public: VmaDefragmentationStats* pStats); ~VmaDefragmentationContext_T(); - void AddPools(uint32_t poolCount, VmaPool* pPools); + void AddPools(uint32_t poolCount, const VmaPool* pPools); void AddAllocations( uint32_t allocationCount, - VmaAllocation* pAllocations, + const VmaAllocation* pAllocations, VkBool32* pAllocationsChanged); /* @@ -6427,13 +7661,22 @@ public: VkResult Defragment( VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove, VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove, - VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats); + VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags); + + VkResult DefragmentPassBegin(VmaDefragmentationPassInfo* pInfo); + VkResult DefragmentPassEnd(); private: const VmaAllocator m_hAllocator; const uint32_t m_CurrFrameIndex; const uint32_t m_Flags; VmaDefragmentationStats* const m_pStats; + + VkDeviceSize m_MaxCpuBytesToMove; + uint32_t m_MaxCpuAllocationsToMove; + VkDeviceSize m_MaxGpuBytesToMove; + uint32_t m_MaxGpuAllocationsToMove; + // Owner of these objects. VmaBlockVectorDefragmentationContext* m_DefaultPoolContexts[VK_MAX_MEMORY_TYPES]; // Owner of these objects. @@ -6450,7 +7693,11 @@ public: void WriteConfiguration( const VkPhysicalDeviceProperties& devProps, const VkPhysicalDeviceMemoryProperties& memProps, - bool dedicatedAllocationExtensionEnabled); + uint32_t vulkanApiVersion, + bool dedicatedAllocationExtensionEnabled, + bool bindMemory2ExtensionEnabled, + bool memoryBudgetExtensionEnabled, + bool deviceCoherentMemoryExtensionEnabled); ~VmaRecorder(); void RecordCreateAllocator(uint32_t frameIndex); @@ -6485,10 +7732,6 @@ public: void RecordFreeMemoryPages(uint32_t frameIndex, uint64_t allocationCount, const VmaAllocation* pAllocations); - void RecordResizeAllocation( - uint32_t frameIndex, - VmaAllocation allocation, - VkDeviceSize newSize); void RecordSetAllocationUserData(uint32_t frameIndex, VmaAllocation allocation, const void* pUserData); @@ -6525,6 +7768,9 @@ public: VmaDefragmentationContext ctx); void RecordDefragmentationEnd(uint32_t frameIndex, VmaDefragmentationContext ctx); + void RecordSetPoolName(uint32_t frameIndex, + VmaPool pool, + const char* name); private: struct CallParams @@ -6548,8 +7794,7 @@ private: VmaRecordFlags m_Flags; FILE* m_File; VMA_MUTEX m_FileMutex; - int64_t m_Freq; - int64_t m_StartCounter; + std::chrono::time_point m_RecordingStartTime; void GetBasicParams(CallParams& outParams); @@ -6582,7 +7827,7 @@ class VmaAllocationObjectAllocator public: VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks); - VmaAllocation Allocate(); + template VmaAllocation Allocate(Types... args); void Free(VmaAllocation hAlloc); private: @@ -6590,22 +7835,77 @@ private: VmaPoolAllocator m_Allocator; }; +struct VmaCurrentBudgetData +{ + VMA_ATOMIC_UINT64 m_BlockBytes[VK_MAX_MEMORY_HEAPS]; + VMA_ATOMIC_UINT64 m_AllocationBytes[VK_MAX_MEMORY_HEAPS]; + +#if VMA_MEMORY_BUDGET + VMA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch; + VMA_RW_MUTEX m_BudgetMutex; + uint64_t m_VulkanUsage[VK_MAX_MEMORY_HEAPS]; + uint64_t m_VulkanBudget[VK_MAX_MEMORY_HEAPS]; + uint64_t m_BlockBytesAtBudgetFetch[VK_MAX_MEMORY_HEAPS]; +#endif // #if VMA_MEMORY_BUDGET + + VmaCurrentBudgetData() + { + for(uint32_t heapIndex = 0; heapIndex < VK_MAX_MEMORY_HEAPS; ++heapIndex) + { + m_BlockBytes[heapIndex] = 0; + m_AllocationBytes[heapIndex] = 0; +#if VMA_MEMORY_BUDGET + m_VulkanUsage[heapIndex] = 0; + m_VulkanBudget[heapIndex] = 0; + m_BlockBytesAtBudgetFetch[heapIndex] = 0; +#endif + } + +#if VMA_MEMORY_BUDGET + m_OperationsSinceBudgetFetch = 0; +#endif + } + + void AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) + { + m_AllocationBytes[heapIndex] += allocationSize; +#if VMA_MEMORY_BUDGET + ++m_OperationsSinceBudgetFetch; +#endif + } + + void RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) + { + VMA_ASSERT(m_AllocationBytes[heapIndex] >= allocationSize); // DELME + m_AllocationBytes[heapIndex] -= allocationSize; +#if VMA_MEMORY_BUDGET + ++m_OperationsSinceBudgetFetch; +#endif + } +}; + // Main allocator object. struct VmaAllocator_T { VMA_CLASS_NO_COPY(VmaAllocator_T) public: bool m_UseMutex; - bool m_UseKhrDedicatedAllocation; + uint32_t m_VulkanApiVersion; + bool m_UseKhrDedicatedAllocation; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). + bool m_UseKhrBindMemory2; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). + bool m_UseExtMemoryBudget; + bool m_UseAmdDeviceCoherentMemory; + bool m_UseKhrBufferDeviceAddress; + bool m_UseExtMemoryPriority; VkDevice m_hDevice; + VkInstance m_hInstance; bool m_AllocationCallbacksSpecified; VkAllocationCallbacks m_AllocationCallbacks; VmaDeviceMemoryCallbacks m_DeviceMemoryCallbacks; VmaAllocationObjectAllocator m_AllocationObjectAllocator; - - // Number of bytes free out of limit, or VK_WHOLE_SIZE if no limit for that heap. - VkDeviceSize m_HeapSizeLimit[VK_MAX_MEMORY_HEAPS]; - VMA_MUTEX m_HeapSizeLimitMutex; + + // Each bit (1 << i) is set if HeapSizeLimit is enabled for that heap, so cannot allocate more than the heap size. + uint32_t m_HeapSizeLimitMask; VkPhysicalDeviceProperties m_PhysicalDeviceProperties; VkPhysicalDeviceMemoryProperties m_MemProps; @@ -6618,6 +7918,8 @@ public: AllocationVectorType* m_pDedicatedAllocations[VK_MAX_MEMORY_TYPES]; VMA_RW_MUTEX m_DedicatedAllocationsMutex[VK_MAX_MEMORY_TYPES]; + VmaCurrentBudgetData m_Budget; + VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo); VkResult Init(const VmaAllocatorCreateInfo* pCreateInfo); ~VmaAllocator_T(); @@ -6631,6 +7933,8 @@ public: return m_VulkanFunctions; } + VkPhysicalDevice GetPhysicalDevice() const { return m_PhysicalDevice; } + VkDeviceSize GetBufferImageGranularity() const { return VMA_MAX( @@ -6665,6 +7969,8 @@ public: return m_PhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; } + uint32_t GetGlobalMemoryTypeBits() const { return m_GlobalMemoryTypeBits; } + #if VMA_RECORDING_ENABLED VmaRecorder* GetRecorder() const { return m_pRecorder; } #endif @@ -6686,6 +7992,7 @@ public: bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, // UINT32_MAX when unknown. VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, @@ -6703,6 +8010,9 @@ public: void CalculateStats(VmaStats* pStats); + void GetBudget( + VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount); + #if VMA_STATS_STRING_ENABLED void PrintDetailedMap(class VmaJsonWriter& json); #endif @@ -6714,6 +8024,12 @@ public: VkResult DefragmentationEnd( VmaDefragmentationContext context); + VkResult DefragmentationPassBegin( + VmaDefragmentationPassInfo* pInfo, + VmaDefragmentationContext context); + VkResult DefragmentationPassEnd( + VmaDefragmentationContext context); + void GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo); bool TouchAllocation(VmaAllocation hAllocation); @@ -6732,28 +8048,62 @@ public: void CreateLostAllocation(VmaAllocation* pAllocation); + // Call to Vulkan function vkAllocateMemory with accompanying bookkeeping. VkResult AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory); + // Call to Vulkan function vkFreeMemory with accompanying bookkeeping. void FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory); + // Call to Vulkan function vkBindBufferMemory or vkBindBufferMemory2KHR. + VkResult BindVulkanBuffer( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkBuffer buffer, + const void* pNext); + // Call to Vulkan function vkBindImageMemory or vkBindImageMemory2KHR. + VkResult BindVulkanImage( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkImage image, + const void* pNext); VkResult Map(VmaAllocation hAllocation, void** ppData); void Unmap(VmaAllocation hAllocation); - VkResult BindBufferMemory(VmaAllocation hAllocation, VkBuffer hBuffer); - VkResult BindImageMemory(VmaAllocation hAllocation, VkImage hImage); + VkResult BindBufferMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext); + VkResult BindImageMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext); - void FlushOrInvalidateAllocation( + VkResult FlushOrInvalidateAllocation( VmaAllocation hAllocation, VkDeviceSize offset, VkDeviceSize size, VMA_CACHE_OPERATION op); + VkResult FlushOrInvalidateAllocations( + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + VMA_CACHE_OPERATION op); void FillAllocation(const VmaAllocation hAllocation, uint8_t pattern); + /* + Returns bit mask of memory types that can support defragmentation on GPU as + they support creation of required buffer for copy operations. + */ + uint32_t GetGpuDefragmentationMemoryTypeBits(); + private: VkDeviceSize m_PreferredLargeHeapBlockSize; VkPhysicalDevice m_PhysicalDevice; VMA_ATOMIC_UINT32 m_CurrentFrameIndex; - + VMA_ATOMIC_UINT32 m_GpuDefragmentationMemoryTypeBits; // UINT32_MAX means uninitialized. + VMA_RW_MUTEX m_PoolsMutex; // Protected by m_PoolsMutex. Sorted by pointer value. VmaVector > m_Pools; @@ -6761,12 +8111,27 @@ private: VmaVulkanFunctions m_VulkanFunctions; + // Global bit mask AND-ed with any memoryTypeBits to disallow certain memory types. + uint32_t m_GlobalMemoryTypeBits; + #if VMA_RECORDING_ENABLED VmaRecorder* m_pRecorder; #endif void ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions); +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + void ImportVulkanFunctions_Static(); +#endif + + void ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions); + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + void ImportVulkanFunctions_Dynamic(); +#endif + + void ValidateVulkanFunctions(); + VkDeviceSize CalcPreferredBlockSize(uint32_t memTypeIndex); VkResult AllocateMemoryOfType( @@ -6774,6 +8139,7 @@ private: VkDeviceSize alignment, bool dedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, @@ -6797,16 +8163,35 @@ private: VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, + bool withinBudget, bool map, bool isUserDataString, void* pUserData, + float priority, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, size_t allocationCount, VmaAllocation* pAllocations); - // Tries to free pMemory as Dedicated Memory. Returns true if found and freed. - void FreeDedicatedMemory(VmaAllocation allocation); + void FreeDedicatedMemory(const VmaAllocation allocation); + + /* + Calculates and returns bit mask of memory types that can support defragmentation + on GPU as they support creation of required buffer for copy operations. + */ + uint32_t CalculateGpuDefragmentationMemoryTypeBits() const; + + uint32_t CalculateGlobalMemoryTypeBits() const; + + bool GetFlushOrInvalidateRange( + VmaAllocation allocation, + VkDeviceSize offset, VkDeviceSize size, + VkMappedMemoryRange& outRange) const; + +#if VMA_MEMORY_BUDGET + void UpdateVulkanBudget(); +#endif // #if VMA_MEMORY_BUDGET }; //////////////////////////////////////////////////////////////////////////////// @@ -6892,15 +8277,29 @@ void VmaStringBuilder::Add(const char* pStr) void VmaStringBuilder::AddNumber(uint32_t num) { char buf[11]; - VmaUint32ToStr(buf, sizeof(buf), num); - Add(buf); + buf[10] = '\0'; + char *p = &buf[10]; + do + { + *--p = '0' + (num % 10); + num /= 10; + } + while(num); + Add(p); } void VmaStringBuilder::AddNumber(uint64_t num) { char buf[21]; - VmaUint64ToStr(buf, sizeof(buf), num); - Add(buf); + buf[20] = '\0'; + char *p = &buf[20]; + do + { + *--p = '0' + (num % 10); + num /= 10; + } + while(num); + Add(p); } void VmaStringBuilder::AddPointer(const void* ptr) @@ -6926,10 +8325,10 @@ public: void BeginObject(bool singleLine = false); void EndObject(); - + void BeginArray(bool singleLine = false); void EndArray(); - + void WriteString(const char* pStr); void BeginString(const char* pStr = VMA_NULL); void ContinueString(const char* pStr); @@ -6937,7 +8336,7 @@ public: void ContinueString(uint64_t n); void ContinueString_Pointer(const void* ptr); void EndString(const char* pStr = VMA_NULL); - + void WriteNumber(uint32_t n); void WriteNumber(uint64_t n); void WriteBool(bool b); @@ -7185,7 +8584,7 @@ void VmaJsonWriter::WriteIndent(bool oneLess) if(!m_Stack.empty() && !m_Stack.back().singleLineMode) { m_SB.AddNewLine(); - + size_t count = m_Stack.size(); if(count > 0 && oneLess) { @@ -7212,11 +8611,7 @@ void VmaAllocation_T::SetUserData(VmaAllocator hAllocator, void* pUserData) if(pUserData != VMA_NULL) { - const char* const newStrSrc = (char*)pUserData; - const size_t newStrLen = strlen(newStrSrc); - char* const newStrDst = vma_new_array(hAllocator, char, newStrLen + 1); - memcpy(newStrDst, newStrSrc, newStrLen + 1); - m_pUserData = newStrDst; + m_pUserData = VmaCreateStringCopy(hAllocator->GetAllocationCallbacks(), (const char*)pUserData); } } else @@ -7247,12 +8642,6 @@ void VmaAllocation_T::ChangeBlockAllocation( m_BlockAllocation.m_Offset = offset; } -void VmaAllocation_T::ChangeSize(VkDeviceSize newSize) -{ - VMA_ASSERT(newSize > 0); - m_Size = newSize; -} - void VmaAllocation_T::ChangeOffset(VkDeviceSize newOffset) { VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); @@ -7287,20 +8676,6 @@ VkDeviceMemory VmaAllocation_T::GetMemory() const } } -uint32_t VmaAllocation_T::GetMemoryTypeIndex() const -{ - switch(m_Type) - { - case ALLOCATION_TYPE_BLOCK: - return m_BlockAllocation.m_Block->GetMemoryTypeIndex(); - case ALLOCATION_TYPE_DEDICATED: - return m_DedicatedAllocation.m_MemoryTypeIndex; - default: - VMA_ASSERT(0); - return UINT32_MAX; - } -} - void* VmaAllocation_T::GetMappedData() const { switch(m_Type) @@ -7425,13 +8800,8 @@ void VmaAllocation_T::PrintParameters(class VmaJsonWriter& json) const void VmaAllocation_T::FreeUserDataString(VmaAllocator hAllocator) { VMA_ASSERT(IsUserDataString()); - if(m_pUserData != VMA_NULL) - { - char* const oldStr = (char*)m_pUserData; - const size_t oldStrLen = strlen(oldStr); - vma_delete_array(hAllocator, oldStr, oldStrLen + 1); - m_pUserData = VMA_NULL; - } + VmaFreeString(hAllocator->GetAllocationCallbacks(), (char*)m_pUserData); + m_pUserData = VMA_NULL; } void VmaAllocation_T::BlockAllocMap() @@ -7628,7 +8998,7 @@ void VmaBlockMetadata::PrintDetailedMap_Allocation(class VmaJsonWriter& json, VmaAllocation hAllocation) const { json.BeginObject(true); - + json.WriteString("Offset"); json.WriteNumber(offset); @@ -7642,7 +9012,7 @@ void VmaBlockMetadata::PrintDetailedMap_UnusedRange(class VmaJsonWriter& json, VkDeviceSize size) const { json.BeginObject(true); - + json.WriteString("Offset"); json.WriteNumber(offset); @@ -7702,7 +9072,7 @@ void VmaBlockMetadata_Generic::Init(VkDeviceSize size) bool VmaBlockMetadata_Generic::Validate() const { VMA_VALIDATE(!m_Suballocations.empty()); - + // Expected offset of new suballocation as calculated from previous ones. VkDeviceSize calculatedOffset = 0; // Expected number of free suballocations as calculated from traversing their list. @@ -7720,7 +9090,7 @@ bool VmaBlockMetadata_Generic::Validate() const ++suballocItem) { const VmaSuballocation& subAlloc = *suballocItem; - + // Actual offset of this suballocation doesn't match expected one. VMA_VALIDATE(subAlloc.offset == calculatedOffset); @@ -7763,7 +9133,7 @@ bool VmaBlockMetadata_Generic::Validate() const for(size_t i = 0; i < m_FreeSuballocationsBySize.size(); ++i) { VmaSuballocationList::iterator suballocItem = m_FreeSuballocationsBySize[i]; - + // Only free suballocations can be registered in m_FreeSuballocationsBySize. VMA_VALIDATE(suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE); // They must be sorted by size ascending. @@ -7805,7 +9175,7 @@ void VmaBlockMetadata_Generic::CalcAllocationStatInfo(VmaStatInfo& outInfo) cons const uint32_t rangeCount = (uint32_t)m_Suballocations.size(); outInfo.allocationCount = rangeCount - m_FreeCount; outInfo.unusedRangeCount = m_FreeCount; - + outInfo.unusedBytes = m_SumFreeSize; outInfo.usedBytes = GetSize() - outInfo.unusedBytes; @@ -8064,7 +9434,7 @@ bool VmaBlockMetadata_Generic::MakeRequestedAllocationsLost( VMA_HEAVY_ASSERT(Validate()); VMA_ASSERT(pAllocationRequest->item != m_Suballocations.end()); VMA_ASSERT(pAllocationRequest->item->type == VMA_SUBALLOCATION_TYPE_FREE); - + return true; } @@ -8208,133 +9578,6 @@ void VmaBlockMetadata_Generic::FreeAtOffset(VkDeviceSize offset) VMA_ASSERT(0 && "Not found!"); } -bool VmaBlockMetadata_Generic::ResizeAllocation(const VmaAllocation alloc, VkDeviceSize newSize) -{ - typedef VmaSuballocationList::iterator iter_type; - for(iter_type suballocItem = m_Suballocations.begin(); - suballocItem != m_Suballocations.end(); - ++suballocItem) - { - VmaSuballocation& suballoc = *suballocItem; - if(suballoc.hAllocation == alloc) - { - iter_type nextItem = suballocItem; - ++nextItem; - - // Should have been ensured on higher level. - VMA_ASSERT(newSize != alloc->GetSize() && newSize > 0); - - // Shrinking. - if(newSize < alloc->GetSize()) - { - const VkDeviceSize sizeDiff = suballoc.size - newSize; - - // There is next item. - if(nextItem != m_Suballocations.end()) - { - // Next item is free. - if(nextItem->type == VMA_SUBALLOCATION_TYPE_FREE) - { - // Grow this next item backward. - UnregisterFreeSuballocation(nextItem); - nextItem->offset -= sizeDiff; - nextItem->size += sizeDiff; - RegisterFreeSuballocation(nextItem); - } - // Next item is not free. - else - { - // Create free item after current one. - VmaSuballocation newFreeSuballoc; - newFreeSuballoc.hAllocation = VK_NULL_HANDLE; - newFreeSuballoc.offset = suballoc.offset + newSize; - newFreeSuballoc.size = sizeDiff; - newFreeSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; - iter_type newFreeSuballocIt = m_Suballocations.insert(nextItem, newFreeSuballoc); - RegisterFreeSuballocation(newFreeSuballocIt); - - ++m_FreeCount; - } - } - // This is the last item. - else - { - // Create free item at the end. - VmaSuballocation newFreeSuballoc; - newFreeSuballoc.hAllocation = VK_NULL_HANDLE; - newFreeSuballoc.offset = suballoc.offset + newSize; - newFreeSuballoc.size = sizeDiff; - newFreeSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; - m_Suballocations.push_back(newFreeSuballoc); - - iter_type newFreeSuballocIt = m_Suballocations.end(); - RegisterFreeSuballocation(--newFreeSuballocIt); - - ++m_FreeCount; - } - - suballoc.size = newSize; - m_SumFreeSize += sizeDiff; - } - // Growing. - else - { - const VkDeviceSize sizeDiff = newSize - suballoc.size; - - // There is next item. - if(nextItem != m_Suballocations.end()) - { - // Next item is free. - if(nextItem->type == VMA_SUBALLOCATION_TYPE_FREE) - { - // There is not enough free space, including margin. - if(nextItem->size < sizeDiff + VMA_DEBUG_MARGIN) - { - return false; - } - - // There is more free space than required. - if(nextItem->size > sizeDiff) - { - // Move and shrink this next item. - UnregisterFreeSuballocation(nextItem); - nextItem->offset += sizeDiff; - nextItem->size -= sizeDiff; - RegisterFreeSuballocation(nextItem); - } - // There is exactly the amount of free space required. - else - { - // Remove this next free item. - UnregisterFreeSuballocation(nextItem); - m_Suballocations.erase(nextItem); - --m_FreeCount; - } - } - // Next item is not free - there is no space to grow. - else - { - return false; - } - } - // This is the last item - there is no space to grow. - else - { - return false; - } - - suballoc.size = newSize; - m_SumFreeSize -= sizeDiff; - } - - // We cannot call Validate() here because alloc object is updated to new size outside of this call. - return true; - } - } - VMA_ASSERT(0 && "Not found!"); - return false; -} - bool VmaBlockMetadata_Generic::ValidateFreeSuballocationList() const { VkDeviceSize lastSize = 0; @@ -8368,7 +9611,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(suballocItem != m_Suballocations.cend()); VMA_ASSERT(pOffset != VMA_NULL); - + *itemsToMakeLostCount = 0; *pSumFreeSize = 0; *pSumItemSize = 0; @@ -8401,19 +9644,19 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Start from offset equal to beginning of this suballocation. *pOffset = suballocItem->offset; - + // Apply VMA_DEBUG_MARGIN at the beginning. if(VMA_DEBUG_MARGIN > 0) { *pOffset += VMA_DEBUG_MARGIN; } - + // Apply alignment. *pOffset = VmaAlignUp(*pOffset, allocAlignment); // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment) { bool bufferImageGranularityConflict = false; VmaSuballocationList::const_iterator prevSuballocItem = suballocItem; @@ -8438,14 +9681,14 @@ bool VmaBlockMetadata_Generic::CheckAllocation( *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity); } } - + // Now that we have final *pOffset, check if we are past suballocItem. // If yes, return false - this function should be called for another suballocItem as starting point. if(*pOffset >= suballocItem->offset + suballocItem->size) { return false; } - + // Calculate padding at the beginning based on current offset. const VkDeviceSize paddingBegin = *pOffset - suballocItem->offset; @@ -8497,7 +9740,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, we must mark more allocations lost or fail. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || *pOffset % bufferImageGranularity) { VmaSuballocationList::const_iterator nextSuballocItem = lastSuballocItem; ++nextSuballocItem; @@ -8544,19 +9787,19 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Start from offset equal to beginning of this suballocation. *pOffset = suballoc.offset; - + // Apply VMA_DEBUG_MARGIN at the beginning. if(VMA_DEBUG_MARGIN > 0) { *pOffset += VMA_DEBUG_MARGIN; } - + // Apply alignment. *pOffset = VmaAlignUp(*pOffset, allocAlignment); - + // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment) { bool bufferImageGranularityConflict = false; VmaSuballocationList::const_iterator prevSuballocItem = suballocItem; @@ -8581,7 +9824,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity); } } - + // Calculate padding at the beginning based on current offset. const VkDeviceSize paddingBegin = *pOffset - suballoc.offset; @@ -8596,7 +9839,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || *pOffset % bufferImageGranularity) { VmaSuballocationList::const_iterator nextSuballocItem = suballocItem; ++nextSuballocItem; @@ -8628,7 +9871,7 @@ void VmaBlockMetadata_Generic::MergeFreeWithNext(VmaSuballocationList::iterator { VMA_ASSERT(item != m_Suballocations.end()); VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE); - + VmaSuballocationList::iterator nextItem = item; ++nextItem; VMA_ASSERT(nextItem != m_Suballocations.end()); @@ -8645,7 +9888,7 @@ VmaSuballocationList::iterator VmaBlockMetadata_Generic::FreeSuballocation(VmaSu VmaSuballocation& suballoc = *suballocItem; suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; - + // Update totals. ++m_FreeCount; m_SumFreeSize += suballoc.size; @@ -8653,7 +9896,7 @@ VmaSuballocationList::iterator VmaBlockMetadata_Generic::FreeSuballocation(VmaSu // Merge with previous and/or next suballocation if it's also free. bool mergeWithNext = false; bool mergeWithPrev = false; - + VmaSuballocationList::iterator nextItem = suballocItem; ++nextItem; if((nextItem != m_Suballocations.end()) && (nextItem->type == VMA_SUBALLOCATION_TYPE_FREE)) @@ -8951,7 +10194,7 @@ VkDeviceSize VmaBlockMetadata_Linear::GetUnusedRangeSizeMax() const { return size; } - + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); switch(m_2ndVectorMode) @@ -9038,7 +10281,7 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9049,13 +10292,13 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9095,7 +10338,7 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9106,13 +10349,13 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9151,7 +10394,7 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9162,13 +10405,13 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9224,7 +10467,7 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9234,11 +10477,11 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9277,7 +10520,7 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9287,11 +10530,11 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9329,7 +10572,7 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9339,11 +10582,11 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9401,19 +10644,19 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc2ndCount; usedBytes += suballoc.size; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9450,19 +10693,19 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc1stCount; usedBytes += suballoc.size; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9497,19 +10740,19 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc2ndCount; usedBytes += suballoc.size; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9552,7 +10795,7 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9560,11 +10803,11 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9599,7 +10842,7 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9607,11 +10850,11 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9647,7 +10890,7 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9655,11 +10898,11 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9764,7 +11007,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress( // Check next suballocations from 2nd for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1 && !suballocations2nd.empty()) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty()) { bool bufferImageGranularityConflict = false; for(size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) @@ -9869,7 +11112,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1 && !suballocations1st.empty()) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations1st.empty()) { bool bufferImageGranularityConflict = false; for(size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) @@ -9901,7 +11144,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( { // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. - if(bufferImageGranularity > 1 && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + if((allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { for(size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) { @@ -9959,7 +11202,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1 && !suballocations2nd.empty()) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty()) { bool bufferImageGranularityConflict = false; for(size_t prevSuballocIndex = suballocations2nd.size(); prevSuballocIndex--; ) @@ -10017,7 +11260,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, we must mark more allocations lost or fail. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) { while(index1st < suballocations1st.size()) { @@ -10063,7 +11306,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( { // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) { for(size_t nextSuballocIndex = index1st; nextSuballocIndex < suballocations1st.size(); @@ -10111,7 +11354,7 @@ bool VmaBlockMetadata_Linear::MakeRequestedAllocationsLost( } VMA_ASSERT(m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER); - + // We always start from 1st. SuballocationVectorType* suballocations = &AccessSuballocations1st(); size_t index = m_1stNullItemsBeginCount; @@ -10160,14 +11403,14 @@ bool VmaBlockMetadata_Linear::MakeRequestedAllocationsLost( CleanupAfterFree(); //VMA_HEAVY_ASSERT(Validate()); // Already called by ClanupAfterFree(). - + return true; } uint32_t VmaBlockMetadata_Linear::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) { uint32_t lostAllocationCount = 0; - + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); for(size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) { @@ -10374,10 +11617,11 @@ void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset) VmaSuballocation refSuballoc; refSuballoc.offset = offset; // Rest of members stays uninitialized intentionally for better performance. - SuballocationVectorType::iterator it = VmaVectorFindSorted( + SuballocationVectorType::iterator it = VmaBinaryFindSorted( suballocations1st.begin() + m_1stNullItemsBeginCount, suballocations1st.end(), - refSuballoc); + refSuballoc, + VmaSuballocationOffsetLess()); if(it != suballocations1st.end()) { it->type = VMA_SUBALLOCATION_TYPE_FREE; @@ -10396,8 +11640,8 @@ void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset) refSuballoc.offset = offset; // Rest of members stays uninitialized intentionally for better performance. SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? - VmaVectorFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc) : - VmaVectorFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc); + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetLess()) : + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetGreater()); if(it != suballocations2nd.end()) { it->type = VMA_SUBALLOCATION_TYPE_FREE; @@ -10468,7 +11712,7 @@ void VmaBlockMetadata_Linear::CleanupAfterFree() suballocations2nd[0].hAllocation == VK_NULL_HANDLE) { --m_2ndNullItemsCount; - suballocations2nd.remove(0); + VmaVectorRemove(suballocations2nd, 0); } if(ShouldCompact1st()) @@ -10590,7 +11834,7 @@ bool VmaBlockMetadata_Buddy::Validate() const node = node->free.next) { VMA_VALIDATE(node->type == Node::TYPE_FREE); - + if(node->free.next == VMA_NULL) { VMA_VALIDATE(m_FreeList[level].back == node); @@ -10776,7 +12020,7 @@ void VmaBlockMetadata_Buddy::Alloc( const uint32_t targetLevel = AllocSizeToLevel(allocSize); uint32_t currLevel = (uint32_t)(uintptr_t)request.customData; - + Node* currNode = m_FreeList[currLevel].front; VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); while(currNode->offset != request.offset) @@ -10784,14 +12028,14 @@ void VmaBlockMetadata_Buddy::Alloc( currNode = currNode->free.next; VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); } - + // Go down, splitting free nodes. while(currLevel < targetLevel) { // currNode is already first free node at currLevel. // Remove it from list of free nodes at this currLevel. RemoveFromFreeList(currLevel, currNode); - + const uint32_t childrenLevel = currLevel + 1; // Create two free sub-nodes. @@ -10953,7 +12197,7 @@ void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offs vma_delete(GetAllocationCallbacks(), node->buddy); vma_delete(GetAllocationCallbacks(), node); parent->type = Node::TYPE_FREE; - + node = parent; --level; //m_SumFreeSize += LevelToNodeSize(level) % 2; // Useful only when level node sizes can be non power of 2. @@ -11067,7 +12311,7 @@ void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, con PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize); break; case Node::TYPE_ALLOCATION: - { + { PrintDetailedMap_Allocation(json, node->offset, node->allocation.alloc); const VkDeviceSize allocSize = node->allocation.alloc->GetSize(); if(allocSize < levelNodeSize) @@ -11156,7 +12400,7 @@ bool VmaDeviceMemoryBlock::Validate() const { VMA_VALIDATE((m_hMemory != VK_NULL_HANDLE) && (m_pMetadata->GetSize() != 0)); - + return m_pMetadata->Validate(); } @@ -11287,33 +12531,35 @@ VkResult VmaDeviceMemoryBlock::ValidateMagicValueAroundAllocation(VmaAllocator h VkResult VmaDeviceMemoryBlock::BindBufferMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkBuffer hBuffer) + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext) { VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && hAllocation->GetBlock() == this); + VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && + "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); - return hAllocator->GetVulkanFunctions().vkBindBufferMemory( - hAllocator->m_hDevice, - hBuffer, - m_hMemory, - hAllocation->GetOffset()); + return hAllocator->BindVulkanBuffer(m_hMemory, memoryOffset, hBuffer, pNext); } VkResult VmaDeviceMemoryBlock::BindImageMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkImage hImage) + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext) { VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && hAllocation->GetBlock() == this); + VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && + "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); - return hAllocator->GetVulkanFunctions().vkBindImageMemory( - hAllocator->m_hDevice, - hImage, - m_hMemory, - hAllocation->GetOffset()); + return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext); } static void InitStatInfo(VmaStatInfo& outInfo) @@ -11358,10 +12604,11 @@ VmaPool_T::VmaPool_T( createInfo.maxBlockCount, (createInfo.flags & VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT) != 0 ? 1 : hAllocator->GetBufferImageGranularity(), createInfo.frameInUseCount, - true, // isCustomPool createInfo.blockSize != 0, // explicitBlockSize - createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK), // algorithm - m_Id(0) + createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK, + createInfo.priority), // algorithm + m_Id(0), + m_Name(VMA_NULL) { } @@ -11369,6 +12616,21 @@ VmaPool_T::~VmaPool_T() { } +void VmaPool_T::SetName(const char* pName) +{ + const VkAllocationCallbacks* allocs = m_BlockVector.GetAllocator()->GetAllocationCallbacks(); + VmaFreeString(allocs, m_Name); + + if(pName != VMA_NULL) + { + m_Name = VmaCreateStringCopy(allocs, pName); + } + else + { + m_Name = VMA_NULL; + } +} + #if VMA_STATS_STRING_ENABLED #endif // #if VMA_STATS_STRING_ENABLED @@ -11382,9 +12644,9 @@ VmaBlockVector::VmaBlockVector( size_t maxBlockCount, VkDeviceSize bufferImageGranularity, uint32_t frameInUseCount, - bool isCustomPool, bool explicitBlockSize, - uint32_t algorithm) : + uint32_t algorithm, + float priority) : m_hAllocator(hAllocator), m_hParentPool(hParentPool), m_MemoryTypeIndex(memoryTypeIndex), @@ -11393,9 +12655,9 @@ VmaBlockVector::VmaBlockVector( m_MaxBlockCount(maxBlockCount), m_BufferImageGranularity(bufferImageGranularity), m_FrameInUseCount(frameInUseCount), - m_IsCustomPool(isCustomPool), m_ExplicitBlockSize(explicitBlockSize), m_Algorithm(algorithm), + m_Priority(priority), m_HasEmptyBlock(false), m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), m_NextBlockId(0) @@ -11446,6 +12708,12 @@ void VmaBlockVector::GetPoolStats(VmaPoolStats* pStats) } } +bool VmaBlockVector::IsEmpty() +{ + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + return m_Blocks.empty(); +} + bool VmaBlockVector::IsCorruptionDetectionEnabled() const { const uint32_t requiredMemFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; @@ -11518,9 +12786,20 @@ VkResult VmaBlockVector::AllocatePage( bool canMakeOtherLost = (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) != 0; const bool mapped = (createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; const bool isUserDataString = (createInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0; + + VkDeviceSize freeMemory; + { + const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); + VmaBudget heapBudget = {}; + m_hAllocator->GetBudget(&heapBudget, heapIndex, 1); + freeMemory = (heapBudget.usage < heapBudget.budget) ? (heapBudget.budget - heapBudget.usage) : 0; + } + + const bool canFallbackToDedicated = !IsCustomPool(); const bool canCreateNewBlock = ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) && - (m_Blocks.size() < m_MaxBlockCount); + (m_Blocks.size() < m_MaxBlockCount) && + (freeMemory >= size || !canFallbackToDedicated); uint32_t strategy = createInfo.flags & VMA_ALLOCATION_CREATE_STRATEGY_MASK; // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer. @@ -11587,7 +12866,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Returned from last block #%u", (uint32_t)(m_Blocks.size() - 1)); + VMA_DEBUG_LOG(" Returned from last block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } @@ -11613,7 +12892,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); + VMA_DEBUG_LOG(" Returned from existing block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } @@ -11637,7 +12916,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); + VMA_DEBUG_LOG(" Returned from existing block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } @@ -11672,7 +12951,8 @@ VkResult VmaBlockVector::AllocatePage( } size_t newBlockIndex = 0; - VkResult res = CreateBlock(newBlockSize, &newBlockIndex); + VkResult res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? + CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. if(!m_ExplicitBlockSize) { @@ -11683,7 +12963,8 @@ VkResult VmaBlockVector::AllocatePage( { newBlockSize = smallerNewBlockSize; ++newBlockSizeShift; - res = CreateBlock(newBlockSize, &newBlockIndex); + res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? + CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; } else { @@ -11709,7 +12990,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Created new block Size=%llu", newBlockSize); + VMA_DEBUG_LOG(" Created new block #%u Size=%llu", pBlock->GetId(), newBlockSize); return VK_SUCCESS; } else @@ -11823,26 +13104,23 @@ VkResult VmaBlockVector::AllocatePage( m_FrameInUseCount, &bestRequest)) { - // We no longer have an empty Allocation. - if(pBestRequestBlock->m_pMetadata->IsEmpty()) - { - m_HasEmptyBlock = false; - } // Allocate from this pBlock. - *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); + *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(currentFrameIndex, isUserDataString); pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation); + UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBestRequestBlock, bestRequest.offset, alignment, size, + m_MemoryTypeIndex, suballocType, mapped, (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0); VMA_HEAVY_ASSERT(pBestRequestBlock->Validate()); VMA_DEBUG_LOG(" Returned from existing block"); (*pAllocation)->SetUserData(m_hAllocator, createInfo.pUserData); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size); if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); @@ -11875,10 +13153,18 @@ VkResult VmaBlockVector::AllocatePage( } void VmaBlockVector::Free( - VmaAllocation hAllocation) + const VmaAllocation hAllocation) { VmaDeviceMemoryBlock* pBlockToDelete = VMA_NULL; + bool budgetExceeded = false; + { + const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); + VmaBudget heapBudget = {}; + m_hAllocator->GetBudget(&heapBudget, heapIndex, 1); + budgetExceeded = heapBudget.usage >= heapBudget.budget; + } + // Scope for lock. { VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); @@ -11901,42 +13187,39 @@ void VmaBlockVector::Free( VMA_DEBUG_LOG(" Freed from MemoryTypeIndex=%u", m_MemoryTypeIndex); + const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount; // pBlock became empty after this deallocation. if(pBlock->m_pMetadata->IsEmpty()) { - // Already has empty Allocation. We don't want to have two, so delete this one. - if(m_HasEmptyBlock && m_Blocks.size() > m_MinBlockCount) + // Already has empty block. We don't want to have two, so delete this one. + if((m_HasEmptyBlock || budgetExceeded) && canDeleteBlock) { pBlockToDelete = pBlock; Remove(pBlock); } - // We now have first empty block. - else - { - m_HasEmptyBlock = true; - } + // else: We now have an empty block - leave it. } // pBlock didn't become empty, but we have another empty block - find and free that one. // (This is optional, heuristics.) - else if(m_HasEmptyBlock) + else if(m_HasEmptyBlock && canDeleteBlock) { VmaDeviceMemoryBlock* pLastBlock = m_Blocks.back(); - if(pLastBlock->m_pMetadata->IsEmpty() && m_Blocks.size() > m_MinBlockCount) + if(pLastBlock->m_pMetadata->IsEmpty()) { pBlockToDelete = pLastBlock; m_Blocks.pop_back(); - m_HasEmptyBlock = false; } } + UpdateHasEmptyBlock(); IncrementallySortBlocks(); } - // Destruction of a free Allocation. Deferred until this point, outside of mutex + // Destruction of a free block. Deferred until this point, outside of mutex // lock, for performance reason. if(pBlockToDelete != VMA_NULL) { - VMA_DEBUG_LOG(" Deleted empty allocation"); + VMA_DEBUG_LOG(" Deleted empty block"); pBlockToDelete->Destroy(m_hAllocator); vma_delete(m_hAllocator, pBlockToDelete); } @@ -12025,26 +13308,22 @@ VkResult VmaBlockVector::AllocateFromBlock( return res; } } - - // We no longer have an empty Allocation. - if(pBlock->m_pMetadata->IsEmpty()) - { - m_HasEmptyBlock = false; - } - - *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); + + *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(currentFrameIndex, isUserDataString); pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation); + UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBlock, currRequest.offset, alignment, size, + m_MemoryTypeIndex, suballocType, mapped, (allocFlags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0); VMA_HEAVY_ASSERT(pBlock->Validate()); (*pAllocation)->SetUserData(m_hAllocator, pUserData); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size); if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); @@ -12064,6 +13343,26 @@ VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIn VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = m_MemoryTypeIndex; allocInfo.allocationSize = blockSize; + +#if VMA_BUFFER_DEVICE_ADDRESS + // Every standalone block can potentially contain a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT - always enable the feature. + VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR }; + if(m_hAllocator->m_UseKhrBufferDeviceAddress) + { + allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo); + } +#endif // #if VMA_BUFFER_DEVICE_ADDRESS + +#if VMA_MEMORY_PRIORITY + VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT }; + if(m_hAllocator->m_UseExtMemoryPriority) + { + priorityInfo.priority = m_Priority; + VmaPnextChainPushFront(&allocInfo, &priorityInfo); + } +#endif // #if VMA_MEMORY_PRIORITY + VkDeviceMemory mem = VK_NULL_HANDLE; VkResult res = m_hAllocator->AllocateVulkanMemory(&allocInfo, &mem); if(res < 0) @@ -12112,7 +13411,7 @@ void VmaBlockVector::ApplyDefragmentationMovesCpu( void* pMappedData; }; VmaVector< BlockInfo, VmaStlAllocator > - blockInfo(blockCount, VmaStlAllocator(m_hAllocator->GetAllocationCallbacks())); + blockInfo(blockCount, BlockInfo(), VmaStlAllocator(m_hAllocator->GetAllocationCallbacks())); memset(blockInfo.data(), 0, blockCount * sizeof(BlockInfo)); // Go over all moves. Mark blocks that are used with BLOCK_FLAG_USED. @@ -12214,7 +13513,7 @@ void VmaBlockVector::ApplyDefragmentationMovesCpu( void VmaBlockVector::ApplyDefragmentationMovesGpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, - const VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, + VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkCommandBuffer commandBuffer) { const size_t blockCount = m_Blocks.size(); @@ -12227,17 +13526,21 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( for(size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; - pDefragCtx->blockContexts[move.srcBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; - pDefragCtx->blockContexts[move.dstBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; + + //if(move.type == VMA_ALLOCATION_TYPE_UNKNOWN) + { + // Old school move still require us to map the whole block + pDefragCtx->blockContexts[move.srcBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; + pDefragCtx->blockContexts[move.dstBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; + } } VMA_ASSERT(pDefragCtx->res == VK_SUCCESS); // Go over all blocks. Create and bind buffer for whole block if necessary. { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VkBufferCreateInfo bufCreateInfo; + VmaFillGpuDefragmentationBufferCreateInfo(bufCreateInfo); for(size_t blockIndex = 0; pDefragCtx->res == VK_SUCCESS && blockIndex < blockCount; ++blockIndex) { @@ -12260,9 +13563,6 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( // Go over all moves. Post data transfer commands to command buffer. if(pDefragCtx->res == VK_SUCCESS) { - const VkDeviceSize nonCoherentAtomSize = m_hAllocator->m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; - VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; - for(size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; @@ -12290,7 +13590,6 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats) { - m_HasEmptyBlock = false; for(size_t blockIndex = m_Blocks.size(); blockIndex--; ) { VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; @@ -12310,10 +13609,25 @@ void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationSt } else { - m_HasEmptyBlock = true; + break; } } } + UpdateHasEmptyBlock(); +} + +void VmaBlockVector::UpdateHasEmptyBlock() +{ + m_HasEmptyBlock = false; + for(size_t index = 0, count = m_Blocks.size(); index < count; ++index) + { + VmaDeviceMemoryBlock* const pBlock = m_Blocks[index]; + if(pBlock->m_pMetadata->IsEmpty()) + { + m_HasEmptyBlock = true; + break; + } + } } #if VMA_STATS_STRING_ENABLED @@ -12324,8 +13638,15 @@ void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) json.BeginObject(); - if(m_IsCustomPool) + if(IsCustomPool()) { + const char* poolName = m_hParentPool->GetName(); + if(poolName != VMA_NULL && poolName[0] != '\0') + { + json.WriteString("Name"); + json.WriteString(poolName); + } + json.WriteString("MemoryTypeIndex"); json.WriteNumber(m_MemoryTypeIndex); @@ -12385,22 +13706,22 @@ void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) void VmaBlockVector::Defragment( class VmaBlockVectorDefragmentationContext* pCtx, - VmaDefragmentationStats* pStats, + VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags, VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove, VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove, VkCommandBuffer commandBuffer) { pCtx->res = VK_SUCCESS; - + const VkMemoryPropertyFlags memPropFlags = m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags; const bool isHostVisible = (memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0; - const bool isHostCoherent = (memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; const bool canDefragmentOnCpu = maxCpuBytesToMove > 0 && maxCpuAllocationsToMove > 0 && isHostVisible; const bool canDefragmentOnGpu = maxGpuBytesToMove > 0 && maxGpuAllocationsToMove > 0 && - !IsCorruptionDetectionEnabled(); + !IsCorruptionDetectionEnabled() && + ((1u << m_MemoryTypeIndex) & m_hAllocator->GetGpuDefragmentationMemoryTypeBits()) != 0; // There are options to defragment this memory type. if(canDefragmentOnCpu || canDefragmentOnGpu) @@ -12422,19 +13743,28 @@ void VmaBlockVector::Defragment( if(m_hAllocator->m_UseMutex) { - m_Mutex.LockWrite(); - pCtx->mutexLocked = true; + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL) + { + if(!m_Mutex.TryLockWrite()) + { + pCtx->res = VK_ERROR_INITIALIZATION_FAILED; + return; + } + } + else + { + m_Mutex.LockWrite(); + pCtx->mutexLocked = true; + } } - pCtx->Begin(overlappingMoveSupported); + pCtx->Begin(overlappingMoveSupported, flags); // Defragment. const VkDeviceSize maxBytesToMove = defragmentOnGpu ? maxGpuBytesToMove : maxCpuBytesToMove; const uint32_t maxAllocationsToMove = defragmentOnGpu ? maxGpuAllocationsToMove : maxCpuAllocationsToMove; - VmaVector< VmaDefragmentationMove, VmaStlAllocator > moves = - VmaVector< VmaDefragmentationMove, VmaStlAllocator >(VmaStlAllocator(m_hAllocator->GetAllocationCallbacks())); - pCtx->res = pCtx->GetAlgorithm()->Defragment(moves, maxBytesToMove, maxAllocationsToMove); + pCtx->res = pCtx->GetAlgorithm()->Defragment(pCtx->defragmentationMoves, maxBytesToMove, maxAllocationsToMove, flags); // Accumulate statistics. if(pStats != VMA_NULL) @@ -12456,16 +13786,27 @@ void VmaBlockVector::Defragment( maxCpuAllocationsToMove -= allocationsMoved; } } - + + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL) + { + if(m_hAllocator->m_UseMutex) + m_Mutex.UnlockWrite(); + + if(pCtx->res >= VK_SUCCESS && !pCtx->defragmentationMoves.empty()) + pCtx->res = VK_NOT_READY; + + return; + } + if(pCtx->res >= VK_SUCCESS) { if(defragmentOnGpu) { - ApplyDefragmentationMovesGpu(pCtx, moves, commandBuffer); + ApplyDefragmentationMovesGpu(pCtx, pCtx->defragmentationMoves, commandBuffer); } else { - ApplyDefragmentationMovesCpu(pCtx, moves); + ApplyDefragmentationMovesCpu(pCtx, pCtx->defragmentationMoves); } } } @@ -12473,22 +13814,36 @@ void VmaBlockVector::Defragment( void VmaBlockVector::DefragmentationEnd( class VmaBlockVectorDefragmentationContext* pCtx, + uint32_t flags, VmaDefragmentationStats* pStats) { - // Destroy buffers. - for(size_t blockIndex = pCtx->blockContexts.size(); blockIndex--; ) + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL && m_hAllocator->m_UseMutex) { - VmaBlockDefragmentationContext& blockCtx = pCtx->blockContexts[blockIndex]; - if(blockCtx.hBuffer) - { - (*m_hAllocator->GetVulkanFunctions().vkDestroyBuffer)( - m_hAllocator->m_hDevice, blockCtx.hBuffer, m_hAllocator->GetAllocationCallbacks()); - } + VMA_ASSERT(pCtx->mutexLocked == false); + + // Incremental defragmentation doesn't hold the lock, so when we enter here we don't actually have any + // lock protecting us. Since we mutate state here, we have to take the lock out now + m_Mutex.LockWrite(); + pCtx->mutexLocked = true; } - if(pCtx->res >= VK_SUCCESS) + // If the mutex isn't locked we didn't do any work and there is nothing to delete. + if(pCtx->mutexLocked || !m_hAllocator->m_UseMutex) { - FreeEmptyBlocks(pStats); + // Destroy buffers. + for(size_t blockIndex = pCtx->blockContexts.size(); blockIndex--;) + { + VmaBlockDefragmentationContext &blockCtx = pCtx->blockContexts[blockIndex]; + if(blockCtx.hBuffer) + { + (*m_hAllocator->GetVulkanFunctions().vkDestroyBuffer)(m_hAllocator->m_hDevice, blockCtx.hBuffer, m_hAllocator->GetAllocationCallbacks()); + } + } + + if(pCtx->res >= VK_SUCCESS) + { + FreeEmptyBlocks(pStats); + } } if(pCtx->mutexLocked) @@ -12498,6 +13853,48 @@ void VmaBlockVector::DefragmentationEnd( } } +uint32_t VmaBlockVector::ProcessDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, + VmaDefragmentationPassMoveInfo* pMove, uint32_t maxMoves) +{ + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + + const uint32_t moveCount = VMA_MIN(uint32_t(pCtx->defragmentationMoves.size()) - pCtx->defragmentationMovesProcessed, maxMoves); + + for(uint32_t i = 0; i < moveCount; ++ i) + { + VmaDefragmentationMove& move = pCtx->defragmentationMoves[pCtx->defragmentationMovesProcessed + i]; + + pMove->allocation = move.hAllocation; + pMove->memory = move.pDstBlock->GetDeviceMemory(); + pMove->offset = move.dstOffset; + + ++ pMove; + } + + pCtx->defragmentationMovesProcessed += moveCount; + + return moveCount; +} + +void VmaBlockVector::CommitDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, + VmaDefragmentationStats* pStats) +{ + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + + for(uint32_t i = pCtx->defragmentationMovesCommitted; i < pCtx->defragmentationMovesProcessed; ++ i) + { + const VmaDefragmentationMove &move = pCtx->defragmentationMoves[i]; + + move.pSrcBlock->m_pMetadata->FreeAtOffset(move.srcOffset); + move.hAllocation->ChangeBlockAllocation(m_hAllocator, move.pDstBlock, move.dstOffset); + } + + pCtx->defragmentationMovesCommitted = pCtx->defragmentationMovesProcessed; + FreeEmptyBlocks(pStats); +} + size_t VmaBlockVector::CalcAllocationCount() const { size_t result = 0; @@ -12596,8 +13993,8 @@ VmaDefragmentationAlgorithm_Generic::VmaDefragmentationAlgorithm_Generic( uint32_t currentFrameIndex, bool overlappingMoveSupported) : VmaDefragmentationAlgorithm(hAllocator, pBlockVector, currentFrameIndex), - m_AllAllocations(false), m_AllocationCount(0), + m_AllAllocations(false), m_BytesMoved(0), m_AllocationsMoved(0), m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())) @@ -12648,7 +14045,8 @@ void VmaDefragmentationAlgorithm_Generic::AddAllocation(VmaAllocation hAlloc, Vk VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) + uint32_t maxAllocationsToMove, + bool freeOldAllocations) { if(m_Blocks.empty()) { @@ -12703,7 +14101,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( srcAllocIndex = m_Blocks[srcBlockIndex]->m_Allocations.size() - 1; } } - + BlockInfo* pSrcBlockInfo = m_Blocks[srcBlockIndex]; AllocationInfo& allocInfo = pSrcBlockInfo->m_Allocations[srcAllocIndex]; @@ -12740,12 +14138,16 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( return VK_SUCCESS; } - VmaDefragmentationMove move; + VmaDefragmentationMove move = {}; move.srcBlockIndex = pSrcBlockInfo->m_OriginalBlockIndex; move.dstBlockIndex = pDstBlockInfo->m_OriginalBlockIndex; move.srcOffset = srcOffset; move.dstOffset = dstAllocRequest.offset; move.size = size; + move.hAllocation = allocInfo.m_hAllocation; + move.pSrcBlock = pSrcBlockInfo->m_pBlock; + move.pDstBlock = pDstBlockInfo->m_pBlock; + moves.push_back(move); pDstBlockInfo->m_pBlock->m_pMetadata->Alloc( @@ -12753,9 +14155,12 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( suballocType, size, allocInfo.m_hAllocation); - pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset); - - allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset); + + if(freeOldAllocations) + { + pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset); + allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset); + } if(allocInfo.m_pChanged != VMA_NULL) { @@ -12808,7 +14213,8 @@ size_t VmaDefragmentationAlgorithm_Generic::CalcBlocksWithNonMovableCount() cons VkResult VmaDefragmentationAlgorithm_Generic::Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags) { if(!m_AllAllocations && m_AllocationCount == 0) { @@ -12836,7 +14242,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::Defragment( } pBlockInfo->CalcHasNonMovableAllocations(); - + // This is a choice based on research. // Option 1: pBlockInfo->SortAllocationsByOffsetDescending(); @@ -12854,7 +14260,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::Defragment( VkResult result = VK_SUCCESS; for(uint32_t round = 0; (round < roundCount) && (result == VK_SUCCESS); ++round) { - result = DefragmentRound(moves, maxBytesToMove, maxAllocationsToMove); + result = DefragmentRound(moves, maxBytesToMove, maxAllocationsToMove, !(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL)); } return result; @@ -12906,7 +14312,8 @@ VmaDefragmentationAlgorithm_Fast::~VmaDefragmentationAlgorithm_Fast() VkResult VmaDefragmentationAlgorithm_Fast::Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags) { VMA_ASSERT(m_AllAllocations || m_pBlockVector->CalcAllocationCount() == m_AllocationCount); @@ -12962,6 +14369,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( } const VkDeviceSize srcAllocOffset = srcSuballocIt->offset; + VmaDefragmentationMove move = {}; // Try to place it in one of free spaces from the database. size_t freeSpaceInfoIndex; VkDeviceSize dstAllocOffset; @@ -12971,7 +14379,6 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( size_t freeSpaceOrigBlockIndex = m_BlockInfos[freeSpaceInfoIndex].origBlockIndex; VmaDeviceMemoryBlock* pFreeSpaceBlock = m_pBlockVector->GetBlock(freeSpaceOrigBlockIndex); VmaBlockMetadata_Generic* pFreeSpaceMetadata = (VmaBlockMetadata_Generic*)pFreeSpaceBlock->m_pMetadata; - VkDeviceSize freeSpaceBlockSize = pFreeSpaceMetadata->GetSize(); // Same block if(freeSpaceInfoIndex == srcBlockInfoIndex) @@ -12985,7 +14392,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( suballoc.hAllocation->ChangeOffset(dstAllocOffset); m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; - + VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt; ++nextSuballocIt; pSrcMetadata->m_Suballocations.erase(srcSuballocIt); @@ -12993,10 +14400,12 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( InsertSuballoc(pFreeSpaceMetadata, suballoc); - VmaDefragmentationMove move = { - srcOrigBlockIndex, freeSpaceOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = freeSpaceOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } // Different block @@ -13019,10 +14428,12 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( InsertSuballoc(pFreeSpaceMetadata, suballoc); - VmaDefragmentationMove move = { - srcOrigBlockIndex, freeSpaceOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = freeSpaceOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } } @@ -13077,10 +14488,13 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; ++srcSuballocIt; - VmaDefragmentationMove move = { - srcOrigBlockIndex, dstOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = dstOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } } @@ -13106,10 +14520,12 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( pDstMetadata->m_Suballocations.push_back(suballoc); - VmaDefragmentationMove move = { - srcOrigBlockIndex, dstOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = dstOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } } @@ -13117,7 +14533,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( } m_BlockInfos.clear(); - + PostprocessMetadata(); return VK_SUCCESS; @@ -13159,7 +14575,7 @@ void VmaDefragmentationAlgorithm_Fast::PostprocessMetadata() VmaBlockMetadata_Generic* const pMetadata = (VmaBlockMetadata_Generic*)m_pBlockVector->GetBlock(blockIndex)->m_pMetadata; const VkDeviceSize blockSize = pMetadata->GetSize(); - + // No allocations in this block - entire area is free. if(pMetadata->m_Suballocations.empty()) { @@ -13255,16 +14671,18 @@ VmaBlockVectorDefragmentationContext::VmaBlockVectorDefragmentationContext( VmaAllocator hAllocator, VmaPool hCustomPool, VmaBlockVector* pBlockVector, - uint32_t currFrameIndex, - uint32_t algorithmFlags) : + uint32_t currFrameIndex) : res(VK_SUCCESS), mutexLocked(false), blockContexts(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), + defragmentationMoves(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), + defragmentationMovesProcessed(0), + defragmentationMovesCommitted(0), + hasDefragmentationPlan(0), m_hAllocator(hAllocator), m_hCustomPool(hCustomPool), m_pBlockVector(pBlockVector), m_CurrFrameIndex(currFrameIndex), - m_AlgorithmFlags(algorithmFlags), m_pAlgorithm(VMA_NULL), m_Allocations(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), m_AllAllocations(false) @@ -13282,7 +14700,7 @@ void VmaBlockVectorDefragmentationContext::AddAllocation(VmaAllocation hAlloc, V m_Allocations.push_back(info); } -void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported) +void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported, VmaDefragmentationFlags flags) { const bool allAllocations = m_AllAllocations || m_Allocations.size() == m_pBlockVector->CalcAllocationCount(); @@ -13296,10 +14714,12 @@ void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported) - VMA_DEBUG_MARGIN is 0. - All allocations in this block vector are moveable. - There is no possibility of image/buffer granularity conflict. + - The defragmentation is not incremental */ if(VMA_DEBUG_MARGIN == 0 && allAllocations && - !m_pBlockVector->IsBufferImageGranularityConflictPossible()) + !m_pBlockVector->IsBufferImageGranularityConflictPossible() && + !(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL)) { m_pAlgorithm = vma_new(m_hAllocator, VmaDefragmentationAlgorithm_Fast)( m_hAllocator, m_pBlockVector, m_CurrFrameIndex, overlappingMoveSupported); @@ -13345,7 +14765,7 @@ VmaDefragmentationContext_T::~VmaDefragmentationContext_T() for(size_t i = m_CustomPoolContexts.size(); i--; ) { VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_CustomPoolContexts[i]; - pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats); + pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_Flags, m_pStats); vma_delete(m_hAllocator, pBlockVectorCtx); } for(size_t i = m_hAllocator->m_MemProps.memoryTypeCount; i--; ) @@ -13353,13 +14773,13 @@ VmaDefragmentationContext_T::~VmaDefragmentationContext_T() VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_DefaultPoolContexts[i]; if(pBlockVectorCtx) { - pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats); + pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_Flags, m_pStats); vma_delete(m_hAllocator, pBlockVectorCtx); } } } -void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) +void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, const VmaPool* pPools) { for(uint32_t poolIndex = 0; poolIndex < poolCount; ++poolIndex) { @@ -13369,7 +14789,7 @@ void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) if(pool->m_BlockVector.GetAlgorithm() == 0) { VmaBlockVectorDefragmentationContext* pBlockVectorDefragCtx = VMA_NULL; - + for(size_t i = m_CustomPoolContexts.size(); i--; ) { if(m_CustomPoolContexts[i]->GetCustomPool() == pool) @@ -13378,15 +14798,14 @@ void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) break; } } - + if(!pBlockVectorDefragCtx) { pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)( m_hAllocator, pool, &pool->m_BlockVector, - m_CurrFrameIndex, - m_Flags); + m_CurrFrameIndex); m_CustomPoolContexts.push_back(pBlockVectorDefragCtx); } @@ -13397,7 +14816,7 @@ void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) void VmaDefragmentationContext_T::AddAllocations( uint32_t allocationCount, - VmaAllocation* pAllocations, + const VmaAllocation* pAllocations, VkBool32* pAllocationsChanged) { // Dispatch pAllocations among defragmentators. Create them when necessary. @@ -13433,8 +14852,7 @@ void VmaDefragmentationContext_T::AddAllocations( m_hAllocator, hAllocPool, &hAllocPool->m_BlockVector, - m_CurrFrameIndex, - m_Flags); + m_CurrFrameIndex); m_CustomPoolContexts.push_back(pBlockVectorDefragCtx); } } @@ -13450,8 +14868,7 @@ void VmaDefragmentationContext_T::AddAllocations( m_hAllocator, VMA_NULL, // hCustomPool m_hAllocator->m_pBlockVectors[memTypeIndex], - m_CurrFrameIndex, - m_Flags); + m_CurrFrameIndex); m_DefaultPoolContexts[memTypeIndex] = pBlockVectorDefragCtx; } } @@ -13469,13 +14886,30 @@ void VmaDefragmentationContext_T::AddAllocations( VkResult VmaDefragmentationContext_T::Defragment( VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove, VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove, - VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats) + VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags) { if(pStats) { memset(pStats, 0, sizeof(VmaDefragmentationStats)); } + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL) + { + // For incremental defragmetnations, we just earmark how much we can move + // The real meat is in the defragmentation steps + m_MaxCpuBytesToMove = maxCpuBytesToMove; + m_MaxCpuAllocationsToMove = maxCpuAllocationsToMove; + + m_MaxGpuBytesToMove = maxGpuBytesToMove; + m_MaxGpuAllocationsToMove = maxGpuAllocationsToMove; + + if(m_MaxCpuBytesToMove == 0 && m_MaxCpuAllocationsToMove == 0 && + m_MaxGpuBytesToMove == 0 && m_MaxGpuAllocationsToMove == 0) + return VK_SUCCESS; + + return VK_NOT_READY; + } + if(commandBuffer == VK_NULL_HANDLE) { maxGpuBytesToMove = 0; @@ -13495,7 +14929,7 @@ VkResult VmaDefragmentationContext_T::Defragment( VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); pBlockVectorCtx->GetBlockVector()->Defragment( pBlockVectorCtx, - pStats, + pStats, flags, maxCpuBytesToMove, maxCpuAllocationsToMove, maxGpuBytesToMove, maxGpuAllocationsToMove, commandBuffer); @@ -13515,7 +14949,7 @@ VkResult VmaDefragmentationContext_T::Defragment( VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); pBlockVectorCtx->GetBlockVector()->Defragment( pBlockVectorCtx, - pStats, + pStats, flags, maxCpuBytesToMove, maxCpuAllocationsToMove, maxGpuBytesToMove, maxGpuAllocationsToMove, commandBuffer); @@ -13528,6 +14962,132 @@ VkResult VmaDefragmentationContext_T::Defragment( return res; } +VkResult VmaDefragmentationContext_T::DefragmentPassBegin(VmaDefragmentationPassInfo* pInfo) +{ + VmaDefragmentationPassMoveInfo* pCurrentMove = pInfo->pMoves; + uint32_t movesLeft = pInfo->moveCount; + + // Process default pools. + for(uint32_t memTypeIndex = 0; + memTypeIndex < m_hAllocator->GetMemoryTypeCount(); + ++memTypeIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_DefaultPoolContexts[memTypeIndex]; + if(pBlockVectorCtx) + { + VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + pBlockVectorCtx->GetBlockVector()->Defragment( + pBlockVectorCtx, + m_pStats, m_Flags, + m_MaxCpuBytesToMove, m_MaxCpuAllocationsToMove, + m_MaxGpuBytesToMove, m_MaxGpuAllocationsToMove, + VK_NULL_HANDLE); + + if(pBlockVectorCtx->res < VK_SUCCESS) + continue; + + pBlockVectorCtx->hasDefragmentationPlan = true; + } + + const uint32_t processed = pBlockVectorCtx->GetBlockVector()->ProcessDefragmentations( + pBlockVectorCtx, + pCurrentMove, movesLeft); + + movesLeft -= processed; + pCurrentMove += processed; + } + } + + // Process custom pools. + for(size_t customCtxIndex = 0, customCtxCount = m_CustomPoolContexts.size(); + customCtxIndex < customCtxCount; + ++customCtxIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_CustomPoolContexts[customCtxIndex]; + VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + pBlockVectorCtx->GetBlockVector()->Defragment( + pBlockVectorCtx, + m_pStats, m_Flags, + m_MaxCpuBytesToMove, m_MaxCpuAllocationsToMove, + m_MaxGpuBytesToMove, m_MaxGpuAllocationsToMove, + VK_NULL_HANDLE); + + if(pBlockVectorCtx->res < VK_SUCCESS) + continue; + + pBlockVectorCtx->hasDefragmentationPlan = true; + } + + const uint32_t processed = pBlockVectorCtx->GetBlockVector()->ProcessDefragmentations( + pBlockVectorCtx, + pCurrentMove, movesLeft); + + movesLeft -= processed; + pCurrentMove += processed; + } + + pInfo->moveCount = pInfo->moveCount - movesLeft; + + return VK_SUCCESS; +} +VkResult VmaDefragmentationContext_T::DefragmentPassEnd() +{ + VkResult res = VK_SUCCESS; + + // Process default pools. + for(uint32_t memTypeIndex = 0; + memTypeIndex < m_hAllocator->GetMemoryTypeCount(); + ++memTypeIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_DefaultPoolContexts[memTypeIndex]; + if(pBlockVectorCtx) + { + VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + res = VK_NOT_READY; + continue; + } + + pBlockVectorCtx->GetBlockVector()->CommitDefragmentations( + pBlockVectorCtx, m_pStats); + + if(pBlockVectorCtx->defragmentationMoves.size() != pBlockVectorCtx->defragmentationMovesCommitted) + res = VK_NOT_READY; + } + } + + // Process custom pools. + for(size_t customCtxIndex = 0, customCtxCount = m_CustomPoolContexts.size(); + customCtxIndex < customCtxCount; + ++customCtxIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_CustomPoolContexts[customCtxIndex]; + VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + res = VK_NOT_READY; + continue; + } + + pBlockVectorCtx->GetBlockVector()->CommitDefragmentations( + pBlockVectorCtx, m_pStats); + + if(pBlockVectorCtx->defragmentationMoves.size() != pBlockVectorCtx->defragmentationMovesCommitted) + res = VK_NOT_READY; + } + + return res; +} + //////////////////////////////////////////////////////////////////////////////// // VmaRecorder @@ -13537,8 +15097,7 @@ VmaRecorder::VmaRecorder() : m_UseMutex(true), m_Flags(0), m_File(VMA_NULL), - m_Freq(INT64_MAX), - m_StartCounter(INT64_MAX) + m_RecordingStartTime(std::chrono::high_resolution_clock::now()) { } @@ -13547,19 +15106,27 @@ VkResult VmaRecorder::Init(const VmaRecordSettings& settings, bool useMutex) m_UseMutex = useMutex; m_Flags = settings.flags; - QueryPerformanceFrequency((LARGE_INTEGER*)&m_Freq); - QueryPerformanceCounter((LARGE_INTEGER*)&m_StartCounter); - +#if defined(_WIN32) // Open file for writing. errno_t err = fopen_s(&m_File, settings.pFilePath, "wb"); + if(err != 0) { return VK_ERROR_INITIALIZATION_FAILED; } +#else + // Open file for writing. + m_File = fopen(settings.pFilePath, "wb"); + + if(m_File == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } +#endif // Write header. fprintf(m_File, "%s\n", "Vulkan Memory Allocator,Calls recording"); - fprintf(m_File, "%s\n", "1,5"); + fprintf(m_File, "%s\n", "1,8"); return VK_SUCCESS; } @@ -13755,20 +15322,6 @@ void VmaRecorder::RecordFreeMemoryPages(uint32_t frameIndex, Flush(); } -void VmaRecorder::RecordResizeAllocation( - uint32_t frameIndex, - VmaAllocation allocation, - VkDeviceSize newSize) -{ - CallParams callParams; - GetBasicParams(callParams); - - VmaMutexLock lock(m_FileMutex, m_UseMutex); - fprintf(m_File, "%u,%.3f,%u,vmaResizeAllocation,%p,%llu\n", callParams.threadId, callParams.time, frameIndex, - allocation, newSize); - Flush(); -} - void VmaRecorder::RecordSetAllocationUserData(uint32_t frameIndex, VmaAllocation allocation, const void* pUserData) @@ -14006,6 +15559,19 @@ void VmaRecorder::RecordDefragmentationEnd(uint32_t frameIndex, Flush(); } +void VmaRecorder::RecordSetPoolName(uint32_t frameIndex, + VmaPool pool, + const char* name) +{ + CallParams callParams; + GetBasicParams(callParams); + + VmaMutexLock lock(m_FileMutex, m_UseMutex); + fprintf(m_File, "%u,%.3f,%u,vmaSetPoolName,%p,%s\n", callParams.threadId, callParams.time, frameIndex, + pool, name != VMA_NULL ? name : ""); + Flush(); +} + VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, const void* pUserData) { if(pUserData != VMA_NULL) @@ -14016,7 +15582,8 @@ VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, } else { - sprintf_s(m_PtrStr, "%p", pUserData); + // If VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is not specified, convert the string's memory address to a string and store it. + snprintf(m_PtrStr, 17, "%p", pUserData); m_Str = m_PtrStr; } } @@ -14029,10 +15596,16 @@ VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, void VmaRecorder::WriteConfiguration( const VkPhysicalDeviceProperties& devProps, const VkPhysicalDeviceMemoryProperties& memProps, - bool dedicatedAllocationExtensionEnabled) + uint32_t vulkanApiVersion, + bool dedicatedAllocationExtensionEnabled, + bool bindMemory2ExtensionEnabled, + bool memoryBudgetExtensionEnabled, + bool deviceCoherentMemoryExtensionEnabled) { fprintf(m_File, "Config,Begin\n"); + fprintf(m_File, "VulkanApiVersion,%u,%u\n", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion)); + fprintf(m_File, "PhysicalDevice,apiVersion,%u\n", devProps.apiVersion); fprintf(m_File, "PhysicalDevice,driverVersion,%u\n", devProps.driverVersion); fprintf(m_File, "PhysicalDevice,vendorID,%u\n", devProps.vendorID); @@ -14058,6 +15631,9 @@ void VmaRecorder::WriteConfiguration( } fprintf(m_File, "Extension,VK_KHR_dedicated_allocation,%u\n", dedicatedAllocationExtensionEnabled ? 1 : 0); + fprintf(m_File, "Extension,VK_KHR_bind_memory2,%u\n", bindMemory2ExtensionEnabled ? 1 : 0); + fprintf(m_File, "Extension,VK_EXT_memory_budget,%u\n", memoryBudgetExtensionEnabled ? 1 : 0); + fprintf(m_File, "Extension,VK_AMD_device_coherent_memory,%u\n", deviceCoherentMemoryExtensionEnabled ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_ALWAYS_DEDICATED_MEMORY,%u\n", VMA_DEBUG_ALWAYS_DEDICATED_MEMORY ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_ALIGNMENT,%llu\n", (VkDeviceSize)VMA_DEBUG_ALIGNMENT); @@ -14074,11 +15650,22 @@ void VmaRecorder::WriteConfiguration( void VmaRecorder::GetBasicParams(CallParams& outParams) { - outParams.threadId = GetCurrentThreadId(); + #if defined(_WIN32) + outParams.threadId = GetCurrentThreadId(); + #else + // Use C++11 features to get thread id and convert it to uint32_t. + // There is room for optimization since sstream is quite slow. + // Is there a better way to convert std::this_thread::get_id() to uint32_t? + std::thread::id thread_id = std::this_thread::get_id(); + std::stringstream thread_id_to_string_converter; + thread_id_to_string_converter << thread_id; + std::string thread_id_as_string = thread_id_to_string_converter.str(); + outParams.threadId = static_cast(std::stoi(thread_id_as_string.c_str())); + #endif - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - outParams.time = (double)(counter.QuadPart - m_StartCounter) / (double)m_Freq; + auto current_time = std::chrono::high_resolution_clock::now(); + + outParams.time = std::chrono::duration(current_time - m_RecordingStartTime).count(); } void VmaRecorder::PrintPointerList(uint64_t count, const VmaAllocation* pItems) @@ -14111,10 +15698,10 @@ VmaAllocationObjectAllocator::VmaAllocationObjectAllocator(const VkAllocationCal { } -VmaAllocation VmaAllocationObjectAllocator::Allocate() +template VmaAllocation VmaAllocationObjectAllocator::Allocate(Types... args) { VmaMutexLock mutexLock(m_Mutex); - return m_Allocator.Alloc(); + return m_Allocator.Alloc(std::forward(args)...); } void VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc) @@ -14128,50 +15715,102 @@ void VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc) VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : m_UseMutex((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT) == 0), + m_VulkanApiVersion(pCreateInfo->vulkanApiVersion != 0 ? pCreateInfo->vulkanApiVersion : VK_API_VERSION_1_0), m_UseKhrDedicatedAllocation((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0), + m_UseKhrBindMemory2((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0), + m_UseExtMemoryBudget((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0), + m_UseAmdDeviceCoherentMemory((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT) != 0), + m_UseKhrBufferDeviceAddress((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT) != 0), + m_UseExtMemoryPriority((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT) != 0), m_hDevice(pCreateInfo->device), + m_hInstance(pCreateInfo->instance), m_AllocationCallbacksSpecified(pCreateInfo->pAllocationCallbacks != VMA_NULL), m_AllocationCallbacks(pCreateInfo->pAllocationCallbacks ? *pCreateInfo->pAllocationCallbacks : VmaEmptyAllocationCallbacks), m_AllocationObjectAllocator(&m_AllocationCallbacks), + m_HeapSizeLimitMask(0), m_PreferredLargeHeapBlockSize(0), m_PhysicalDevice(pCreateInfo->physicalDevice), m_CurrentFrameIndex(0), + m_GpuDefragmentationMemoryTypeBits(UINT32_MAX), m_Pools(VmaStlAllocator(GetAllocationCallbacks())), - m_NextPoolId(0) + m_NextPoolId(0), + m_GlobalMemoryTypeBits(UINT32_MAX) #if VMA_RECORDING_ENABLED ,m_pRecorder(VMA_NULL) #endif { + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_UseKhrDedicatedAllocation = false; + m_UseKhrBindMemory2 = false; + } + if(VMA_DEBUG_DETECT_CORRUPTION) { // Needs to be multiply of uint32_t size because we are going to write VMA_CORRUPTION_DETECTION_MAGIC_VALUE to it. VMA_ASSERT(VMA_DEBUG_MARGIN % sizeof(uint32_t) == 0); } - VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device); + VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device && pCreateInfo->instance); -#if !(VMA_DEDICATED_ALLOCATION) - if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) + if(m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) { - VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros."); +#if !(VMA_DEDICATED_ALLOCATION) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros."); + } +#endif +#if !(VMA_BIND_MEMORY2) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT set but required extension is disabled by preprocessor macros."); + } +#endif + } +#if !(VMA_MEMORY_BUDGET) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT set but required extension is disabled by preprocessor macros."); + } +#endif +#if !(VMA_BUFFER_DEVICE_ADDRESS) + if(m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT is set but required extension or Vulkan 1.2 is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif +#if VMA_VULKAN_VERSION < 1002000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 2, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_2 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if VMA_VULKAN_VERSION < 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_1 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if !(VMA_MEMORY_PRIORITY) + if(m_UseExtMemoryPriority) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); } #endif memset(&m_DeviceMemoryCallbacks, 0 ,sizeof(m_DeviceMemoryCallbacks)); memset(&m_PhysicalDeviceProperties, 0, sizeof(m_PhysicalDeviceProperties)); memset(&m_MemProps, 0, sizeof(m_MemProps)); - + memset(&m_pBlockVectors, 0, sizeof(m_pBlockVectors)); memset(&m_pDedicatedAllocations, 0, sizeof(m_pDedicatedAllocations)); - - for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) - { - m_HeapSizeLimit[i] = VK_WHOLE_SIZE; - } + memset(&m_VulkanFunctions, 0, sizeof(m_VulkanFunctions)); if(pCreateInfo->pDeviceMemoryCallbacks != VMA_NULL) { + m_DeviceMemoryCallbacks.pUserData = pCreateInfo->pDeviceMemoryCallbacks->pUserData; m_DeviceMemoryCallbacks.pfnAllocate = pCreateInfo->pDeviceMemoryCallbacks->pfnAllocate; m_DeviceMemoryCallbacks.pfnFree = pCreateInfo->pDeviceMemoryCallbacks->pfnFree; } @@ -14189,6 +15828,8 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : m_PreferredLargeHeapBlockSize = (pCreateInfo->preferredLargeHeapBlockSize != 0) ? pCreateInfo->preferredLargeHeapBlockSize : static_cast(VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE); + m_GlobalMemoryTypeBits = CalculateGlobalMemoryTypeBits(); + if(pCreateInfo->pHeapSizeLimit != VMA_NULL) { for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) @@ -14196,7 +15837,7 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : const VkDeviceSize limit = pCreateInfo->pHeapSizeLimit[heapIndex]; if(limit != VK_WHOLE_SIZE) { - m_HeapSizeLimit[heapIndex] = limit; + m_HeapSizeLimitMask |= 1u << heapIndex; if(limit < m_MemProps.memoryHeaps[heapIndex].size) { m_MemProps.memoryHeaps[heapIndex].size = limit; @@ -14218,9 +15859,9 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : SIZE_MAX, GetBufferImageGranularity(), pCreateInfo->frameInUseCount, - false, // isCustomPool false, // explicitBlockSize - false); // linearAlgorithm + false, // linearAlgorithm + 0.5f); // priority (0.5 is the default per Vulkan spec) // No need to call m_pBlockVectors[memTypeIndex][blockVectorTypeIndex]->CreateMinBlocks here, // becase minBlockCount is 0. m_pDedicatedAllocations[memTypeIndex] = vma_new(this, AllocationVectorType)(VmaStlAllocator(GetAllocationCallbacks())); @@ -14245,7 +15886,11 @@ VkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo) m_pRecorder->WriteConfiguration( m_PhysicalDeviceProperties, m_MemProps, - m_UseKhrDedicatedAllocation); + m_VulkanApiVersion, + m_UseKhrDedicatedAllocation, + m_UseKhrBindMemory2, + m_UseExtMemoryBudget, + m_UseAmdDeviceCoherentMemory); m_pRecorder->RecordCreateAllocator(GetCurrentFrameIndex()); #else VMA_ASSERT(0 && "VmaAllocatorCreateInfo::pRecordSettings used, but not supported due to VMA_RECORDING_ENABLED not defined to 1."); @@ -14253,6 +15898,13 @@ VkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo) #endif } +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } +#endif // #if VMA_MEMORY_BUDGET + return res; } @@ -14265,7 +15917,7 @@ VmaAllocator_T::~VmaAllocator_T() vma_delete(this, m_pRecorder); } #endif - + VMA_ASSERT(m_Pools.empty()); for(size_t i = GetMemoryTypeCount(); i--; ) @@ -14283,66 +15935,174 @@ VmaAllocator_T::~VmaAllocator_T() void VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions) { #if VMA_STATIC_VULKAN_FUNCTIONS == 1 - m_VulkanFunctions.vkGetPhysicalDeviceProperties = &vkGetPhysicalDeviceProperties; - m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = &vkGetPhysicalDeviceMemoryProperties; - m_VulkanFunctions.vkAllocateMemory = &vkAllocateMemory; - m_VulkanFunctions.vkFreeMemory = &vkFreeMemory; - m_VulkanFunctions.vkMapMemory = &vkMapMemory; - m_VulkanFunctions.vkUnmapMemory = &vkUnmapMemory; - m_VulkanFunctions.vkFlushMappedMemoryRanges = &vkFlushMappedMemoryRanges; - m_VulkanFunctions.vkInvalidateMappedMemoryRanges = &vkInvalidateMappedMemoryRanges; - m_VulkanFunctions.vkBindBufferMemory = &vkBindBufferMemory; - m_VulkanFunctions.vkBindImageMemory = &vkBindImageMemory; - m_VulkanFunctions.vkGetBufferMemoryRequirements = &vkGetBufferMemoryRequirements; - m_VulkanFunctions.vkGetImageMemoryRequirements = &vkGetImageMemoryRequirements; - m_VulkanFunctions.vkCreateBuffer = &vkCreateBuffer; - m_VulkanFunctions.vkDestroyBuffer = &vkDestroyBuffer; - m_VulkanFunctions.vkCreateImage = &vkCreateImage; - m_VulkanFunctions.vkDestroyImage = &vkDestroyImage; - m_VulkanFunctions.vkCmdCopyBuffer = &vkCmdCopyBuffer; -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) + ImportVulkanFunctions_Static(); +#endif + + if(pVulkanFunctions != VMA_NULL) { - m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = - (PFN_vkGetBufferMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetBufferMemoryRequirements2KHR"); - m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = - (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetImageMemoryRequirements2KHR"); + ImportVulkanFunctions_Custom(pVulkanFunctions); } -#endif // #if VMA_DEDICATED_ALLOCATION + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + ImportVulkanFunctions_Dynamic(); +#endif + + ValidateVulkanFunctions(); +} + +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Static() +{ + // Vulkan 1.0 + m_VulkanFunctions.vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetPhysicalDeviceProperties; + m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)vkGetPhysicalDeviceMemoryProperties; + m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; + m_VulkanFunctions.vkFreeMemory = (PFN_vkFreeMemory)vkFreeMemory; + m_VulkanFunctions.vkMapMemory = (PFN_vkMapMemory)vkMapMemory; + m_VulkanFunctions.vkUnmapMemory = (PFN_vkUnmapMemory)vkUnmapMemory; + m_VulkanFunctions.vkFlushMappedMemoryRanges = (PFN_vkFlushMappedMemoryRanges)vkFlushMappedMemoryRanges; + m_VulkanFunctions.vkInvalidateMappedMemoryRanges = (PFN_vkInvalidateMappedMemoryRanges)vkInvalidateMappedMemoryRanges; + m_VulkanFunctions.vkBindBufferMemory = (PFN_vkBindBufferMemory)vkBindBufferMemory; + m_VulkanFunctions.vkBindImageMemory = (PFN_vkBindImageMemory)vkBindImageMemory; + m_VulkanFunctions.vkGetBufferMemoryRequirements = (PFN_vkGetBufferMemoryRequirements)vkGetBufferMemoryRequirements; + m_VulkanFunctions.vkGetImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)vkGetImageMemoryRequirements; + m_VulkanFunctions.vkCreateBuffer = (PFN_vkCreateBuffer)vkCreateBuffer; + m_VulkanFunctions.vkDestroyBuffer = (PFN_vkDestroyBuffer)vkDestroyBuffer; + m_VulkanFunctions.vkCreateImage = (PFN_vkCreateImage)vkCreateImage; + m_VulkanFunctions.vkDestroyImage = (PFN_vkDestroyImage)vkDestroyImage; + m_VulkanFunctions.vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)vkCmdCopyBuffer; + + // Vulkan 1.1 +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = (PFN_vkGetBufferMemoryRequirements2)vkGetBufferMemoryRequirements2; + m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = (PFN_vkGetImageMemoryRequirements2)vkGetImageMemoryRequirements2; + m_VulkanFunctions.vkBindBufferMemory2KHR = (PFN_vkBindBufferMemory2)vkBindBufferMemory2; + m_VulkanFunctions.vkBindImageMemory2KHR = (PFN_vkBindImageMemory2)vkBindImageMemory2; + m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2)vkGetPhysicalDeviceMemoryProperties2; + } +#endif +} + #endif // #if VMA_STATIC_VULKAN_FUNCTIONS == 1 +void VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions) +{ + VMA_ASSERT(pVulkanFunctions != VMA_NULL); + #define VMA_COPY_IF_NOT_NULL(funcName) \ if(pVulkanFunctions->funcName != VMA_NULL) m_VulkanFunctions.funcName = pVulkanFunctions->funcName; - if(pVulkanFunctions != VMA_NULL) - { - VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties); - VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties); - VMA_COPY_IF_NOT_NULL(vkAllocateMemory); - VMA_COPY_IF_NOT_NULL(vkFreeMemory); - VMA_COPY_IF_NOT_NULL(vkMapMemory); - VMA_COPY_IF_NOT_NULL(vkUnmapMemory); - VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges); - VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges); - VMA_COPY_IF_NOT_NULL(vkBindBufferMemory); - VMA_COPY_IF_NOT_NULL(vkBindImageMemory); - VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements); - VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements); - VMA_COPY_IF_NOT_NULL(vkCreateBuffer); - VMA_COPY_IF_NOT_NULL(vkDestroyBuffer); - VMA_COPY_IF_NOT_NULL(vkCreateImage); - VMA_COPY_IF_NOT_NULL(vkDestroyImage); - VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer); -#if VMA_DEDICATED_ALLOCATION - VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR); - VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties); + VMA_COPY_IF_NOT_NULL(vkAllocateMemory); + VMA_COPY_IF_NOT_NULL(vkFreeMemory); + VMA_COPY_IF_NOT_NULL(vkMapMemory); + VMA_COPY_IF_NOT_NULL(vkUnmapMemory); + VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges); + VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges); + VMA_COPY_IF_NOT_NULL(vkBindBufferMemory); + VMA_COPY_IF_NOT_NULL(vkBindImageMemory); + VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkCreateBuffer); + VMA_COPY_IF_NOT_NULL(vkDestroyBuffer); + VMA_COPY_IF_NOT_NULL(vkCreateImage); + VMA_COPY_IF_NOT_NULL(vkDestroyImage); + VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer); + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR); + VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR); +#endif + +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkBindBufferMemory2KHR); + VMA_COPY_IF_NOT_NULL(vkBindImageMemory2KHR); +#endif + +#if VMA_MEMORY_BUDGET + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR); #endif - } #undef VMA_COPY_IF_NOT_NULL +} - // If these asserts are hit, you must either #define VMA_STATIC_VULKAN_FUNCTIONS 1 - // or pass valid pointers as VmaAllocatorCreateInfo::pVulkanFunctions. +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Dynamic() +{ +#define VMA_FETCH_INSTANCE_FUNC(memberName, functionPointerType, functionNameString) \ + if(m_VulkanFunctions.memberName == VMA_NULL) \ + m_VulkanFunctions.memberName = \ + (functionPointerType)vkGetInstanceProcAddr(m_hInstance, functionNameString); +#define VMA_FETCH_DEVICE_FUNC(memberName, functionPointerType, functionNameString) \ + if(m_VulkanFunctions.memberName == VMA_NULL) \ + m_VulkanFunctions.memberName = \ + (functionPointerType)vkGetDeviceProcAddr(m_hDevice, functionNameString); + + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties, PFN_vkGetPhysicalDeviceProperties, "vkGetPhysicalDeviceProperties"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties, PFN_vkGetPhysicalDeviceMemoryProperties, "vkGetPhysicalDeviceMemoryProperties"); + VMA_FETCH_DEVICE_FUNC(vkAllocateMemory, PFN_vkAllocateMemory, "vkAllocateMemory"); + VMA_FETCH_DEVICE_FUNC(vkFreeMemory, PFN_vkFreeMemory, "vkFreeMemory"); + VMA_FETCH_DEVICE_FUNC(vkMapMemory, PFN_vkMapMemory, "vkMapMemory"); + VMA_FETCH_DEVICE_FUNC(vkUnmapMemory, PFN_vkUnmapMemory, "vkUnmapMemory"); + VMA_FETCH_DEVICE_FUNC(vkFlushMappedMemoryRanges, PFN_vkFlushMappedMemoryRanges, "vkFlushMappedMemoryRanges"); + VMA_FETCH_DEVICE_FUNC(vkInvalidateMappedMemoryRanges, PFN_vkInvalidateMappedMemoryRanges, "vkInvalidateMappedMemoryRanges"); + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory, PFN_vkBindBufferMemory, "vkBindBufferMemory"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory, PFN_vkBindImageMemory, "vkBindImageMemory"); + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements, PFN_vkGetBufferMemoryRequirements, "vkGetBufferMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements, PFN_vkGetImageMemoryRequirements, "vkGetImageMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkCreateBuffer, PFN_vkCreateBuffer, "vkCreateBuffer"); + VMA_FETCH_DEVICE_FUNC(vkDestroyBuffer, PFN_vkDestroyBuffer, "vkDestroyBuffer"); + VMA_FETCH_DEVICE_FUNC(vkCreateImage, PFN_vkCreateImage, "vkCreateImage"); + VMA_FETCH_DEVICE_FUNC(vkDestroyImage, PFN_vkDestroyImage, "vkDestroyImage"); + VMA_FETCH_DEVICE_FUNC(vkCmdCopyBuffer, PFN_vkCmdCopyBuffer, "vkCmdCopyBuffer"); + +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2, "vkGetBufferMemoryRequirements2"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2, "vkGetImageMemoryRequirements2"); + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2, "vkBindBufferMemory2"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2, "vkBindImageMemory2"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2, "vkGetPhysicalDeviceMemoryProperties2"); + } +#endif + +#if VMA_DEDICATED_ALLOCATION + if(m_UseKhrDedicatedAllocation) + { + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2KHR, "vkGetBufferMemoryRequirements2KHR"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2KHR, "vkGetImageMemoryRequirements2KHR"); + } +#endif + +#if VMA_BIND_MEMORY2 + if(m_UseKhrBindMemory2) + { + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2KHR, "vkBindBufferMemory2KHR"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2KHR, "vkBindImageMemory2KHR"); + } +#endif // #if VMA_BIND_MEMORY2 + +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); + } +#endif // #if VMA_MEMORY_BUDGET + +#undef VMA_FETCH_DEVICE_FUNC +#undef VMA_FETCH_INSTANCE_FUNC +} + +#endif // #if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ValidateVulkanFunctions() +{ VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkAllocateMemory != VMA_NULL); @@ -14360,13 +16120,29 @@ void VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunc VMA_ASSERT(m_VulkanFunctions.vkCreateImage != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkDestroyImage != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkCmdCopyBuffer != VMA_NULL); -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrDedicatedAllocation) { VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements2KHR != VMA_NULL); } #endif + +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrBindMemory2) + { + VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL); + } +#endif + +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + if(m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL); + } +#endif } VkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex) @@ -14374,7 +16150,7 @@ VkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex) const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; const bool isSmallHeap = heapSize <= VMA_SMALL_HEAP_MAX_SIZE; - return isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize; + return VmaAlignUp(isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize, (VkDeviceSize)32); } VkResult VmaAllocator_T::AllocateMemoryOfType( @@ -14382,6 +16158,7 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( VkDeviceSize alignment, bool dedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, @@ -14400,6 +16177,11 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( { finalCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; } + // If memory is lazily allocated, it should be always dedicated. + if(finalCreateInfo.usage == VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED) + { + finalCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } VmaBlockVector* const blockVector = m_pBlockVectors[memTypeIndex]; VMA_ASSERT(blockVector); @@ -14430,10 +16212,13 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( size, suballocType, memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, finalCreateInfo.pUserData, + finalCreateInfo.priority, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, allocationCount, pAllocations); @@ -14465,10 +16250,13 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( size, suballocType, memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, finalCreateInfo.pUserData, + finalCreateInfo.priority, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, allocationCount, pAllocations); @@ -14492,40 +16280,85 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, + bool withinBudget, bool map, bool isUserDataString, void* pUserData, + float priority, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, size_t allocationCount, VmaAllocation* pAllocations) { VMA_ASSERT(allocationCount > 0 && pAllocations); + if(withinBudget) + { + const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); + VmaBudget heapBudget = {}; + GetBudget(&heapBudget, heapIndex, 1); + if(heapBudget.usage + size * allocationCount > heapBudget.budget) + { + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + } + VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = memTypeIndex; allocInfo.allocationSize = size; -#if VMA_DEDICATED_ALLOCATION +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 VkMemoryDedicatedAllocateInfoKHR dedicatedAllocInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; - if(m_UseKhrDedicatedAllocation) + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { if(dedicatedBuffer != VK_NULL_HANDLE) { VMA_ASSERT(dedicatedImage == VK_NULL_HANDLE); dedicatedAllocInfo.buffer = dedicatedBuffer; - allocInfo.pNext = &dedicatedAllocInfo; + VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo); } else if(dedicatedImage != VK_NULL_HANDLE) { dedicatedAllocInfo.image = dedicatedImage; - allocInfo.pNext = &dedicatedAllocInfo; + VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo); } } -#endif // #if VMA_DEDICATED_ALLOCATION +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + +#if VMA_BUFFER_DEVICE_ADDRESS + VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR }; + if(m_UseKhrBufferDeviceAddress) + { + bool canContainBufferWithDeviceAddress = true; + if(dedicatedBuffer != VK_NULL_HANDLE) + { + canContainBufferWithDeviceAddress = dedicatedBufferUsage == UINT32_MAX || // Usage flags unknown + (dedicatedBufferUsage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT) != 0; + } + else if(dedicatedImage != VK_NULL_HANDLE) + { + canContainBufferWithDeviceAddress = false; + } + if(canContainBufferWithDeviceAddress) + { + allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo); + } + } +#endif // #if VMA_BUFFER_DEVICE_ADDRESS + +#if VMA_MEMORY_PRIORITY + VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT }; + if(m_UseExtMemoryPriority) + { + priorityInfo.priority = priority; + VmaPnextChainPushFront(&allocInfo, &priorityInfo); + } +#endif // #if VMA_MEMORY_PRIORITY size_t allocIndex; - VkResult res; + VkResult res = VK_SUCCESS; for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex) { res = AllocateDedicatedMemoryPage( @@ -14565,7 +16398,7 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( { VmaAllocation currAlloc = pAllocations[allocIndex]; VkDeviceMemory hMemory = currAlloc->GetMemory(); - + /* There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory before vkFreeMemory. @@ -14575,11 +16408,10 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); } */ - - FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); + FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); + m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), currAlloc->GetSize()); currAlloc->SetUserData(this, VMA_NULL); - currAlloc->Dtor(); m_AllocationObjectAllocator.Free(currAlloc); } @@ -14625,10 +16457,10 @@ VkResult VmaAllocator_T::AllocateDedicatedMemoryPage( } } - *pAllocation = m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(m_CurrentFrameIndex.load(), isUserDataString); + *pAllocation = m_AllocationObjectAllocator.Allocate(m_CurrentFrameIndex.load(), isUserDataString); (*pAllocation)->InitDedicatedAllocation(memTypeIndex, hMemory, suballocType, pMappedData, size); (*pAllocation)->SetUserData(this, pUserData); + m_Budget.AddAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), size); if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); @@ -14643,8 +16475,8 @@ void VmaAllocator_T::GetBufferMemoryRequirements( bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const { -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkBufferMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR }; memReqInfo.buffer = hBuffer; @@ -14652,7 +16484,7 @@ void VmaAllocator_T::GetBufferMemoryRequirements( VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; - memReq2.pNext = &memDedicatedReq; + VmaPnextChainPushFront(&memReq2, &memDedicatedReq); (*m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); @@ -14661,7 +16493,7 @@ void VmaAllocator_T::GetBufferMemoryRequirements( prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else -#endif // #if VMA_DEDICATED_ALLOCATION +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 { (*m_VulkanFunctions.vkGetBufferMemoryRequirements)(m_hDevice, hBuffer, &memReq); requiresDedicatedAllocation = false; @@ -14675,8 +16507,8 @@ void VmaAllocator_T::GetImageMemoryRequirements( bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const { -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkImageMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR }; memReqInfo.image = hImage; @@ -14684,7 +16516,7 @@ void VmaAllocator_T::GetImageMemoryRequirements( VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; - memReq2.pNext = &memDedicatedReq; + VmaPnextChainPushFront(&memReq2, &memDedicatedReq); (*m_VulkanFunctions.vkGetImageMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); @@ -14693,7 +16525,7 @@ void VmaAllocator_T::GetImageMemoryRequirements( prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else -#endif // #if VMA_DEDICATED_ALLOCATION +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 { (*m_VulkanFunctions.vkGetImageMemoryRequirements)(m_hDevice, hImage, &memReq); requiresDedicatedAllocation = false; @@ -14706,6 +16538,7 @@ VkResult VmaAllocator_T::AllocateMemory( bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, @@ -14757,11 +16590,20 @@ VkResult VmaAllocator_T::AllocateMemory( const VkDeviceSize alignmentForPool = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(createInfo.pool->m_BlockVector.GetMemoryTypeIndex())); + + VmaAllocationCreateInfo createInfoForPool = createInfo; + // If memory type is not HOST_VISIBLE, disable MAPPED. + if((createInfoForPool.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 && + (m_MemProps.memoryTypes[createInfo.pool->m_BlockVector.GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) + { + createInfoForPool.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; + } + return createInfo.pool->m_BlockVector.Allocate( m_CurrentFrameIndex.load(), vkMemReq.size, alignmentForPool, - createInfo, + createInfoForPool, suballocType, allocationCount, pAllocations); @@ -14783,6 +16625,7 @@ VkResult VmaAllocator_T::AllocateMemory( alignmentForMemType, requiresDedicatedAllocation || prefersDedicatedAllocation, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, createInfo, memTypeIndex, @@ -14808,12 +16651,13 @@ VkResult VmaAllocator_T::AllocateMemory( alignmentForMemType = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(memTypeIndex)); - + res = AllocateMemoryOfType( vkMemReq.size, alignmentForMemType, requiresDedicatedAllocation || prefersDedicatedAllocation, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, createInfo, memTypeIndex, @@ -14887,8 +16731,9 @@ void VmaAllocator_T::FreeMemory( } } + // Do this regardless of whether the allocation is lost. Lost allocations still account to Budget.AllocationBytes. + m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(allocation->GetMemoryTypeIndex()), allocation->GetSize()); allocation->SetUserData(this, VMA_NULL); - allocation->Dtor(); m_AllocationObjectAllocator.Free(allocation); } } @@ -14898,6 +16743,7 @@ VkResult VmaAllocator_T::ResizeAllocation( const VmaAllocation alloc, VkDeviceSize newSize) { + // This function is deprecated and so it does nothing. It's left for backward compatibility. if(newSize == 0 || alloc->GetLastUseFrameIndex() == VMA_FRAME_INDEX_LOST) { return VK_ERROR_VALIDATION_FAILED_EXT; @@ -14906,26 +16752,7 @@ VkResult VmaAllocator_T::ResizeAllocation( { return VK_SUCCESS; } - - switch(alloc->GetType()) - { - case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - return VK_ERROR_FEATURE_NOT_PRESENT; - case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: - if(alloc->GetBlock()->m_pMetadata->ResizeAllocation(alloc, newSize)) - { - alloc->ChangeSize(newSize); - VMA_HEAVY_ASSERT(alloc->GetBlock()->m_pMetadata->Validate()); - return VK_SUCCESS; - } - else - { - return VK_ERROR_OUT_OF_POOL_MEMORY; - } - default: - VMA_ASSERT(0); - return VK_ERROR_VALIDATION_FAILED_EXT; - } + return VK_ERROR_OUT_OF_POOL_MEMORY; } void VmaAllocator_T::CalculateStats(VmaStats* pStats) @@ -14936,7 +16763,7 @@ void VmaAllocator_T::CalculateStats(VmaStats* pStats) InitStatInfo(pStats->memoryType[i]); for(size_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) InitStatInfo(pStats->memoryHeap[i]); - + // Process default pools. for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { @@ -14979,6 +16806,58 @@ void VmaAllocator_T::CalculateStats(VmaStats* pStats) VmaPostprocessCalcStatInfo(pStats->memoryHeap[i]); } +void VmaAllocator_T::GetBudget(VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount) +{ +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + if(m_Budget.m_OperationsSinceBudgetFetch < 30) + { + VmaMutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex); + for(uint32_t i = 0; i < heapCount; ++i, ++outBudget) + { + const uint32_t heapIndex = firstHeap + i; + + outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex]; + outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; + + if(m_Budget.m_VulkanUsage[heapIndex] + outBudget->blockBytes > m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]) + { + outBudget->usage = m_Budget.m_VulkanUsage[heapIndex] + + outBudget->blockBytes - m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; + } + else + { + outBudget->usage = 0; + } + + // Have to take MIN with heap size because explicit HeapSizeLimit is included in it. + outBudget->budget = VMA_MIN( + m_Budget.m_VulkanBudget[heapIndex], m_MemProps.memoryHeaps[heapIndex].size); + } + } + else + { + UpdateVulkanBudget(); // Outside of mutex lock + GetBudget(outBudget, firstHeap, heapCount); // Recursion + } + } + else +#endif + { + for(uint32_t i = 0; i < heapCount; ++i, ++outBudget) + { + const uint32_t heapIndex = firstHeap + i; + + outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex]; + outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; + + outBudget->usage = outBudget->blockBytes; + outBudget->budget = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. + } + } +} + static const uint32_t VMA_VENDOR_ID_AMD = 4098; VkResult VmaAllocator_T::DefragmentationBegin( @@ -15001,7 +16880,7 @@ VkResult VmaAllocator_T::DefragmentationBegin( VkResult res = (*pContext)->Defragment( info.maxCpuBytesToMove, info.maxCpuAllocationsToMove, info.maxGpuBytesToMove, info.maxGpuAllocationsToMove, - info.commandBuffer, pStats); + info.commandBuffer, pStats, info.flags); if(res != VK_NOT_READY) { @@ -15019,6 +16898,19 @@ VkResult VmaAllocator_T::DefragmentationEnd( return VK_SUCCESS; } +VkResult VmaAllocator_T::DefragmentationPassBegin( + VmaDefragmentationPassInfo* pInfo, + VmaDefragmentationContext context) +{ + return context->DefragmentPassBegin(pInfo); +} +VkResult VmaAllocator_T::DefragmentationPassEnd( + VmaDefragmentationContext context) +{ + return context->DefragmentPassEnd(); + +} + void VmaAllocator_T::GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo) { if(hAllocation->CanBecomeLost()) @@ -15157,6 +17049,12 @@ VkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPoo { return VK_ERROR_INITIALIZATION_FAILED; } + // Memory type index out of range or forbidden. + if(pCreateInfo->memoryTypeIndex >= GetMemoryTypeCount() || + ((1u << pCreateInfo->memoryTypeIndex) & m_GlobalMemoryTypeBits) == 0) + { + return VK_ERROR_FEATURE_NOT_PRESENT; + } const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(newCreateInfo.memoryTypeIndex); @@ -15200,6 +17098,13 @@ void VmaAllocator_T::GetPoolStats(VmaPool pool, VmaPoolStats* pPoolStats) void VmaAllocator_T::SetCurrentFrameIndex(uint32_t frameIndex) { m_CurrentFrameIndex.store(frameIndex); + +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } +#endif // #if VMA_MEMORY_BUDGET } void VmaAllocator_T::MakePoolAllocationsLost( @@ -15268,8 +17173,7 @@ VkResult VmaAllocator_T::CheckCorruption(uint32_t memoryTypeBits) void VmaAllocator_T::CreateLostAllocation(VmaAllocation* pAllocation) { - *pAllocation = m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(VMA_FRAME_INDEX_LOST, false); + *pAllocation = m_AllocationObjectAllocator.Allocate(VMA_FRAME_INDEX_LOST, false); (*pAllocation)->InitLost(); } @@ -15277,31 +17181,47 @@ VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAlloc { const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(pAllocateInfo->memoryTypeIndex); - VkResult res; - if(m_HeapSizeLimit[heapIndex] != VK_WHOLE_SIZE) + // HeapSizeLimit is in effect for this heap. + if((m_HeapSizeLimitMask & (1u << heapIndex)) != 0) { - VmaMutexLock lock(m_HeapSizeLimitMutex, m_UseMutex); - if(m_HeapSizeLimit[heapIndex] >= pAllocateInfo->allocationSize) + const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; + VkDeviceSize blockBytes = m_Budget.m_BlockBytes[heapIndex]; + for(;;) { - res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); - if(res == VK_SUCCESS) + const VkDeviceSize blockBytesAfterAllocation = blockBytes + pAllocateInfo->allocationSize; + if(blockBytesAfterAllocation > heapSize) { - m_HeapSizeLimit[heapIndex] -= pAllocateInfo->allocationSize; + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + if(m_Budget.m_BlockBytes[heapIndex].compare_exchange_strong(blockBytes, blockBytesAfterAllocation)) + { + break; } - } - else - { - res = VK_ERROR_OUT_OF_DEVICE_MEMORY; } } else { - res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); + m_Budget.m_BlockBytes[heapIndex] += pAllocateInfo->allocationSize; } - if(res == VK_SUCCESS && m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) + // VULKAN CALL vkAllocateMemory. + VkResult res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); + + if(res == VK_SUCCESS) { - (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize); +#if VMA_MEMORY_BUDGET + ++m_Budget.m_OperationsSinceBudgetFetch; +#endif + + // Informative callback. + if(m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) + { + (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize, m_DeviceMemoryCallbacks.pUserData); + } + } + else + { + m_Budget.m_BlockBytes[heapIndex] -= pAllocateInfo->allocationSize; } return res; @@ -15309,18 +17229,77 @@ VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAlloc void VmaAllocator_T::FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory) { + // Informative callback. if(m_DeviceMemoryCallbacks.pfnFree != VMA_NULL) { - (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size); + (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size, m_DeviceMemoryCallbacks.pUserData); } + // VULKAN CALL vkFreeMemory. (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks()); - const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memoryType); - if(m_HeapSizeLimit[heapIndex] != VK_WHOLE_SIZE) + m_Budget.m_BlockBytes[MemoryTypeIndexToHeapIndex(memoryType)] -= size; +} + +VkResult VmaAllocator_T::BindVulkanBuffer( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkBuffer buffer, + const void* pNext) +{ + if(pNext != VMA_NULL) { - VmaMutexLock lock(m_HeapSizeLimitMutex, m_UseMutex); - m_HeapSizeLimit[heapIndex] += size; +#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + if((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && + m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL) + { + VkBindBufferMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR }; + bindBufferMemoryInfo.pNext = pNext; + bindBufferMemoryInfo.buffer = buffer; + bindBufferMemoryInfo.memory = memory; + bindBufferMemoryInfo.memoryOffset = memoryOffset; + return (*m_VulkanFunctions.vkBindBufferMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); + } + else +#endif // #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + else + { + return (*m_VulkanFunctions.vkBindBufferMemory)(m_hDevice, buffer, memory, memoryOffset); + } +} + +VkResult VmaAllocator_T::BindVulkanImage( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkImage image, + const void* pNext) +{ + if(pNext != VMA_NULL) + { +#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + if((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && + m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL) + { + VkBindImageMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR }; + bindBufferMemoryInfo.pNext = pNext; + bindBufferMemoryInfo.image = image; + bindBufferMemoryInfo.memory = memory; + bindBufferMemoryInfo.memoryOffset = memoryOffset; + return (*m_VulkanFunctions.vkBindImageMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); + } + else +#endif // #if VMA_BIND_MEMORY2 + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + else + { + return (*m_VulkanFunctions.vkBindImageMemory)(m_hDevice, image, memory, memoryOffset); } } @@ -15372,23 +17351,23 @@ void VmaAllocator_T::Unmap(VmaAllocation hAllocation) } } -VkResult VmaAllocator_T::BindBufferMemory(VmaAllocation hAllocation, VkBuffer hBuffer) +VkResult VmaAllocator_T::BindBufferMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext) { VkResult res = VK_SUCCESS; switch(hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - res = GetVulkanFunctions().vkBindBufferMemory( - m_hDevice, - hBuffer, - hAllocation->GetMemory(), - 0); //memoryOffset + res = BindVulkanBuffer(hAllocation->GetMemory(), allocationLocalOffset, hBuffer, pNext); break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { - VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); + VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); VMA_ASSERT(pBlock && "Binding buffer to allocation that doesn't belong to any block. Is the allocation lost?"); - res = pBlock->BindBufferMemory(this, hAllocation, hBuffer); + res = pBlock->BindBufferMemory(this, hAllocation, allocationLocalOffset, hBuffer, pNext); break; } default: @@ -15397,23 +17376,23 @@ VkResult VmaAllocator_T::BindBufferMemory(VmaAllocation hAllocation, VkBuffer hB return res; } -VkResult VmaAllocator_T::BindImageMemory(VmaAllocation hAllocation, VkImage hImage) +VkResult VmaAllocator_T::BindImageMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext) { VkResult res = VK_SUCCESS; switch(hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - res = GetVulkanFunctions().vkBindImageMemory( - m_hDevice, - hImage, - hAllocation->GetMemory(), - 0); //memoryOffset + res = BindVulkanImage(hAllocation->GetMemory(), allocationLocalOffset, hImage, pNext); break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); VMA_ASSERT(pBlock && "Binding image to allocation that doesn't belong to any block. Is the allocation lost?"); - res = pBlock->BindImageMemory(this, hAllocation, hImage); + res = pBlock->BindImageMemory(this, hAllocation, allocationLocalOffset, hImage, pNext); break; } default: @@ -15422,83 +17401,74 @@ VkResult VmaAllocator_T::BindImageMemory(VmaAllocation hAllocation, VkImage hIma return res; } -void VmaAllocator_T::FlushOrInvalidateAllocation( +VkResult VmaAllocator_T::FlushOrInvalidateAllocation( VmaAllocation hAllocation, VkDeviceSize offset, VkDeviceSize size, VMA_CACHE_OPERATION op) { - const uint32_t memTypeIndex = hAllocation->GetMemoryTypeIndex(); - if(size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) + VkResult res = VK_SUCCESS; + + VkMappedMemoryRange memRange = {}; + if(GetFlushOrInvalidateRange(hAllocation, offset, size, memRange)) { - const VkDeviceSize allocationSize = hAllocation->GetSize(); - VMA_ASSERT(offset <= allocationSize); - - const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; - - VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; - memRange.memory = hAllocation->GetMemory(); - - switch(hAllocation->GetType()) - { - case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); - if(size == VK_WHOLE_SIZE) - { - memRange.size = allocationSize - memRange.offset; - } - else - { - VMA_ASSERT(offset + size <= allocationSize); - memRange.size = VMA_MIN( - VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize), - allocationSize - memRange.offset); - } - break; - - case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: - { - // 1. Still within this allocation. - memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); - if(size == VK_WHOLE_SIZE) - { - size = allocationSize - offset; - } - else - { - VMA_ASSERT(offset + size <= allocationSize); - } - memRange.size = VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize); - - // 2. Adjust to whole block. - const VkDeviceSize allocationOffset = hAllocation->GetOffset(); - VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0); - const VkDeviceSize blockSize = hAllocation->GetBlock()->m_pMetadata->GetSize(); - memRange.offset += allocationOffset; - memRange.size = VMA_MIN(memRange.size, blockSize - memRange.offset); - - break; - } - - default: - VMA_ASSERT(0); - } - switch(op) { case VMA_CACHE_FLUSH: - (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange); + res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange); break; case VMA_CACHE_INVALIDATE: - (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange); + res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange); break; default: VMA_ASSERT(0); } } // else: Just ignore this call. + return res; } -void VmaAllocator_T::FreeDedicatedMemory(VmaAllocation allocation) +VkResult VmaAllocator_T::FlushOrInvalidateAllocations( + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + VMA_CACHE_OPERATION op) +{ + typedef VmaStlAllocator RangeAllocator; + typedef VmaSmallVector RangeVector; + RangeVector ranges = RangeVector(RangeAllocator(GetAllocationCallbacks())); + + for(uint32_t allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + const VmaAllocation alloc = allocations[allocIndex]; + const VkDeviceSize offset = offsets != VMA_NULL ? offsets[allocIndex] : 0; + const VkDeviceSize size = sizes != VMA_NULL ? sizes[allocIndex] : VK_WHOLE_SIZE; + VkMappedMemoryRange newRange; + if(GetFlushOrInvalidateRange(alloc, offset, size, newRange)) + { + ranges.push_back(newRange); + } + } + + VkResult res = VK_SUCCESS; + if(!ranges.empty()) + { + switch(op) + { + case VMA_CACHE_FLUSH: + res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data()); + break; + case VMA_CACHE_INVALIDATE: + res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data()); + break; + default: + VMA_ASSERT(0); + } + } + // else: Just ignore this call. + return res; +} + +void VmaAllocator_T::FreeDedicatedMemory(const VmaAllocation allocation) { VMA_ASSERT(allocation && allocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); @@ -15512,7 +17482,7 @@ void VmaAllocator_T::FreeDedicatedMemory(VmaAllocation allocation) } VkDeviceMemory hMemory = allocation->GetMemory(); - + /* There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory before vkFreeMemory. @@ -15522,12 +17492,164 @@ void VmaAllocator_T::FreeDedicatedMemory(VmaAllocation allocation) (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); } */ - + FreeVulkanMemory(memTypeIndex, allocation->GetSize(), hMemory); VMA_DEBUG_LOG(" Freed DedicatedMemory MemoryTypeIndex=%u", memTypeIndex); } +uint32_t VmaAllocator_T::CalculateGpuDefragmentationMemoryTypeBits() const +{ + VkBufferCreateInfo dummyBufCreateInfo; + VmaFillGpuDefragmentationBufferCreateInfo(dummyBufCreateInfo); + + uint32_t memoryTypeBits = 0; + + // Create buffer. + VkBuffer buf = VK_NULL_HANDLE; + VkResult res = (*GetVulkanFunctions().vkCreateBuffer)( + m_hDevice, &dummyBufCreateInfo, GetAllocationCallbacks(), &buf); + if(res == VK_SUCCESS) + { + // Query for supported memory types. + VkMemoryRequirements memReq; + (*GetVulkanFunctions().vkGetBufferMemoryRequirements)(m_hDevice, buf, &memReq); + memoryTypeBits = memReq.memoryTypeBits; + + // Destroy buffer. + (*GetVulkanFunctions().vkDestroyBuffer)(m_hDevice, buf, GetAllocationCallbacks()); + } + + return memoryTypeBits; +} + +uint32_t VmaAllocator_T::CalculateGlobalMemoryTypeBits() const +{ + // Make sure memory information is already fetched. + VMA_ASSERT(GetMemoryTypeCount() > 0); + + uint32_t memoryTypeBits = UINT32_MAX; + + if(!m_UseAmdDeviceCoherentMemory) + { + // Exclude memory types that have VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + if((m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0) + { + memoryTypeBits &= ~(1u << memTypeIndex); + } + } + } + + return memoryTypeBits; +} + +bool VmaAllocator_T::GetFlushOrInvalidateRange( + VmaAllocation allocation, + VkDeviceSize offset, VkDeviceSize size, + VkMappedMemoryRange& outRange) const +{ + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + if(size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) + { + const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; + const VkDeviceSize allocationSize = allocation->GetSize(); + VMA_ASSERT(offset <= allocationSize); + + outRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + outRange.pNext = VMA_NULL; + outRange.memory = allocation->GetMemory(); + + switch(allocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); + if(size == VK_WHOLE_SIZE) + { + outRange.size = allocationSize - outRange.offset; + } + else + { + VMA_ASSERT(offset + size <= allocationSize); + outRange.size = VMA_MIN( + VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize), + allocationSize - outRange.offset); + } + break; + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + // 1. Still within this allocation. + outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); + if(size == VK_WHOLE_SIZE) + { + size = allocationSize - offset; + } + else + { + VMA_ASSERT(offset + size <= allocationSize); + } + outRange.size = VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize); + + // 2. Adjust to whole block. + const VkDeviceSize allocationOffset = allocation->GetOffset(); + VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0); + const VkDeviceSize blockSize = allocation->GetBlock()->m_pMetadata->GetSize(); + outRange.offset += allocationOffset; + outRange.size = VMA_MIN(outRange.size, blockSize - outRange.offset); + + break; + } + default: + VMA_ASSERT(0); + } + return true; + } + return false; +} + +#if VMA_MEMORY_BUDGET + +void VmaAllocator_T::UpdateVulkanBudget() +{ + VMA_ASSERT(m_UseExtMemoryBudget); + + VkPhysicalDeviceMemoryProperties2KHR memProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR }; + + VkPhysicalDeviceMemoryBudgetPropertiesEXT budgetProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT }; + VmaPnextChainPushFront(&memProps, &budgetProps); + + GetVulkanFunctions().vkGetPhysicalDeviceMemoryProperties2KHR(m_PhysicalDevice, &memProps); + + { + VmaMutexLockWrite lockWrite(m_Budget.m_BudgetMutex, m_UseMutex); + + for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) + { + m_Budget.m_VulkanUsage[heapIndex] = budgetProps.heapUsage[heapIndex]; + m_Budget.m_VulkanBudget[heapIndex] = budgetProps.heapBudget[heapIndex]; + m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] = m_Budget.m_BlockBytes[heapIndex].load(); + + // Some bugged drivers return the budget incorrectly, e.g. 0 or much bigger than heap size. + if(m_Budget.m_VulkanBudget[heapIndex] == 0) + { + m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. + } + else if(m_Budget.m_VulkanBudget[heapIndex] > m_MemProps.memoryHeaps[heapIndex].size) + { + m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size; + } + if(m_Budget.m_VulkanUsage[heapIndex] == 0 && m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] > 0) + { + m_Budget.m_VulkanUsage[heapIndex] = m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; + } + } + m_Budget.m_OperationsSinceBudgetFetch = 0; + } +} + +#endif // #if VMA_MEMORY_BUDGET + void VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pattern) { if(VMA_DEBUG_INITIALIZE_ALLOCATIONS && @@ -15549,6 +17671,17 @@ void VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pat } } +uint32_t VmaAllocator_T::GetGpuDefragmentationMemoryTypeBits() +{ + uint32_t memoryTypeBits = m_GpuDefragmentationMemoryTypeBits.load(); + if(memoryTypeBits == UINT32_MAX) + { + memoryTypeBits = CalculateGpuDefragmentationMemoryTypeBits(); + m_GpuDefragmentationMemoryTypeBits.store(memoryTypeBits); + } + return memoryTypeBits; +} + #if VMA_STATS_STRING_ENABLED void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) @@ -15571,7 +17704,7 @@ void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) json.BeginString("Type "); json.ContinueString(memTypeIndex); json.EndString(); - + json.BeginArray(); for(size_t i = 0; i < pDedicatedAllocVector->size(); ++i) @@ -15642,17 +17775,19 @@ void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) //////////////////////////////////////////////////////////////////////////////// // Public interface -VkResult vmaCreateAllocator( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( const VmaAllocatorCreateInfo* pCreateInfo, VmaAllocator* pAllocator) { VMA_ASSERT(pCreateInfo && pAllocator); + VMA_ASSERT(pCreateInfo->vulkanApiVersion == 0 || + (VK_VERSION_MAJOR(pCreateInfo->vulkanApiVersion) == 1 && VK_VERSION_MINOR(pCreateInfo->vulkanApiVersion) <= 2)); VMA_DEBUG_LOG("vmaCreateAllocator"); *pAllocator = vma_new(pCreateInfo->pAllocationCallbacks, VmaAllocator_T)(pCreateInfo); return (*pAllocator)->Init(pCreateInfo); } -void vmaDestroyAllocator( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( VmaAllocator allocator) { if(allocator != VK_NULL_HANDLE) @@ -15663,7 +17798,15 @@ void vmaDestroyAllocator( } } -void vmaGetPhysicalDeviceProperties( +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(VmaAllocator allocator, VmaAllocatorInfo* pAllocatorInfo) +{ + VMA_ASSERT(allocator && pAllocatorInfo); + pAllocatorInfo->instance = allocator->m_hInstance; + pAllocatorInfo->physicalDevice = allocator->GetPhysicalDevice(); + pAllocatorInfo->device = allocator->m_hDevice; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( VmaAllocator allocator, const VkPhysicalDeviceProperties **ppPhysicalDeviceProperties) { @@ -15671,7 +17814,7 @@ void vmaGetPhysicalDeviceProperties( *ppPhysicalDeviceProperties = &allocator->m_PhysicalDeviceProperties; } -void vmaGetMemoryProperties( +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( VmaAllocator allocator, const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties) { @@ -15679,7 +17822,7 @@ void vmaGetMemoryProperties( *ppPhysicalDeviceMemoryProperties = &allocator->m_MemProps; } -void vmaGetMemoryTypeProperties( +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( VmaAllocator allocator, uint32_t memoryTypeIndex, VkMemoryPropertyFlags* pFlags) @@ -15689,7 +17832,7 @@ void vmaGetMemoryTypeProperties( *pFlags = allocator->m_MemProps.memoryTypes[memoryTypeIndex].propertyFlags; } -void vmaSetCurrentFrameIndex( +VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( VmaAllocator allocator, uint32_t frameIndex) { @@ -15701,7 +17844,7 @@ void vmaSetCurrentFrameIndex( allocator->SetCurrentFrameIndex(frameIndex); } -void vmaCalculateStats( +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats( VmaAllocator allocator, VmaStats* pStats) { @@ -15710,9 +17853,18 @@ void vmaCalculateStats( allocator->CalculateStats(pStats); } +VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget( + VmaAllocator allocator, + VmaBudget* pBudget) +{ + VMA_ASSERT(allocator && pBudget); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + allocator->GetBudget(pBudget, 0, allocator->GetMemoryHeapCount()); +} + #if VMA_STATS_STRING_ENABLED -void vmaBuildStatsString( +VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( VmaAllocator allocator, char** ppStatsString, VkBool32 detailedMap) @@ -15725,12 +17877,15 @@ void vmaBuildStatsString( VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb); json.BeginObject(); + VmaBudget budget[VK_MAX_MEMORY_HEAPS]; + allocator->GetBudget(budget, 0, allocator->GetMemoryHeapCount()); + VmaStats stats; allocator->CalculateStats(&stats); json.WriteString("Total"); VmaPrintStatInfo(json, stats.total); - + for(uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex) { json.BeginString("Heap "); @@ -15749,6 +17904,20 @@ void vmaBuildStatsString( } json.EndArray(); + json.WriteString("Budget"); + json.BeginObject(); + { + json.WriteString("BlockBytes"); + json.WriteNumber(budget[heapIndex].blockBytes); + json.WriteString("AllocationBytes"); + json.WriteNumber(budget[heapIndex].allocationBytes); + json.WriteString("Usage"); + json.WriteNumber(budget[heapIndex].usage); + json.WriteString("Budget"); + json.WriteNumber(budget[heapIndex].budget); + } + json.EndObject(); + if(stats.memoryHeap[heapIndex].blockCount > 0) { json.WriteString("Stats"); @@ -15788,6 +17957,18 @@ void vmaBuildStatsString( { json.WriteString("LAZILY_ALLOCATED"); } + if((flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) + { + json.WriteString(" PROTECTED"); + } + if((flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0) + { + json.WriteString(" DEVICE_COHERENT"); + } + if((flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY) != 0) + { + json.WriteString(" DEVICE_UNCACHED"); + } json.EndArray(); if(stats.memoryType[typeIndex].blockCount > 0) @@ -15820,7 +18001,7 @@ void vmaBuildStatsString( *ppStatsString = pChars; } -void vmaFreeStatsString( +VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( VmaAllocator allocator, char* pStatsString) { @@ -15837,7 +18018,7 @@ void vmaFreeStatsString( /* This function is not protected by any mutex because it just reads immutable data. */ -VkResult vmaFindMemoryTypeIndex( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( VmaAllocator allocator, uint32_t memoryTypeBits, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -15847,19 +18028,16 @@ VkResult vmaFindMemoryTypeIndex( VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); + memoryTypeBits &= allocator->GetGlobalMemoryTypeBits(); + if(pAllocationCreateInfo->memoryTypeBits != 0) { memoryTypeBits &= pAllocationCreateInfo->memoryTypeBits; } - + uint32_t requiredFlags = pAllocationCreateInfo->requiredFlags; uint32_t preferredFlags = pAllocationCreateInfo->preferredFlags; - - const bool mapped = (pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; - if(mapped) - { - preferredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - } + uint32_t notPreferredFlags = 0; // Convert usage to requiredFlags and preferredFlags. switch(pAllocationCreateInfo->usage) @@ -15884,12 +18062,26 @@ VkResult vmaFindMemoryTypeIndex( break; case VMA_MEMORY_USAGE_GPU_TO_CPU: requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - preferredFlags |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + break; + case VMA_MEMORY_USAGE_CPU_COPY: + notPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + break; + case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: + requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; break; default: + VMA_ASSERT(0); break; } + // Avoid DEVICE_COHERENT unless explicitly requested. + if(((pAllocationCreateInfo->requiredFlags | pAllocationCreateInfo->preferredFlags) & + (VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY)) == 0) + { + notPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY; + } + *pMemoryTypeIndex = UINT32_MAX; uint32_t minCost = UINT32_MAX; for(uint32_t memTypeIndex = 0, memTypeBit = 1; @@ -15905,7 +18097,8 @@ VkResult vmaFindMemoryTypeIndex( if((requiredFlags & ~currFlags) == 0) { // Calculate cost as number of bits from preferredFlags not present in this memory type. - uint32_t currCost = VmaCountBitsSet(preferredFlags & ~currFlags); + uint32_t currCost = VmaCountBitsSet(preferredFlags & ~currFlags) + + VmaCountBitsSet(currFlags & notPreferredFlags); // Remember memory type with lowest cost. if(currCost < minCost) { @@ -15922,7 +18115,7 @@ VkResult vmaFindMemoryTypeIndex( return (*pMemoryTypeIndex != UINT32_MAX) ? VK_SUCCESS : VK_ERROR_FEATURE_NOT_PRESENT; } -VkResult vmaFindMemoryTypeIndexForBufferInfo( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -15955,7 +18148,7 @@ VkResult vmaFindMemoryTypeIndexForBufferInfo( return res; } -VkResult vmaFindMemoryTypeIndexForImageInfo( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -15988,44 +18181,44 @@ VkResult vmaFindMemoryTypeIndexForImageInfo( return res; } -VkResult vmaCreatePool( - VmaAllocator allocator, - const VmaPoolCreateInfo* pCreateInfo, - VmaPool* pPool) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( + VmaAllocator allocator, + const VmaPoolCreateInfo* pCreateInfo, + VmaPool* pPool) { VMA_ASSERT(allocator && pCreateInfo && pPool); - + VMA_DEBUG_LOG("vmaCreatePool"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK - + VkResult res = allocator->CreatePool(pCreateInfo, pPool); - + #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordCreatePool(allocator->GetCurrentFrameIndex(), *pCreateInfo, *pPool); } #endif - + return res; } -void vmaDestroyPool( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( VmaAllocator allocator, VmaPool pool) { VMA_ASSERT(allocator); - + if(pool == VK_NULL_HANDLE) { return; } - + VMA_DEBUG_LOG("vmaDestroyPool"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK - + #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) { @@ -16036,7 +18229,7 @@ void vmaDestroyPool( allocator->DestroyPool(pool); } -void vmaGetPoolStats( +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats( VmaAllocator allocator, VmaPool pool, VmaPoolStats* pPoolStats) @@ -16048,7 +18241,7 @@ void vmaGetPoolStats( allocator->GetPoolStats(pool, pPoolStats); } -void vmaMakePoolAllocationsLost( +VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost( VmaAllocator allocator, VmaPool pool, size_t* pLostAllocationCount) @@ -16067,7 +18260,7 @@ void vmaMakePoolAllocationsLost( allocator->MakePoolAllocationsLost(pool, pLostAllocationCount); } -VkResult vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) { VMA_ASSERT(allocator && pool); @@ -16078,7 +18271,42 @@ VkResult vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) return allocator->CheckPoolCorruption(pool); } -VkResult vmaAllocateMemory( +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( + VmaAllocator allocator, + VmaPool pool, + const char** ppName) +{ + VMA_ASSERT(allocator && pool && ppName); + + VMA_DEBUG_LOG("vmaGetPoolName"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *ppName = pool->GetName(); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( + VmaAllocator allocator, + VmaPool pool, + const char* pName) +{ + VMA_ASSERT(allocator && pool); + + VMA_DEBUG_LOG("vmaSetPoolName"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + pool->SetName(pName); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + allocator->GetRecorder()->RecordSetPoolName(allocator->GetCurrentFrameIndex(), pool, pName); + } +#endif +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, @@ -16091,11 +18319,12 @@ VkResult vmaAllocateMemory( VMA_DEBUG_GLOBAL_MUTEX_LOCK - VkResult result = allocator->AllocateMemory( + VkResult result = allocator->AllocateMemory( *pVkMemoryRequirements, false, // requiresDedicatedAllocation false, // prefersDedicatedAllocation VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, @@ -16112,16 +18341,16 @@ VkResult vmaAllocateMemory( *pAllocation); } #endif - + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } - return result; + return result; } -VkResult vmaAllocateMemoryPages( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, @@ -16140,11 +18369,12 @@ VkResult vmaAllocateMemoryPages( VMA_DEBUG_GLOBAL_MUTEX_LOCK - VkResult result = allocator->AllocateMemory( + VkResult result = allocator->AllocateMemory( *pVkMemoryRequirements, false, // requiresDedicatedAllocation false, // prefersDedicatedAllocation VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, @@ -16162,7 +18392,7 @@ VkResult vmaAllocateMemoryPages( pAllocations); } #endif - + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) { for(size_t i = 0; i < allocationCount; ++i) @@ -16171,10 +18401,10 @@ VkResult vmaAllocateMemoryPages( } } - return result; + return result; } -VkResult vmaAllocateMemoryForBuffer( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( VmaAllocator allocator, VkBuffer buffer, const VmaAllocationCreateInfo* pCreateInfo, @@ -16199,6 +18429,7 @@ VkResult vmaAllocateMemoryForBuffer( requiresDedicatedAllocation, prefersDedicatedAllocation, buffer, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, @@ -16223,10 +18454,10 @@ VkResult vmaAllocateMemoryForBuffer( allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } - return result; + return result; } -VkResult vmaAllocateMemoryForImage( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( VmaAllocator allocator, VkImage image, const VmaAllocationCreateInfo* pCreateInfo, @@ -16250,6 +18481,7 @@ VkResult vmaAllocateMemoryForImage( requiresDedicatedAllocation, prefersDedicatedAllocation, VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage image, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN, @@ -16274,22 +18506,22 @@ VkResult vmaAllocateMemoryForImage( allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } - return result; + return result; } -void vmaFreeMemory( +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( VmaAllocator allocator, VmaAllocation allocation) { VMA_ASSERT(allocator); - + if(allocation == VK_NULL_HANDLE) { return; } - + VMA_DEBUG_LOG("vmaFreeMemory"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED @@ -16300,16 +18532,16 @@ void vmaFreeMemory( allocation); } #endif - + allocator->FreeMemory( 1, // allocationCount &allocation); } -void vmaFreeMemoryPages( +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( VmaAllocator allocator, size_t allocationCount, - VmaAllocation* pAllocations) + const VmaAllocation* pAllocations) { if(allocationCount == 0) { @@ -16317,9 +18549,9 @@ void vmaFreeMemoryPages( } VMA_ASSERT(allocator); - + VMA_DEBUG_LOG("vmaFreeMemoryPages"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED @@ -16331,35 +18563,25 @@ void vmaFreeMemoryPages( pAllocations); } #endif - + allocator->FreeMemory(allocationCount, pAllocations); } -VkResult vmaResizeAllocation( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize newSize) { VMA_ASSERT(allocator && allocation); - + VMA_DEBUG_LOG("vmaResizeAllocation"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK -#if VMA_RECORDING_ENABLED - if(allocator->GetRecorder() != VMA_NULL) - { - allocator->GetRecorder()->RecordResizeAllocation( - allocator->GetCurrentFrameIndex(), - allocation, - newSize); - } -#endif - return allocator->ResizeAllocation(allocation, newSize); } -void vmaGetAllocationInfo( +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( VmaAllocator allocator, VmaAllocation allocation, VmaAllocationInfo* pAllocationInfo) @@ -16380,7 +18602,7 @@ void vmaGetAllocationInfo( allocator->GetAllocationInfo(allocation, pAllocationInfo); } -VkBool32 vmaTouchAllocation( +VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation( VmaAllocator allocator, VmaAllocation allocation) { @@ -16400,7 +18622,7 @@ VkBool32 vmaTouchAllocation( return allocator->TouchAllocation(allocation); } -void vmaSetAllocationUserData( +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( VmaAllocator allocator, VmaAllocation allocation, void* pUserData) @@ -16422,7 +18644,7 @@ void vmaSetAllocationUserData( #endif } -void vmaCreateLostAllocation( +VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation( VmaAllocator allocator, VmaAllocation* pAllocation) { @@ -16442,7 +18664,7 @@ void vmaCreateLostAllocation( #endif } -VkResult vmaMapMemory( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( VmaAllocator allocator, VmaAllocation allocation, void** ppData) @@ -16465,7 +18687,7 @@ VkResult vmaMapMemory( return res; } -void vmaUnmapMemory( +VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( VmaAllocator allocator, VmaAllocation allocation) { @@ -16485,7 +18707,7 @@ void vmaUnmapMemory( allocator->Unmap(allocation); } -void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { VMA_ASSERT(allocator && allocation); @@ -16493,7 +18715,7 @@ void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDevi VMA_DEBUG_GLOBAL_MUTEX_LOCK - allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH); + const VkResult res = allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH); #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) @@ -16503,9 +18725,11 @@ void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDevi allocation, offset, size); } #endif + + return res; } -void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { VMA_ASSERT(allocator && allocation); @@ -16513,7 +18737,7 @@ void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, V VMA_DEBUG_GLOBAL_MUTEX_LOCK - allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE); + const VkResult res = allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE); #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) @@ -16523,9 +18747,75 @@ void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, V allocation, offset, size); } #endif + + return res; } -VkResult vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations( + VmaAllocator allocator, + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, + const VkDeviceSize* sizes) +{ + VMA_ASSERT(allocator); + + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocations); + + VMA_DEBUG_LOG("vmaFlushAllocations"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + const VkResult res = allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_FLUSH); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + //TODO + } +#endif + + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations( + VmaAllocator allocator, + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, + const VkDeviceSize* sizes) +{ + VMA_ASSERT(allocator); + + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocations); + + VMA_DEBUG_LOG("vmaInvalidateAllocations"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + const VkResult res = allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_INVALIDATE); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + //TODO + } +#endif + + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) { VMA_ASSERT(allocator); @@ -16536,9 +18826,9 @@ VkResult vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) return allocator->CheckCorruption(memoryTypeBits); } -VkResult vmaDefragment( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment( VmaAllocator allocator, - VmaAllocation* pAllocations, + const VmaAllocation* pAllocations, size_t allocationCount, VkBool32* pAllocationsChanged, const VmaDefragmentationInfo *pDefragmentationInfo, @@ -16571,7 +18861,7 @@ VkResult vmaDefragment( return res; } -VkResult vmaDefragmentationBegin( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin( VmaAllocator allocator, const VmaDefragmentationInfo2* pInfo, VmaDefragmentationStats* pStats, @@ -16607,7 +18897,7 @@ VkResult vmaDefragmentationBegin( return res; } -VkResult vmaDefragmentationEnd( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd( VmaAllocator allocator, VmaDefragmentationContext context) { @@ -16635,7 +18925,43 @@ VkResult vmaDefragmentationEnd( } } -VkResult vmaBindBufferMemory( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass( + VmaAllocator allocator, + VmaDefragmentationContext context, + VmaDefragmentationPassInfo* pInfo + ) +{ + VMA_ASSERT(allocator); + VMA_ASSERT(pInfo); + + VMA_DEBUG_LOG("vmaBeginDefragmentationPass"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if(context == VK_NULL_HANDLE) + { + pInfo->moveCount = 0; + return VK_SUCCESS; + } + + return allocator->DefragmentationPassBegin(pInfo, context); +} +VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass( + VmaAllocator allocator, + VmaDefragmentationContext context) +{ + VMA_ASSERT(allocator); + + VMA_DEBUG_LOG("vmaEndDefragmentationPass"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if(context == VK_NULL_HANDLE) + return VK_SUCCESS; + + return allocator->DefragmentationPassEnd(context); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( VmaAllocator allocator, VmaAllocation allocation, VkBuffer buffer) @@ -16646,10 +18972,26 @@ VkResult vmaBindBufferMemory( VMA_DEBUG_GLOBAL_MUTEX_LOCK - return allocator->BindBufferMemory(allocation, buffer); + return allocator->BindBufferMemory(allocation, 0, buffer, VMA_NULL); } -VkResult vmaBindImageMemory( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize allocationLocalOffset, + VkBuffer buffer, + const void* pNext) +{ + VMA_ASSERT(allocator && allocation && buffer); + + VMA_DEBUG_LOG("vmaBindBufferMemory2"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindBufferMemory(allocation, allocationLocalOffset, buffer, pNext); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( VmaAllocator allocator, VmaAllocation allocation, VkImage image) @@ -16660,10 +19002,26 @@ VkResult vmaBindImageMemory( VMA_DEBUG_GLOBAL_MUTEX_LOCK - return allocator->BindImageMemory(allocation, image); + return allocator->BindImageMemory(allocation, 0, image, VMA_NULL); } -VkResult vmaCreateBuffer( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize allocationLocalOffset, + VkImage image, + const void* pNext) +{ + VMA_ASSERT(allocator && allocation && image); + + VMA_DEBUG_LOG("vmaBindImageMemory2"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindImageMemory(allocation, allocationLocalOffset, image, pNext); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -16677,9 +19035,15 @@ VkResult vmaCreateBuffer( { return VK_ERROR_VALIDATION_FAILED_EXT; } - + if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 && + !allocator->m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used."); + return VK_ERROR_VALIDATION_FAILED_EXT; + } + VMA_DEBUG_LOG("vmaCreateBuffer"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK *pBuffer = VK_NULL_HANDLE; @@ -16700,30 +19064,13 @@ VkResult vmaCreateBuffer( allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation); - // Make sure alignment requirements for specific buffer usages reported - // in Physical Device Properties are included in alignment reported by memory requirements. - if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0) - { - VMA_ASSERT(vkMemReq.alignment % - allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0); - } - if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0) - { - VMA_ASSERT(vkMemReq.alignment % - allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0); - } - if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) - { - VMA_ASSERT(vkMemReq.alignment % - allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0); - } - // 3. Allocate memory using allocator. res = allocator->AllocateMemory( vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, *pBuffer, // dedicatedBuffer + pBufferCreateInfo->usage, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pAllocationCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, @@ -16744,7 +19091,10 @@ VkResult vmaCreateBuffer( if(res >= 0) { // 3. Bind buffer with memory. - res = allocator->BindBufferMemory(*pAllocation, *pBuffer); + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL); + } if(res >= 0) { // All steps succeeded. @@ -16773,7 +19123,7 @@ VkResult vmaCreateBuffer( return res; } -void vmaDestroyBuffer( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( VmaAllocator allocator, VkBuffer buffer, VmaAllocation allocation) @@ -16811,7 +19161,7 @@ void vmaDestroyBuffer( } } -VkResult vmaCreateImage( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -16848,7 +19198,7 @@ VkResult vmaCreateImage( VmaSuballocationType suballocType = pImageCreateInfo->tiling == VK_IMAGE_TILING_OPTIMAL ? VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL : VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR; - + // 2. Allocate memory using allocator. VkMemoryRequirements vkMemReq = {}; bool requiresDedicatedAllocation = false; @@ -16861,6 +19211,7 @@ VkResult vmaCreateImage( requiresDedicatedAllocation, prefersDedicatedAllocation, VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage *pImage, // dedicatedImage *pAllocationCreateInfo, suballocType, @@ -16881,7 +19232,10 @@ VkResult vmaCreateImage( if(res >= 0) { // 3. Bind image with memory. - res = allocator->BindImageMemory(*pAllocation, *pImage); + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindImageMemory(*pAllocation, 0, *pImage, VMA_NULL); + } if(res >= 0) { // All steps succeeded. @@ -16910,7 +19264,7 @@ VkResult vmaCreateImage( return res; } -void vmaDestroyImage( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( VmaAllocator allocator, VkImage image, VmaAllocation allocation)