diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax index 13af92065..519aebba2 100644 --- a/Content/Editor/IconsAtlas.flax +++ b/Content/Editor/IconsAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de930eef22208b95882bc9f870063e339672b577d381bcfa6603f0be45c814c5 -size 5610110 +oid sha256:63e9f8409580a700befdfceac9f8db3123efb72eb589b54d86429d330ff36a8f +size 5611340 diff --git a/Content/Shaders/BakeLightmap.flax b/Content/Shaders/BakeLightmap.flax index fac992469..37ae66747 100644 --- a/Content/Shaders/BakeLightmap.flax +++ b/Content/Shaders/BakeLightmap.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc4b141137661d995ff571191150c5997fa6f6576572b5c2281c395a12772d7c -size 16095 +oid sha256:6f3f83f05ee829a7981dc8b832f5b1160f23cf779aa84a7838c63aa7ff0bb336 +size 16284 diff --git a/Flax.flaxproj b/Flax.flaxproj index 6826bedfe..e135c2741 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 1, - "Build": 6219 + "Build": 6221 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.", diff --git a/README.md b/README.md index be785107d..1c11dec82 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,16 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de * Compile Flax project (hit F7 or CTRL+Shift+B) * Run Flax (hit F5 key) +--- + +**Note** + +If building on Windows to support Vulkan rendering, first install the Vulkan SDK then set an environment variable to provide the path to the SDK prior to running GenerateProjectFiles.bat: + +    set VULKAN_SDK=C:\VulkanSDK\version\ + +--- + ## Linux * Install Visual Studio Code diff --git a/Source/Editor/Content/Create/SettingsCreateEntry.cs b/Source/Editor/Content/Create/SettingsCreateEntry.cs index 974de76cc..8445425b0 100644 --- a/Source/Editor/Content/Create/SettingsCreateEntry.cs +++ b/Source/Editor/Content/Create/SettingsCreateEntry.cs @@ -68,6 +68,11 @@ namespace FlaxEditor.Content.Create /// InputSettings, + /// + /// The streaming settings. + /// + StreamingSettings, + /// /// The Windows settings. /// @@ -116,6 +121,7 @@ namespace FlaxEditor.Content.Create typeof(LocalizationSettings), typeof(BuildSettings), typeof(InputSettings), + typeof(StreamingSettings), typeof(WindowsPlatformSettings), typeof(UWPPlatformSettings), typeof(LinuxPlatformSettings), diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs index db1e3ae8c..0985f7e5a 100644 --- a/Source/Editor/Content/Import/TextureImportEntry.cs +++ b/Source/Editor/Content/Import/TextureImportEntry.cs @@ -283,6 +283,13 @@ namespace FlaxEditor.Content.Import [EditorOrder(250), VisibleIf("PreserveAlphaCoverage"), DefaultValue(0.5f), Tooltip("The reference value for the alpha coverage preserving.")] public float PreserveAlphaCoverageReference { get; set; } = 0.5f; + /// + /// Texture group for streaming (negative if unused). See Streaming Settings. + /// + [CustomEditor(typeof(CustomEditors.Dedicated.TextureGroupEditor))] + [EditorOrder(300), Tooltip("Texture group for streaming (negative if unused). See Streaming Settings.")] + public int TextureGroup = -1; + /// /// The sprites. Used to keep created sprites on sprite atlas reimport. /// @@ -305,6 +312,7 @@ namespace FlaxEditor.Content.Import public float PreserveAlphaCoverageReference; public float Scale; public int MaxSize; + public int TextureGroup; public Int2 Size; public Rectangle[] SpriteAreas; public string[] SpriteNames; @@ -327,7 +335,8 @@ namespace FlaxEditor.Content.Import PreserveAlphaCoverageReference = PreserveAlphaCoverageReference, Scale = Scale, Size = Size, - MaxSize = (int)MaxSize + MaxSize = (int)MaxSize, + TextureGroup = TextureGroup, }; if (Sprites != null && Sprites.Count > 0) { @@ -362,6 +371,7 @@ namespace FlaxEditor.Content.Import PreserveAlphaCoverageReference = options.PreserveAlphaCoverageReference; Scale = options.Scale; MaxSize = ConvertMaxSize(options.MaxSize); + TextureGroup = options.TextureGroup; Size = options.Size; if (options.SpriteAreas != null) { diff --git a/Source/Editor/Content/Items/AssetItem.cs b/Source/Editor/Content/Items/AssetItem.cs index afb68d42a..f534e93eb 100644 --- a/Source/Editor/Content/Items/AssetItem.cs +++ b/Source/Editor/Content/Items/AssetItem.cs @@ -108,6 +108,26 @@ namespace FlaxEditor.Content return false; } + /// + /// Called when user dags this item into editor viewport or scene tree node. + /// + /// The editor context (eg. editor viewport or scene tree node). + /// True if item can be dropped in, otherwise false. + public virtual bool OnEditorDrag(object context) + { + return false; + } + + /// + /// Called when user drops the item into editor viewport or scene tree node. + /// + /// The editor context (eg. editor viewport or scene tree node). + /// The spawned object. + public virtual Actor OnEditorDrop(object context) + { + throw new NotSupportedException($"Asset {GetType()} doesn't support dropping into viewport."); + } + /// protected override bool DrawShadow => true; diff --git a/Source/Editor/Content/Items/BinaryAssetItem.cs b/Source/Editor/Content/Items/BinaryAssetItem.cs index 417bd23e4..2503e9a38 100644 --- a/Source/Editor/Content/Items/BinaryAssetItem.cs +++ b/Source/Editor/Content/Items/BinaryAssetItem.cs @@ -103,14 +103,26 @@ namespace FlaxEditor.Content /// Implementation of for assets. /// /// - public class ModelAssetItem : BinaryAssetItem + public class ModelItem : BinaryAssetItem { /// - public ModelAssetItem(string path, ref Guid id, string typeName, Type type) + public ModelItem(string path, ref Guid id, string typeName, Type type) : base(path, ref id, typeName, type, ContentItemSearchFilter.Model) { } + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return new StaticModel { Model = FlaxEngine.Content.LoadAsync(ID) }; + } + /// protected override void OnBuildTooltipText(StringBuilder sb) { @@ -142,14 +154,26 @@ namespace FlaxEditor.Content /// Implementation of for assets. /// /// - public class SkinnedModelAssetItem : BinaryAssetItem + public class SkinnedModeItem : BinaryAssetItem { /// - public SkinnedModelAssetItem(string path, ref Guid id, string typeName, Type type) + public SkinnedModeItem(string path, ref Guid id, string typeName, Type type) : base(path, ref id, typeName, type, ContentItemSearchFilter.Model) { } + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return new AnimatedModel { SkinnedModel = FlaxEngine.Content.LoadAsync(ID) }; + } + /// protected override void OnBuildTooltipText(StringBuilder sb) { diff --git a/Source/Editor/Content/Items/JsonAssetItem.cs b/Source/Editor/Content/Items/JsonAssetItem.cs index 1c9232c9c..80957f181 100644 --- a/Source/Editor/Content/Items/JsonAssetItem.cs +++ b/Source/Editor/Content/Items/JsonAssetItem.cs @@ -22,7 +22,7 @@ namespace FlaxEditor.Content public JsonAssetItem(string path, Guid id, string typeName) : base(path, typeName, ref id) { - _thumbnail = Editor.Instance.Icons.Document128; + _thumbnail = Editor.Instance.Icons.Json128; } /// diff --git a/Source/Editor/Content/Items/PrefabItem.cs b/Source/Editor/Content/Items/PrefabItem.cs index aee264d55..e758595ae 100644 --- a/Source/Editor/Content/Items/PrefabItem.cs +++ b/Source/Editor/Content/Items/PrefabItem.cs @@ -21,6 +21,18 @@ namespace FlaxEditor.Content { } + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return PrefabManager.SpawnPrefab(FlaxEngine.Content.LoadAsync(ID), null); + } + /// public override ContentItemType ItemType => ContentItemType.Asset; diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 04311644b..c1ccf5d73 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -538,6 +538,18 @@ namespace FlaxEditor.Content Editor.Instance.CodeEditing.ClearTypes(); } + /// + public override bool OnEditorDrag(object context) + { + return new ScriptType(typeof(Actor)).IsAssignableFrom(ScriptType) && ScriptType.CanCreateInstance; + } + + /// + public override Actor OnEditorDrop(object context) + { + return (Actor)ScriptType.CreateInstance(); + } + /// public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.VisualScript128; diff --git a/Source/Editor/Content/PreviewsCache.cpp b/Source/Editor/Content/PreviewsCache.cpp index c89b00004..e05f16600 100644 --- a/Source/Editor/Content/PreviewsCache.cpp +++ b/Source/Editor/Content/PreviewsCache.cpp @@ -241,16 +241,13 @@ CreateAssetResult PreviewsCache::create(CreateAssetContext& context) IMPORT_SETUP(PreviewsCache, 4); // Create texture header (custom data) - TextureHeader header; - header.Width = ASSETS_ICONS_ATLAS_SIZE; - header.Height = ASSETS_ICONS_ATLAS_SIZE; - header.Format = ASSETS_ICONS_ATLAS_FORMAT; - header.IsSRGB = false; - header.IsCubeMap = false; - header.MipLevels = 1; - header.NeverStream = true; - header.Type = TextureFormatType::Unknown; - context.Data.CustomData.Copy(&header); + TextureHeader textureHeader; + textureHeader.Width = ASSETS_ICONS_ATLAS_SIZE; + textureHeader.Height = ASSETS_ICONS_ATLAS_SIZE; + textureHeader.Format = ASSETS_ICONS_ATLAS_FORMAT; + textureHeader.MipLevels = 1; + textureHeader.NeverStream = true; + context.Data.CustomData.Copy(&textureHeader); // Create blank image (chunk 0) uint64 imageSize = CalculateTextureMemoryUsage(ASSETS_ICONS_ATLAS_FORMAT, ASSETS_ICONS_ATLAS_SIZE, ASSETS_ICONS_ATLAS_SIZE, 1); diff --git a/Source/Editor/Content/Proxy/AudioClipProxy.cs b/Source/Editor/Content/Proxy/AudioClipProxy.cs index ba60d6ef9..ceff22dd1 100644 --- a/Source/Editor/Content/Proxy/AudioClipProxy.cs +++ b/Source/Editor/Content/Proxy/AudioClipProxy.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Text; using FlaxEditor.Content.Thumbnails; using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows; @@ -11,11 +12,51 @@ using FlaxEngine.GUI; namespace FlaxEditor.Content { + /// + /// Implementation of for assets. + /// + /// + class AudioClipItem : BinaryAssetItem + { + /// + public AudioClipItem(string path, ref Guid id, string typeName, Type type) + : base(path, ref id, typeName, type, ContentItemSearchFilter.Audio) + { + } + + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return new AudioSource { Clip = FlaxEngine.Content.LoadAsync(ID) }; + } + + /// + protected override void OnBuildTooltipText(StringBuilder sb) + { + base.OnBuildTooltipText(sb); + + var asset = FlaxEngine.Content.Load(ID, 100); + if (asset) + { + var info = asset.Info; + sb.Append("Duration: ").Append(asset.Length).AppendLine(); + sb.Append("Channels: ").Append(info.NumChannels).AppendLine(); + sb.Append("Bit Depth: ").Append(info.BitDepth).AppendLine(); + } + } + } + /// /// A asset proxy object. /// /// - public class AudioClipProxy : BinaryAssetProxy + class AudioClipProxy : BinaryAssetProxy { private List _previews; @@ -34,6 +75,12 @@ namespace FlaxEditor.Content return new AudioClipWindow(editor, (AssetItem)item); } + /// + public override AssetItem ConstructItem(string path, string typeName, ref Guid id) + { + return new AudioClipItem(path, ref id, typeName, AssetType); + } + /// public override Color AccentColor => Color.FromRGB(0xB3452B); diff --git a/Source/Editor/Content/Proxy/BinaryAssetProxy.cs b/Source/Editor/Content/Proxy/BinaryAssetProxy.cs index 4d27f325b..d19f8fd63 100644 --- a/Source/Editor/Content/Proxy/BinaryAssetProxy.cs +++ b/Source/Editor/Content/Proxy/BinaryAssetProxy.cs @@ -47,9 +47,9 @@ namespace FlaxEditor.Content if (typeof(TextureBase).IsAssignableFrom(type)) return new TextureAssetItem(path, ref id, typeName, type); if (typeof(Model).IsAssignableFrom(type)) - return new ModelAssetItem(path, ref id, typeName, type); + return new ModelItem(path, ref id, typeName, type); if (typeof(SkinnedModel).IsAssignableFrom(type)) - return new SkinnedModelAssetItem(path, ref id, typeName, type); + return new SkinnedModeItem(path, ref id, typeName, type); ContentItemSearchFilter searchFilter; if (typeof(MaterialBase).IsAssignableFrom(type)) @@ -58,11 +58,9 @@ namespace FlaxEditor.Content searchFilter = ContentItemSearchFilter.Prefab; else if (typeof(SceneAsset).IsAssignableFrom(type)) searchFilter = ContentItemSearchFilter.Scene; - else if (typeof(AudioClip).IsAssignableFrom(type)) - searchFilter = ContentItemSearchFilter.Audio; else if (typeof(Animation).IsAssignableFrom(type)) searchFilter = ContentItemSearchFilter.Animation; - else if (typeof(ParticleEmitter).IsAssignableFrom(type) || typeof(ParticleSystem).IsAssignableFrom(type)) + else if (typeof(ParticleEmitter).IsAssignableFrom(type)) searchFilter = ContentItemSearchFilter.Particles; else searchFilter = ContentItemSearchFilter.Other; diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index c23730375..e7d437adc 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -8,11 +8,36 @@ using FlaxEngine; namespace FlaxEditor.Content { + /// + /// Implementation of for assets. + /// + /// + class CollisionDataItem : BinaryAssetItem + { + /// + public CollisionDataItem(string path, ref Guid id, string typeName, Type type) + : base(path, ref id, typeName, type, ContentItemSearchFilter.Other) + { + } + + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return new MeshCollider { CollisionData = FlaxEngine.Content.LoadAsync(ID) }; + } + } + /// /// A asset proxy object. /// /// - public class CollisionDataProxy : BinaryAssetProxy + class CollisionDataProxy : BinaryAssetProxy { /// public override string Name => "Collision Data"; @@ -23,6 +48,12 @@ namespace FlaxEditor.Content return new CollisionDataWindow(editor, item as AssetItem); } + /// + public override AssetItem ConstructItem(string path, string typeName, ref Guid id) + { + return new CollisionDataItem(path, ref id, typeName, AssetType); + } + /// public override Color AccentColor => Color.FromRGB(0x2c3e50); diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs index 41cc06ee1..5029c2eb4 100644 --- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs +++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs @@ -92,6 +92,7 @@ namespace FlaxEditor.Content !type.IsAbstract && !type.IsGenericType && type.Type.GetConstructor(Type.EmptyTypes) != null && + !typeof(FlaxEngine.GUI.Control).IsAssignableFrom(type.Type) && !typeof(FlaxEngine.Object).IsAssignableFrom(type.Type); } } diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs index a28a022a8..2baf717e1 100644 --- a/Source/Editor/Content/Proxy/ModelProxy.cs +++ b/Source/Editor/Content/Proxy/ModelProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content menu.AddButton("Create collision data", () => { - var model = FlaxEngine.Content.LoadAsync(((ModelAssetItem)item).ID); + var model = FlaxEngine.Content.LoadAsync(((ModelItem)item).ID); var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy(); collisionDataProxy.CreateCollisionDataFromModel(model); }); diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index f3b2eb67a..7e206fba9 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -10,6 +10,31 @@ using FlaxEngine.GUI; namespace FlaxEditor.Content { + /// + /// Implementation of for assets. + /// + /// + class ParticleSystemItem : BinaryAssetItem + { + /// + public ParticleSystemItem(string path, ref Guid id, string typeName, Type type) + : base(path, ref id, typeName, type, ContentItemSearchFilter.Particles) + { + } + + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return new ParticleEffect { ParticleSystem = FlaxEngine.Content.LoadAsync(ID) }; + } + } + /// /// A asset proxy object. /// @@ -28,6 +53,12 @@ namespace FlaxEditor.Content return new ParticleSystemWindow(editor, item as AssetItem); } + /// + public override AssetItem ConstructItem(string path, string typeName, ref Guid id) + { + return new ParticleSystemItem(path, ref id, typeName, AssetType); + } + /// public override Color AccentColor => Color.FromRGB(0xFF790200); diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs index 83d9247d8..66c1bfff2 100644 --- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs +++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs @@ -7,6 +7,31 @@ using FlaxEngine; namespace FlaxEditor.Content { + /// + /// Implementation of for assets. + /// + /// + class SceneAnimationItem : BinaryAssetItem + { + /// + public SceneAnimationItem(string path, ref Guid id, string typeName, Type type) + : base(path, ref id, typeName, type, ContentItemSearchFilter.Other) + { + } + + /// + public override bool OnEditorDrag(object context) + { + return true; + } + + /// + public override Actor OnEditorDrop(object context) + { + return new SceneAnimationPlayer { Animation = FlaxEngine.Content.LoadAsync(ID) }; + } + } + /// /// A asset proxy object. /// @@ -22,6 +47,12 @@ namespace FlaxEditor.Content return new SceneAnimationWindow(editor, item as AssetItem); } + /// + public override AssetItem ConstructItem(string path, string typeName, ref Guid id) + { + return new SceneAnimationItem(path, ref id, typeName, AssetType); + } + /// public override Color AccentColor => Color.FromRGB(0xff5c4a87); diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h index 20d578d2e..63b018343 100644 --- a/Source/Editor/Cooker/PlatformTools.h +++ b/Source/Editor/Cooker/PlatformTools.h @@ -136,6 +136,8 @@ public: String AssemblerArgs; String ArchiverPath; String ArchiverArgs; + String AuxToolPath; + String AuxToolArgs; String AotCachePath; Dictionary EnvVars; Array AssembliesSearchDirs; diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 5f097f638..703b4179a 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -25,6 +25,7 @@ #include "Engine/Core/Config/PlatformSettings.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" +#include "Engine/Streaming/StreamingSettings.h" #include "Engine/ShadersCompilation/ShadersCompilation.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" @@ -44,6 +45,35 @@ Dictionary CookAssetsStep::AssetProcessors; +bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies) +{ + AssetInfo assetInfo; + if (Content::GetAssetInfo(ID, assetInfo)) + { + if (TypeName == assetInfo.TypeName) + { + if (FileSystem::GetFileLastEditTime(assetInfo.Path) <= FileModified) + { + bool isValid = true; + if (withDependencies) + { + for (auto& f : FileDependencies) + { + if (FileSystem::GetFileLastEditTime(f.First) > f.Second) + { + isValid = false; + break; + } + } + } + if (isValid) + return true; + } + } + } + return false; +} + CookAssetsStep::CacheEntry& CookAssetsStep::CacheData::CreateEntry(const JsonAssetBase* asset, String& cachedFilePath) { ASSERT(asset->DataTypeName.HasChars()); @@ -68,7 +98,6 @@ CookAssetsStep::CacheEntry& CookAssetsStep::CacheData::CreateEntry(const Asset* void CookAssetsStep::CacheData::InvalidateShaders() { LOG(Info, "Invalidating cached shader assets."); - for (auto e = Entries.Begin(); e.IsNotEnd(); ++e) { auto& typeName = e->Value.TypeName; @@ -83,6 +112,23 @@ void CookAssetsStep::CacheData::InvalidateShaders() } } +void CookAssetsStep::CacheData::InvalidateTextures() +{ + LOG(Info, "Invalidating cached texture assets."); + for (auto e = Entries.Begin(); e.IsNotEnd(); ++e) + { + auto& typeName = e->Value.TypeName; + if ( + typeName == Texture::TypeName || + typeName == CubeTexture::TypeName || + typeName == SpriteAtlas::TypeName + ) + { + Entries.Remove(e); + } + } +} + void CookAssetsStep::CacheData::Load(CookingData& data) { HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD); @@ -159,17 +205,17 @@ void CookAssetsStep::CacheData::Load(CookingData& data) Entries.Clear(); } + const auto buildSettings = BuildSettings::Get(); + const auto gameSettings = GameSettings::Get(); + // Invalidate shaders and assets with shaders if need to rebuild them bool invalidateShaders = false; - const auto buildSettings = BuildSettings::Get(); - const bool shadersNoOptimize = buildSettings->ShadersNoOptimize; - const bool shadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; - if (shadersNoOptimize != Settings.Global.ShadersNoOptimize) + if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize) { LOG(Info, "ShadersNoOptimize option has been modified."); invalidateShaders = true; } - if (shadersGenerateDebugData != Settings.Global.ShadersGenerateDebugData) + if (buildSettings->ShadersGenerateDebugData != Settings.Global.ShadersGenerateDebugData) { LOG(Info, "ShadersGenerateDebugData option has been modified."); invalidateShaders = true; @@ -218,6 +264,12 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #endif if (invalidateShaders) InvalidateShaders(); + + // Invalidate textures if streaming settings gets modified + if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid())) + { + InvalidateTextures(); + } } void CookAssetsStep::CacheData::Save() @@ -541,91 +593,127 @@ bool ProcessParticleEmitter(CookAssetsStep::AssetCookData& data) bool ProcessTextureBase(CookAssetsStep::AssetCookData& data) { const auto asset = static_cast(data.Asset); - - // Check if target platform doesn't support the texture format + const auto& assetHeader = asset->StreamingTexture()->GetHeader(); const auto format = asset->Format(); const auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format); + const auto streamingSettings = StreamingSettings::Get(); + int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS; + if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count()) + { + auto& group = streamingSettings->TextureGroups[assetHeader->TextureGroup]; + mipLevelsMax = group.MipLevelsMax; + group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax); + } + + // Faster path if don't need to modify texture for the target platform + if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax) + { + return CookAssetsStep::ProcessDefaultAsset(data); + } + + // Extract texture data from the asset + TextureData textureDataSrc; + auto assetLock = asset->LockData(); + if (asset->GetTextureData(textureDataSrc, false)) + { + LOG(Error, "Failed to load data from texture {0}", asset->ToString()); + return true; + } + + TextureData* textureData = &textureDataSrc; + TextureData textureDataTmp1; + if (format != targetFormat) { - // Extract texture data from the asset - TextureData textureData; - auto assetLock = asset->LockData(); - if (asset->GetTextureData(textureData, false)) - { - LOG(Error, "Failed to load data from texture {0}", asset->ToString()); - return true; - } - // Convert texture data to the target format - TextureData targetTextureData; - if (TextureTool::Convert(targetTextureData, textureData, targetFormat)) + if (TextureTool::Convert(textureDataTmp1, *textureData, targetFormat)) { LOG(Error, "Failed to convert texture {0} from format {1} to {2}", asset->ToString(), (int32)format, (int32)targetFormat); return true; } - - // Adjust texture header - auto& header = *(TextureHeader*)data.InitData.CustomData.Get(); - header.Width = targetTextureData.Width; - header.Height = targetTextureData.Height; - header.Format = targetTextureData.Format; - header.MipLevels = targetTextureData.GetMipLevels(); - - // Serialize texture data into the asset chunks - for (int32 mipIndex = 0; mipIndex < targetTextureData.GetMipLevels(); mipIndex++) - { - auto chunk = New(); - data.InitData.Header.Chunks[mipIndex] = chunk; - - // Calculate the texture data storage layout - uint32 rowPitch, slicePitch; - const int32 mipWidth = Math::Max(1, targetTextureData.Width >> mipIndex); - const int32 mipHeight = Math::Max(1, targetTextureData.Height >> mipIndex); - RenderTools::ComputePitch(targetTextureData.Format, mipWidth, mipHeight, rowPitch, slicePitch); - chunk->Data.Allocate(slicePitch * targetTextureData.GetArraySize()); - - // Copy array slices into mip data (sequential) - for (int32 arrayIndex = 0; arrayIndex < targetTextureData.Items.Count(); arrayIndex++) - { - auto& mipData = targetTextureData.Items[arrayIndex].Mips[mipIndex]; - byte* src = mipData.Data.Get(); - byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex); - - // Faster path if source and destination data layout matches - if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch) - { - Platform::MemoryCopy(dst, src, slicePitch); - } - else - { - const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch); - for (uint32 line = 0; line < mipData.Lines; line++) - { - Platform::MemoryCopy(dst, src, copyRowSize); - src += mipData.RowPitch; - dst += rowPitch; - } - } - } - } - - // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks) - for (int32 i = 14; i < ASSET_FILE_DATA_CHUNKS; i++) - { - const auto chunk = asset->GetChunk(i); - if (chunk != nullptr && chunk->IsMissing() && chunk->ExistsInFile()) - { - if (asset->Storage->LoadAssetChunk(chunk)) - return true; - data.InitData.Header.Chunks[i] = chunk->Clone(); - } - } - - return false; + textureData = &textureDataSrc; } - // Fallback to the default asset processing - return CookAssetsStep::ProcessDefaultAsset(data); + if (assetHeader->MipLevels > mipLevelsMax) + { + // Reduce texture quality + const int32 mipLevelsToStrip = assetHeader->MipLevels - mipLevelsMax; + textureData->Width = Math::Max(1, textureData->Width >> mipLevelsToStrip); + textureData->Height = Math::Max(1, textureData->Height >> mipLevelsToStrip); + textureData->Depth = Math::Max(1, textureData->Depth >> mipLevelsToStrip); + for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) + { + auto& item = textureData->Items[arrayIndex]; + Array> oldMips(MoveTemp(item.Mips)); + item.Mips.Resize(mipLevelsMax); + for (int32 mipIndex = 0; mipIndex < mipLevelsMax; mipIndex++) + { + auto& dstMip = item.Mips[mipIndex]; + auto& srcMip = oldMips[mipIndex + mipLevelsToStrip]; + dstMip = MoveTemp(srcMip); + } + } + } + + // Adjust texture header + auto& header = *(TextureHeader*)data.InitData.CustomData.Get(); + header.Width = textureData->Width; + header.Height = textureData->Height; + header.Depth = textureData->Depth; + header.Format = textureData->Format; + header.MipLevels = textureData->GetMipLevels(); + + // Serialize texture data into the asset chunks + for (int32 mipIndex = 0; mipIndex < textureData->GetMipLevels(); mipIndex++) + { + auto chunk = New(); + data.InitData.Header.Chunks[mipIndex] = chunk; + + // Calculate the texture data storage layout + uint32 rowPitch, slicePitch; + const int32 mipWidth = Math::Max(1, textureData->Width >> mipIndex); + const int32 mipHeight = Math::Max(1, textureData->Height >> mipIndex); + RenderTools::ComputePitch(textureData->Format, mipWidth, mipHeight, rowPitch, slicePitch); + chunk->Data.Allocate(slicePitch * textureData->GetArraySize()); + + // Copy array slices into mip data (sequential) + for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) + { + auto& mipData = textureData->Items[arrayIndex].Mips[mipIndex]; + byte* src = mipData.Data.Get(); + byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex); + + // Faster path if source and destination data layout matches + if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch) + { + Platform::MemoryCopy(dst, src, slicePitch); + } + else + { + const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch); + for (uint32 line = 0; line < mipData.Lines; line++) + { + Platform::MemoryCopy(dst, src, copyRowSize); + src += mipData.RowPitch; + dst += rowPitch; + } + } + } + } + + // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks) + for (int32 i = 14; i < ASSET_FILE_DATA_CHUNKS; i++) + { + const auto chunk = asset->GetChunk(i); + if (chunk != nullptr && chunk->IsMissing() && chunk->ExistsInFile()) + { + if (asset->Storage->LoadAssetChunk(chunk)) + return true; + data.InitData.Header.Chunks[i] = chunk->Clone(); + } + } + + return false; } CookAssetsStep::CookAssetsStep() @@ -938,6 +1026,7 @@ bool CookAssetsStep::Perform(CookingData& data) { cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize; cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; + cache.Settings.Global.StreamingSettingsAssetId = gameSettings->Streaming; } // Note: this step converts all the assets (even the json) into the binary files (FlaxStorage format). diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.h b/Source/Editor/Cooker/Steps/CookAssetsStep.h index 8896e1e99..f134d3dd9 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.h +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.h @@ -49,6 +49,8 @@ public: /// The list of files on which this entry depends on. Cached date is the last edit time used to discard cache result on modification. /// FileDependenciesList FileDependencies; + + bool IsValid(bool withDependencies = false); }; /// @@ -93,6 +95,7 @@ public: { bool ShadersNoOptimize; bool ShadersGenerateDebugData; + Guid StreamingSettingsAssetId; } Global; } Settings; @@ -134,6 +137,11 @@ public: /// void InvalidateShaders(); + /// + /// Removes all cached entries for assets that contain a texture. This forces rebuild for them. + /// + void InvalidateTextures(); + /// /// Loads the cache for the given cooking data. /// diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index ca45652b9..c1c004d43 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -142,6 +142,10 @@ namespace FlaxEditor.CustomEditors /// public void RebuildLayout() { + // Skip rebuilding during init + if (CurrentCustomEditor == this) + return; + // Special case for root objects to run normal layout build if (_presenter.Selection == Values) { @@ -789,8 +793,7 @@ namespace FlaxEditor.CustomEditors /// True if allow to handle this event, otherwise false. protected virtual bool OnDirty(CustomEditor editor, object value, object token = null) { - ParentEditor.OnDirty(editor, value, token); - return true; + return ParentEditor.OnDirty(editor, value, token); } /// diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index ffe4ce585..8a6ae54a9 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -68,19 +68,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var cm = new ItemsListContextMenu(180); for (int i = 0; i < scripts.Count; i++) { - var scriptType = scripts[i]; - var item = new ItemsListContextMenu.Item(scriptType.Name, scriptType) - { - TooltipText = scriptType.TypeName, - }; - var attributes = scriptType.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - item.TooltipText += '\n'; - item.TooltipText += tooltipAttribute.Text; - } - cm.AddItem(item); + cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } cm.ItemClicked += item => AddScript((ScriptType)item.Tag); cm.SortChildren(); diff --git a/Source/Editor/CustomEditors/Dedicated/TextureGroupEditor.cs b/Source/Editor/CustomEditors/Dedicated/TextureGroupEditor.cs new file mode 100644 index 000000000..1aff3583b --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/TextureGroupEditor.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Content.Settings; +using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.GUI; +using FlaxEngine; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for index. + /// + internal class TextureGroupEditor : CustomEditor + { + private ComboBoxElement _element; + + /// + public override DisplayStyle Style => DisplayStyle.Inline; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + _element = layout.ComboBox(); + _element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged; + _element.ComboBox.AddItem("None"); + var groups = GameSettings.Load(); + if (groups?.TextureGroups != null) + { + for (int i = 0; i < groups.TextureGroups.Length; i++) + { + _element.ComboBox.AddItem(groups.TextureGroups[i].Name); + } + } + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + var value = comboBox.HasSelection ? comboBox.SelectedIndex - 1 : -1; + SetValue(value); + } + + /// + public override void Refresh() + { + base.Refresh(); + + var value = (int)Values[0]; + _element.ComboBox.SelectedIndex = value + 1; + } + } +} diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index dcd70a340..d7a6f2617 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -412,6 +412,11 @@ namespace FlaxEditor.CustomEditors.Dedicated public override void Initialize(LayoutElementsContainer layout) { _cachedType = null; + if (HasDifferentTypes) + { + // TODO: support stable editing multiple different control types (via generic way or for transform-only) + return; + } // Set control type button var space = layout.Space(20); @@ -600,34 +605,26 @@ namespace FlaxEditor.CustomEditors.Dedicated private bool _cachedXEq; private bool _cachedYEq; - /// - /// Refreshes if equality of anchors does not correspond to the cached equality - /// - public void RefreshBaseOnAnchorsEquality() - { - if (Values.HasNull) - return; - - GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes); - if (xEq != _cachedXEq || yEq != _cachedYEq) - { - RebuildLayout(); - return; - } - } - /// public override void Refresh() { - // Automatic layout rebuild if control type gets changed - var type = Values.HasNull ? null : Values[0].GetType(); - if (type != _cachedType) + if (_cachedType != null) { - RebuildLayout(); - return; + // Automatic layout rebuild if control type gets changed + var type = Values.HasNull ? null : Values[0].GetType(); + if (type != _cachedType) + { + RebuildLayout(); + return; + } + + // Refresh anchors + GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes); + if (xEq != _cachedXEq || yEq != _cachedYEq) + { + RebuildLayout(); + } } - RefreshBaseOnAnchorsEquality(); - //RefreshValues(); base.Refresh(); } @@ -642,59 +639,39 @@ namespace FlaxEditor.CustomEditors.Dedicated var cm = new ItemsListContextMenu(180); for (int i = 0; i < controlTypes.Count; i++) { - var controlType = controlTypes[i]; - var item = new ItemsListContextMenu.Item(controlType.Name, controlType) - { - TooltipText = controlType.TypeName, - }; - var attributes = controlType.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - item.TooltipText += '\n'; - item.TooltipText += tooltipAttribute.Text; - } - cm.AddItem(item); + cm.AddItem(new TypeSearchPopup.TypeItemView(controlTypes[i])); } - cm.ItemClicked += controlType => SetType((ScriptType)controlType.Tag); cm.SortChildren(); cm.Show(button.Parent, button.BottomLeft); } + private void SetType(ref ScriptType controlType, UIControl uiControl) + { + string previousName = uiControl.Control?.GetType().Name ?? nameof(UIControl); + uiControl.Control = (Control)controlType.CreateInstance(); + if (uiControl.Name.StartsWith(previousName)) + { + string newName = controlType.Name + uiControl.Name.Substring(previousName.Length); + uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null); + } + } + private void SetType(ScriptType controlType) { var uiControls = ParentEditor.Values; - if (Presenter.Undo != null) + if (Presenter.Undo?.Enabled ?? false) { using (new UndoMultiBlock(Presenter.Undo, uiControls, "Set Control Type")) { for (int i = 0; i < uiControls.Count; i++) - { - var uiControl = (UIControl)uiControls[i]; - string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name; - uiControl.Control = (Control)controlType.CreateInstance(); - if (uiControl.Name.StartsWith(previousName)) - { - string newName = controlType.Name + uiControl.Name.Substring(previousName.Length); - uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null); - } - } + SetType(ref controlType, (UIControl)uiControls[i]); } } else { for (int i = 0; i < uiControls.Count; i++) - { - var uiControl = (UIControl)uiControls[i]; - string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name; - uiControl.Control = (Control)controlType.CreateInstance(); - if (uiControl.Name.StartsWith(previousName)) - { - string newName = controlType.Name + uiControl.Name.Substring(previousName.Length); - uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null); - } - } + SetType(ref controlType, (UIControl)uiControls[i]); } ParentEditor.RebuildLayout(); diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index 41ca6ba86..7f023e8bc 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Editors // Copy old values Array.Copy(array, 0, newValues, 0, sharedCount); - if (elementType.IsValueType) + if (elementType.IsValueType || NotNullItems) { // Fill new entries with the last value for (int i = oldSize; i < newSize; i++) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 141330aea..6c3dda04e 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -77,11 +77,16 @@ namespace FlaxEditor.CustomEditors.Editors } } + /// + /// Determines if value of collection can be null. + /// + protected bool NotNullItems; + private IntegerValueElement _size; + private Color _background; private int _elementsCount; private bool _readOnly; private bool _canReorderItems; - private bool _notNullItems; /// /// Gets the length of the collection. @@ -103,15 +108,15 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - _readOnly = false; - _canReorderItems = true; - _notNullItems = false; - // No support for different collections for now if (HasDifferentValues || HasDifferentTypes) return; var size = Count; + _readOnly = false; + _canReorderItems = true; + _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; + NotNullItems = false; // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); @@ -120,17 +125,17 @@ namespace FlaxEditor.CustomEditors.Editors var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { - // TODO: handle NotNullItems by filtering child editors SetValue - _readOnly = collection.ReadOnly; _canReorderItems = collection.CanReorderItems; - _notNullItems = collection.NotNullItems; + NotNullItems = collection.NotNullItems; + if (collection.BackgroundColor.HasValue) + _background = collection.BackgroundColor.Value; overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; spacing = collection.Spacing; } // Size - if (_readOnly) + if (_readOnly || (NotNullItems && size == 0)) { layout.Label("Size", size.ToString()); } @@ -146,6 +151,8 @@ namespace FlaxEditor.CustomEditors.Editors // Elements if (size > 0) { + var panel = layout.VerticalPanel(); + panel.Panel.BackgroundColor = _background; var elementType = ElementType; if (_canReorderItems) { @@ -153,7 +160,7 @@ namespace FlaxEditor.CustomEditors.Editors { if (i != 0 && spacing > 0f) { - if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement) + if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) { if (propertiesListElement.Labels.Count > 0) { @@ -166,12 +173,12 @@ namespace FlaxEditor.CustomEditors.Editors } else { - layout.Space(spacing); + panel.Space(spacing); } } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - layout.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor); + panel.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor); } } else @@ -180,14 +187,14 @@ namespace FlaxEditor.CustomEditors.Editors { if (i != 0 && spacing > 0f) { - if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement) + if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) propertiesListElement.Space(spacing); else - layout.Space(spacing); + panel.Space(spacing); } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - layout.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor); + panel.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor); } } } @@ -202,7 +209,8 @@ namespace FlaxEditor.CustomEditors.Editors Text = "+", TooltipText = "Add new item", AnchorPreset = AnchorPresets.TopRight, - Parent = area.ContainerControl + Parent = area.ContainerControl, + Enabled = !NotNullItems || size > 0, }; addButton.Clicked += () => { @@ -217,7 +225,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Remove last item", AnchorPreset = AnchorPresets.TopRight, Parent = area.ContainerControl, - Enabled = size > 0 + Enabled = size > 0, }; removeButton.Clicked += () => { @@ -229,6 +237,24 @@ namespace FlaxEditor.CustomEditors.Editors } } + /// + /// Rebuilds the parent layout if its collection. + /// + public void RebuildParentCollection() + { + if (ParentEditor is DictionaryEditor dictionaryEditor) + { + dictionaryEditor.RebuildParentCollection(); + dictionaryEditor.RebuildLayout(); + return; + } + if (ParentEditor is CollectionEditor collectionEditor) + { + collectionEditor.RebuildParentCollection(); + collectionEditor.RebuildLayout(); + } + } + private void OnSizeChanged() { if (IsSetBlocked) @@ -312,7 +338,27 @@ namespace FlaxEditor.CustomEditors.Editors if (Count != _elementsCount) { RebuildLayout(); + RebuildParentCollection(); } } + + /// + protected override bool OnDirty(CustomEditor editor, object value, object token = null) + { + if (NotNullItems) + { + if (value == null && editor.ParentEditor == this) + return false; + if (editor == this && value is IList list) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i] == null) + return false; + } + } + } + return base.OnDirty(editor, value, token); + } } } diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 4a94136db..51309a2fa 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -135,10 +135,12 @@ namespace FlaxEditor.CustomEditors.Editors } private IntegerValueElement _size; + private Color _background; private int _elementsCount; private bool _readOnly; private bool _notNullItems; private bool _canEditKeys; + private bool _keyEdited; /// /// Determines whether this editor[can edit the specified dictionary type. @@ -164,9 +166,6 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - _readOnly = false; - _notNullItems = false; - // No support for different collections for now if (HasDifferentValues || HasDifferentTypes) return; @@ -177,23 +176,23 @@ namespace FlaxEditor.CustomEditors.Editors var keyType = argTypes[0]; var valueType = argTypes[1]; _canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum; + _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; + _readOnly = false; + _notNullItems = false; // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; float spacing = 0.0f; - if (attributes != null) + var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); + if (collection != null) { - var collection = (CollectionAttribute)attributes.FirstOrDefault(x => x is CollectionAttribute); - if (collection != null) - { - // TODO: handle ReadOnly and NotNullItems by filtering child editors SetValue - - _readOnly = collection.ReadOnly; - _notNullItems = collection.NotNullItems; - overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; - spacing = collection.Spacing; - } + _readOnly = collection.ReadOnly; + _notNullItems = collection.NotNullItems; + if (collection.BackgroundColor.HasValue) + _background = collection.BackgroundColor.Value; + overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; + spacing = collection.Spacing; } // Size @@ -205,7 +204,7 @@ namespace FlaxEditor.CustomEditors.Editors { _size = layout.IntegerValue("Size"); _size.IntValue.MinValue = 0; - _size.IntValue.MaxValue = ushort.MaxValue; + _size.IntValue.MaxValue = _notNullItems ? size : ushort.MaxValue; _size.IntValue.Value = size; _size.IntValue.ValueChanged += OnSizeChanged; } @@ -213,13 +212,15 @@ namespace FlaxEditor.CustomEditors.Editors // Elements if (size > 0) { + var panel = layout.VerticalPanel(); + panel.Panel.BackgroundColor = _background; var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); for (int i = 0; i < size; i++) { if (i != 0 && spacing > 0f) { - if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement) + if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) { if (propertiesListElement.Labels.Count > 0) { @@ -232,13 +233,13 @@ namespace FlaxEditor.CustomEditors.Editors } else { - layout.Space(spacing); + panel.Space(spacing); } } var key = keys.ElementAt(i); var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - layout.Object(new DictionaryItemLabel(this, key), new DictionaryValueContainer(new ScriptType(valueType), key, Values), overrideEditor); + panel.Object(new DictionaryItemLabel(this, key), new DictionaryValueContainer(new ScriptType(valueType), key, Values), overrideEditor); } } _elementsCount = size; @@ -252,7 +253,8 @@ namespace FlaxEditor.CustomEditors.Editors Text = "+", TooltipText = "Add new item", AnchorPreset = AnchorPresets.TopRight, - Parent = area.ContainerControl + Parent = area.ContainerControl, + Enabled = !_notNullItems, }; addButton.Clicked += () => { @@ -267,7 +269,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Remove last item", AnchorPreset = AnchorPresets.TopRight, Parent = area.ContainerControl, - Enabled = size > 0 + Enabled = size > 0, }; removeButton.Clicked += () => { @@ -279,6 +281,24 @@ namespace FlaxEditor.CustomEditors.Editors } } + /// + /// Rebuilds the parent layout if its collection. + /// + public void RebuildParentCollection() + { + if (ParentEditor is DictionaryEditor dictionaryEditor) + { + dictionaryEditor.RebuildParentCollection(); + dictionaryEditor.RebuildLayout(); + return; + } + if (ParentEditor is CollectionEditor collectionEditor) + { + collectionEditor.RebuildParentCollection(); + collectionEditor.RebuildLayout(); + } + } + private void OnSizeChanged() { if (IsSetBlocked) @@ -332,6 +352,7 @@ namespace FlaxEditor.CustomEditors.Editors newValues[e] = dictionary[e]; } SetValue(newValues); + _keyEdited = true; // TODO: use custom UndoAction to rebuild UI after key modification } /// @@ -444,6 +465,13 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Refresh() { + if (_keyEdited) + { + _keyEdited = false; + RebuildLayout(); + RebuildParentCollection(); + } + base.Refresh(); // No support for different collections for now @@ -454,6 +482,7 @@ namespace FlaxEditor.CustomEditors.Editors if (Count != _elementsCount) { RebuildLayout(); + RebuildParentCollection(); } } } diff --git a/Source/Editor/CustomEditors/Editors/FloatEditor.cs b/Source/Editor/CustomEditors/Editors/FloatEditor.cs index 523db92cf..456eae9ee 100644 --- a/Source/Editor/CustomEditors/Editors/FloatEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FloatEditor.cs @@ -15,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Editors { private IFloatValueEditor _element; + /// + /// Gets the element. + /// + public IFloatValueEditor Element => _element; + /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -84,7 +89,7 @@ namespace FlaxEditor.CustomEditors.Editors else if (value is double asDouble) _element.Value = (float)asDouble; else - throw new Exception("Invalid value."); + throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "")); } } } diff --git a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs index f0c194eef..24f144aed 100644 --- a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs +++ b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs @@ -22,6 +22,8 @@ namespace FlaxEditor.CustomEditors.Editors new OptionType("Material", typeof(MaterialBrush)), new OptionType("Solid Color", typeof(SolidColorBrush)), new OptionType("Linear Gradient", typeof(LinearGradientBrush)), + new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)), + new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)), }; } } diff --git a/Source/Editor/CustomEditors/Editors/IntegerEditor.cs b/Source/Editor/CustomEditors/Editors/IntegerEditor.cs index 3c2ca8872..5822b1c83 100644 --- a/Source/Editor/CustomEditors/Editors/IntegerEditor.cs +++ b/Source/Editor/CustomEditors/Editors/IntegerEditor.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using System.Linq; using FlaxEditor.CustomEditors.Elements; using FlaxEngine; @@ -77,7 +78,13 @@ namespace FlaxEditor.CustomEditors.Editors } else { - _element.Value = (int)Values[0]; + var value = Values[0]; + if (value is int asInt) + _element.Value = asInt; + else if (value is float asFloat) + _element.Value = (int)asFloat; + else + throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "")); } } } diff --git a/Source/Editor/CustomEditors/Editors/ListEditor.cs b/Source/Editor/CustomEditors/Editors/ListEditor.cs index 0c6efaea4..afae52139 100644 --- a/Source/Editor/CustomEditors/Editors/ListEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ListEditor.cs @@ -22,9 +22,7 @@ namespace FlaxEditor.CustomEditors.Editors var list = (IList)listType.CreateInstance(); var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType); for (int i = 0; i < size; i++) - { list.Add(defaultValue); - } return list; } @@ -48,7 +46,7 @@ namespace FlaxEditor.CustomEditors.Editors for (int i = 0; i < sharedCount; i++) newValues.Add(list[i]); - if (elementType.IsValueType) + if (elementType.IsValueType || NotNullItems) { // Fill new entries with the last value for (int i = oldSize; i < newSize; i++) @@ -86,9 +84,7 @@ namespace FlaxEditor.CustomEditors.Editors var cloned = (IList)listType.CreateInstance(); for (int i = 0; i < size; i++) - { cloned.Add(list[i]); - } return cloned; } diff --git a/Source/Editor/CustomEditors/Editors/MarginEditor.cs b/Source/Editor/CustomEditors/Editors/MarginEditor.cs new file mode 100644 index 000000000..758264330 --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/MarginEditor.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Linq; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Default implementation of the inspector used to edit Version value type properties. + /// + [CustomEditor(typeof(Margin)), DefaultEditor] + public class MarginEditor : GenericEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + var attributes = Values.GetAttributes(); + if (attributes != null) + { + var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + if (limit != null) + { + for (var i = 0; i < ChildrenEditors.Count; i++) + { + if (ChildrenEditors[i] is FloatEditor floatEditor) + floatEditor.Element.SetLimits(limit); + } + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs index 6ecac91ee..f150eec88 100644 --- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs @@ -38,8 +38,12 @@ namespace FlaxEditor.CustomEditors.Editors var model = staticModel.Model; if (model && model.IsLoaded) { - _group.Panel.HeaderText = "Entry " + model.MaterialSlots[entryIndex].Name; - _updateName = false; + var slots = model.MaterialSlots; + if (slots != null && slots.Length > entryIndex) + { + _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name; + _updateName = false; + } } } else if (ParentEditor.ParentEditor.Values[0] is AnimatedModel animatedModel) @@ -47,8 +51,12 @@ namespace FlaxEditor.CustomEditors.Editors var model = animatedModel.SkinnedModel; if (model && model.IsLoaded) { - _group.Panel.HeaderText = "Entry " + model.MaterialSlots[entryIndex].Name; - _updateName = false; + var slots = model.MaterialSlots; + if (slots != null && slots.Length > entryIndex) + { + _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name; + _updateName = false; + } } } } diff --git a/Source/Editor/CustomEditors/Elements/IFloatValueEditor.cs b/Source/Editor/CustomEditors/Elements/IFloatValueEditor.cs index 2e67cb5f7..59c0b7a8c 100644 --- a/Source/Editor/CustomEditors/Elements/IFloatValueEditor.cs +++ b/Source/Editor/CustomEditors/Elements/IFloatValueEditor.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using FlaxEngine; + namespace FlaxEditor.CustomEditors.Elements { /// @@ -16,5 +18,11 @@ namespace FlaxEditor.CustomEditors.Elements /// Gets a value indicating whether user is using a slider. /// bool IsSliding { get; } + + /// + /// Sets the editor limits from member . + /// + /// The limit. + void SetLimits(LimitAttribute limit); } } diff --git a/Source/Editor/CustomEditors/Elements/SliderElement.cs b/Source/Editor/CustomEditors/Elements/SliderElement.cs index 6e981a257..988c86a69 100644 --- a/Source/Editor/CustomEditors/Elements/SliderElement.cs +++ b/Source/Editor/CustomEditors/Elements/SliderElement.cs @@ -76,5 +76,14 @@ namespace FlaxEditor.CustomEditors.Elements /// public bool IsSliding => Slider.IsSliding; + + /// + public void SetLimits(LimitAttribute limit) + { + if (limit != null) + { + Slider.SetLimits(limit); + } + } } } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 6f645789a..d7df2fbb1 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -114,7 +114,7 @@ namespace FlaxEditor.CustomEditors } /// - /// Adds new horizontal panel element. + /// Adds new vertical panel element. /// /// The created element. public VerticalPanelElement VerticalPanel() diff --git a/Source/Editor/CustomEditors/SyncPointEditor.cs b/Source/Editor/CustomEditors/SyncPointEditor.cs index 120648230..3555aaea4 100644 --- a/Source/Editor/CustomEditors/SyncPointEditor.cs +++ b/Source/Editor/CustomEditors/SyncPointEditor.cs @@ -112,9 +112,9 @@ namespace FlaxEditor.CustomEditors EndUndoRecord(); _setValueToken = token; - // Mark as modified and don't pass event further + // Mark as modified and don't pass event further to the higher editors (don't call parent) _isDirty = true; - return false; + return true; } /// diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index 49d3b5591..9bdbc4bcf 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -255,8 +255,15 @@ namespace FlaxEditor.CustomEditors if (instanceValues._referenceValue == null && !instanceValues.Type.IsValueType) return; - _referenceValue = Info.GetValue(instanceValues._referenceValue); - _hasReferenceValue = true; + try + { + _referenceValue = Info.GetValue(instanceValues._referenceValue); + _hasReferenceValue = true; + } + catch + { + // Ignore error if reference value has different type or is invalid for this member + } } } diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index 2284478dd..1b18034c4 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -518,7 +518,7 @@ bool Editor::Init() exit(failed ? 1 : 0); return true; } - + // If during last lightmaps baking engine crashed we could try to restore the progress ShadowsOfMordor::Builder::Instance()->CheckIfRestoreState(); @@ -534,6 +534,12 @@ bool Editor::Init() // Initialize managed editor Managed->Init(); + // Start play if requested by cmd line + if (CommandLine::Options.Play.HasValue()) + { + Managed->RequestStartPlayOnEditMode(); + } + return false; } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index adf4d566b..70f52268c 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -46,6 +46,7 @@ namespace FlaxEditor private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode; private string _projectToOpen; private float _lastAutoSaveTimer; + private Guid _startupSceneCmdLine; private const string ProjectDataLastScene = "LastScene"; private const string ProjectDataLastSceneSpawn = "LastSceneSpawn"; @@ -271,10 +272,11 @@ namespace FlaxEditor module.OnEndInit(); } - internal void Init(bool isHeadless, bool skipCompile) + internal void Init(bool isHeadless, bool skipCompile, Guid startupScene) { EnsureState(); _isHeadlessMode = isHeadless; + _startupSceneCmdLine = startupScene; Log("Editor init"); if (isHeadless) Log("Running in headless mode"); @@ -332,6 +334,17 @@ namespace FlaxEditor } // Load scene + + // scene cmd line argument + var scene = ContentDatabase.Find(_startupSceneCmdLine); + if (scene is SceneItem) + { + Editor.Log("Loading scene specified in command line"); + Scene.OpenScene(_startupSceneCmdLine); + return; + } + + // if no scene cmd line argument is provided var startupSceneMode = Options.Options.General.StartupSceneMode; if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene)) { @@ -1293,20 +1306,17 @@ namespace FlaxEditor VisualScriptingDebugFlow?.Invoke(debugFlow); } - [StructLayout(LayoutKind.Sequential)] - internal struct AnimGraphDebugFlowInfo + private static void RequestStartPlayOnEditMode() { - public Asset Asset; - public FlaxEngine.Object Object; - public uint NodeId; - public int BoxId; + if (Instance.StateMachine.IsEditMode) + Instance.Simulation.RequestStartPlay(); + if (Instance.StateMachine.IsPlayMode) + Instance.StateMachine.StateChanged -= RequestStartPlayOnEditMode; } - internal static event Action AnimGraphDebugFlow; - - internal static void Internal_OnAnimGraphDebugFlow(ref AnimGraphDebugFlowInfo debugFlow) + internal static void Internal_RequestStartPlayOnEditMode() { - AnimGraphDebugFlow?.Invoke(debugFlow); + Instance.StateMachine.StateChanged += RequestStartPlayOnEditMode; } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/Source/Editor/EditorIcons.cs b/Source/Editor/EditorIcons.cs index d4b89b8d3..1f7be216e 100644 --- a/Source/Editor/EditorIcons.cs +++ b/Source/Editor/EditorIcons.cs @@ -46,6 +46,12 @@ namespace FlaxEditor public SpriteHandle Down32; public SpriteHandle FolderClosed32; public SpriteHandle FolderOpen32; + public SpriteHandle Folder32; + public SpriteHandle CameraFill32; + public SpriteHandle Search32; + public SpriteHandle Info32; + public SpriteHandle Warning32; + public SpriteHandle Error32; // Visject public SpriteHandle VisjectBoxOpen32; @@ -128,6 +134,10 @@ namespace FlaxEditor public SpriteHandle AndroidIcon128; public SpriteHandle PS4Icon128; public SpriteHandle FlaxLogo128; + public SpriteHandle SwitchIcon128; + public SpriteHandle SwitchSettings128; + public SpriteHandle LocalizationSettings128; + public SpriteHandle Json128; internal void LoadIcons() { diff --git a/Source/Editor/GUI/ClickableLabel.cs b/Source/Editor/GUI/ClickableLabel.cs index f8940e714..325d8822a 100644 --- a/Source/Editor/GUI/ClickableLabel.cs +++ b/Source/Editor/GUI/ClickableLabel.cs @@ -10,6 +10,7 @@ namespace FlaxEditor.GUI /// The label that contains events for mouse interaction. /// /// + [HideInEditor] public class ClickableLabel : Label { private bool _leftClick; diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index f968c2501..0f31759f2 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -84,7 +84,7 @@ namespace FlaxEditor.GUI.Docking /// /// The default tabs header buttons size. /// - public const float DefaultButtonsSize = 12; + public const float DefaultButtonsSize = 15; /// /// The default tabs header buttons margin. diff --git a/Source/Editor/GUI/Input/SliderControl.cs b/Source/Editor/GUI/Input/SliderControl.cs index 77e8a5da8..d1a2e26d1 100644 --- a/Source/Editor/GUI/Input/SliderControl.cs +++ b/Source/Editor/GUI/Input/SliderControl.cs @@ -410,6 +410,17 @@ namespace FlaxEditor.GUI.Input Value = Value; } + /// + /// Sets the limits from the attribute. + /// + /// The limits. + public void SetLimits(LimitAttribute limits) + { + _min = limits.Min; + _max = Mathf.Max(_min, limits.Max); + Value = Value; + } + /// /// Updates the text of the textbox. /// diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 8c4dd723d..4abe39f0b 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -32,9 +32,14 @@ namespace FlaxEditor.GUI protected List _highlights; /// - /// Gets or sets the name. + /// The item name. /// - public string Name { get; set; } + public string Name; + + /// + /// The item category name (optional). + /// + public string Category; /// /// Occurs when items gets clicked by the user. @@ -49,18 +54,6 @@ namespace FlaxEditor.GUI { } - /// - /// Initializes a new instance of the class. - /// - /// The item name. - /// The item tag object. - public Item(string name, object tag = null) - : base(0, 0, 120, 12) - { - Name = name; - Tag = tag; - } - /// /// Updates the filter. /// @@ -181,6 +174,7 @@ namespace FlaxEditor.GUI } private readonly TextBox _searchBox; + private List _categoryPanels; private bool _waitingForInput; /// @@ -242,6 +236,23 @@ namespace FlaxEditor.GUI if (items[i] is Item item) item.UpdateFilter(_searchBox.Text); } + if (_categoryPanels != null) + { + for (int i = 0; i < _categoryPanels.Count; i++) + { + var category = _categoryPanels[i]; + bool anyVisible = false; + for (int j = 0; j < category.Children.Count; j++) + { + if (category.Children[j] is Item item2) + { + item2.UpdateFilter(_searchBox.Text); + anyVisible |= item2.Visible; + } + } + category.Visible = anyVisible; + } + } UnlockChildrenRecursive(); PerformLayout(true); @@ -254,8 +265,33 @@ namespace FlaxEditor.GUI /// The item. public void AddItem(Item item) { - item.Parent = ItemsPanel; item.Clicked += OnClickItem; + ContainerControl parent = ItemsPanel; + if (!string.IsNullOrEmpty(item.Category)) + { + if (_categoryPanels == null) + _categoryPanels = new List(); + for (int i = 0; i < _categoryPanels.Count; i++) + { + if (string.Equals(_categoryPanels[i].HeaderText, item.Category, StringComparison.Ordinal)) + { + parent = _categoryPanels[i]; + break; + } + } + if (parent == ItemsPanel) + { + var categoryPanel = new DropPanel + { + HeaderText = item.Category, + Parent = parent, + }; + categoryPanel.Open(false); + _categoryPanels.Add(categoryPanel); + parent = categoryPanel; + } + } + item.Parent = parent; } /// @@ -281,6 +317,19 @@ namespace FlaxEditor.GUI if (items[i] is Item item) item.UpdateFilter(null); } + if (_categoryPanels != null) + { + for (int i = 0; i < _categoryPanels.Count; i++) + { + var category = _categoryPanels[i]; + for (int j = 0; j < category.Children.Count; j++) + { + if (category.Children[j] is Item item2) + item2.UpdateFilter(null); + } + category.Visible = true; + } + } _searchBox.Clear(); UnlockChildrenRecursive(); diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index bf375a358..650f7737a 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -19,6 +19,7 @@ namespace FlaxEditor.GUI private Button _closeButton; private Button _minimizeButton; private Button _maximizeButton; + private LocalizedString _charChromeRestore, _charChromeMaximize; private Window _window; #endif private MainMenuButton _selected; @@ -151,14 +152,12 @@ namespace FlaxEditor.GUI _maximizeButton.Clicked += () => { if (_window.IsMaximized) - { _window.Restore(); - } else - { _window.Maximize(); - } }; + _charChromeRestore = ((char)EditorAssets.SegMDL2Icons.ChromeRestore).ToString(); + _charChromeMaximize = ((char)EditorAssets.SegMDL2Icons.ChromeMaximize).ToString(); } else #endif @@ -175,7 +174,7 @@ namespace FlaxEditor.GUI if (_maximizeButton != null) { - _maximizeButton.Text = ((char)(_window.IsMaximized ? EditorAssets.SegMDL2Icons.ChromeRestore : EditorAssets.SegMDL2Icons.ChromeMaximize)).ToString(); + _maximizeButton.Text = _window.IsMaximized ? _charChromeRestore : _charChromeMaximize; } } diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs index cc7bc003a..3b9802873 100644 --- a/Source/Editor/GUI/PlatformSelector.cs +++ b/Source/Editor/GUI/PlatformSelector.cs @@ -89,11 +89,10 @@ namespace FlaxEditor.GUI new PlatformData(PlatformType.PS4, icons.PS4Icon128, "PlayStation 4"), new PlatformData(PlatformType.XboxScarlett, icons.XBoxScarletIcon128, "Xbox Scarlett"), new PlatformData(PlatformType.Android, icons.AndroidIcon128, "Android"), - new PlatformData(PlatformType.Switch, icons.ColorWheel128, "Switch"), - + new PlatformData(PlatformType.Switch, icons.SwitchIcon128, "Switch"), }; - const float IconSize = 48.0f; + const float IconSize = 64.0f; TileSize = new Vector2(IconSize); AutoResize = true; Offsets = new Margin(0, 0, 0, IconSize); diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs index a35b2a1c5..71a97ed05 100644 --- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs +++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs @@ -27,6 +27,15 @@ namespace FlaxEditor.GUI /// public ScriptType Type => _type; + /// + /// Initializes a new instance of the class. + /// + /// The type. + public TypeItemView(ScriptType type) + : this(type, type.GetAttributes(false)) + { + } + /// /// Initializes a new instance of the class. /// @@ -38,12 +47,18 @@ namespace FlaxEditor.GUI Name = type.Name; TooltipText = type.TypeName; + Tag = type; var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); if (tooltipAttribute != null) { TooltipText += '\n'; TooltipText += tooltipAttribute.Text; } + var categoryAttribute = (CategoryAttribute)attributes.FirstOrDefault(x => x is CategoryAttribute); + if (categoryAttribute != null) + { + Category = categoryAttribute.Name; + } } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 580d1f266..22dae35e0 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -70,7 +70,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks : base(ref options) { // Select Actor button - const float buttonSize = 14; + const float buttonSize = 18; var icons = Editor.Instance.Icons; _selectActor = new Image { @@ -80,7 +80,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.Search12), + Brush = new SpriteBrush(icons.Search32), Offsets = new Margin(-buttonSize - 2 + _addButton.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; diff --git a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs index 98cb3979f..f9b7fa3f7 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs @@ -312,7 +312,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks Curve.UnlockChildrenRecursive(); // Navigation buttons - const float buttonSize = 14; + const float keySize = 18; + const float addSize = 20; var icons = Editor.Instance.Icons; var rightKey = new Image { @@ -323,7 +324,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks Color = Style.Current.ForegroundGrey, Margin = new Margin(1), Brush = new SpriteBrush(icons.Right32), - Offsets = new Margin(-buttonSize - 2 + _muteCheckbox.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), + Offsets = new Margin(-keySize - 2 + _muteCheckbox.Offsets.Left, keySize, keySize * -0.5f, keySize), Parent = this, }; rightKey.Clicked += OnRightKeyClicked; @@ -336,7 +337,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks Color = Style.Current.ForegroundGrey, Margin = new Margin(3), Brush = new SpriteBrush(icons.Add32), - Offsets = new Margin(-buttonSize - 2 + rightKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), + Offsets = new Margin(-addSize - 2 + rightKey.Offsets.Left, addSize, addSize * -0.5f, addSize), Parent = this, }; addKey.Clicked += OnAddKeyClicked; @@ -349,7 +350,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks Color = Style.Current.ForegroundGrey, Margin = new Margin(1), Brush = new SpriteBrush(icons.Left32), - Offsets = new Margin(-buttonSize - 2 + addKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), + Offsets = new Margin(-keySize - 2 + addKey.Offsets.Left, keySize, keySize * -0.5f, keySize), Parent = this, }; leftKey.Clicked += OnLeftKeyClicked; diff --git a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs index fc9b17601..05f0c42ae 100644 --- a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs @@ -471,7 +471,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks _atlases = new List(4); if (_output == null) { - _output = GPUDevice.Instance.CreateTexture(); + _output = GPUDevice.Instance.CreateTexture("CameraCutMedia.Output"); var desc = GPUTextureDescription.New2D(Width, Height, PixelFormat.R8G8B8A8_UNorm); _output.Init(ref desc); } @@ -682,10 +682,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks public CameraCutTrack(ref TrackCreateOptions options) : base(ref options) { - Height = CameraCutThumbnailRenderer.Height + 4 + 4; + Height = CameraCutThumbnailRenderer.Height + 8; // Pilot Camera button - const float buttonSize = 14; + const float buttonSize = 18; var icons = Editor.Instance.Icons; _pilotCamera = new Image { @@ -695,7 +695,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.Camera64), + Brush = new SpriteBrush(icons.CameraFill32), Offsets = new Margin(-buttonSize - 2 + _selectActor.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), Parent = this, }; diff --git a/Source/Editor/GUI/Timeline/Tracks/FolderTrack.cs b/Source/Editor/GUI/Timeline/Tracks/FolderTrack.cs index 5e251b6d2..c01719b97 100644 --- a/Source/Editor/GUI/Timeline/Tracks/FolderTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/FolderTrack.cs @@ -24,7 +24,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks { TypeId = 1, Name = "Folder", - Icon = Editor.Instance.Icons.Folder64, + Icon = Editor.Instance.Icons.Folder32, Create = options => new FolderTrack(ref options), Load = LoadTrack, Save = SaveTrack, diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 302570fae..64beb855d 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -118,7 +118,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (useNavigationButtons) { // Navigation buttons - const float buttonSize = 14; + const float keySize = 18; + const float addSize = 20; var icons = Editor.Instance.Icons; _rightKey = new Image { @@ -128,8 +129,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks IsScrollable = false, Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.Right64), - Offsets = new Margin(-buttonSize - 2 + uiLeft, buttonSize, buttonSize * -0.5f, buttonSize), + Brush = new SpriteBrush(icons.Right32), + Offsets = new Margin(-keySize - 2 + uiLeft, keySize, keySize * -0.5f, keySize), Parent = this, }; _addKey = new Image @@ -138,10 +139,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks AutoFocus = true, AnchorPreset = AnchorPresets.MiddleRight, IsScrollable = false, - Color = Style.Current.Foreground, + Color = Style.Current.ForegroundGrey, Margin = new Margin(3), - Brush = new SpriteBrush(icons.Add64), - Offsets = new Margin(-buttonSize - 2 + _rightKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), + Brush = new SpriteBrush(icons.Add32), + Offsets = new Margin(-addSize - 2 + _rightKey.Offsets.Left, addSize, addSize * -0.5f, addSize), Parent = this, }; _leftKey = new Image @@ -150,10 +151,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks AutoFocus = true, AnchorPreset = AnchorPresets.MiddleRight, IsScrollable = false, - Color = Style.Current.Foreground, + Color = Style.Current.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(icons.Left64), - Offsets = new Margin(-buttonSize - 2 + _addKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), + Brush = new SpriteBrush(icons.Left32), + Offsets = new Margin(-keySize - 2 + _addKey.Offsets.Left, keySize, keySize * -0.5f, keySize), Parent = this, }; uiLeft = _leftKey.Offsets.Left; diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index f7e5f115a..7fdc1c746 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -723,12 +723,6 @@ namespace FlaxEditor.GUI.Tree /// public override bool OnMouseUp(Vector2 location, MouseButton button) { - // Check if mouse hits bar - if (button == MouseButton.Right && TestHeaderHit(ref location)) - { - ParentTree.OnRightClickInternal(this, ref location); - } - // Clear flag for left button if (button == MouseButton.Left) { @@ -773,11 +767,23 @@ namespace FlaxEditor.GUI.Tree Expand(); } + // Check if mouse hits bar + if (button == MouseButton.Right && TestHeaderHit(ref location)) + { + ParentTree.OnRightClickInternal(this, ref location); + } + // Handled Focus(); return true; } + // Check if mouse hits bar + if (button == MouseButton.Right && TestHeaderHit(ref location)) + { + ParentTree.OnRightClickInternal(this, ref location); + } + // Base return base.OnMouseUp(location, button); } diff --git a/Source/Editor/Gizmo/EditorPrimitives.cs b/Source/Editor/Gizmo/EditorPrimitives.cs index cdb9f4747..3e37a2361 100644 --- a/Source/Editor/Gizmo/EditorPrimitives.cs +++ b/Source/Editor/Gizmo/EditorPrimitives.cs @@ -8,9 +8,8 @@ namespace FlaxEditor.Gizmo /// /// Interface for editor viewports that can contain and use . /// - /// [HideInEditor] - public interface IEditorPrimitivesOwner : IGizmoOwner + public interface IEditorPrimitivesOwner { /// /// Draws the custom editor primitives. @@ -68,11 +67,14 @@ namespace FlaxEditor.Gizmo var renderList = RenderList.GetFromPool(); var prevList = renderContext.List; renderContext.List = renderList; - for (int i = 0; i < Viewport.Gizmos.Count; i++) + try { - Viewport.Gizmos[i].Draw(ref renderContext); + Viewport.DrawEditorPrimitives(context, ref renderContext, target, targetDepth); + } + catch (Exception ex) + { + Editor.LogWarning(ex); } - Viewport.DrawEditorPrimitives(context, ref renderContext, target, targetDepth); // Sort draw calls renderList.SortDrawCalls(ref renderContext, false, DrawCallsListType.GBuffer); diff --git a/Source/Editor/Gizmo/GizmoBase.cs b/Source/Editor/Gizmo/GizmoBase.cs index 6159c7673..a44c677dd 100644 --- a/Source/Editor/Gizmo/GizmoBase.cs +++ b/Source/Editor/Gizmo/GizmoBase.cs @@ -22,6 +22,16 @@ namespace FlaxEditor.Gizmo /// public bool IsActive => Owner.Gizmos.Active == this; + /// + /// Gets a value indicating whether this gizmo is using mouse currently (eg. user moving objects). + /// + public virtual bool IsControllingMouse => false; + + /// + /// Gets the custom world-space bounds for current gizmo mode focus for used (eg. selected object part bounds). Returns if not used. + /// + public virtual BoundingSphere FocusBounds => BoundingSphere.Empty; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs index 0a11ecd77..8772b04d2 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs @@ -70,8 +70,6 @@ namespace FlaxEditor.Gizmo { if (!_modelTranslateAxis || !_modelTranslateAxis.IsLoaded || !_modelBox || !_modelBox.IsLoaded) break; - - // Cache data Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3); Matrix.Multiply(ref m3, ref _gizmoWorld, out m1); var axisMesh = _modelTranslateAxis.LODs[0].Meshes[0]; @@ -113,8 +111,6 @@ namespace FlaxEditor.Gizmo { if (!_modelCircle || !_modelCircle.IsLoaded || !_modelBox || !_modelBox.IsLoaded) break; - - // Cache data var circleMesh = _modelCircle.LODs[0].Meshes[0]; var boxMesh = _modelBox.LODs[0].Meshes[0]; Matrix.Scaling(8.0f, out m3); @@ -147,8 +143,6 @@ namespace FlaxEditor.Gizmo { if (!_modelScaleAxis || !_modelScaleAxis.IsLoaded || !_modelBox || !_modelBox.IsLoaded) break; - - // Cache data Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3); Matrix.Multiply(ref m3, ref _gizmoWorld, out m1); var axisMesh = _modelScaleAxis.LODs[0].Meshes[0]; diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index e123bd3f6..768fa0617 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using FlaxEngine; @@ -65,6 +66,16 @@ namespace FlaxEditor.Gizmo /// public Transform LastDelta { get; private set; } + /// + /// Occurs when transforming selection started. + /// + public event Action TransformingStarted; + + /// + /// Occurs when transforming selection ended. + /// + public event Action TransformingEnded; + /// /// Initializes a new instance of the class. /// @@ -118,10 +129,10 @@ namespace FlaxEditor.Gizmo return; // End action - OnEndTransforming(); - _startTransforms.Clear(); _isTransforming = false; _isDuplicating = false; + OnEndTransforming(); + _startTransforms.Clear(); } private void UpdateGizmoPosition() @@ -202,52 +213,77 @@ namespace FlaxEditor.Gizmo ray.Position = Vector3.Transform(ray.Position, invRotationMatrix); Vector3.TransformNormal(ref ray.Direction, ref invRotationMatrix, out ray.Direction); + var planeXY = new Plane(Vector3.Backward, Vector3.Transform(Position, invRotationMatrix).Z); + var planeYZ = new Plane(Vector3.Left, Vector3.Transform(Position, invRotationMatrix).X); + var planeZX = new Plane(Vector3.Down, Vector3.Transform(Position, invRotationMatrix).Y); + var dir = Vector3.Normalize(ray.Position - Position); + var planeDotXY = Mathf.Abs(Vector3.Dot(planeXY.Normal, dir)); + var planeDotYZ = Mathf.Abs(Vector3.Dot(planeYZ.Normal, dir)); + var planeDotZX = Mathf.Abs(Vector3.Dot(planeZX.Normal, dir)); + switch (_activeAxis) { - case Axis.XY: case Axis.X: { - var plane = new Plane(Vector3.Backward, Vector3.Transform(Position, invRotationMatrix).Z); + var plane = planeDotXY > planeDotZX ? planeXY : planeZX; if (ray.Intersects(ref plane, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; - delta = _activeAxis == Axis.X - ? new Vector3(_tDelta.X, 0, 0) - : new Vector3(_tDelta.X, _tDelta.Y, 0); + delta = new Vector3(_tDelta.X, 0, 0); + } + break; + } + case Axis.Y: + { + var plane = planeDotXY > planeDotYZ ? planeXY : planeYZ; + if (ray.Intersects(ref plane, out float intersection)) + { + _intersectPosition = ray.Position + ray.Direction * intersection; + if (_lastIntersectionPosition != Vector3.Zero) + _tDelta = _intersectPosition - _lastIntersectionPosition; + delta = new Vector3(0, _tDelta.Y, 0); } break; } case Axis.Z: - case Axis.YZ: - case Axis.Y: { - var plane = new Plane(Vector3.Left, Vector3.Transform(Position, invRotationMatrix).X); + var plane = planeDotZX > planeDotYZ ? planeZX : planeYZ; if (ray.Intersects(ref plane, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; - switch (_activeAxis) - { - case Axis.Y: - delta = new Vector3(0, _tDelta.Y, 0); - break; - case Axis.Z: - delta = new Vector3(0, 0, _tDelta.Z); - break; - default: - delta = new Vector3(0, _tDelta.Y, _tDelta.Z); - break; - } + delta = new Vector3(0, 0, _tDelta.Z); + } + break; + } + case Axis.YZ: + { + if (ray.Intersects(ref planeYZ, out float intersection)) + { + _intersectPosition = ray.Position + ray.Direction * intersection; + if (_lastIntersectionPosition != Vector3.Zero) + _tDelta = _intersectPosition - _lastIntersectionPosition; + delta = new Vector3(0, _tDelta.Y, _tDelta.Z); + } + break; + } + case Axis.XY: + { + if (ray.Intersects(ref planeXY, out float intersection)) + { + _intersectPosition = ray.Position + ray.Direction * intersection; + if (_lastIntersectionPosition != Vector3.Zero) + _tDelta = _intersectPosition - _lastIntersectionPosition; + delta = new Vector3(_tDelta.X, _tDelta.Y, 0); } break; } case Axis.ZX: { - var plane = new Plane(Vector3.Down, Vector3.Transform(Position, invRotationMatrix).Y); - if (ray.Intersects(ref plane, out float intersection)) + if (ray.Intersects(ref planeZX, out float intersection)) { _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) @@ -271,12 +307,11 @@ namespace FlaxEditor.Gizmo } } + // Modifiers if (isScaling) delta *= 0.01f; - if (Owner.IsAltKeyDown) delta *= 0.5f; - if ((isScaling ? ScaleSnapEnabled : TranslationSnapEnable) || Owner.UseSnapping) { float snapValue = isScaling ? ScaleSnapValue : TranslationSnapValue; @@ -347,6 +382,9 @@ namespace FlaxEditor.Gizmo } } + /// + public override bool IsControllingMouse => _isTransforming; + /// public override void Update(float dt) { @@ -512,6 +550,7 @@ namespace FlaxEditor.Gizmo /// protected virtual void OnStartTransforming() { + TransformingStarted?.Invoke(); } /// @@ -529,6 +568,7 @@ namespace FlaxEditor.Gizmo /// protected virtual void OnEndTransforming() { + TransformingEnded?.Invoke(); } /// diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index a312b38c4..2ef78052e 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -66,6 +66,7 @@ struct InternalTextureOptions float PreserveAlphaCoverageReference; float Scale; int32 MaxSize; + int32 TextureGroup; int32 SizeX; int32 SizeY; MonoArray* SpriteAreas; @@ -86,6 +87,7 @@ struct InternalTextureOptions to->PreserveAlphaCoverage = from->PreserveAlphaCoverage; to->PreserveAlphaCoverageReference = from->PreserveAlphaCoverageReference; to->MaxSize = from->MaxSize; + to->TextureGroup = from->TextureGroup; to->SizeX = from->SizeX; to->SizeY = from->SizeY; to->Sprites.Clear(); @@ -118,6 +120,7 @@ struct InternalTextureOptions to->PreserveAlphaCoverageReference = from->PreserveAlphaCoverageReference; to->Scale = from->Scale; to->MaxSize = from->MaxSize; + to->TextureGroup = from->TextureGroup; to->SizeX = from->SizeX; to->SizeY = from->SizeY; if (from->Sprites.HasItems()) diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 35219a906..6a83ceeb5 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -34,7 +34,7 @@ MMethod* Internal_GetGameWinPtr = nullptr; MMethod* Internal_GetGameWindowSize = nullptr; MMethod* Internal_OnAppExit = nullptr; MMethod* Internal_OnVisualScriptingDebugFlow = nullptr; -MMethod* Internal_OnAnimGraphDebugFlow = nullptr; +MMethod* Internal_RequestStartPlayOnEditMode = nullptr; void OnLightmapsBake(ShadowsOfMordor::BuildProgressStep step, float stepProgress, float totalProgress, bool isProgressEvent) { @@ -137,38 +137,6 @@ void OnVisualScriptingDebugFlow() } } -struct AnimGraphDebugFlowInfo -{ - MonoObject* Asset; - MonoObject* Object; - uint32 NodeId; - int32 BoxId; -}; - -void OnAnimGraphDebugFlow(Asset* asset, ScriptingObject* object, uint32 nodeId, uint32 boxId) -{ - if (Internal_OnAnimGraphDebugFlow == nullptr) - { - Internal_OnAnimGraphDebugFlow = ManagedEditor::GetStaticClass()->GetMethod("Internal_OnAnimGraphDebugFlow", 1); - ASSERT(Internal_OnAnimGraphDebugFlow); - } - - AnimGraphDebugFlowInfo flowInfo; - flowInfo.Asset = asset ? asset->GetOrCreateManagedInstance() : nullptr; - flowInfo.Object = object ? object->GetOrCreateManagedInstance() : nullptr; - flowInfo.NodeId = nodeId; - flowInfo.BoxId = boxId; - MonoObject* exception = nullptr; - void* params[1]; - params[0] = &flowInfo; - Internal_OnAnimGraphDebugFlow->Invoke(nullptr, params, &exception); - if (exception) - { - MException ex(exception); - ex.Log(LogType::Error, TEXT("OnAnimGraphDebugFlow")); - } -} - void OnLogMessage(LogType type, const StringView& msg); ManagedEditor::ManagedEditor() @@ -186,7 +154,6 @@ ManagedEditor::ManagedEditor() CSG::Builder::OnBrushModified.Bind(); Log::Logger::OnMessage.Bind(); VisualScripting::DebugFlow.Bind(); - AnimGraphExecutor::DebugFlow.Bind(); } ManagedEditor::~ManagedEditor() @@ -203,13 +170,12 @@ ManagedEditor::~ManagedEditor() CSG::Builder::OnBrushModified.Unbind(); Log::Logger::OnMessage.Unbind(); VisualScripting::DebugFlow.Unbind(); - AnimGraphExecutor::DebugFlow.Unbind(); } void ManagedEditor::Init() { // Note: editor modules should perform quite fast init, any longer things should be done in async during 'editor splash screen time - void* args[2]; + void* args[3]; MClass* mclass = GetClass(); if (mclass == nullptr) { @@ -230,6 +196,12 @@ void ManagedEditor::Init() bool skipCompile = CommandLine::Options.SkipCompile.IsTrue(); args[0] = &isHeadless; args[1] = &skipCompile; + Guid sceneId; + if (!CommandLine::Options.Play.HasValue() || (CommandLine::Options.Play.HasValue() && Guid::Parse(CommandLine::Options.Play.GetValue(), sceneId))) + { + sceneId = Guid::Empty; + } + args[2] = &sceneId; initMethod->Invoke(instance, args, &exception); if (exception) { @@ -481,6 +453,18 @@ bool ManagedEditor::OnAppExit() return MUtils::Unbox(Internal_OnAppExit->Invoke(GetManagedInstance(), nullptr, nullptr)); } +void ManagedEditor::RequestStartPlayOnEditMode() +{ + if (!HasManagedInstance()) + return; + if (Internal_RequestStartPlayOnEditMode == nullptr) + { + Internal_RequestStartPlayOnEditMode = GetClass()->GetMethod("Internal_RequestStartPlayOnEditMode"); + ASSERT(Internal_RequestStartPlayOnEditMode); + } + Internal_RequestStartPlayOnEditMode->Invoke(GetManagedInstance(), nullptr, nullptr); +} + void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) { ASSERT(!HasManagedInstance()); @@ -511,7 +495,6 @@ void ManagedEditor::DestroyManaged() Internal_GetGameWinPtr = nullptr; Internal_OnAppExit = nullptr; Internal_OnVisualScriptingDebugFlow = nullptr; - Internal_OnAnimGraphDebugFlow = nullptr; // Base PersistentScriptingObject::DestroyManaged(); diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 5e0b612b5..df05e87bd 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -133,6 +133,11 @@ public: /// True if exit engine, otherwise false. bool OnAppExit(); + /// + /// Requests play mode when the editor is in edit mode ( once ). + /// + void RequestStartPlayOnEditMode(); + private: void OnEditorAssemblyLoaded(MAssembly* assembly); diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index ec71b8564..12e7abb96 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -936,23 +936,27 @@ namespace FlaxEditor.Modules Proxy.Add(new SettingsProxy(typeof(PhysicsSettings), Editor.Instance.Icons.PhysicsSettings128)); Proxy.Add(new SettingsProxy(typeof(GraphicsSettings), Editor.Instance.Icons.GraphicsSettings128)); Proxy.Add(new SettingsProxy(typeof(NavigationSettings), Editor.Instance.Icons.NavigationSettings128)); - Proxy.Add(new SettingsProxy(typeof(LocalizationSettings), Editor.Instance.Icons.Document128)); + Proxy.Add(new SettingsProxy(typeof(LocalizationSettings), Editor.Instance.Icons.LocalizationSettings128)); + Proxy.Add(new SettingsProxy(typeof(AudioSettings), Editor.Instance.Icons.AudioSettings128)); Proxy.Add(new SettingsProxy(typeof(BuildSettings), Editor.Instance.Icons.BuildSettings128)); Proxy.Add(new SettingsProxy(typeof(InputSettings), Editor.Instance.Icons.InputSettings128)); + Proxy.Add(new SettingsProxy(typeof(StreamingSettings), Editor.Instance.Icons.BuildSettings128)); Proxy.Add(new SettingsProxy(typeof(WindowsPlatformSettings), Editor.Instance.Icons.WindowsSettings128)); Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings), Editor.Instance.Icons.UWPSettings128)); Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings), Editor.Instance.Icons.LinuxSettings128)); + Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings), Editor.Instance.Icons.AndroidSettings128)); + var typePS4PlatformSettings = TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename); if (typePS4PlatformSettings != null) Proxy.Add(new SettingsProxy(typePS4PlatformSettings, Editor.Instance.Icons.PlaystationSettings128)); + var typeXboxScarlettPlatformSettings = TypeUtils.GetManagedType(GameSettings.XboxScarlettPlatformSettingsTypename); if (typeXboxScarlettPlatformSettings != null) Proxy.Add(new SettingsProxy(typeXboxScarlettPlatformSettings, Editor.Instance.Icons.XBoxScarletIcon128)); - Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings), Editor.Instance.Icons.AndroidSettings128)); + var typeSwitchPlatformSettings = TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename); if (typeSwitchPlatformSettings != null) - Proxy.Add(new SettingsProxy(typeSwitchPlatformSettings, Editor.Instance.Icons.Document128)); - Proxy.Add(new SettingsProxy(typeof(AudioSettings), Editor.Instance.Icons.AudioSettings128)); + Proxy.Add(new SettingsProxy(typeSwitchPlatformSettings, Editor.Instance.Icons.SwitchSettings128)); // Last add generic json (won't override other json proxies) Proxy.Add(new GenericJsonAssetProxy()); diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs index 45748f7f4..b8f7f74a0 100644 --- a/Source/Editor/Modules/ContentFindingModule.cs +++ b/Source/Editor/Modules/ContentFindingModule.cs @@ -287,11 +287,11 @@ namespace FlaxEditor.Modules break; case ActorNode actorNode: Editor.Instance.SceneEditing.Select(actorNode.Actor); - Editor.Instance.Windows.EditWin.ShowSelectedActors(); + Editor.Instance.Windows.EditWin.Viewport.FocusSelection(); break; case Actor actor: Editor.Instance.SceneEditing.Select(actor); - Editor.Instance.Windows.EditWin.ShowSelectedActors(); + Editor.Instance.Windows.EditWin.Viewport.FocusSelection(); break; } } diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index c1f2a3e7b..3faf5cd7e 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -203,6 +203,7 @@ namespace FlaxEditor.Modules else if (!gameWin.IsSelected) { gameWin.SelectTab(false); + gameWin.RootWindow?.Window?.Focus(); FlaxEngine.GUI.RootControl.GameRoot.Focus(); } } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 8f690454e..b6b716fb5 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -151,6 +151,30 @@ namespace FlaxEditor.Options [DefaultValue(typeof(InputBinding), "Q")] [EditorDisplay("Viewport"), EditorOrder(1550)] public InputBinding Down = new InputBinding(KeyboardKeys.Q); + + [DefaultValue(typeof(InputBinding), "Numpad0")] + [EditorDisplay("Viewport"), EditorOrder(1600)] + public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0); + + [DefaultValue(typeof(InputBinding), "Numpad5")] + [EditorDisplay("Viewport"), EditorOrder(1610)] + public InputBinding ViewpointBack = new InputBinding(KeyboardKeys.Numpad5); + + [DefaultValue(typeof(InputBinding), "Numpad4")] + [EditorDisplay("Viewport"), EditorOrder(1620)] + public InputBinding ViewpointLeft = new InputBinding(KeyboardKeys.Numpad4); + + [DefaultValue(typeof(InputBinding), "Numpad6")] + [EditorDisplay("Viewport"), EditorOrder(1630)] + public InputBinding ViewpointRight = new InputBinding(KeyboardKeys.Numpad6); + + [DefaultValue(typeof(InputBinding), "Numpad8")] + [EditorDisplay("Viewport"), EditorOrder(1640)] + public InputBinding ViewpointTop = new InputBinding(KeyboardKeys.Numpad8); + + [DefaultValue(typeof(InputBinding), "Numpad2")] + [EditorDisplay("Viewport"), EditorOrder(1650)] + public InputBinding ViewpointBottom = new InputBinding(KeyboardKeys.Numpad2); #endregion diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index ea42ee99b..fa1b46f30 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -232,6 +232,7 @@ namespace FlaxEditor.Options BorderNormal = Color.FromBgra(0xFF54545C), TextBoxBackground = Color.FromBgra(0xFF333337), TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), + CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), ProgressNormal = Color.FromBgra(0xFF0ad328), // Fonts diff --git a/Source/Editor/Progress/Handlers/BakeLightmapsProgress.cs b/Source/Editor/Progress/Handlers/BakeLightmapsProgress.cs index 39e46d19e..c2f23285f 100644 --- a/Source/Editor/Progress/Handlers/BakeLightmapsProgress.cs +++ b/Source/Editor/Progress/Handlers/BakeLightmapsProgress.cs @@ -10,8 +10,6 @@ namespace FlaxEditor.Progress.Handlers /// public sealed class BakeLightmapsProgress : ProgressHandler { - private static int _canBake; - /// /// Gets a value indicating whether GPU lightmaps baking is supported on this device. /// @@ -19,15 +17,11 @@ namespace FlaxEditor.Progress.Handlers { get { - if (_canBake == 0) - { - var instance = GPUDevice.Instance; - if (instance == null) - return false; - var limits = instance.Limits; - _canBake = limits.HasCompute && limits.MaximumTexture2DSize >= 8 * 1024 && instance.TotalGraphicsMemory >= 2 * 1024 ? 1 : 2; - } - return _canBake == 1; + var instance = GPUDevice.Instance; + if (instance == null) + return false; + var limits = instance.Limits; + return limits.HasCompute && limits.HasTypedUAVLoad && limits.MaximumTexture2DSize >= 8 * 1024 && instance.TotalGraphicsMemory >= 2 * 1024; } } diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 5e2792dc4..cebbfc544 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -50,6 +50,7 @@ namespace FlaxEditor.SceneGraph.GUI public ActorTreeNode() : base(true) { + ChildrenIndent = 16.0f; } internal virtual void LinkNode(ActorNode node) @@ -292,21 +293,6 @@ namespace FlaxEditor.SceneGraph.GUI } } - /// - public override bool OnKeyDown(KeyboardKeys key) - { - if (IsFocused) - { - if (key == KeyboardKeys.F2) - { - StartRenaming(); - return true; - } - } - - return base.OnKeyDown(key); - } - /// protected override DragDropEffect OnDragEnterHeader(DragData data) { @@ -567,121 +553,12 @@ namespace FlaxEditor.SceneGraph.GUI { for (int i = 0; i < _dragAssets.Objects.Count; i++) { - var assetItem = _dragAssets.Objects[i]; - - if (assetItem.IsOfType()) - { - // Create actor - var model = FlaxEngine.Content.LoadAsync(assetItem.ID); - - var actor = new AnimatedModel - { - StaticFlags = Actor.StaticFlags, - Name = assetItem.ShortName, - SkinnedModel = model, - Transform = Actor.Transform - }; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } - else if (assetItem.IsOfType()) - { - // Create actor - var model = FlaxEngine.Content.LoadAsync(assetItem.ID); - - var actor = new StaticModel - { - StaticFlags = Actor.StaticFlags, - Name = assetItem.ShortName, - Model = model, - Transform = Actor.Transform - }; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } - else if (assetItem.IsOfType()) - { - // Create actor - var actor = new MeshCollider - { - StaticFlags = Actor.StaticFlags, - Name = assetItem.ShortName, - CollisionData = FlaxEngine.Content.LoadAsync(assetItem.ID), - Transform = Actor.Transform - }; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } - else if (assetItem.IsOfType()) - { - // Create actor - var actor = new ParticleEffect - { - StaticFlags = Actor.StaticFlags, - Name = assetItem.ShortName, - ParticleSystem = FlaxEngine.Content.LoadAsync(assetItem.ID), - Transform = Actor.Transform - }; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } - else if (assetItem.IsOfType()) - { - // Create actor - var actor = new SceneAnimationPlayer - { - StaticFlags = Actor.StaticFlags, - Name = assetItem.ShortName, - Animation = FlaxEngine.Content.LoadAsync(assetItem.ID), - Transform = Actor.Transform - }; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } - else if (assetItem.IsOfType()) - { - // Create actor - var actor = new AudioSource - { - StaticFlags = Actor.StaticFlags, - Name = assetItem.ShortName, - Clip = FlaxEngine.Content.LoadAsync(assetItem.ID), - Transform = Actor.Transform - }; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - - break; - } - else if (assetItem.IsOfType()) - { - // Create prefab instance - var prefab = FlaxEngine.Content.LoadAsync(assetItem.ID); - var actor = PrefabManager.SpawnPrefab(prefab, null); - actor.StaticFlags = Actor.StaticFlags; - actor.Name = assetItem.ShortName; - actor.Transform = Actor.Transform; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } - else if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) - { - // Create actor - var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); - actor.StaticFlags = Actor.StaticFlags; - actor.Name = assetItem.ShortName; - actor.Transform = Actor.Transform; - - // Spawn - ActorNode.Root.Spawn(actor, Actor); - } + var item = _dragAssets.Objects[i]; + var actor = item.OnEditorDrop(this); + actor.StaticFlags = Actor.StaticFlags; + actor.Name = item.ShortName; + actor.Transform = Actor.Transform; + ActorNode.Root.Spawn(actor, Actor); } result = DragDropEffect.Move; @@ -736,46 +613,12 @@ namespace FlaxEditor.SceneGraph.GUI return actorNode.Actor != null && actorNode != ActorNode && actorNode.Find(Actor) == null; } - /// - /// Validates the asset for drag and drop into one of the scene tree nodes. - /// - /// The item. - /// True if can drag and drop it, otherwise false. - public static bool ValidateDragAsset(AssetItem assetItem) + private bool ValidateDragAsset(AssetItem assetItem) { - if (assetItem.IsOfType()) - return true; - - if (assetItem.IsOfType()) - return true; - - if (assetItem.IsOfType()) - return true; - - if (assetItem.IsOfType()) - return true; - - if (assetItem.IsOfType()) - return true; - - if (assetItem.IsOfType()) - return true; - - if (assetItem.IsOfType()) - return true; - - if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) - return true; - - return false; + return assetItem.OnEditorDrag(this); } - /// - /// Validates the type of the actor for drag and drop into one of the scene tree nodes. - /// - /// Type of the actor. - /// True if can drag and drop it, otherwise false. - public static bool ValidateDragActorType(ScriptType actorType) + private static bool ValidateDragActorType(ScriptType actorType) { return true; } diff --git a/Source/Editor/Scripting/CodeEditor.cpp b/Source/Editor/Scripting/CodeEditor.cpp index a95334554..cab5ace17 100644 --- a/Source/Editor/Scripting/CodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditor.cpp @@ -180,7 +180,7 @@ void CodeEditingManager::OpenSolution(CodeEditorTypes editorType) const auto editor = GetCodeEditor(editorType); if (editor) { - editor->OpenSolution(); + OpenSolution(editor); } else { diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 4299b8865..2c5265a06 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -10,12 +10,24 @@ #if PLATFORM_WINDOWS +#include "Engine/Core/Collections/Sorting.h" #include "Engine/Platform/File.h" #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" #include "Engine/Serialization/Json.h" namespace { + struct RiderInstallation + { + String path; + String version; + + RiderInstallation(const String& path_, const String& version_) + : path(path_), version(version_) + { + } + }; + bool FindRegistryKeyItems(HKEY hKey, Array& results) { Char nameBuffer[256]; @@ -31,7 +43,7 @@ namespace return true; } - void SearchDirectory(Array* output, const String& directory) + void SearchDirectory(Array* installations, const String& directory) { if (!FileSystem::DirectoryExists(directory)) return; @@ -46,24 +58,29 @@ namespace if (document.HasParseError()) return; + // Find version + auto versionMember = document.FindMember("version"); + if (versionMember == document.MemberEnd()) + return; + // Find executable file path auto launchMember = document.FindMember("launch"); - if (launchMember != document.MemberEnd() && launchMember->value.IsArray() && launchMember->value.Size() > 0) - { - auto launcherPathMember = launchMember->value[0].FindMember("launcherPath"); - if (launcherPathMember != launchMember->value[0].MemberEnd()) - { - auto launcherPath = launcherPathMember->value.GetText(); - auto exePath = directory / launcherPath; - if (launcherPath.HasChars() && FileSystem::FileExists(exePath)) - { - output->Add(New(exePath)); - } - } - } + if (launchMember == document.MemberEnd() || !launchMember->value.IsArray() || launchMember->value.Size() == 0) + return; + + auto launcherPathMember = launchMember->value[0].FindMember("launcherPath"); + if (launcherPathMember == launchMember->value[0].MemberEnd()) + return; + + auto launcherPath = launcherPathMember->value.GetText(); + auto exePath = directory / launcherPath; + if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath)) + return; + + installations->Add(New(exePath, versionMember->value.GetText())); } - void SearchRegistry(Array* output, HKEY root, const Char* key) + void SearchRegistry(Array* installations, HKEY root, const Char* key, const Char* valueName = TEXT("")) { // Open key HKEY keyH; @@ -83,14 +100,14 @@ namespace // Read subkey value DWORD type; DWORD cbData; - if (RegQueryValueExW(subKeyH, TEXT(""), nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS || type != REG_SZ) + if (RegQueryValueExW(subKeyH, valueName, nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS || type != REG_SZ) { RegCloseKey(subKeyH); continue; } Array data; data.Resize((int32)cbData / sizeof(Char)); - if (RegQueryValueExW(subKeyH, TEXT(""), nullptr, nullptr, reinterpret_cast(data.Get()), &cbData) != ERROR_SUCCESS) + if (RegQueryValueExW(subKeyH, valueName, nullptr, nullptr, reinterpret_cast(data.Get()), &cbData) != ERROR_SUCCESS) { RegCloseKey(subKeyH); continue; @@ -98,7 +115,7 @@ namespace // Check if it's a valid installation path String path(data.Get(), data.Count() - 1); - SearchDirectory(output, path); + SearchDirectory(installations, path); RegCloseKey(subKeyH); } @@ -108,6 +125,31 @@ namespace } } +bool sortInstallations(RiderInstallation* const& i1, RiderInstallation* const& i2) +{ + Array values1, values2; + i1->version.Split('.', values1); + i2->version.Split('.', values2); + + int32 version1[3] = { 0 }; + int32 version2[3] = { 0 }; + StringUtils::Parse(values1[0].Get(), &version1[0]); + StringUtils::Parse(values1[1].Get(), &version1[1]); + StringUtils::Parse(values1[2].Get(), &version1[2]); + StringUtils::Parse(values2[0].Get(), &version2[0]); + StringUtils::Parse(values2[1].Get(), &version2[1]); + StringUtils::Parse(values2[2].Get(), &version2[2]); + + // Compare by MAJOR.MINOR.BUILD + if (version1[0] == version2[0]) + { + if (version1[1] == version2[1]) + return version1[2] > version2[2]; + return version1[1] > version2[1]; + } + return version1[0] > version2[0]; +} + #endif RiderCodeEditor::RiderCodeEditor(const String& execPath) @@ -119,8 +161,23 @@ RiderCodeEditor::RiderCodeEditor(const String& execPath) void RiderCodeEditor::FindEditors(Array* output) { #if PLATFORM_WINDOWS - SearchRegistry(output, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider")); - SearchRegistry(output, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider")); + Array installations; + + // For versions 2021 or later + SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\JetBrains\\Rider"), TEXT("InstallDir")); + + // For versions 2020 or earlier + SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider")); + SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\JetBrains Rider")); + + // Sort found installations by version number + Sorting::QuickSort(installations.Get(), installations.Count(), &sortInstallations); + + for (RiderInstallation* installation : installations) + { + output->Add(New(installation->path)); + Delete(installation); + } #endif } diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp index 54ba119f2..ffb4ff7b3 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp @@ -152,34 +152,10 @@ public: namespace VisualStudio { - bool SameFile(HANDLE h1, HANDLE h2) - { - BY_HANDLE_FILE_INFORMATION bhfi1 = { 0 }; - BY_HANDLE_FILE_INFORMATION bhfi2 = { 0 }; - - if (::GetFileInformationByHandle(h1, &bhfi1) && ::GetFileInformationByHandle(h2, &bhfi2)) - { - return ((bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh) && (bhfi1.nFileIndexLow == bhfi2.nFileIndexLow) && (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber)); - } - - return false; - } - bool AreFilePathsEqual(const wchar_t* path1, const wchar_t* path2) - { - if (wcscmp(path1, path2) == 0) - return true; - - HANDLE file1 = CreateFileW(path1, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - HANDLE file2 = CreateFileW(path2, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - bool result = SameFile(file1, file2); - - CloseHandle(file1); - CloseHandle(file2); - - return result; - } + { + return _wcsicmp(path1, path2) == 0; + } class ConnectionInternal { diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp index dde2455a7..e8b221a5d 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp @@ -44,6 +44,7 @@ VisualStudioEditor::VisualStudioEditor(VisualStudioVersion version, const String break; } _solutionPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln"); + _solutionPath.Replace('/', '\\'); // Use Windows-style path separators } void VisualStudioEditor::FindEditors(Array* output) @@ -145,7 +146,9 @@ void VisualStudioEditor::OpenFile(const String& path, int32 line) // Open file const VisualStudio::Connection connection(*_CLSID, *_solutionPath); - const auto result = connection.OpenFile(*path, line); + String tmp = path; + tmp.Replace('/', '\\'); // Use Windows-style path separators + const auto result = connection.OpenFile(*tmp, line); if (result.Failed()) { LOG(Warning, "Cannot open file \'{0}\':{1}. {2}.", path, line, String(result.Message.c_str())); diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 71323dc34..ed30922f7 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -304,7 +304,7 @@ MClass* ScriptsBuilder::FindScript(const StringView& scriptName) GetClassName(mclass->GetFullName(), mclassName); if (className == mclassName) { - LOG(Info, "Found {0} type for type {1} (assembly {2})", mclass->ToString(), String(scriptName.Get(), scriptName.Length()), assembly->ToString()); + LOG(Info, "Found {0} type for type {1} (assembly {2})", String(mclass->GetFullName()), String(scriptName.Get(), scriptName.Length()), assembly->ToString()); return mclass; } } diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs index ffcfb719a..d7fc5dcc2 100644 --- a/Source/Editor/Surface/Archetypes/Flow.cs +++ b/Source/Editor/Surface/Archetypes/Flow.cs @@ -293,6 +293,21 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(1, "Value", true, null, 1), } }, + new NodeArchetype + { + TypeID = 6, + Title = "Delay", + Description = "Delays the graph execution. If delay is 0 then it will pass though.", + Flags = NodeFlags.VisualScriptGraph, + Size = new Vector2(150, 40), + DefaultValues = new object[] { 1.0f }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(1, "Duration", true, typeof(float), 1, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 4e017248a..141028081 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -605,8 +605,7 @@ namespace FlaxEditor.Surface.Archetypes MakeBox(0, string.Empty, typeof(void), true); } - // Update size - Resize(Style.Current.FontLarge.MeasureText(Title).X + 30, 20 + (_parameters?.Length * 20 ?? 0)); + ResizeAuto(); } private void MakeBox(int id, string text, Type type, bool single = false) diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 28d6dce6a..f6fd2d003 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -118,8 +118,10 @@ namespace FlaxEditor.Surface.Archetypes var cm = new ItemsListContextMenu(180); foreach (var module in modules) { - cm.AddItem(new ItemsListContextMenu.Item(module.Title, module.TypeID) + cm.AddItem(new ItemsListContextMenu.Item { + Name = module.Title, + Tag = module.TypeID, TooltipText = module.Description, }); } diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 005af20cc..645a243f6 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -677,19 +677,21 @@ namespace FlaxEditor.Surface.Archetypes /// public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) - {} + { + } /// public override void OnLoaded() { base.OnLoaded(); - var vss = (VisualScriptSurface)this.Context.Surface; - var type = TypeUtils.GetType(vss.Script.ScriptTypeName); + + var surface = (VisualScriptSurface)Context.Surface; + var type = TypeUtils.GetType(surface.Script.ScriptTypeName); var box = (OutputBox)GetBox(0); box.CurrentType = type ? type : new ScriptType(typeof(VisualScript)); } } - + private class AssetReferenceNode : SurfaceNode { /// diff --git a/Source/Editor/Surface/Constants.cs b/Source/Editor/Surface/Constants.cs index 3636d193c..a63d8b489 100644 --- a/Source/Editor/Surface/Constants.cs +++ b/Source/Editor/Surface/Constants.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.Surface /// /// The box size (with and height). /// - public const float BoxSize = 16.0f; + public const float BoxSize = 20.0f; /// /// The node layout offset on the y axis (height of the boxes rows, etc.). It's used to make the design more consistent. diff --git a/Source/Editor/Surface/ParticleEmitterSurface.cs b/Source/Editor/Surface/ParticleEmitterSurface.cs index 4368569a1..905d41882 100644 --- a/Source/Editor/Surface/ParticleEmitterSurface.cs +++ b/Source/Editor/Surface/ParticleEmitterSurface.cs @@ -17,12 +17,12 @@ namespace FlaxEditor.Surface [HideInEditor] public class ParticleEmitterSurface : VisjectSurface { - internal Particles.ParticleEmitterNode _rootNode; + internal FlaxEditor.Surface.Archetypes.Particles.ParticleEmitterNode _rootNode; /// /// Gets the root node of the emitter graph. /// - public Particles.ParticleEmitterNode RootNode => _rootNode; + public FlaxEditor.Surface.Archetypes.Particles.ParticleEmitterNode RootNode => _rootNode; /// public ParticleEmitterSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 810f125ae..119c01c30 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -447,18 +447,13 @@ namespace FlaxEditor.Surface var cm = new ItemsListContextMenu(180); foreach (var newParameterType in newParameterTypes) { - var name = newParameterType.Type != null ? window.VisjectSurface.GetTypeName(newParameterType) : newParameterType.Name; - var item = new ItemsListContextMenu.Item(name, newParameterType) + var item = new TypeSearchPopup.TypeItemView(newParameterType) { + Tag = newParameterType, TooltipText = newParameterType.TypeName, }; - var attributes = newParameterType.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - item.TooltipText += '\n'; - item.TooltipText += tooltipAttribute.Text; - } + if (newParameterType.Type != null) + item.Name = window.VisjectSurface.GetTypeName(newParameterType); cm.AddItem(item); } cm.ItemClicked += OnAddParameterItemClicked; diff --git a/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs b/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs index 3e3730769..7443b52ad 100644 --- a/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs +++ b/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs @@ -33,6 +33,25 @@ namespace FlaxEditor.Tools.Foliage GizmoMode = mode; } + /// + public override BoundingSphere FocusBounds + { + get + { + var foliage = GizmoMode.SelectedFoliage; + if (foliage) + { + var instanceIndex = GizmoMode.SelectedInstanceIndex; + if (instanceIndex >= 0 && instanceIndex < foliage.InstancesCount) + { + var instance = foliage.GetInstance(instanceIndex); + return instance.Bounds; + } + } + return base.FocusBounds; + } + } + /// protected override int SelectionCount { diff --git a/Source/Editor/Tools/Foliage/PaintFoliageGizmo.cs b/Source/Editor/Tools/Foliage/PaintFoliageGizmo.cs index b24f503aa..9e6d78920 100644 --- a/Source/Editor/Tools/Foliage/PaintFoliageGizmo.cs +++ b/Source/Editor/Tools/Foliage/PaintFoliageGizmo.cs @@ -169,6 +169,9 @@ namespace FlaxEditor.Tools.Foliage PaintEnded?.Invoke(); } + /// + public override bool IsControllingMouse => IsPainting; + /// public override void Update(float dt) { diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs index 51372f290..de95e4005 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs @@ -135,6 +135,9 @@ namespace FlaxEditor.Tools.Terrain PaintEnded?.Invoke(); } + /// + public override bool IsControllingMouse => IsPainting; + /// public override void Update(float dt) { diff --git a/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs b/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs index 88cd83d46..5900bc601 100644 --- a/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs +++ b/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs @@ -135,6 +135,9 @@ namespace FlaxEditor.Tools.Terrain PaintEnded?.Invoke(); } + /// + public override bool IsControllingMouse => IsPainting; + /// public override void Update(float dt) { diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp index e89b5e3e9..fb90d786a 100644 --- a/Source/Editor/Tools/Terrain/TerrainTools.cpp +++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "TerrainTools.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Cache.h" #include "Engine/Core/Math/Int2.h" #include "Engine/Core/Math/Color32.h" diff --git a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs index f9a48d659..6a90bc61d 100644 --- a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs +++ b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs @@ -124,7 +124,7 @@ namespace FlaxEditor.Tools.Terrain.Undo { var data = Marshal.AllocHGlobal(_heightmapDataSize); var source = GetData(ref patchCoord, tag); - Utils.MemoryCopy(source, data, _heightmapDataSize); + Utils.MemoryCopy(data, source, (ulong)_heightmapDataSize); _patches.Add(new PatchData { PatchCoord = patchCoord, @@ -150,7 +150,7 @@ namespace FlaxEditor.Tools.Terrain.Undo var data = Marshal.AllocHGlobal(_heightmapDataSize); var source = GetData(ref patch.PatchCoord, patch.Tag); - Utils.MemoryCopy(source, data, _heightmapDataSize); + Utils.MemoryCopy(data, source, (ulong)_heightmapDataSize); patch.After = data; _patches[i] = patch; } diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 29d9daedc..715f075ca 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -540,6 +540,12 @@ namespace FlaxEditor.Tools _paintUpdateCount = 0; } + /// + public override bool IsControllingMouse => IsPainting; + + /// + public override BoundingSphere FocusBounds => _selectedModel?.Sphere ?? base.FocusBounds; + /// public override void Update(float dt) { diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index 22abccf69..b6cd14273 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -143,13 +143,22 @@ namespace FlaxEditor.Viewport.Cameras ShowSphere(ref mergesSphere, ref orientation); } - private void ShowSphere(ref BoundingSphere sphere) + /// + /// Moves the camera to visualize given world area defined by the sphere. + /// + /// The sphere. + public void ShowSphere(ref BoundingSphere sphere) { var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); ShowSphere(ref sphere, ref q); } - private void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) + /// + /// Moves the camera to visualize given world area defined by the sphere. + /// + /// The sphere. + /// The camera orientation. + public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) { Vector3 position; if (Viewport.UseOrthographicProjection) diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 93b2c7fbe..5c9fcf034 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -73,6 +73,9 @@ namespace FlaxEditor.Viewport /// public Undo Undo { get; } + /// + protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; + /// protected override void AddUpdateCallbacks(RootControl root) { diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 2dee43614..9a9ae9d7d 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Options; @@ -136,7 +137,7 @@ namespace FlaxEditor.Viewport // Input - private bool _isControllingMouse; + private bool _isControllingMouse, _isViewportControllingMouse; private int _deltaFilteringStep; private Vector2 _startPos; private Vector2 _mouseDeltaLast; @@ -649,10 +650,32 @@ namespace FlaxEditor.Viewport } } + InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); + InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); + InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); + InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); + InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); + InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + // Link for task event task.Begin += OnRenderBegin; } + /// + /// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects). + /// + protected virtual bool IsControllingMouse => false; + + /// + /// Orients the viewport. + /// + /// The orientation. + protected void OrientViewport(Quaternion orientation) + { + var quat = orientation; + OrientViewport(ref quat); + } + /// /// Orients the viewport. /// @@ -661,12 +684,12 @@ namespace FlaxEditor.Viewport { if (ViewportCamera is FPSCamera fpsCamera) { - var pos = Vector3.Zero + Vector3.Backward * orientation * 2000.0f; + var pos = ViewPosition + Vector3.Backward * orientation * 2000.0f; fpsCamera.MoveViewport(pos, orientation); } else { - ViewportCamera.SetArcBallView(orientation, Vector3.Zero, 2000.0f); + ViewportCamera.SetArcBallView(orientation, ViewPosition, 2000.0f); } } @@ -972,7 +995,16 @@ namespace FlaxEditor.Viewport // Update input { // Get input buttons and keys (skip if viewport has no focus or mouse is over a child control) - bool useMouse = Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height); + var isViewportControllingMouse = IsControllingMouse; + if (isViewportControllingMouse != _isViewportControllingMouse) + { + _isViewportControllingMouse = isViewportControllingMouse; + if (isViewportControllingMouse) + StartMouseCapture(); + else + EndMouseCapture(); + } + bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height)); _prevInput = _input; var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl)); if (ContainsFocus && hit == null) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 0c895640d..0c759c950 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -22,7 +22,7 @@ namespace FlaxEditor.Viewport /// Main editor gizmo viewport used by the . /// /// - public partial class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner + public partial class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner, IGizmoOwner { private readonly Editor _editor; @@ -36,7 +36,7 @@ namespace FlaxEditor.Viewport private readonly ViewportWidgetButton _rotateSnapping; private readonly ViewportWidgetButton _scaleSnapping; - private readonly DragAssets _dragAssets = new DragAssets(ValidateDragItem); + private readonly DragAssets _dragAssets; private readonly DragActorType _dragActorType = new DragActorType(ValidateDragActorType); private SelectionOutline _customSelectionOutline; @@ -187,6 +187,7 @@ namespace FlaxEditor.Viewport : base(Object.New(), editor.Undo) { _editor = editor; + _dragAssets = new DragAssets(ValidateDragItem); // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; @@ -360,7 +361,7 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.FocusSelection, () => _editor.Windows.EditWin.ShowSelectedActors()); + InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete); } @@ -449,6 +450,12 @@ namespace FlaxEditor.Viewport /// public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) { + // Draw gizmos + for (int i = 0; i < Gizmos.Count; i++) + { + Gizmos[i].Draw(ref renderContext); + } + // Draw selected objects debug shapes and visuals if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) { @@ -651,6 +658,31 @@ namespace FlaxEditor.Viewport Gizmos.ForEach(x => x.OnSelectionChanged(selection)); } + /// + /// Focuses the viewport on the current selection of the gizmo. + /// + public void FocusSelection() + { + var orientation = ViewOrientation; + FocusSelection(ref orientation); + } + + /// + /// Focuses the viewport on the current selection of the gizmo. + /// + /// The target view orientation. + public void FocusSelection(ref Quaternion orientation) + { + if (TransformGizmo.SelectedParents.Count == 0) + return; + + var gizmoBounds = Gizmos.Active.FocusBounds; + if (gizmoBounds != BoundingSphere.Empty) + ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); + else + ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + } + /// /// Applies the transform to the collection of scene graph nodes. /// @@ -709,11 +741,9 @@ namespace FlaxEditor.Viewport protected override void OrientViewport(ref Quaternion orientation) { if (TransformGizmo.SelectedParents.Count != 0) - { - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); - } - - base.OrientViewport(ref orientation); + FocusSelection(ref orientation); + else + base.OrientViewport(ref orientation); } /// @@ -800,31 +830,19 @@ namespace FlaxEditor.Viewport return result; } - private static bool ValidateDragItem(ContentItem contentItem) + private bool ValidateDragItem(ContentItem contentItem) { if (!Level.IsAnySceneLoaded) return false; if (contentItem is AssetItem assetItem) { - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) + if (assetItem.OnEditorDrag(this)) return true; if (assetItem.IsOfType()) return true; - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) - return true; if (assetItem.IsOfType()) return true; - if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) - return true; } return false; @@ -891,119 +909,40 @@ namespace FlaxEditor.Viewport private void Spawn(AssetItem item, SceneGraphNode hit, ref Vector2 location, ref Vector3 hitLocation) { - if (item is AssetItem assetItem) + if (item.IsOfType()) { - if (assetItem.IsOfType()) + if (hit is StaticModelNode staticModelNode) { - var asset = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new ParticleEffect - { - Name = item.ShortName, - ParticleSystem = asset - }; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - var asset = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new SceneAnimationPlayer - { - Name = item.ShortName, - Animation = asset - }; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - if (hit is StaticModelNode staticModelNode) - { - var staticModel = (StaticModel)staticModelNode.Actor; - var ray = ConvertMouseToRay(ref location); - if (staticModel.IntersectsEntry(ref ray, out _, out _, out var entryIndex)) - { - var material = FlaxEngine.Content.LoadAsync(item.ID); - using (new UndoBlock(Undo, staticModel, "Change material")) - staticModel.SetMaterial(entryIndex, material); - } - } - else if (hit is BoxBrushNode.SideLinkNode brushSurfaceNode) + var staticModel = (StaticModel)staticModelNode.Actor; + var ray = ConvertMouseToRay(ref location); + if (staticModel.IntersectsEntry(ref ray, out _, out _, out var entryIndex)) { var material = FlaxEngine.Content.LoadAsync(item.ID); - using (new UndoBlock(Undo, brushSurfaceNode.Brush, "Change material")) - { - var surface = brushSurfaceNode.Surface; - surface.Material = material; - brushSurfaceNode.Surface = surface; - } + using (new UndoBlock(Undo, staticModel, "Change material")) + staticModel.SetMaterial(entryIndex, material); } - return; } - if (assetItem.IsOfType()) + else if (hit is BoxBrushNode.SideLinkNode brushSurfaceNode) { - var model = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new AnimatedModel + var material = FlaxEngine.Content.LoadAsync(item.ID); + using (new UndoBlock(Undo, brushSurfaceNode.Brush, "Change material")) { - Name = item.ShortName, - SkinnedModel = model - }; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - var model = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new StaticModel - { - Name = item.ShortName, - Model = model - }; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - var collisionData = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new MeshCollider - { - Name = item.ShortName, - CollisionData = collisionData - }; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - var clip = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new AudioSource - { - Name = item.ShortName, - Clip = clip - }; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - var prefab = FlaxEngine.Content.LoadAsync(item.ID); - var actor = PrefabManager.SpawnPrefab(prefab, null); - actor.Name = item.ShortName; - Spawn(actor, ref hitLocation); - return; - } - if (assetItem.IsOfType()) - { - Editor.Instance.Scene.OpenScene(item.ID, true); - return; - } - if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) - { - var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); - actor.Name = item.ShortName; - Spawn(actor, ref hitLocation); - return; + var surface = brushSurfaceNode.Surface; + surface.Material = material; + brushSurfaceNode.Surface = surface; + } } + return; + } + if (item.IsOfType()) + { + Editor.Instance.Scene.OpenScene(item.ID, true); + return; + } + { + var actor = item.OnEditorDrop(this); + actor.Name = item.ShortName; + Spawn(actor, ref hitLocation); } } diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 6483700b6..4e71677b2 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -24,7 +24,7 @@ namespace FlaxEditor.Viewport /// /// /// - public class PrefabWindowViewport : PrefabPreview, IEditorPrimitivesOwner + public class PrefabWindowViewport : PrefabPreview, IGizmoOwner { [HideInEditor] private sealed class PrefabSpritesRenderer : MainEditorGizmoViewport.EditorSpritesRenderer @@ -51,9 +51,8 @@ namespace FlaxEditor.Viewport private ViewportWidgetButton _scaleSnapping; private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); - private IntPtr _debugDrawContext; private PrefabSpritesRenderer _spritesRenderer; - private readonly DragAssets _dragAssets = new DragAssets(ValidateDragItem); + private readonly DragAssets _dragAssets; private readonly DragActorType _dragActorType = new DragActorType(ValidateDragActorType); private readonly DragHandlers _dragHandlers = new DragHandlers(); @@ -67,16 +66,6 @@ namespace FlaxEditor.Viewport /// public SelectionOutline SelectionOutline; - /// - /// The editor primitives postFx. - /// - public EditorPrimitives EditorPrimitives; - - /// - /// Gets or sets a value indicating whether draw shapes. - /// - public bool DrawDebugDraw = true; - /// /// Initializes a new instance of the class. /// @@ -88,7 +77,9 @@ namespace FlaxEditor.Viewport _window.SelectionChanged += OnSelectionChanged; Undo = window.Undo; ViewportCamera = new FPSCamera(); - _debugDrawContext = DebugDraw.AllocateContext(); + _dragAssets = new DragAssets(ValidateDragItem); + ShowDebugDraw = true; + ShowEditorPrimitives = true; // Prepare rendering task Task.ActorsSource = ActorsSources.CustomActors; @@ -101,9 +92,6 @@ namespace FlaxEditor.Viewport SelectionOutline = FlaxEngine.Object.New(); SelectionOutline.SelectionGetter = () => TransformGizmo.SelectedParents; Task.CustomPostFx.Add(SelectionOutline); - EditorPrimitives = FlaxEngine.Object.New(); - EditorPrimitives.Viewport = this; - Task.CustomPostFx.Add(EditorPrimitives); _spritesRenderer = FlaxEngine.Object.New(); _spritesRenderer.Task = Task; _spritesRenderer.Viewport = this; @@ -281,13 +269,6 @@ namespace FlaxEditor.Viewport { var task = renderContext.Task; - // Render editor primitives, gizmo and debug shapes in debug view modes - // Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers - if (EditorPrimitives && EditorPrimitives.CanRender) - { - EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output); - } - // Render editor sprites if (_spritesRenderer && _spritesRenderer.CanRender) { @@ -315,7 +296,8 @@ namespace FlaxEditor.Viewport /// public void ShowSelectedActors() { - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents); + var orient = Viewport.ViewOrientation; + ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); } /// @@ -354,6 +336,12 @@ namespace FlaxEditor.Viewport /// public Undo Undo { get; } + /// + public EditorViewport Viewport => this; + + /// + protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; + /// protected override void AddUpdateCallbacks(RootControl root) { @@ -673,24 +661,14 @@ namespace FlaxEditor.Viewport return _dragHandlers.OnDragEnter(data); } - private static bool ValidateDragItem(ContentItem contentItem) + private bool ValidateDragItem(ContentItem contentItem) { if (contentItem is AssetItem assetItem) { - if (assetItem.IsOfType()) + if (assetItem.OnEditorDrag(this)) return true; if (assetItem.IsOfType()) return true; - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) - return true; - if (assetItem.IsOfType()) - return true; - if (assetItem is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) - return true; } return false; @@ -743,17 +721,6 @@ namespace FlaxEditor.Viewport { if (item is BinaryAssetItem binaryAssetItem) { - if (binaryAssetItem.Type == typeof(ParticleSystem)) - { - var particleSystem = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new ParticleEffect - { - Name = item.ShortName, - ParticleSystem = particleSystem - }; - Spawn(actor, ref hitLocation); - return; - } if (typeof(MaterialBase).IsAssignableFrom(binaryAssetItem.Type)) { if (hit is StaticModelNode staticModelNode) @@ -769,65 +736,11 @@ namespace FlaxEditor.Viewport } return; } - if (typeof(SkinnedModel).IsAssignableFrom(binaryAssetItem.Type)) - { - var model = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new AnimatedModel - { - Name = item.ShortName, - SkinnedModel = model - }; - Spawn(actor, ref hitLocation); - return; - } - if (typeof(Model).IsAssignableFrom(binaryAssetItem.Type)) - { - var model = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new StaticModel - { - Name = item.ShortName, - Model = model - }; - Spawn(actor, ref hitLocation); - return; - } - if (binaryAssetItem.IsOfType()) - { - var collisionData = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new MeshCollider - { - Name = item.ShortName, - CollisionData = collisionData - }; - Spawn(actor, ref hitLocation); - return; - } - if (typeof(AudioClip).IsAssignableFrom(binaryAssetItem.Type)) - { - var clip = FlaxEngine.Content.LoadAsync(item.ID); - var actor = new AudioSource - { - Name = item.ShortName, - Clip = clip - }; - Spawn(actor, ref hitLocation); - return; - } - if (typeof(Prefab).IsAssignableFrom(binaryAssetItem.Type)) - { - var prefab = FlaxEngine.Content.LoadAsync(item.ID); - var actor = PrefabManager.SpawnPrefab(prefab, null); - actor.Name = item.ShortName; - Spawn(actor, ref hitLocation); - return; - } } - if (item is VisualScriptItem visualScriptItem && new ScriptType(typeof(Actor)).IsAssignableFrom(visualScriptItem.ScriptType) && visualScriptItem.ScriptType.CanCreateInstance) { - var actor = (Actor)visualScriptItem.ScriptType.CreateInstance(); + var actor = item.OnEditorDrop(this); actor.Name = item.ShortName; Spawn(actor, ref hitLocation); - return; } } @@ -850,15 +763,38 @@ namespace FlaxEditor.Viewport Spawn(actor, ref hitLocation); } + /// + /// Focuses the viewport on the current selection of the gizmo. + /// + public void FocusSelection() + { + var orientation = ViewOrientation; + FocusSelection(ref orientation); + } + + /// + /// Focuses the viewport on the current selection of the gizmo. + /// + /// The target view orientation. + public void FocusSelection(ref Quaternion orientation) + { + if (TransformGizmo.SelectedParents.Count == 0) + return; + + var gizmoBounds = Gizmos.Active.FocusBounds; + if (gizmoBounds != BoundingSphere.Empty) + ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); + else + ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + } + /// protected override void OrientViewport(ref Quaternion orientation) { if (TransformGizmo.SelectedParents.Count != 0) - { - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); - } - - base.OrientViewport(ref orientation); + FocusSelection(ref orientation); + else + base.OrientViewport(ref orientation); } /// @@ -920,35 +856,35 @@ namespace FlaxEditor.Viewport /// public override void OnDestroy() { - if (_debugDrawContext != IntPtr.Zero) - { - DebugDraw.FreeContext(_debugDrawContext); - _debugDrawContext = IntPtr.Zero; - } FlaxEngine.Object.Destroy(ref SelectionOutline); - FlaxEngine.Object.Destroy(ref EditorPrimitives); FlaxEngine.Object.Destroy(ref _spritesRenderer); base.OnDestroy(); } /// - public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) + public override void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) { - // Draw selected objects debug shapes and visuals - if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) + // Draw gizmos + for (int i = 0; i < Gizmos.Count; i++) { - DebugDraw.SetContext(_debugDrawContext); - DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Engine.FramesPerSecond); - unsafe + Gizmos[i].Draw(ref renderContext); + } + + base.DrawEditorPrimitives(context, ref renderContext, target, targetDepth); + } + + /// + protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext) + { + base.OnDebugDraw(context, ref renderContext); + + unsafe + { + fixed (IntPtr* actors = _debugDrawData.ActorsPtrs) { - fixed (IntPtr* actors = _debugDrawData.ActorsPtrs) - { - DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, false); - } + DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, false); } - DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true); - DebugDraw.SetContext(IntPtr.Zero); } } } diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 697a7c1c1..158ccc370 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -1,6 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System.Collections.Generic; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEditor.GUI.Input; using Object = FlaxEngine.Object; @@ -13,12 +13,9 @@ namespace FlaxEditor.Viewport.Previews /// public class AnimatedModelPreview : AssetPreview { + private ContextMenuButton _showBoundsButton; private AnimatedModel _previewModel; - private StaticModel _previewNodesActor; - private Model _previewNodesModel; - private int _previewNodesCounter; - private List _previewNodesVB; - private List _previewNodesIB; + private bool _showNodes, _showBounds; /// /// Gets or sets the skinned model asset to preview. @@ -42,7 +39,32 @@ namespace FlaxEditor.Viewport.Previews /// /// Gets or sets a value indicating whether show animated model skeleton nodes debug view. /// - public bool ShowNodes { get; set; } = false; + public bool ShowNodes + { + get => _showNodes; + set + { + _showNodes = value; + if (value) + ShowDebugDraw = true; + } + } + + /// + /// Gets or sets a value indicating whether show animated model bounding box debug view. + /// + public bool ShowBounds + { + get => _showBounds; + set + { + _showBounds = value; + if (value) + ShowDebugDraw = true; + if (_showBoundsButton != null) + _showBoundsButton.Checked = value; + } + } /// /// Gets or sets a value indicating whether scale the model to the normalized bounds. @@ -72,20 +94,14 @@ namespace FlaxEditor.Viewport.Previews UpdateMode = AnimatedModel.AnimationUpdateMode.Manual }; - _previewNodesModel = FlaxEngine.Content.CreateVirtualAsset(); - _previewNodesModel.SetupLODs(new[] { 1 }); - _previewNodesActor = new StaticModel - { - Model = _previewNodesModel - }; - _previewNodesActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WiresDebugMaterial)); - // Link actors for rendering Task.AddCustomActor(_previewModel); - Task.AddCustomActor(_previewNodesActor); if (useWidgets) { + // Show Bounds + _showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds); + // Preview LOD { var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); @@ -121,6 +137,55 @@ namespace FlaxEditor.Viewport.Previews } } + /// + protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext) + { + base.OnDebugDraw(context, ref renderContext); + + // Draw skeleton nodes + if (_showNodes) + { + _previewModel.GetCurrentPose(out var pose); + var nodes = _previewModel.SkinnedModel?.Nodes; + if (pose != null && pose.Length != 0 && nodes != null) + { + // Draw bounding box at the node locations + var nodesMask = NodesMask != null && NodesMask.Length == nodes.Length ? NodesMask : null; + var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f)); + for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++) + { + if (nodesMask != null && !nodesMask[nodeIndex]) + continue; + var transform = pose[nodeIndex]; + transform.Decompose(out var scale, out Matrix _, out _); + transform = Matrix.Invert(Matrix.Scaling(scale)) * transform; + var box = localBox * transform; + DebugDraw.DrawWireBox(box, Color.Green, 0, false); + } + + // Nodes connections + for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++) + { + int parentIndex = nodes[nodeIndex].ParentIndex; + if (parentIndex != -1) + { + if (nodesMask != null && (!nodesMask[nodeIndex] || !nodesMask[parentIndex])) + continue; + var parentPos = pose[parentIndex].TranslationVector; + var bonePos = pose[nodeIndex].TranslationVector; + DebugDraw.DrawLine(parentPos, bonePos, Color.Green, 0, false); + } + } + } + } + + // Draw bounds + if (_showBounds) + { + DebugDraw.DrawWireBox(_previewModel.Box, Color.Violet.RGBMultiplied(0.8f), 0, false); + } + } + /// public override void Update(float deltaTime) { @@ -131,122 +196,6 @@ namespace FlaxEditor.Viewport.Previews { _previewModel.UpdateAnimation(); } - - // Update the nodes debug (once every few frames) - _previewNodesActor.Transform = _previewModel.Transform; - var updateNodesCount = PlayAnimation || _previewNodesVB?.Count == 0 ? 1 : 10; - _previewNodesActor.IsActive = ShowNodes; - if (_previewNodesCounter++ % updateNodesCount == 0 && ShowNodes) - { - _previewModel.GetCurrentPose(out var pose); - var nodes = _previewModel.SkinnedModel?.Nodes; - if (pose == null || pose.Length == 0 || nodes == null) - { - _previewNodesActor.IsActive = false; - } - else - { - if (_previewNodesVB == null) - _previewNodesVB = new List(1024 * 2); - else - _previewNodesVB.Clear(); - if (_previewNodesIB == null) - _previewNodesIB = new List(1024 * 3); - else - _previewNodesIB.Clear(); - - // Draw bounding box at the node locations - var nodesMask = NodesMask != null && NodesMask.Length == nodes.Length ? NodesMask : null; - var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f)); - for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++) - { - if (nodesMask != null && !nodesMask[nodeIndex]) - continue; - - var transform = pose[nodeIndex]; - transform.Decompose(out var scale, out Matrix _, out _); - transform = Matrix.Invert(Matrix.Scaling(scale)) * transform; - - // Some inlined code to improve performance - var box = localBox * transform; - // - var iStart = _previewNodesVB.Count; - box.GetCorners(_previewNodesVB); - // - _previewNodesIB.Add(iStart + 0); - _previewNodesIB.Add(iStart + 1); - _previewNodesIB.Add(iStart + 0); - // - _previewNodesIB.Add(iStart + 0); - _previewNodesIB.Add(iStart + 4); - _previewNodesIB.Add(iStart + 0); - // - _previewNodesIB.Add(iStart + 1); - _previewNodesIB.Add(iStart + 2); - _previewNodesIB.Add(iStart + 1); - // - _previewNodesIB.Add(iStart + 1); - _previewNodesIB.Add(iStart + 5); - _previewNodesIB.Add(iStart + 1); - // - _previewNodesIB.Add(iStart + 2); - _previewNodesIB.Add(iStart + 3); - _previewNodesIB.Add(iStart + 2); - // - _previewNodesIB.Add(iStart + 2); - _previewNodesIB.Add(iStart + 6); - _previewNodesIB.Add(iStart + 2); - // - _previewNodesIB.Add(iStart + 3); - _previewNodesIB.Add(iStart + 7); - _previewNodesIB.Add(iStart + 3); - // - _previewNodesIB.Add(iStart + 4); - _previewNodesIB.Add(iStart + 5); - _previewNodesIB.Add(iStart + 4); - // - _previewNodesIB.Add(iStart + 4); - _previewNodesIB.Add(iStart + 7); - _previewNodesIB.Add(iStart + 4); - // - _previewNodesIB.Add(iStart + 5); - _previewNodesIB.Add(iStart + 6); - _previewNodesIB.Add(iStart + 5); - // - _previewNodesIB.Add(iStart + 6); - _previewNodesIB.Add(iStart + 7); - _previewNodesIB.Add(iStart + 6); - // - } - - // Nodes connections - for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++) - { - int parentIndex = nodes[nodeIndex].ParentIndex; - - if (parentIndex != -1) - { - if (nodesMask != null && (!nodesMask[nodeIndex] || !nodesMask[parentIndex])) - continue; - - var parentPos = pose[parentIndex].TranslationVector; - var bonePos = pose[nodeIndex].TranslationVector; - - var iStart = _previewNodesVB.Count; - _previewNodesVB.Add(parentPos); - _previewNodesVB.Add(bonePos); - _previewNodesIB.Add(iStart + 0); - _previewNodesIB.Add(iStart + 1); - _previewNodesIB.Add(iStart + 0); - } - } - - if (_previewNodesIB.Count > 0) - _previewNodesModel.LODs[0].Meshes[0].UpdateMesh(_previewNodesVB, _previewNodesIB); - else - _previewNodesActor.IsActive = false; - } - } } /// @@ -265,11 +214,7 @@ namespace FlaxEditor.Viewport.Previews /// public override void OnDestroy() { - // Ensure to cleanup created actor objects - _previewNodesActor.Model = null; Object.Destroy(ref _previewModel); - Object.Destroy(ref _previewNodesActor); - Object.Destroy(ref _previewNodesModel); NodesMask = null; base.OnDestroy(); diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index 791e98143..150990447 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; +using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Viewport.Cameras; using FlaxEngine; @@ -11,9 +13,13 @@ namespace FlaxEditor.Viewport.Previews /// Generic asset preview editor viewport base class. /// /// - public abstract class AssetPreview : EditorViewport + public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner { private ContextMenuButton _showDefaultSceneButton; + private IntPtr _debugDrawContext; + private bool _debugDrawEnable; + private bool _editorPrimitivesEnable; + private EditorPrimitives _editorPrimitives; /// /// The preview light. Allows to modify rendering settings. @@ -61,6 +67,61 @@ namespace FlaxEditor.Viewport.Previews } } + /// + /// Gets or sets a value indicating whether draw shapes. + /// + public bool ShowDebugDraw + { + get => _debugDrawEnable; + set + { + if (_debugDrawEnable == value) + return; + _debugDrawEnable = value; + if (_debugDrawContext == IntPtr.Zero) + { + _debugDrawContext = DebugDraw.AllocateContext(); + var view = Task.View; + view.Flags |= ViewFlags.DebugDraw; + Task.View = view; + } + if (value) + { + // Need to show editor primitives to show debug shapes + ShowEditorPrimitives = true; + } + } + } + + /// + /// Gets or sets a value indicating whether draw shapes and other editor primitives such as gizmos. + /// + public bool ShowEditorPrimitives + { + get => _editorPrimitivesEnable; + set + { + if (_editorPrimitivesEnable == value) + return; + _editorPrimitivesEnable = value; + if (_editorPrimitives == null) + { + _editorPrimitives = Object.New(); + _editorPrimitives.Viewport = this; + Task.CustomPostFx.Add(_editorPrimitives); + Task.PostRender += OnPostRender; + var view = Task.View; + view.Flags |= ViewFlags.CustomPostProcess; + Task.View = view; + } + } + } + + /// + /// Gets the editor primitives renderer. Valid only if is true. + /// + public EditorPrimitives EditorPrimitives => _editorPrimitives; + /// /// Initializes a new instance of the class. /// @@ -114,7 +175,7 @@ namespace FlaxEditor.Viewport.Previews SunPower = 9.0f }; // - SkyLight = new SkyLight() + SkyLight = new SkyLight { Mode = SkyLight.Modes.CustomTexture, Brightness = 2.1f, @@ -135,20 +196,58 @@ namespace FlaxEditor.Viewport.Previews Task.AddCustomActor(PostFxVolume); } + private void OnPostRender(GPUContext context, RenderContext renderContext) + { + if (renderContext.View.Mode != ViewMode.Default && _editorPrimitives && _editorPrimitives.CanRender) + { + // Render editor primitives, gizmo and debug shapes in debug view modes + // Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers + _editorPrimitives.Render(context, ref renderContext, renderContext.Task.Output, renderContext.Task.Output); + } + } + + /// + /// Called when drawing debug shapes with for this viewport. + /// + /// The GPU context. + /// The render context. + protected virtual void OnDebugDraw(GPUContext context, ref RenderContext renderContext) + { + } + /// public override bool HasLoadedAssets => base.HasLoadedAssets && Sky.HasContentLoaded && EnvProbe.Probe.IsLoaded && PostFxVolume.HasContentLoaded; /// public override void OnDestroy() { - // Ensure to cleanup created actor objects Object.Destroy(ref PreviewLight); Object.Destroy(ref EnvProbe); Object.Destroy(ref Sky); Object.Destroy(ref SkyLight); Object.Destroy(ref PostFxVolume); + Object.Destroy(ref _editorPrimitives); + if (_debugDrawContext != IntPtr.Zero) + { + DebugDraw.FreeContext(_debugDrawContext); + _debugDrawContext = IntPtr.Zero; + } base.OnDestroy(); } + + /// + public virtual void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) + { + // Draw selected objects debug shapes and visuals + if (ShowDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) + { + DebugDraw.SetContext(_debugDrawContext); + DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1)); + OnDebugDraw(context, ref renderContext); + DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true); + DebugDraw.SetContext(IntPtr.Zero); + } + } } } diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index 3f285ddcd..81c0c350f 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEngine; using Object = FlaxEngine.Object; @@ -12,7 +13,9 @@ namespace FlaxEditor.Viewport.Previews /// public class ModelPreview : AssetPreview { + private ContextMenuButton _showBoundsButton; private StaticModel _previewModel; + private bool _showBounds; /// /// Gets or sets the model asset to preview. @@ -28,6 +31,22 @@ namespace FlaxEditor.Viewport.Previews /// public StaticModel PreviewActor => _previewModel; + /// + /// Gets or sets a value indicating whether show animated model bounding box debug view. + /// + public bool ShowBounds + { + get => _showBounds; + set + { + _showBounds = value; + if (value) + ShowDebugDraw = true; + if (_showBoundsButton != null) + _showBoundsButton.Checked = value; + } + } + /// /// Gets or sets a value indicating whether scale the model to the normalized bounds. /// @@ -50,6 +69,9 @@ namespace FlaxEditor.Viewport.Previews if (useWidgets) { + // Show Bounds + _showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds); + // Preview LOD { var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD"); @@ -85,6 +107,18 @@ namespace FlaxEditor.Viewport.Previews } } + /// + protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext) + { + base.OnDebugDraw(context, ref renderContext); + + // Draw bounds + if (_showBounds) + { + DebugDraw.DrawWireBox(_previewModel.Box, Color.Violet.RGBMultiplied(0.8f), 0, false); + } + } + /// public override bool OnKeyDown(KeyboardKeys key) { diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index 634202061..603fc6c7c 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using FlaxEditor.Content; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Editors; @@ -13,6 +14,7 @@ using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; using FlaxEngine; using FlaxEngine.GUI; +using Object = FlaxEngine.Object; // ReSharper disable UnusedMember.Local // ReSharper disable UnusedMember.Global @@ -206,11 +208,18 @@ namespace FlaxEditor.Windows.Assets } } + [StructLayout(LayoutKind.Sequential)] + private struct AnimGraphDebugFlowInfo + { + public uint NodeId; + public int BoxId; + } + private FlaxObjectRefPickerControl _debugPicker; private NavigationBar _navigationBar; private PropertiesProxy _properties; private Tab _previewTab; - private readonly List _debugFlows = new List(); + private readonly List _debugFlows = new List(); /// /// Gets the animated model actor used for the animation preview. @@ -285,7 +294,7 @@ namespace FlaxEditor.Windows.Assets Parent = this }; - Editor.AnimGraphDebugFlow += OnDebugFlow; + Animations.DebugFlow += OnDebugFlow; } private void OnSurfaceContextChanged(VisjectSurfaceContext context) @@ -293,26 +302,27 @@ namespace FlaxEditor.Windows.Assets _surface.UpdateNavigationBar(_navigationBar, _toolstrip); } - private bool OnCheckValid(FlaxEngine.Object obj, ScriptType type) + private bool OnCheckValid(Object obj, ScriptType type) { return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset; } - private void OnDebugFlow(Editor.AnimGraphDebugFlowInfo flowInfo) + private void OnDebugFlow(Asset asset, Object obj, uint nodeId, uint boxId) { // Filter the flow if (_debugPicker.Value != null) { - if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Object) + if (asset != OriginalAsset || _debugPicker.Value != obj) return; } else { - if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Object) + if (asset != Asset || _preview.PreviewActor != obj) return; } // Register flow to show it in UI on a surface + var flowInfo = new AnimGraphDebugFlowInfo { NodeId = nodeId, BoxId = (int)boxId }; lock (_debugFlows) { _debugFlows.Add(flowInfo); @@ -457,7 +467,7 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { - Editor.AnimGraphDebugFlow -= OnDebugFlow; + Animations.DebugFlow -= OnDebugFlow; _properties = null; _navigationBar = null; diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index 63201ea9e..a4e843c42 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -135,7 +135,7 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { - _textPreview.Font = new FontReference(); + _textPreview.Font = null; base.UnlinkItem(); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index af568cd3f..6687bc548 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -41,6 +41,8 @@ namespace FlaxEditor.Windows.Assets var newRoot = Object.Find(ref newRootId); _window.Graph.MainActor = null; + _window.Viewport.Instance = null; + if (SceneGraphFactory.Nodes.TryGetValue(oldRootId, out var oldRootNode)) oldRootNode.Dispose(); if (SceneGraphFactory.Nodes.TryGetValue(newRootId, out var newRootNode)) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index afba74fbc..a37d88b61 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -163,7 +163,13 @@ namespace FlaxEditor.Windows.Assets private void Rename() { - ((ActorNode)Selection[0]).TreeNode.StartRenaming(); + var selection = Selection; + if (selection.Count != 0 && selection[0] is ActorNode actor) + { + if (selection.Count != 0) + Select(actor); + actor.TreeNode.StartRenaming(); + } } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs index be4bf3070..5b501d8cc 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.GUI; +using FlaxEditor.Viewport.Cameras; using FlaxEngine; namespace FlaxEditor.Windows.Assets diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index bec9bf7fe..3820e762c 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -10,7 +10,6 @@ using FlaxEditor.SceneGraph; using FlaxEditor.Viewport; using FlaxEngine; using FlaxEngine.GUI; -using Object = FlaxEngine.Object; namespace FlaxEditor.Windows.Assets { @@ -185,6 +184,8 @@ namespace FlaxEditor.Windows.Assets InputActions.Add(options => options.Paste, Paste); InputActions.Add(options => options.Duplicate, Duplicate); InputActions.Add(options => options.Delete, Delete); + InputActions.Add(options => options.Rename, Rename); + InputActions.Add(options => options.FocusSelection, _viewport.FocusSelection); } private void OnSearchBoxTextChanged() @@ -428,7 +429,6 @@ namespace FlaxEditor.Windows.Assets /// public override void OnLayoutDeserialize(XmlElement node) { - if (float.TryParse(node.GetAttribute("Split1"), out float value1)) _split1.SplitterValue = value1; diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 481c48f47..0ae8abe6d 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -4,7 +4,10 @@ using System.Xml; using FlaxEditor.Content; using FlaxEditor.Content.Import; using FlaxEditor.CustomEditors; +using FlaxEditor.CustomEditors.Dedicated; using FlaxEditor.CustomEditors.Editors; +using FlaxEditor.GUI; +using FlaxEditor.Scripting; using FlaxEditor.Viewport.Previews; using FlaxEngine; using FlaxEngine.GUI; @@ -18,47 +21,57 @@ namespace FlaxEditor.Windows.Assets /// public sealed class TextureWindow : AssetEditorWindowBase { + private sealed class ProxyEditor : GenericEditor + { + public override void Initialize(LayoutElementsContainer layout) + { + var window = ((PropertiesProxy)Values[0])._window; + var texture = window?.Asset; + if (texture == null || !texture.IsLoaded) + { + layout.Label("Loading...", TextAlignment.Center); + return; + } + + // Texture info + var general = layout.Group("General"); + general.Label("Format: " + texture.Format); + general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)); + general.Label("Mip levels: " + texture.MipLevels); + general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); + + // Texture properties + var properties = layout.Group("Properties"); + var textureGroup = new CustomValueContainer(new ScriptType(typeof(int)), texture.TextureGroup, + (instance, index) => texture.TextureGroup, + (instance, index, value) => + { + texture.TextureGroup = (int)value; + window.MarkAsEdited(); + }); + properties.Property("Texture Group", textureGroup, new TextureGroupEditor(), "The texture group used by this texture."); + + // Import settings + base.Initialize(layout); + + // Reimport + layout.Space(10); + var reimportButton = layout.Button("Reimport"); + reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); + } + } + /// /// The texture properties proxy object. /// [CustomEditor(typeof(ProxyEditor))] private sealed class PropertiesProxy { - private TextureWindow _window; + internal TextureWindow _window; [EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)] public TextureImportSettings ImportSettings = new TextureImportSettings(); - public sealed class ProxyEditor : GenericEditor - { - public override void Initialize(LayoutElementsContainer layout) - { - var window = ((PropertiesProxy)Values[0])._window; - if (window == null) - { - layout.Label("Loading...", TextAlignment.Center); - return; - } - - // Texture properties - { - var texture = window.Asset; - - var group = layout.Group("General"); - group.Label("Format: " + texture.Format); - group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)); - group.Label("Mip levels: " + texture.MipLevels); - group.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); - } - - base.Initialize(layout); - - layout.Space(10); - var reimportButton = layout.Button("Reimport"); - reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); - } - } - /// /// Gathers parameters from the specified texture. /// @@ -110,7 +123,7 @@ namespace FlaxEditor.Windows.Assets private readonly SplitPanel _split; private readonly TexturePreview _preview; private readonly CustomEditorPresenter _propertiesEditor; - + private readonly ToolStripButton _saveButton; private readonly PropertiesProxy _properties; private bool _isWaitingForLoad; @@ -140,6 +153,7 @@ namespace FlaxEditor.Windows.Assets _propertiesEditor.Select(_properties); // Toolstrip + _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport"); _toolstrip.AddSeparator(); _toolstrip.AddButton(Editor.Icons.CenterView64, _preview.CenterView).LinkTooltip("Center view"); @@ -173,6 +187,14 @@ namespace FlaxEditor.Windows.Assets _isWaitingForLoad = true; } + /// + protected override void UpdateToolstrip() + { + _saveButton.Enabled = IsEdited; + + base.UpdateToolstrip(); + } + /// protected override void OnClose() { @@ -182,6 +204,21 @@ namespace FlaxEditor.Windows.Assets base.OnClose(); } + /// + public override void Save() + { + if (!IsEdited) + return; + + if (Asset.Save()) + { + Editor.LogError("Cannot save asset."); + return; + } + + ClearEditedFlag(); + } + /// public override void Update(float deltaTime) { diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index b9ddafa87..e19fe54ca 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -167,6 +167,8 @@ namespace FlaxEditor.Windows.Assets /// public override DisplayStyle Style => DisplayStyle.InlineIntoParent; + private Control _overrideButton; + /// public override void Initialize(LayoutElementsContainer layout) { @@ -178,6 +180,21 @@ namespace FlaxEditor.Windows.Assets var group = layout.Group("Functions"); var nodes = window.VisjectSurface.Nodes; + var grid = group.CustomContainer(); + var gridControl = grid.CustomControl; + gridControl.ClipChildren = false; + gridControl.Height = Button.DefaultHeight; + gridControl.SlotsHorizontally = 2; + gridControl.SlotsVertically = 1; + + var addOverride = grid.Button("Add Override"); + addOverride.Button.Clicked += OnOverrideMethodClicked; + // TODO: Add sender arg to button clicked action? + _overrideButton = addOverride.Control; + + var addFuncction = grid.Button("Add Function"); + addFuncction.Button.Clicked += OnAddNewFunctionClicked; + // List of functions in the graph for (int i = 0; i < nodes.Count; i++) { @@ -192,60 +209,17 @@ namespace FlaxEditor.Windows.Assets } else if (node is Surface.Archetypes.Function.MethodOverrideNode overrideNode) { - var label = group.ClickableLabel(overrideNode.Title + " (override)").CustomControl; + var label = group.ClickableLabel($"{overrideNode.Title} (override)").CustomControl; label.TextColorHighlighted = Color.FromBgra(0xFFA0A0A0); label.TooltipText = overrideNode.TooltipText; label.DoubleClick += () => ((VisualScriptWindow)Values[0]).Surface.FocusNode(overrideNode); label.RightClick += () => ShowContextMenu(overrideNode, label); } } - - // New function button - const float groupPanelButtonSize = 14; - var addNewFunction = new Image - { - TooltipText = "Add new function", - AutoFocus = true, - AnchorPreset = AnchorPresets.TopRight, - Parent = group.Panel, - Bounds = new Rectangle(group.Panel.Width - groupPanelButtonSize, 0, groupPanelButtonSize, groupPanelButtonSize), - IsScrollable = false, - Color = FlaxEngine.GUI.Style.Current.ForegroundGrey, - Margin = new Margin(1), - Brush = new SpriteBrush(Editor.Instance.Icons.Add64), - }; - addNewFunction.Clicked += OnAddNewFunctionClicked; - - // Override method button - var overrideMethod = new Image - { - TooltipText = "Override method", - AutoFocus = true, - AnchorPreset = AnchorPresets.TopRight, - Parent = group.Panel, - Bounds = new Rectangle(group.Panel.Width - groupPanelButtonSize * 2, 0, groupPanelButtonSize, groupPanelButtonSize), - IsScrollable = false, - Color = FlaxEngine.GUI.Style.Current.ForegroundGrey, - Margin = new Margin(1), - Brush = new SpriteBrush(Editor.Instance.Icons.Import64), - }; - overrideMethod.Clicked += OnOverrideMethodClicked; - } - - private void OnAddNewFunctionClicked(Image image, MouseButton button) - { - if (button != MouseButton.Left) - return; - var surface = ((VisualScriptWindow)Values[0]).Surface; - 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)); - surface.Select(node); } private void ShowContextMenu(SurfaceNode node, ClickableLabel label) { - ((VisualScriptWindow)Values[0]).Surface.FocusNode(node); var cm = new ContextMenu(); cm.AddButton("Show", () => ((VisualScriptWindow)Values[0]).Surface.FocusNode(node)).Icon = Editor.Instance.Icons.Search12; cm.AddButton("Delete", () => ((VisualScriptWindow)Values[0]).Surface.Delete(node)).Icon = Editor.Instance.Icons.Cross12; @@ -253,11 +227,17 @@ namespace FlaxEditor.Windows.Assets cm.Show(label, new Vector2(0, label.Height)); } - private void OnOverrideMethodClicked(Image image, MouseButton button) + private void OnAddNewFunctionClicked() { - if (button != MouseButton.Left) - return; + var surface = ((VisualScriptWindow)Values[0]).Surface; + 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)); + surface.Select(node); + } + private void OnOverrideMethodClicked() + { var cm = new ContextMenu(); var window = (VisualScriptWindow)Values[0]; var scriptMeta = window.Asset.Meta; @@ -310,7 +290,7 @@ namespace FlaxEditor.Windows.Assets { cm.AddButton("Nothing to override"); } - cm.Show(image, new Vector2(0, image.Height)); + cm.Show(_overrideButton, new Vector2(0, _overrideButton.Height)); } } diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 079df66a5..cded93cc0 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -65,6 +65,13 @@ namespace FlaxEditor.Windows b = cm.AddButton("Open", () => Open(item)); b.Enabled = proxy != null || isFolder; + if (_view.SelectedCount > 1) + b = cm.AddButton("Open (all selected)", () => + { + foreach (var e in _view.Selection) + Open(e); + }); + cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path))); if (item.HasDefaultThumbnail == false) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 88a976d2b..8a4db3789 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -66,7 +66,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Content"; - Icon = editor.Icons.Folder64; + Icon = editor.Icons.Folder32; // Content database events editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index bc80a0ba7..dd9de3edc 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -312,9 +312,9 @@ namespace FlaxEditor.Windows _clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode"); _pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error"); toolstrip.AddSeparator(); - _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error64, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages"); - _groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning64, () => UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides warning messages"); - _groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info64, () => UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides info messages"); + _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages"); + _groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides warning messages"); + _groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides info messages"); UpdateCount(); // Split panel @@ -387,10 +387,10 @@ namespace FlaxEditor.Windows switch (_timestampsFormats) { case InterfaceOptions.TimestampsFormats.Utc: - desc.Title = string.Format("[{0}] ", DateTime.UtcNow) + desc.Title; + desc.Title = $"[{DateTime.UtcNow}] {desc.Title}"; break; case InterfaceOptions.TimestampsFormats.LocalTime: - desc.Title = string.Format("[{0}] ", DateTime.Now) + desc.Title; + desc.Title = $"[{DateTime.Now}] {desc.Title}"; break; case InterfaceOptions.TimestampsFormats.TimeSinceStartup: desc.Title = string.Format("[{0:g}] ", TimeSpan.FromSeconds(Time.TimeSinceStartup)) + desc.Title; @@ -480,15 +480,15 @@ namespace FlaxEditor.Windows { if (_iconType == LogType.Warning) { - Icon = IconWarning; + Icon = Editor.Icons.Warning32; } else if (_iconType == LogType.Error) { - Icon = IconError; + Icon = Editor.Icons.Error32; } else { - Icon = IconInfo; + Icon = Editor.Icons.Info32; } } @@ -587,13 +587,10 @@ namespace FlaxEditor.Windows /// public override void Update(float deltaTime) { - // Check pending events (in a thread safe manner) lock (_locker) { if (_pendingEntries.Count > 0) { - // TODO: we should provide max limit for entries count and remove if too many - // Check if user want's to scroll view by var (or is viewing earlier entry) var panelScroll = (Panel)_entriesPanel.Parent; bool scrollView = (panelScroll.VScrollBar.Maximum - panelScroll.VScrollBar.TargetValue) < LogEntry.DefaultHeight * 1.5f; @@ -601,14 +598,24 @@ namespace FlaxEditor.Windows // Add pending entries LogEntry newEntry = null; bool anyVisible = false; + _entriesPanel.IsLayoutLocked = true; + var spacing = _entriesPanel.Spacing; + var margin = _entriesPanel.Margin; + var offset = _entriesPanel.Offset; + var width = _entriesPanel.Width - margin.Width; + var top = _entriesPanel.Children.Count != 0 ? _entriesPanel.Children[_entriesPanel.Children.Count - 1].Bottom + spacing : margin.Top; for (int i = 0; i < _pendingEntries.Count; i++) { newEntry = _pendingEntries[i]; newEntry.Visible = _groupButtons[(int)newEntry.Group].Checked; anyVisible |= newEntry.Visible; newEntry.Parent = _entriesPanel; + newEntry.Bounds = new Rectangle(margin.Left + offset.X, top + offset.Y, width, newEntry.Height); + top = newEntry.Bottom + spacing; _logCountPerGroup[(int)newEntry.Group]++; } + _entriesPanel.Height = top + margin.Bottom; + _entriesPanel.IsLayoutLocked = false; _pendingEntries.Clear(); UpdateCount(); Assert.IsNotNull(newEntry); diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs index 4025f68ab..eb9dd8034 100644 --- a/Source/Editor/Windows/EditGameWindow.cs +++ b/Source/Editor/Windows/EditGameWindow.cs @@ -6,9 +6,7 @@ using System.Linq; using System.Xml; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; -using FlaxEditor.States; using FlaxEditor.Viewport; -using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Widgets; using FlaxEngine; using FlaxEngine.GUI; @@ -238,20 +236,6 @@ namespace FlaxEditor.Windows } } - /// - /// Moves the viewport to visualize selected actors. - /// - public void ShowSelectedActors() - { - if (Viewport.UseOrthographicProjection) - { - var orient = Viewport.ViewOrientation; - ((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents, ref orient); - } - else - ((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents); - } - /// /// Updates the camera previews. /// diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 33e5a3ff4..3880a4391 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using System.ComponentModel; using FlaxEditor.CustomEditors; using FlaxEngine; @@ -9,7 +10,7 @@ using FlaxEngine.Json; namespace FlaxEditor.Windows { /// - /// Window used to show and edit current graphics rendering settings via . + /// Window used to show and edit current graphics rendering settings via and . /// /// public class GraphicsQualityWindow : EditorWindow @@ -86,6 +87,18 @@ namespace FlaxEditor.Windows get => Graphics.AllowCSMBlending; set => Graphics.AllowCSMBlending = value; } + + [NoSerialize] + [EditorOrder(2000), EditorDisplay("Textures", EditorDisplayAttribute.InlineStyle), Tooltip("Textures streaming configuration.")] + public TextureGroup[] TextureGroups + { + get => Streaming.TextureGroups; + set + { + Streaming.TextureGroups = value; + Streaming.RequestStreamingUpdate(); + } + } } /// diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index d33325fa3..18317216f 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; @@ -24,6 +25,17 @@ namespace FlaxEngine } } } + + internal unsafe bool NameStartsWith(string prefix) + { + fixed (char* name = &Name0) + { + fixed (char* p = prefix) + { + return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0; + } + } + } } } } @@ -39,7 +51,10 @@ namespace FlaxEditor.Windows.Profiler private readonly SingleChart _mainChart; private readonly Timeline _timeline; private readonly Table _table; - private readonly SamplesBuffer _events = new SamplesBuffer(); + private SamplesBuffer _events; + private List _timelineLabelsCache; + private List _timelineEventsCache; + private List _tableRowsCache; private bool _showOnlyLastUpdateEvents; public CPU() @@ -152,7 +167,7 @@ namespace FlaxEditor.Windows.Profiler public override void Clear() { _mainChart.Clear(); - _events.Clear(); + _events?.Clear(); } /// @@ -162,15 +177,9 @@ namespace FlaxEditor.Windows.Profiler // Gather CPU events var events = sharedData.GetEventsCPU(); + if (_events == null) + _events = new SamplesBuffer(); _events.Add(events); - - // Update timeline if using the last frame - if (_mainChart.SelectedSampleIndex == -1) - { - var viewRange = GetEventsViewRange(); - UpdateTimeline(ref viewRange); - UpdateTable(ref viewRange); - } } /// @@ -179,11 +188,31 @@ namespace FlaxEditor.Windows.Profiler _showOnlyLastUpdateEvents = showOnlyLastUpdateEvents; _mainChart.SelectedSampleIndex = selectedFrame; + if (_events == null) + return; + if (_timelineLabelsCache == null) + _timelineLabelsCache = new List(); + if (_timelineEventsCache == null) + _timelineEventsCache = new List(); + if (_tableRowsCache == null) + _tableRowsCache = new List(); + var viewRange = GetEventsViewRange(); UpdateTimeline(ref viewRange); UpdateTable(ref viewRange); } + /// + public override void OnDestroy() + { + Clear(); + _timelineLabelsCache?.Clear(); + _timelineEventsCache?.Clear(); + _tableRowsCache?.Clear(); + + base.OnDestroy(); + } + private struct ViewRange { public double Start; @@ -212,7 +241,7 @@ namespace FlaxEditor.Windows.Profiler if (_showOnlyLastUpdateEvents) { // Find root event named 'Update' and use it as a view range - if (_events.Count != 0) + if (_events != null && _events.Count != 0) { var data = _events.Get(_mainChart.SelectedSampleIndex); if (data != null) @@ -245,17 +274,26 @@ namespace FlaxEditor.Windows.Profiler ref ProfilerCPU.Event e = ref events[index]; double length = e.End - e.Start; + if (length <= 0.0) + return; double scale = 100.0; float x = (float)((e.Start - startTime) * scale); float width = (float)(length * scale); - string name = e.Name.Replace("::", "."); - - var control = new Timeline.Event(x + xOffset, e.Depth + depthOffset, width) + Timeline.Event control; + if (_timelineEventsCache.Count != 0) { - Name = name, - TooltipText = string.Format("{0}, {1} ms", name, ((int)(length * 1000.0) / 1000.0f)), - Parent = parent, - }; + var last = _timelineEventsCache.Count - 1; + control = _timelineEventsCache[last]; + _timelineEventsCache.RemoveAt(last); + } + else + { + control = new Timeline.Event(); + } + control.Bounds = new Rectangle(x + xOffset, (e.Depth + depthOffset) * Timeline.Event.DefaultHeight, width, Timeline.Event.DefaultHeight - 1); + control.Name = e.Name.Replace("::", "."); + control.TooltipText = string.Format("{0}, {1} ms", control.Name, ((int)(length * 1000.0) / 1000.0f)); + control.Parent = parent; // Spawn sub events int childrenDepth = e.Depth + 1; @@ -264,7 +302,6 @@ namespace FlaxEditor.Windows.Profiler while (++index < events.Length) { int subDepth = events[index].Depth; - if (subDepth <= e.Depth) break; if (subDepth == childrenDepth) @@ -279,10 +316,26 @@ namespace FlaxEditor.Windows.Profiler { var container = _timeline.EventsContainer; - // Clear previous events - container.DisposeChildren(); - - container.LockChildrenRecursive(); + container.IsLayoutLocked = true; + int idx = 0; + while (container.Children.Count > idx) + { + var child = container.Children[idx]; + if (child is Timeline.Event e) + { + _timelineEventsCache.Add(e); + child.Parent = null; + } + else if (child is Timeline.TrackLabel l) + { + _timelineLabelsCache.Add(l); + child.Parent = null; + } + else + { + idx++; + } + } _timeline.Height = UpdateTimelineInner(ref viewRange); @@ -323,11 +376,8 @@ namespace FlaxEditor.Windows.Profiler for (int j = 0; j < events.Length; j++) { var e = events[j]; - - // Reject events outside the view range if (viewRange.SkipEvent(ref e)) continue; - maxDepth = Mathf.Max(maxDepth, e.Depth); } @@ -337,13 +387,21 @@ namespace FlaxEditor.Windows.Profiler // Add thread label float xOffset = 90; - var label = new Timeline.TrackLabel + Timeline.TrackLabel trackLabel; + if (_timelineLabelsCache.Count != 0) { - Bounds = new Rectangle(0, depthOffset * Timeline.Event.DefaultHeight, xOffset, (maxDepth + 2) * Timeline.Event.DefaultHeight), - Name = data[i].Name, - BackgroundColor = Style.Current.Background * 1.1f, - Parent = container, - }; + var last = _timelineLabelsCache.Count - 1; + trackLabel = _timelineLabelsCache[last]; + _timelineLabelsCache.RemoveAt(last); + } + else + { + trackLabel = new Timeline.TrackLabel(); + } + trackLabel.Bounds = new Rectangle(0, depthOffset * Timeline.Event.DefaultHeight, xOffset, (maxDepth + 2) * Timeline.Event.DefaultHeight); + trackLabel.Name = data[i].Name; + trackLabel.BackgroundColor = Style.Current.Background * 1.1f; + trackLabel.Parent = container; // Add events for (int j = 0; j < events.Length; j++) @@ -351,10 +409,8 @@ namespace FlaxEditor.Windows.Profiler var e = events[j]; if (e.Depth == 0) { - // Reject events outside the view range if (viewRange.SkipEvent(ref e)) continue; - AddEvent(startTime, maxDepth, xOffset, depthOffset, j, events, container); } } @@ -367,9 +423,21 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable(ref ViewRange viewRange) { - _table.DisposeChildren(); - - _table.LockChildrenRecursive(); + _table.IsLayoutLocked = true; + int idx = 0; + while (_table.Children.Count > idx) + { + var child = _table.Children[idx]; + if (child is Row row) + { + _tableRowsCache.Add(row); + child.Parent = null; + } + else + { + idx++; + } + } UpdateTableInner(ref viewRange); @@ -399,9 +467,7 @@ namespace FlaxEditor.Windows.Profiler { var e = events[i]; var time = Math.Max(e.End - e.Start, MinEventTimeMs); - - // Reject events outside the view range - if (viewRange.SkipEvent(ref e)) + if (e.End <= 0.0f || viewRange.SkipEvent(ref e)) continue; // Count sub-events time @@ -410,7 +476,7 @@ namespace FlaxEditor.Windows.Profiler for (int k = i + 1; k < events.Length; k++) { var sub = events[k]; - if (sub.Depth == e.Depth + 1) + if (sub.Depth == e.Depth + 1 && e.End > 0.0f) { subEventsTimeTotal += Math.Max(sub.End - sub.Start, MinEventTimeMs); } @@ -418,42 +484,49 @@ namespace FlaxEditor.Windows.Profiler { break; } - subEventsMemoryTotal += sub.ManagedMemoryAllocation + e.NativeMemoryAllocation; } string name = e.Name.Replace("::", "."); - var row = new Row + Row row; + if (_tableRowsCache.Count != 0) { - Values = new object[] + var last = _tableRowsCache.Count - 1; + row = _tableRowsCache[last]; + _tableRowsCache.RemoveAt(last); + } + else + { + row = new Row { - // Event - name, + Values = new object[6], + }; + } + { + // Event + row.Values[0] = name; - // Total (%) - (int)(time / totalTimeMs * 1000.0f) / 10.0f, + // Total (%) + row.Values[1] = (int)(time / totalTimeMs * 1000.0f) / 10.0f; - // Self (%) - (int)((time - subEventsTimeTotal) / time * 1000.0f) / 10.0f, + // Self (%) + row.Values[2] = (int)((time - subEventsTimeTotal) / time * 1000.0f) / 10.0f; - // Time ms - (float)((time * 10000.0f) / 10000.0f), + // Time ms + row.Values[3] = (float)((time * 10000.0f) / 10000.0f); - // Self ms - (float)(((time - subEventsTimeTotal) * 10000.0f) / 10000.0f), + // Self ms + row.Values[4] = (float)(((time - subEventsTimeTotal) * 10000.0f) / 10000.0f); - // Memory Alloc - subEventsMemoryTotal, - }, - Depth = e.Depth, - Width = _table.Width, - Parent = _table, - }; - - if (i % 2 == 0) - row.BackgroundColor = rowColor2; - row.Visible = e.Depth < 3; + // Memory Alloc + row.Values[5] = subEventsMemoryTotal; + } + row.Depth = e.Depth; + row.Width = _table.Width; + row.Visible = e.Depth < 2; + row.BackgroundColor = i % 2 == 0 ? rowColor2 : Color.Transparent; + row.Parent = _table; } } } diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index cd1c4cfed..cc28bb545 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Collections.Generic; using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; @@ -16,7 +17,9 @@ namespace FlaxEditor.Windows.Profiler private readonly SingleChart _drawTimeGPU; private readonly Timeline _timeline; private readonly Table _table; - private readonly SamplesBuffer _events = new SamplesBuffer(); + private SamplesBuffer _events; + private List _timelineEventsCache; + private List _tableRowsCache; public GPU() : base("GPU") @@ -118,7 +121,7 @@ namespace FlaxEditor.Windows.Profiler { _drawTimeCPU.Clear(); _drawTimeGPU.Clear(); - _events.Clear(); + _events?.Clear(); } /// @@ -126,18 +129,13 @@ namespace FlaxEditor.Windows.Profiler { // Gather GPU events var data = sharedData.GetEventsGPU(); + if (_events == null) + _events = new SamplesBuffer(); _events.Add(data); // Peek draw time _drawTimeCPU.AddSample(sharedData.Stats.DrawCPUTimeMs); _drawTimeGPU.AddSample(sharedData.Stats.DrawGPUTimeMs); - - // Update timeline if using the last frame - if (_drawTimeCPU.SelectedSampleIndex == -1) - { - UpdateTimeline(); - UpdateTable(); - } } /// @@ -145,10 +143,28 @@ namespace FlaxEditor.Windows.Profiler { _drawTimeCPU.SelectedSampleIndex = selectedFrame; _drawTimeGPU.SelectedSampleIndex = selectedFrame; + + if (_events == null) + return; + if (_timelineEventsCache == null) + _timelineEventsCache = new List(); + if (_tableRowsCache == null) + _tableRowsCache = new List(); + UpdateTimeline(); UpdateTable(); } + /// + public override void OnDestroy() + { + Clear(); + _timelineEventsCache?.Clear(); + _tableRowsCache?.Clear(); + + base.OnDestroy(); + } + private float AddEvent(float x, int maxDepth, int index, ProfilerGPU.Event[] events, ContainerControl parent) { ref ProfilerGPU.Event e = ref events[index]; @@ -157,12 +173,21 @@ namespace FlaxEditor.Windows.Profiler float width = (float)(e.Time * scale); string name = new string(e.Name); - new Timeline.Event(x, e.Depth, width) + Timeline.Event control; + if (_timelineEventsCache.Count != 0) { - Name = name, - TooltipText = string.Format("{0}, {1} ms", name, ((int)(e.Time * 10000.0) / 10000.0f)), - Parent = parent, - }; + var last = _timelineEventsCache.Count - 1; + control = _timelineEventsCache[last]; + _timelineEventsCache.RemoveAt(last); + } + else + { + control = new Timeline.Event(); + } + control.Bounds = new Rectangle(x, e.Depth * Timeline.Event.DefaultHeight, width, Timeline.Event.DefaultHeight - 1); + control.Name = name; + control.TooltipText = string.Format("{0}, {1} ms", name, ((int)(e.Time * 10000.0) / 10000.0f)); + control.Parent = parent; // Spawn sub events int childrenDepth = e.Depth + 1; @@ -208,8 +233,21 @@ namespace FlaxEditor.Windows.Profiler { var container = _timeline.EventsContainer; - // Clear previous events - container.DisposeChildren(); + container.IsLayoutLocked = true; + int idx = 0; + while (container.Children.Count > idx) + { + var child = container.Children[idx]; + if (child is Timeline.Event e) + { + _timelineEventsCache.Add(e); + child.Parent = null; + } + else + { + idx++; + } + } container.LockChildrenRecursive(); @@ -252,8 +290,21 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable() { - _table.DisposeChildren(); - + _table.IsLayoutLocked = true; + int idx = 0; + while (_table.Children.Count > idx) + { + var child = _table.Children[idx]; + if (child is Row row) + { + _tableRowsCache.Add(row); + child.Parent = null; + } + else + { + idx++; + } + } _table.LockChildrenRecursive(); UpdateTableInner(); @@ -279,36 +330,44 @@ namespace FlaxEditor.Windows.Profiler var e = data[i]; string name = new string(e.Name); - var row = new Row + Row row; + if (_tableRowsCache.Count != 0) { - Values = new object[] + var last = _tableRowsCache.Count - 1; + row = _tableRowsCache[last]; + _tableRowsCache.RemoveAt(last); + } + else + { + row = new Row { - // Event - name, + Values = new object[6], + }; + } + { + // Event + row.Values[0] = name; - // Total (%) - (int)(e.Time / totalTimeMs * 1000.0f) / 10.0f, + // Total (%) + row.Values[1] = (int)(e.Time / totalTimeMs * 1000.0f) / 10.0f; - // GPU ms - (e.Time * 10000.0f) / 10000.0f, + // GPU ms + row.Values[2] = (e.Time * 10000.0f) / 10000.0f; - // Draw Calls - e.Stats.DrawCalls, + // Draw Calls + row.Values[3] = e.Stats.DrawCalls; - // Triangles - e.Stats.Triangles, + // Triangles + row.Values[4] = e.Stats.Triangles; - // Vertices - e.Stats.Vertices, - }, - Depth = e.Depth, - Width = _table.Width, - Parent = _table, - }; - - if (i % 2 == 0) - row.BackgroundColor = rowColor2; + // Vertices + row.Values[5] = e.Stats.Vertices; + } + row.Depth = e.Depth; + row.Width = _table.Width; row.Visible = e.Depth < 3; + row.BackgroundColor = i % 2 == 0 ? rowColor2 : Color.Transparent; + row.Parent = _table; } } } diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs index 4cc48ce73..3105996f7 100644 --- a/Source/Editor/Windows/Profiler/Memory.cs +++ b/Source/Editor/Windows/Profiler/Memory.cs @@ -72,6 +72,8 @@ namespace FlaxEditor.Windows.Profiler for (int j = 0; j < ee.Length; j++) { ref var e = ref ee[j]; + if (e.NameStartsWith("ProfilerWindow")) + continue; nativeMemoryAllocation += e.NativeMemoryAllocation; managedMemoryAllocation += e.ManagedMemoryAllocation; } diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs index 578cfaebf..fe13b65ef 100644 --- a/Source/Editor/Windows/Profiler/ProfilerMode.cs +++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs @@ -65,7 +65,7 @@ namespace FlaxEditor.Windows.Profiler /// /// The maximum amount of samples to collect. /// - public const int MaxSamples = 60 * 5; + public const int MaxSamples = 60 * 10; /// /// The minimum event time in ms. diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index 27a718077..d70ba7681 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Windows.Profiler } /// - /// Gets or sets the index of the selected frame to view (note: some view modes may not use it). + /// Gets or sets the index of the selected frame to view (note: some view modes may not use it). -1 for the last frame. /// public int ViewFrameIndex { @@ -188,7 +188,7 @@ namespace FlaxEditor.Windows.Profiler for (int i = 0; i < _tabs.ChildrenCount; i++) { if (_tabs.Children[i] is ProfilerMode mode) - mode.UpdateView(ViewFrameIndex, _showOnlyLastUpdateEvents); + mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents); } UpdateButtons(); @@ -208,6 +208,10 @@ namespace FlaxEditor.Windows.Profiler if (_tabs.Children[i] is ProfilerMode mode) mode.Update(ref sharedData); } + { + if (_tabs.SelectedTab is ProfilerMode mode) + mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents); + } sharedData.End(); _framesCount = Mathf.Min(_framesCount + 1, ProfilerMode.MaxSamples); diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 53f4c9987..8e7ab145d 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -40,7 +40,8 @@ namespace FlaxEditor.Windows.Profiler }; private Color _color; - private float _nameLength; + private string _name; + private float _nameLength = -1; /// /// The default height of the event. @@ -50,18 +51,14 @@ namespace FlaxEditor.Windows.Profiler /// /// Gets or sets the event name. /// - public string Name { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The x position. - /// The timeline row index (event depth). - /// The width. - public Event(float x, int depth, float width) - : base(x, depth * DefaultHeight, width, DefaultHeight - 1) + public string Name { - _nameLength = -1; + get => _name; + set + { + _name = value; + _nameLength = -1; + } } /// @@ -88,12 +85,12 @@ namespace FlaxEditor.Windows.Profiler Render2D.DrawRectangle(bounds, color * 0.5f); if (_nameLength < 0 && style.FontMedium) - _nameLength = style.FontMedium.MeasureText(Name).X; + _nameLength = style.FontMedium.MeasureText(_name).X; if (_nameLength < bounds.Width + 4) { Render2D.PushClip(bounds); - Render2D.DrawText(style.FontMedium, Name, bounds, Color.White, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, _name, bounds, Color.White, TextAlignment.Center, TextAlignment.Center); Render2D.PopClip(); } } diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs deleted file mode 100644 index 04d5a3ced..000000000 --- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; -using System.Collections.Generic; -using FlaxEngine; - -namespace FlaxEditor.Windows -{ - public partial class SceneTreeWindow - { - /// - /// The spawnable actors group. - /// - public struct ActorsGroup - { - /// - /// The group name. - /// - public string Name; - - /// - /// The types to spawn (name and type). - /// - public KeyValuePair[] Types; - } - - /// - /// The Spawnable actors (groups with single entry are inlined without a child menu) - /// - public static readonly ActorsGroup[] SpawnActorsGroups = - { - new ActorsGroup - { - Types = new[] { new KeyValuePair("Actor", typeof(EmptyActor)) } - }, - new ActorsGroup - { - Types = new[] { new KeyValuePair("Model", typeof(StaticModel)) } - }, - new ActorsGroup - { - Types = new[] { new KeyValuePair("Camera", typeof(Camera)) } - }, - new ActorsGroup - { - Name = "Lights", - Types = new[] - { - new KeyValuePair("Directional Light", typeof(DirectionalLight)), - new KeyValuePair("Point Light", typeof(PointLight)), - new KeyValuePair("Spot Light", typeof(SpotLight)), - new KeyValuePair("Sky Light", typeof(SkyLight)), - } - }, - new ActorsGroup - { - Name = "Visuals", - Types = new[] - { - new KeyValuePair("Environment Probe", typeof(EnvironmentProbe)), - new KeyValuePair("Sky", typeof(Sky)), - new KeyValuePair("Skybox", typeof(Skybox)), - new KeyValuePair("Exponential Height Fog", typeof(ExponentialHeightFog)), - new KeyValuePair("PostFx Volume", typeof(PostFxVolume)), - new KeyValuePair("Decal", typeof(Decal)), - new KeyValuePair("Particle Effect", typeof(ParticleEffect)), - } - }, - new ActorsGroup - { - Name = "Physics", - Types = new[] - { - new KeyValuePair("Rigid Body", typeof(RigidBody)), - new KeyValuePair("Character Controller", typeof(CharacterController)), - new KeyValuePair("Box Collider", typeof(BoxCollider)), - new KeyValuePair("Sphere Collider", typeof(SphereCollider)), - new KeyValuePair("Capsule Collider", typeof(CapsuleCollider)), - new KeyValuePair("Mesh Collider", typeof(MeshCollider)), - new KeyValuePair("Fixed Joint", typeof(FixedJoint)), - new KeyValuePair("Distance Joint", typeof(DistanceJoint)), - new KeyValuePair("Slider Joint", typeof(SliderJoint)), - new KeyValuePair("Spherical Joint", typeof(SphericalJoint)), - new KeyValuePair("Hinge Joint", typeof(HingeJoint)), - new KeyValuePair("D6 Joint", typeof(D6Joint)), - } - }, - new ActorsGroup - { - Name = "Other", - Types = new[] - { - new KeyValuePair("Animated Model", typeof(AnimatedModel)), - new KeyValuePair("Bone Socket", typeof(BoneSocket)), - new KeyValuePair("CSG Box Brush", typeof(BoxBrush)), - new KeyValuePair("Audio Source", typeof(AudioSource)), - new KeyValuePair("Audio Listener", typeof(AudioListener)), - new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)), - 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 - { - Name = "GUI", - Types = new[] - { - new KeyValuePair("UI Control", typeof(UIControl)), - new KeyValuePair("UI Canvas", typeof(UICanvas)), - new KeyValuePair("Text Render", typeof(TextRender)), - new KeyValuePair("Sprite Render", typeof(SpriteRender)), - } - }, - }; - } -} diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index e33d658e6..abe67190e 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -18,6 +18,113 @@ namespace FlaxEditor.Windows /// public partial class SceneTreeWindow : SceneEditorWindow { + /// + /// The spawnable actors group. + /// + public struct ActorsGroup + { + /// + /// The group name. + /// + public string Name; + + /// + /// The types to spawn (name and type). + /// + public KeyValuePair[] Types; + } + + /// + /// The Spawnable actors (groups with single entry are inlined without a child menu) + /// + public static readonly ActorsGroup[] SpawnActorsGroups = + { + new ActorsGroup + { + Types = new[] { new KeyValuePair("Actor", typeof(EmptyActor)) } + }, + new ActorsGroup + { + Types = new[] { new KeyValuePair("Model", typeof(StaticModel)) } + }, + new ActorsGroup + { + Types = new[] { new KeyValuePair("Camera", typeof(Camera)) } + }, + new ActorsGroup + { + Name = "Lights", + Types = new[] + { + new KeyValuePair("Directional Light", typeof(DirectionalLight)), + new KeyValuePair("Point Light", typeof(PointLight)), + new KeyValuePair("Spot Light", typeof(SpotLight)), + new KeyValuePair("Sky Light", typeof(SkyLight)), + } + }, + new ActorsGroup + { + Name = "Visuals", + Types = new[] + { + new KeyValuePair("Environment Probe", typeof(EnvironmentProbe)), + new KeyValuePair("Sky", typeof(Sky)), + new KeyValuePair("Skybox", typeof(Skybox)), + new KeyValuePair("Exponential Height Fog", typeof(ExponentialHeightFog)), + new KeyValuePair("PostFx Volume", typeof(PostFxVolume)), + new KeyValuePair("Decal", typeof(Decal)), + new KeyValuePair("Particle Effect", typeof(ParticleEffect)), + } + }, + new ActorsGroup + { + Name = "Physics", + Types = new[] + { + new KeyValuePair("Rigid Body", typeof(RigidBody)), + new KeyValuePair("Character Controller", typeof(CharacterController)), + new KeyValuePair("Box Collider", typeof(BoxCollider)), + new KeyValuePair("Sphere Collider", typeof(SphereCollider)), + new KeyValuePair("Capsule Collider", typeof(CapsuleCollider)), + new KeyValuePair("Mesh Collider", typeof(MeshCollider)), + new KeyValuePair("Fixed Joint", typeof(FixedJoint)), + new KeyValuePair("Distance Joint", typeof(DistanceJoint)), + new KeyValuePair("Slider Joint", typeof(SliderJoint)), + new KeyValuePair("Spherical Joint", typeof(SphericalJoint)), + new KeyValuePair("Hinge Joint", typeof(HingeJoint)), + new KeyValuePair("D6 Joint", typeof(D6Joint)), + } + }, + new ActorsGroup + { + Name = "Other", + Types = new[] + { + new KeyValuePair("Animated Model", typeof(AnimatedModel)), + new KeyValuePair("Bone Socket", typeof(BoneSocket)), + new KeyValuePair("CSG Box Brush", typeof(BoxBrush)), + new KeyValuePair("Audio Source", typeof(AudioSource)), + new KeyValuePair("Audio Listener", typeof(AudioListener)), + new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)), + 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 + { + Name = "GUI", + Types = new[] + { + new KeyValuePair("UI Control", typeof(UIControl)), + new KeyValuePair("UI Canvas", typeof(UICanvas)), + new KeyValuePair("Text Render", typeof(TextRender)), + new KeyValuePair("Sprite Render", typeof(SpriteRender)), + } + }, + }; + private TextBox _searchBox; private Tree _tree; private bool _isUpdatingSelection; @@ -68,7 +175,8 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.TranslateMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.FocusSelection, () => Editor.Windows.EditWin.ShowSelectedActors()); + InputActions.Add(options => options.FocusSelection, () => Editor.Windows.EditWin.Viewport.FocusSelection()); + InputActions.Add(options => options.Rename, Rename); } private void OnSearchBoxTextChanged() @@ -91,7 +199,13 @@ namespace FlaxEditor.Windows private void Rename() { - (Editor.SceneEditing.Selection[0] as ActorNode).TreeNode.StartRenaming(); + var selection = Editor.SceneEditing.Selection; + if (selection.Count != 0 && selection[0] is ActorNode actor) + { + if (selection.Count != 0) + Editor.SceneEditing.Select(actor); + actor.TreeNode.StartRenaming(); + } } private void Spawn(Type type) diff --git a/Source/Engine/Animations/AnimationGraph.cs b/Source/Engine/Animations/AnimationGraph.cs index 1a5c9133d..0bdc95708 100644 --- a/Source/Engine/Animations/AnimationGraph.cs +++ b/Source/Engine/Animations/AnimationGraph.cs @@ -193,7 +193,7 @@ namespace FlaxEngine throw new ArgumentNullException(nameof(destination)); destination->NodesCount = source->NodesCount; destination->Unused = source->Unused; - Utils.MemoryCopy(new IntPtr(source->Nodes), new IntPtr(destination->Nodes), source->NodesCount * sizeof(Transform)); + Utils.MemoryCopy(new IntPtr(destination->Nodes), new IntPtr(source->Nodes), (ulong)(source->NodesCount * sizeof(Transform))); destination->RootMotionTranslation = source->RootMotionTranslation; destination->RootMotionRotation = source->RootMotionRotation; destination->Position = source->Position; diff --git a/Source/Engine/Animations/AnimationManager.cpp b/Source/Engine/Animations/AnimationManager.cpp deleted file mode 100644 index 6614c611a..000000000 --- a/Source/Engine/Animations/AnimationManager.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "AnimationManager.h" -#include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Level/Actors/AnimatedModel.h" -#include "Engine/Engine/Time.h" -#include "Engine/Engine/EngineService.h" - -Array UpdateList; -Array UpdateBones; - -class AnimationManagerService : public EngineService -{ -public: - - AnimationManagerService() - : EngineService(TEXT("Animation Manager"), -10) - { - } - - void Update() override; - void Dispose() override; -}; - -AnimationManagerService AnimationManagerInstance; - -void AnimationManagerService::Update() -{ - PROFILE_CPU_NAMED("Animations"); - - // TODO: implement the thread jobs pipeline to run set of tasks at once (use it for multi-threaded rendering and animations evaluation) - - const auto& tickData = Time::Update; - const float deltaTime = tickData.DeltaTime.GetTotalSeconds(); - const float unscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds(); - const float time = tickData.Time.GetTotalSeconds(); - const float unscaledTime = tickData.UnscaledTime.GetTotalSeconds(); - - for (int32 i = 0; i < UpdateList.Count(); i++) - { - auto animatedModel = UpdateList[i]; - if (animatedModel->SkinnedModel == nullptr || !animatedModel->SkinnedModel->IsLoaded()) - continue; - - // Prepare skinning data - animatedModel->SetupSkinningData(); - - // Update the animation graph and the skinning - auto graph = animatedModel->AnimationGraph.Get(); - if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(animatedModel->SkinnedModel) -#if USE_EDITOR - && graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes -#endif - ) - { -#if USE_EDITOR - // Lock in editor only (more reloads during asset live editing) - ScopeLock lock(animatedModel->AnimationGraph->Locker); -#endif - - // Animation delta time can be based on a time since last update or the current delta - float dt = animatedModel->UseTimeScale ? deltaTime : unscaledDeltaTime; - float t = animatedModel->UseTimeScale ? time : unscaledTime; - const float lastUpdateTime = animatedModel->GraphInstance.LastUpdateTime; - if (lastUpdateTime > 0 && t > lastUpdateTime) - { - dt = t - lastUpdateTime; - } - animatedModel->GraphInstance.LastUpdateTime = t; - - // Evaluate animated nodes pose - graph->GraphExecutor.Update(animatedModel->GraphInstance, dt); - - // Update gameplay - animatedModel->OnAnimationUpdated(); - } - } - UpdateList.Clear(); -} - -void AnimationManagerService::Dispose() -{ - UpdateList.Resize(0); - UpdateBones.Resize(0); -} - -void AnimationManager::AddToUpdate(AnimatedModel* obj) -{ - UpdateList.Add(obj); -} - -void AnimationManager::RemoveFromUpdate(AnimatedModel* obj) -{ - UpdateList.Remove(obj); -} diff --git a/Source/Engine/Animations/AnimationManager.h b/Source/Engine/Animations/AnimationManager.h deleted file mode 100644 index fc4e733ce..000000000 --- a/Source/Engine/Animations/AnimationManager.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -class AnimatedModel; - -/// -/// The animations service. -/// -class AnimationManager -{ -public: - - /// - /// Adds an animated model to update. - /// - /// The object. - static void AddToUpdate(AnimatedModel* obj); - - /// - /// Removes the animated model from update. - /// - /// The object. - static void RemoveFromUpdate(AnimatedModel* obj); -}; diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp new file mode 100644 index 000000000..efff52998 --- /dev/null +++ b/Source/Engine/Animations/Animations.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Animations.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Level/Actors/AnimatedModel.h" +#include "Engine/Engine/Time.h" +#include "Engine/Engine/EngineService.h" +#include "Engine/Threading/TaskGraph.h" + +class AnimationsService : public EngineService +{ +public: + + AnimationsService() + : EngineService(TEXT("Animations"), -10) + { + } + + bool Init() override; + void Dispose() override; +}; + +class AnimationsSystem : public TaskGraphSystem +{ +public: + float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime; + void Job(int32 index); + void Execute(TaskGraph* graph) override; + void PostExecute(TaskGraph* graph) override; +}; + +AnimationsService AnimationManagerInstance; +Array UpdateList; +TaskGraphSystem* Animations::System = nullptr; +#if USE_EDITOR +Delegate Animations::DebugFlow; +#endif + +bool AnimationsService::Init() +{ + Animations::System = New(); + Engine::UpdateGraph->AddSystem(Animations::System); + return false; +} + +void AnimationsService::Dispose() +{ + UpdateList.Resize(0); + SAFE_DELETE(Animations::System); +} + +void AnimationsSystem::Job(int32 index) +{ + PROFILE_CPU_NAMED("Animations.Job"); + auto animatedModel = UpdateList[index]; + auto skinnedModel = animatedModel->SkinnedModel.Get(); + auto graph = animatedModel->AnimationGraph.Get(); + if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(skinnedModel) +#if USE_EDITOR + && graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes +#endif + ) + { + // Prepare skinning data + animatedModel->SetupSkinningData(); + + // Animation delta time can be based on a time since last update or the current delta + float dt = animatedModel->UseTimeScale ? DeltaTime : UnscaledDeltaTime; + float t = animatedModel->UseTimeScale ? Time : UnscaledTime; + const float lastUpdateTime = animatedModel->GraphInstance.LastUpdateTime; + if (lastUpdateTime > 0 && t > lastUpdateTime) + { + dt = t - lastUpdateTime; + } + animatedModel->GraphInstance.LastUpdateTime = t; + + // Evaluate animated nodes pose + graph->GraphExecutor.Update(animatedModel->GraphInstance, dt); + + // Update gameplay + animatedModel->OnAnimationUpdated_Async(); + } +} + +void AnimationsSystem::Execute(TaskGraph* graph) +{ + if (UpdateList.Count() == 0) + return; + + // Setup data for async update + const auto& tickData = Time::Update; + DeltaTime = tickData.DeltaTime.GetTotalSeconds(); + UnscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds(); + Time = tickData.Time.GetTotalSeconds(); + UnscaledTime = tickData.UnscaledTime.GetTotalSeconds(); + + // Schedule work to update all animated models in async + Function job; + job.Bind(this); + graph->DispatchJob(job, UpdateList.Count()); +} + +void AnimationsSystem::PostExecute(TaskGraph* graph) +{ + PROFILE_CPU_NAMED("Animations.PostExecute"); + + // Update gameplay + for (int32 index = 0; index < UpdateList.Count(); index++) + { + auto animatedModel = UpdateList[index]; + auto skinnedModel = animatedModel->SkinnedModel.Get(); + auto animGraph = animatedModel->AnimationGraph.Get(); + if (animGraph && animGraph->IsLoaded() && animGraph->Graph.CanUseWithSkeleton(skinnedModel) +#if USE_EDITOR + && animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes +#endif + ) + { + animatedModel->OnAnimationUpdated_Sync(); + } + } + + // Cleanup + UpdateList.Clear(); +} + +void Animations::AddToUpdate(AnimatedModel* obj) +{ + UpdateList.Add(obj); +} + +void Animations::RemoveFromUpdate(AnimatedModel* obj) +{ + UpdateList.Remove(obj); +} diff --git a/Source/Engine/Animations/Animations.h b/Source/Engine/Animations/Animations.h new file mode 100644 index 000000000..8ebc5b5b7 --- /dev/null +++ b/Source/Engine/Animations/Animations.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingType.h" +#include "Engine/Core/Delegate.h" + +class TaskGraphSystem; +class AnimatedModel; +class Asset; + +/// +/// The animations playback service. +/// +API_CLASS(Static) class FLAXENGINE_API Animations +{ +DECLARE_SCRIPTING_TYPE_NO_SPAWN(Animations); + + /// + /// The system for Animations update. + /// + API_FIELD(ReadOnly) static TaskGraphSystem* System; + +#if USE_EDITOR + // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. Args are: anim graph asset, animated object, node id, box id + API_EVENT() static Delegate DebugFlow; +#endif + + /// + /// Adds an animated model to update. + /// + /// The object. + static void AddToUpdate(AnimatedModel* obj); + + /// + /// Removes the animated model from update. + /// + /// The object. + static void RemoveFromUpdate(AnimatedModel* obj); +}; diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 22b74e6f8..8748e99bb 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -9,25 +9,6 @@ #include "Engine/Utilities/Delaunay2D.h" #include "Engine/Serialization/MemoryReadStream.h" -void AnimGraphBase::ClearCache() -{ - // Clear sub-graphs - for (int32 i = 0; i < SubGraphs.Count(); i++) - { - SubGraphs[i]->ClearCache(); - } - - // Clear cache - for (int32 i = 0; i < Nodes.Count(); i++) - { - auto& node = Nodes[i]; - for (int32 j = 0; j < node.Boxes.Count(); j++) - { - node.Boxes[j].InvalidateCache(); - } - } -} - AnimSubGraph* AnimGraphBase::LoadSubGraph(const void* data, int32 dataLength, const Char* name) { if (data == nullptr || dataLength == 0) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index 22fc6af28..5c082a8b5 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -80,10 +80,6 @@ namespace AnimGraphInternal } } -#if USE_EDITOR -Delegate AnimGraphExecutor::DebugFlow; -#endif - void AnimGraphExecutor::initRuntime() { ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal::HasConnection); @@ -93,13 +89,10 @@ void AnimGraphExecutor::initRuntime() void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value) { - auto box = (AnimGraphBox*)boxBase; - if (box->IsCacheValid()) - { - // Return cache - value = box->Cache; + auto& context = Context.Get(); + if (context.ValueCache.TryGet(boxBase, value)) return; - } + auto box = (AnimGraphBox*)boxBase; auto node = (AnimGraphNode*)nodeBase; auto& data = node->Data.Custom; value = Value::Null; @@ -109,16 +102,16 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& return; // Prepare node context - InternalContext context; - context.Graph = &_graph; - context.GraphExecutor = this; - context.Node = node; - context.NodeId = node->ID; - context.BoxId = box->ID; - context.DeltaTime = _deltaTime; - context.CurrentFrameIndex = _currentFrameIndex;; - context.BaseModel = _graph.BaseModel->GetOrCreateManagedInstance(); - context.Instance = _data->Object ? _data->Object->GetOrCreateManagedInstance() : nullptr; + InternalContext internalContext; + internalContext.Graph = &_graph; + internalContext.GraphExecutor = this; + internalContext.Node = node; + internalContext.NodeId = node->ID; + internalContext.BoxId = box->ID; + internalContext.DeltaTime = context.DeltaTime; + internalContext.CurrentFrameIndex = context.CurrentFrameIndex; + internalContext.BaseModel = _graph.BaseModel->GetOrCreateManagedInstance(); + internalContext.Instance = context.Data->Object ? context.Data->Object->GetOrCreateManagedInstance() : nullptr; // Peek managed object const auto obj = mono_gchandle_get_target(data.Handle); @@ -130,7 +123,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& // Evaluate node void* params[1]; - params[0] = &context; + params[0] = &internalContext; MonoObject* exception = nullptr; MonoObject* result = data.Evaluate->Invoke(obj, params, &exception); if (exception) @@ -142,7 +135,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& // Extract result value = MUtils::UnboxVariant(result); - box->Cache = value; + context.ValueCache.Add(boxBase, value); } bool AnimGraph::IsReady() const diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 2932f7c3f..e726fbacc 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -1,10 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "AnimGraph.h" +#include "Engine/Animations/Animations.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Scripting/Scripting.h" -#include "Engine/Engine/Time.h" + +ThreadLocal AnimGraphExecutor::Context; RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) }; @@ -78,16 +80,44 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32 parentTransform.WorldToLocal(value, Nodes[nodeIndex]); } +void AnimGraphInstanceData::Clear() +{ + Version = 0; + LastUpdateTime = -1; + CurrentFrame = 0; + RootTransform = Transform::Identity; + RootMotion = RootMotionData::Identity; + Parameters.Resize(0); + State.Resize(0); + NodesPose.Resize(0); +} + +void AnimGraphInstanceData::ClearState() +{ + Version = 0; + LastUpdateTime = -1; + CurrentFrame = 0; + RootTransform = Transform::Identity; + RootMotion = RootMotionData::Identity; + State.Resize(0); + NodesPose.Resize(0); +} + +void AnimGraphInstanceData::Invalidate() +{ + LastUpdateTime = -1; + CurrentFrame = 0; +} + AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor) { - // Ensure to have memory + auto& context = AnimGraphExecutor::Context.Get(); const int32 count = executor->_skeletonNodesCount; - if (Nodes.Nodes.Count() != count) - { - Nodes.Nodes.Resize(count, false); - } - - return &Nodes; + if (context.PoseCacheSize == context.PoseCache.Count()) + context.PoseCache.AddOne(); + auto& nodes = context.PoseCache[context.PoseCacheSize++]; + nodes.Nodes.Resize(count, false); + return &nodes; } bool AnimGraph::Load(ReadStream* stream, bool loadMeta) @@ -181,20 +211,24 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Initialize auto& skeleton = _graph.BaseModel->Skeleton; + auto& context = Context.Get(); { ANIM_GRAPH_PROFILE_EVENT("Init"); - // Prepare graph data for the evaluation + // Init data from base model _skeletonNodesCount = skeleton.Nodes.Count(); - _graphStack.Clear(); - _graphStack.Push((Graph*)&_graph); - _data = &data; - _deltaTime = dt; _rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0]; - _currentFrameIndex = ++data.CurrentFrame; - _callStack.Clear(); - _functions.Clear(); - _graph.ClearCache(); + + // Prepare context data for the evaluation + context.GraphStack.Clear(); + context.GraphStack.Push((Graph*)&_graph); + context.Data = &data; + context.DeltaTime = dt; + context.CurrentFrameIndex = ++data.CurrentFrame; + context.CallStack.Clear(); + context.Functions.Clear(); + context.PoseCacheSize = 0; + context.ValueCache.Clear(); // Prepare instance data if (data.Version != _graph.Version) @@ -208,18 +242,18 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) data.State.Resize(_graph.BucketsCountTotal, false); // Initialize buckets - ResetBuckets(&_graph); + ResetBuckets(context, &_graph); } // Init empty nodes data - _emptyNodes.RootMotion = RootMotionData::Identity; - _emptyNodes.Position = 0.0f; - _emptyNodes.Length = 0.0f; - _emptyNodes.Nodes.Resize(_skeletonNodesCount, false); + context.EmptyNodes.RootMotion = RootMotionData::Identity; + context.EmptyNodes.Position = 0.0f; + context.EmptyNodes.Length = 0.0f; + context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false); for (int32 i = 0; i < _skeletonNodesCount; i++) { auto& node = skeleton.Nodes[i]; - _emptyNodes.Nodes[i] = node.LocalTransform; + context.EmptyNodes.Nodes[i] = node.LocalTransform; } } @@ -244,7 +278,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) { ANIM_GRAPH_PROFILE_EVENT("Global Pose"); - _data->NodesPose.Resize(_skeletonNodesCount, false); + data.NodesPose.Resize(_skeletonNodesCount, false); // Note: this assumes that nodes are sorted (parents first) for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++) @@ -254,18 +288,16 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) { nodesTransformations[nodeIndex] = nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex]); } - nodesTransformations[nodeIndex].GetWorld(_data->NodesPose[nodeIndex]); + nodesTransformations[nodeIndex].GetWorld(data.NodesPose[nodeIndex]); } - } - // Process the root node transformation and the motion - { - _data->RootTransform = nodesTransformations[0]; - _data->RootMotion = animResult->RootMotion; + // Process the root node transformation and the motion + data.RootTransform = nodesTransformations[0]; + data.RootMotion = animResult->RootMotion; } // Cleanup - _data = nullptr; + context.Data = nullptr; } void AnimGraphExecutor::GetInputValue(Box* box, Value& result) @@ -273,29 +305,39 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result) result = eatBox(box->GetParent(), box->FirstConnection()); } -void AnimGraphExecutor::ResetBucket(int32 bucketIndex) +AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes() { - auto& stateBucket = _data->State[bucketIndex]; - _graph._bucketInitializerList[bucketIndex](stateBucket); + return &Context.Get().EmptyNodes; } -void AnimGraphExecutor::ResetBuckets(AnimGraphBase* graph) +void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const +{ + const auto& emptyNodes = Context.Get().EmptyNodes; + Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); + nodes->RootMotion = emptyNodes.RootMotion; + nodes->Position = emptyNodes.Position; + nodes->Length = emptyNodes.Length; +} + +void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph) { if (graph == nullptr) return; - ASSERT(_data); + auto& state = context.Data->State; for (int32 i = 0; i < graph->BucketsCountTotal; i++) { const int32 bucketIndex = graph->BucketsStart + i; - _graph._bucketInitializerList[bucketIndex](_data->State[bucketIndex]); + _graph._bucketInitializerList[bucketIndex](state[bucketIndex]); } } VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) { + auto& context = Context.Get(); + // Check if graph is looped or is too deep - if (_callStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) + if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); return Value::Zero; @@ -309,10 +351,10 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) #endif // Add to the calling stack - _callStack.Add(caller); + context.CallStack.Add(caller); #if USE_EDITOR - DebugFlow(_graph._owner, _data->Object, box->GetParent()->ID, box->ID); + Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent()->ID, box->ID); #endif // Call per group custom processing event @@ -322,12 +364,13 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) (this->*func)(box, parentNode, value); // Remove from the calling stack - _callStack.RemoveLast(); + context.CallStack.RemoveLast(); return value; } VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const { - return _graphStack.Peek(); + auto& context = Context.Get(); + return context.GraphStack.Peek(); } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 8ca4b7247..1e011aed3 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -4,6 +4,7 @@ #include "Engine/Visject/VisjectGraph.h" #include "Engine/Content/Assets/Animation.h" +#include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Animations/AlphaBlend.h" #include "Engine/Core/Math/Matrix.h" #include "../Config.h" @@ -362,40 +363,17 @@ public: /// /// Clears this container data. /// - void Clear() - { - Version = 0; - LastUpdateTime = -1; - CurrentFrame = 0; - RootTransform = Transform::Identity; - RootMotion = RootMotionData::Identity; - Parameters.Resize(0); - State.Resize(0); - NodesPose.Resize(0); - } + void Clear(); /// /// Clears this container state data. /// - void ClearState() - { - Version = 0; - LastUpdateTime = -1; - CurrentFrame = 0; - RootTransform = Transform::Identity; - RootMotion = RootMotionData::Identity; - State.Resize(0); - NodesPose.Resize(0); - } + void ClearState(); /// /// Invalidates the update timer. /// - void Invalidate() - { - LastUpdateTime = -1; - CurrentFrame = 0; - } + void Invalidate(); }; /// @@ -424,18 +402,6 @@ public: : VisjectGraphBox(parent, id, type) { } - -public: - - bool IsCacheValid() const - { - return Cache.Type.Type != VariantType::Pointer || Cache.AsPointer != nullptr; - } - - void InvalidateCache() - { - Cache = Variant::Null; - } }; class AnimGraphNode : public VisjectGraphNode @@ -575,13 +541,6 @@ public: /// int32 BucketIndex = -1; - // TODO: use shared allocator per AnimGraph to reduce dynamic memory allocation (also bones data would be closer in memory -> less cache misses) - - /// - /// The node transformations (layout matches the linked to graph skinned model skeleton). - /// - AnimGraphImpulse Nodes; - /// /// The custom data (depends on node type). Used to cache data for faster usage at runtime. /// @@ -661,17 +620,11 @@ public: /// /// Gets the root node of the graph (cache don load). /// - /// The root node. FORCE_INLINE Node* GetRootNode() const { return _rootNode; } - /// - /// Clear all cached values in the graph nodes and the sub-graphs data. - /// - void ClearCache(); - /// /// Loads the sub-graph. /// @@ -751,9 +704,9 @@ public: AnimGraph(Asset* owner, bool isFunction = false) : AnimGraphBase(this) , _isFunction(isFunction) + , _isRegisteredForScriptingEvents(false) , _bucketInitializerList(64) , _owner(owner) - , _isRegisteredForScriptingEvents(false) { } @@ -806,6 +759,24 @@ public: bool onParamCreated(Parameter* p) override; }; +/// +/// The Animation Graph evaluation context. +/// +struct AnimGraphContext +{ + float DeltaTime; + uint64 CurrentFrameIndex; + AnimGraphInstanceData* Data; + AnimGraphImpulse EmptyNodes; + AnimGraphTransitionData TransitionData; + Array> CallStack; + Array> GraphStack; + Dictionary Functions; + ChunkedArray PoseCache; + int32 PoseCacheSize; + Dictionary ValueCache; +}; + /// /// The Animation Graph executor runtime for animation pose evaluation. /// @@ -815,24 +786,14 @@ class AnimGraphExecutor : public VisjectExecutor private: AnimGraph& _graph; - float _deltaTime = 0.0f; - uint64 _currentFrameIndex = 0; - int32 _skeletonNodesCount = 0; RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction; - AnimGraphInstanceData* _data = nullptr; - AnimGraphImpulse _emptyNodes; - AnimGraphTransitionData _transitionData; - Array> _callStack; - Array> _graphStack; - Dictionary _functions; + int32 _skeletonNodesCount = 0; + + // Per-thread context to allow async execution + static ThreadLocal Context; public: -#if USE_EDITOR - // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. - static Delegate DebugFlow; -#endif - /// /// Initializes the managed runtime calls. /// @@ -858,34 +819,10 @@ public: /// /// Gets the skeleton nodes transformations structure containing identity matrices. /// - FORCE_INLINE const AnimGraphImpulse* GetEmptyNodes() const - { - return &_emptyNodes; - } + AnimGraphImpulse* GetEmptyNodes(); - /// - /// Gets the skeleton nodes transformations structure containing identity matrices. - /// - /// The data. - FORCE_INLINE AnimGraphImpulse* GetEmptyNodes() - { - return &_emptyNodes; - } - - FORCE_INLINE void InitNodes(AnimGraphImpulse* nodes) const - { - // Initialize with cached node transformations - Platform::MemoryCopy(nodes->Nodes.Get(), _emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); - nodes->RootMotion = _emptyNodes.RootMotion; - nodes->Position = _emptyNodes.Position; - nodes->Length = _emptyNodes.Length; - } - - FORCE_INLINE void InitNode(AnimGraphImpulse* nodes, int32 index) const - { - // Initialize with cached node transformation - nodes->Nodes[index] = GetEmptyNodes()->Nodes[index]; - } + // Initialize impulse with cached node transformations + void InitNodes(AnimGraphImpulse* nodes) const; FORCE_INLINE void CopyNodes(AnimGraphImpulse* dstNodes, AnimGraphImpulse* srcNodes) const { @@ -903,16 +840,10 @@ public: CopyNodes(dstNodes, static_cast(value.AsPointer)); } - /// - /// Resets the state bucket. - /// - /// The zero-based index of the bucket. - void ResetBucket(int32 bucketIndex); - /// /// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children). /// - void ResetBuckets(AnimGraphBase* graph); + void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph); private: diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index d7baadfc0..89ee914c8 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -14,7 +14,7 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim) if (anim->Data.RootNodeName.HasChars()) { auto& skeleton = _graph.BaseModel->Skeleton; - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < skeleton.Nodes.Count(); i++) { if (skeleton.Nodes[i].Name == anim->Data.RootNodeName) { @@ -119,7 +119,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed) // Also, scale the animation to fit the total animation node length without cut in a middle const auto animLength = anim->GetLength(); const int32 cyclesCount = Math::FloorToInt(length / animLength); - const float cycleLength = animLength * cyclesCount; + const float cycleLength = animLength * (float)cyclesCount; const float adjustRateScale = length / cycleLength; auto animPos = pos * speed * adjustRateScale; while (animPos > animLength) @@ -152,10 +152,11 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float nodes->Position = pos; nodes->Length = length; const auto mapping = anim->GetMapping(_graph.BaseModel); - for (int32 i = 0; i < _skeletonNodesCount; i++) + const auto emptyNodes = GetEmptyNodes(); + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 nodeToChannel = mapping->At(i); - InitNode(nodes, i); + nodes->Nodes[i] = emptyNodes->Nodes[i]; if (nodeToChannel != -1) { // Calculate the animated node transformation @@ -197,7 +198,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l nodes->Length = length; const auto mappingA = animA->GetMapping(_graph.BaseModel); const auto mappingB = animB->GetMapping(_graph.BaseModel); - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 nodeToChannelA = mappingA->At(i); const int32 nodeToChannelB = mappingB->At(i); @@ -286,12 +287,13 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l const auto mappingB = animB->GetMapping(_graph.BaseModel); const auto mappingC = animC->GetMapping(_graph.BaseModel); Transform tmp, t; - for (int32 i = 0; i < _skeletonNodesCount; i++) + const auto emptyNodes = GetEmptyNodes(); + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 nodeToChannelA = mappingA->At(i); const int32 nodeToChannelB = mappingB->At(i); const int32 nodeToChannelC = mappingC->At(i); - tmp = t = GetEmptyNodes()->Nodes[i]; + tmp = t = emptyNodes->Nodes[i]; // Calculate the animated node transformations if (nodeToChannelA != -1) @@ -384,7 +386,7 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const if (!ANIM_GRAPH_IS_VALID_PTR(poseB)) nodesB = GetEmptyNodes(); - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); } @@ -443,6 +445,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node) void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { + auto& context = Context.Get(); switch (node->TypeID) { // Get @@ -453,7 +456,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex); if (param) { - value = _data->Parameters[paramIndex].Value; + value = context.Data->Parameters[paramIndex].Value; switch (param->Type.Type) { case VariantType::Vector2: @@ -523,19 +526,20 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value) { + auto& context = Context.Get(); auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { // Time case 5: { - auto& bucket = _data->State[node->BucketIndex].Animation; - if (bucket.LastUpdateFrame != _currentFrameIndex) + auto& bucket = context.Data->State[node->BucketIndex].Animation; + if (bucket.LastUpdateFrame != context.CurrentFrameIndex) { - bucket.TimePosition += _deltaTime; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.TimePosition += context.DeltaTime; + bucket.LastUpdateFrame = context.CurrentFrameIndex; } - value = box->ID == 0 ? bucket.TimePosition : _deltaTime; + value = box->ID == 0 ? bucket.TimePosition : context.DeltaTime; break; } default: @@ -546,13 +550,10 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Value& value) { - auto box = (AnimGraphBox*)boxBase; - if (box->IsCacheValid()) - { - // Return cache - value = box->Cache; + auto& context = Context.Get(); + if (context.ValueCache.TryGet(boxBase, value)) return; - } + auto box = (AnimGraphBox*)boxBase; auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { @@ -569,7 +570,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu case 2: { const auto anim = node->Assets[0].As(); - auto& bucket = _data->State[node->BucketIndex].Animation; + auto& bucket = context.Data->State[node->BucketIndex].Animation; const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]); const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]); const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]); @@ -584,17 +585,17 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const float length = anim ? anim->GetLength() : 0.0f; // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1) + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) { // If speed is negative and it's the first node update then start playing from end bucket.TimePosition = length; } - float newTimePos = bucket.TimePosition + _deltaTime * speed; + float newTimePos = bucket.TimePosition + context.DeltaTime * speed; value = SampleAnimation(node, loop, length, startTimePos, bucket.TimePosition, newTimePos, anim, 1.0f); bucket.TimePosition = newTimePos; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -615,7 +616,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Is Playing case 4: // If anim was updated during this or a previous frame - value = bucket.LastUpdateFrame >= _currentFrameIndex - 1; + value = bucket.LastUpdateFrame >= context.CurrentFrameIndex - 1; break; } break; @@ -643,7 +644,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu value = Value::Null; if (inputBox->HasConnection()) value = eatBox(nodeBase, inputBox->FirstConnection()); - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } const auto nodeIndex = _graph.BaseModel->Skeleton.Bones[boneIndex].NodeIndex; @@ -690,7 +691,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Transform every node const auto& skeleton = BaseModel->Skeleton; - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 parentIndex = skeleton.Nodes[i].ParentIndex; if (parentIndex != -1) @@ -729,7 +730,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Inv transform every node const auto& skeleton = BaseModel->Skeleton; - for (int32 i = _skeletonNodesCount - 1; i >= 0; i--) + for (int32 i = nodes->Nodes.Count() - 1; i >= 0; i--) { const int32 parentIndex = skeleton.Nodes[i].ParentIndex; if (parentIndex != -1) @@ -775,7 +776,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { // Pass through the input value = input; - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } @@ -836,7 +837,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (!ANIM_GRAPH_IS_VALID_PTR(valueB)) nodesB = GetEmptyNodes(); - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); } @@ -876,7 +877,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto nodesA = static_cast(valueA.AsPointer); const auto nodesB = static_cast(valueB.AsPointer); Transform t, tA, tB; - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { tA = nodesA->Nodes[i]; tB = nodesB->Nodes[i]; @@ -921,7 +922,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Blend all nodes masked by the user Transform tA, tB; auto& nodesMask = mask->GetNodesMask(); - for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++) + for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++) { tA = nodesA->Nodes[nodeIndex]; if (nodesMask[nodeIndex]) @@ -956,7 +957,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [1]: Guid Animation // Prepare - auto& bucket = _data->State[node->BucketIndex].MultiBlend; + auto& bucket = context.Data->State[node->BucketIndex].MultiBlend; const auto range = node->Values[0].AsVector4(); const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); @@ -988,12 +989,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1) + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) { // If speed is negative and it's the first node update then start playing from end bucket.TimePosition = data.Length; } - float newTimePos = bucket.TimePosition + _deltaTime * speed; + float newTimePos = bucket.TimePosition + context.DeltaTime * speed; ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D"); @@ -1035,7 +1036,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.TimePosition = newTimePos; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -1054,7 +1055,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [1]: Guid Animation // Prepare - auto& bucket = _data->State[node->BucketIndex].MultiBlend; + auto& bucket = context.Data->State[node->BucketIndex].MultiBlend; const auto range = node->Values[0].AsVector4(); const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); @@ -1090,12 +1091,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1) + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) { // If speed is negative and it's the first node update then start playing from end bucket.TimePosition = data.Length; } - float newTimePos = bucket.TimePosition + _deltaTime * speed; + float newTimePos = bucket.TimePosition + context.DeltaTime * speed; ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D"); @@ -1227,7 +1228,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.TimePosition = newTimePos; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -1246,7 +1247,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [3]: AlphaBlendMode Mode // Prepare - auto& bucket = _data->State[node->BucketIndex].BlendPose; + auto& bucket = context.Data->State[node->BucketIndex].BlendPose; const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]); const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]); const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses); @@ -1259,7 +1260,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Check if transition is not active (first update, pose not changing or transition ended) - bucket.TransitionPosition += _deltaTime; + bucket.TransitionPosition += context.DeltaTime; if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD) { bucket.TransitionPosition = 0.0f; @@ -1356,11 +1357,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu ANIM_GRAPH_PROFILE_EVENT("State Machine"); // Prepare - auto& bucket = _data->State[node->BucketIndex].StateMachine; + auto& bucket = context.Data->State[node->BucketIndex].StateMachine; auto& data = node->Data.StateMachine; int32 transitionsLeft = maxTransitionsPerUpdate == 0 ? MAX_uint16 : maxTransitionsPerUpdate; bool isFirstUpdate = bucket.LastUpdateFrame == 0 || bucket.CurrentState == nullptr; - if (bucket.LastUpdateFrame != _currentFrameIndex - 1 && reinitializeOnBecomingRelevant) + if (bucket.LastUpdateFrame != context.CurrentFrameIndex - 1 && reinitializeOnBecomingRelevant) { // Reset on becoming relevant isFirstUpdate = true; @@ -1384,19 +1385,19 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.TransitionPosition = 0.0f; // Reset all state buckets pof the graphs and nodes included inside the state machine - ResetBuckets(data.Graph); + ResetBuckets(context, data.Graph); } // Update the active transition if (bucket.ActiveTransition) { - bucket.TransitionPosition += _deltaTime; + bucket.TransitionPosition += context.DeltaTime; - // Check ofr transition end + // Check for transition end if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration) { // End transition - ResetBuckets(bucket.CurrentState->Data.State.Graph); + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); bucket.CurrentState = bucket.ActiveTransition->Destination; bucket.ActiveTransition = nullptr; bucket.TransitionPosition = 0.0f; @@ -1422,7 +1423,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Evaluate source state transition data (position, length, etc.) const Value sourceStatePtr = SampleState(bucket.CurrentState); - auto& transitionData = _transitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? + auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr)) { // Use source state as data provider @@ -1475,7 +1476,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance) { // End transition - ResetBuckets(bucket.CurrentState->Data.State.Graph); + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); bucket.CurrentState = bucket.ActiveTransition->Destination; bucket.ActiveTransition = nullptr; bucket.TransitionPosition = 0.0f; @@ -1498,7 +1499,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Update bucket - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -1537,7 +1538,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Transition Source State Anim case 23: { - const AnimGraphTransitionData& transitionsData = _transitionData; + const AnimGraphTransitionData& transitionsData = context.TransitionData; switch (box->ID) { // Length @@ -1579,15 +1580,15 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu #if 0 // Prevent recursive calls - for (int32 i = _callStack.Count() - 1; i >= 0; i--) + for (int32 i = context.CallStack.Count() - 1; i >= 0; i--) { - if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24)) + if (context.CallStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24)) { - const auto callFunc = _callStack[i]->Assets[0].Get(); + const auto callFunc = context.CallStack[i]->Assets[0].Get(); if (callFunc == function) { value = Value::Zero; - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } } @@ -1606,12 +1607,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu Box* functionOutputBox = functionOutputNode->TryGetBox(0); // Cache relation between current node in the call stack to the actual function graph - _functions[nodeBase] = (Graph*)data.Graph; + context.Functions[nodeBase] = (Graph*)data.Graph; // Evaluate the function output - _graphStack.Push((Graph*)data.Graph); + context.GraphStack.Push((Graph*)data.Graph); value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero; - _graphStack.Pop(); + context.GraphStack.Pop(); break; } // Transform Bone (local/model space) @@ -1635,7 +1636,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu value = Value::Null; if (inputBox->HasConnection()) value = eatBox(nodeBase, inputBox->FirstConnection()); - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } const auto nodes = node->GetNodes(this); @@ -1704,7 +1705,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { // Pass through the input value = input; - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } @@ -1859,18 +1860,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu default: break; } - box->Cache = value; + context.ValueCache.Add(boxBase, value); } void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value) { - auto box = (AnimGraphBox*)boxBase; - if (box->IsCacheValid()) - { - // Return cache - value = box->Cache; + auto& context = Context.Get(); + if (context.ValueCache.TryGet(boxBase, value)) return; - } switch (node->TypeID) { // Function Input @@ -1878,13 +1875,13 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va { // Find the function call AnimGraphNode* functionCallNode = nullptr; - ASSERT(_graphStack.Count() >= 2); + ASSERT(context.GraphStack.Count() >= 2); Graph* graph; - for (int32 i = _callStack.Count() - 1; i >= 0; i--) + for (int32 i = context.CallStack.Count() - 1; i >= 0; i--) { - if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24) && _functions.TryGet(_callStack[i], graph) && _graphStack[_graphStack.Count() - 1] == (Graph*)graph) + if (context.CallStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24) && context.Functions.TryGet(context.CallStack[i], graph) && context.GraphStack.Last() == graph) { - functionCallNode = (AnimGraphNode*)_callStack[i]; + functionCallNode = (AnimGraphNode*)context.CallStack[i]; break; } } @@ -1926,19 +1923,19 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va if (functionCallBox && functionCallBox->HasConnection()) { // Use provided input value from the function call - _graphStack.Pop(); + context.GraphStack.Pop(); value = eatBox(node, functionCallBox->FirstConnection()); - _graphStack.Push(graph); + context.GraphStack.Push(graph); } else { // Use the default value from the function graph value = tryGetValue(node->TryGetBox(1), Value::Zero); } + context.ValueCache.Add(boxBase, value); break; } default: break; } - box->Cache = value; } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index f669c72c2..e3246705a 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "SceneAnimation.h" +#include "Engine/Core/Log.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/Content.h" diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp index 51f674e73..913ffe87a 100644 --- a/Source/Engine/Audio/Audio.cpp +++ b/Source/Engine/Audio/Audio.cpp @@ -171,6 +171,7 @@ void Audio::OnRemoveSource(AudioSource* source) bool AudioService::Init() { + PROFILE_CPU_NAMED("Audio.Init"); const auto settings = AudioSettings::Get(); const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio; @@ -233,7 +234,7 @@ bool AudioService::Init() void AudioService::Update() { - PROFILE_CPU(); + PROFILE_CPU_NAMED("Audio.Update"); // Update the master volume float masterVolume = MasterVolume; @@ -242,10 +243,9 @@ void AudioService::Update() // Mute audio if app has no user focus masterVolume = 0.0f; } - if (Volume != masterVolume) + if (Math::NotNearEqual(Volume, masterVolume)) { Volume = masterVolume; - AudioBackend::SetVolume(masterVolume); } diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 3213850d3..7c1945803 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -401,7 +401,7 @@ Asset::LoadResult AudioClip::load() if (AudioHeader.Streamable) { // Do nothing because data streaming starts when any AudioSource requests the data - startStreaming(false); + StartStreaming(false); return LoadResult::Ok; } @@ -450,7 +450,7 @@ Asset::LoadResult AudioClip::load() void AudioClip::unload(bool isReloading) { - stopStreaming(); + StopStreaming(); StreamingQueue.Clear(); if (Buffers.HasItems()) { diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index 8969bc4d3..2b82ec6ea 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -1,11 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "AudioSource.h" +#include "Engine/Core/Log.h" #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Engine/Time.h" #include "Engine/Level/Scene/Scene.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "AudioBackend.h" #include "Audio.h" @@ -104,7 +106,7 @@ void AudioSource::Play() return; if (Clip == nullptr) { - LOG(Warning, "Cannot play audio source without a clip ({0})", GetName()); + LOG(Warning, "Cannot play audio source without a clip ({0})", GetNamePath()); return; } @@ -133,7 +135,7 @@ void AudioSource::Pause() { if (_state != States::Playing) { - LOG(Warning, "Cannot pause audio source that is not playing ({0})", GetName()); + LOG(Warning, "Cannot pause audio source that is not playing ({0})", GetNamePath()); return; } @@ -340,6 +342,8 @@ bool AudioSource::IntersectsItself(const Ray& ray, float& distance, Vector3& nor void AudioSource::Update() { + PROFILE_CPU(); + // Update the velocity const Vector3 pos = GetPosition(); const float dt = Math::Max(Time::Update.UnscaledDeltaTime.GetTotalSeconds(), ZeroTolerance); diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 7fcd2f405..5436cb6b9 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -13,6 +13,93 @@ #include "Engine/Threading/ConcurrentTaskQueue.h" #include +AssetReferenceBase::~AssetReferenceBase() +{ + if (_asset) + { + _asset->OnLoaded.Unbind(this); + _asset->OnUnloaded.Unbind(this); + _asset->RemoveReference(); + } +} + +String AssetReferenceBase::ToString() const +{ + return _asset ? _asset->ToString() : TEXT(""); +} + +void AssetReferenceBase::OnSet(Asset* asset) +{ + auto e = _asset; + if (e != asset) + { + if (e) + { + e->OnLoaded.Unbind(this); + e->OnUnloaded.Unbind(this); + e->RemoveReference(); + } + _asset = e = asset; + if (e) + { + e->AddReference(); + e->OnLoaded.Bind(this); + e->OnUnloaded.Bind(this); + } + Changed(); + if (e && e->IsLoaded()) + Loaded(); + } +} + +void AssetReferenceBase::OnLoaded(Asset* asset) +{ + if (_asset != asset) + return; + Loaded(); +} + +void AssetReferenceBase::OnUnloaded(Asset* asset) +{ + if (_asset != asset) + return; + Unload(); + OnSet(nullptr); +} + +WeakAssetReferenceBase::~WeakAssetReferenceBase() +{ + if (_asset) + _asset->OnUnloaded.Unbind(this); +} + +String WeakAssetReferenceBase::ToString() const +{ + return _asset ? _asset->ToString() : TEXT(""); +} + +void WeakAssetReferenceBase::OnSet(Asset* asset) +{ + auto e = _asset; + if (e != asset) + { + if (e) + e->OnUnloaded.Unbind(this); + _asset = e = asset; + if (e) + e->OnUnloaded.Bind(this); + } +} + +void WeakAssetReferenceBase::OnUnloaded(Asset* asset) +{ + if (_asset != asset) + return; + Unload(); + asset->OnUnloaded.Unbind(this); + _asset = nullptr; +} + Asset::Asset(const SpawnParams& params, const AssetInfo* info) : ManagedScriptingObject(params) , _refCount(0) @@ -24,9 +111,14 @@ Asset::Asset(const SpawnParams& params, const AssetInfo* info) { } +int32 Asset::GetReferencesCount() const +{ + return (int32)Platform::AtomicRead(const_cast(&_refCount)); +} + String Asset::ToString() const { - return String::Format(TEXT("{0}: {1}, \'{2}\', Refs: {3}"), GetTypeName(), GetID(), GetPath(), GetReferencesCount()); + return String::Format(TEXT("{0}, {1}, {2}"), GetTypeName(), GetID(), GetPath()); } void Asset::OnDeleteObject() @@ -348,6 +440,15 @@ void Asset::startLoading() _loadingTask->Start(); } +void Asset::releaseStorage() +{ +} + +bool Asset::IsInternalType() const +{ + return false; +} + bool Asset::onLoad(LoadAssetTask* task) { // It may fail when task is cancelled and new one is created later (don't crash but just end with an error) diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index a831ee773..3d4ca2789 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -82,10 +82,7 @@ public: /// /// Gets asset's reference count. Asset will be automatically unloaded when this reaches zero. /// - API_PROPERTY() int32 GetReferencesCount() const - { - return (int32)Platform::AtomicRead(const_cast(&_refCount)); - } + API_PROPERTY() int32 GetReferencesCount() const; /// /// Adds reference to that asset. @@ -213,9 +210,7 @@ protected: /// /// Releases the storage file/container handle to prevent issues when renaming or moving the asset. /// - virtual void releaseStorage() - { - } + virtual void releaseStorage(); /// /// Loads asset @@ -231,10 +226,7 @@ protected: protected: - virtual bool IsInternalType() const - { - return false; - } + virtual bool IsInternalType() const; bool onLoad(LoadAssetTask* task); void onLoaded(); diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index 0956e82be..90f472fd6 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -15,7 +15,7 @@ public: protected: - Asset* _asset; + Asset* _asset = nullptr; public: @@ -35,35 +35,23 @@ public: EventType Changed; public: + NON_COPYABLE(AssetReferenceBase); /// /// Initializes a new instance of the class. /// - AssetReferenceBase() - : _asset(nullptr) - { - } + AssetReferenceBase() = default; /// /// Finalizes an instance of the class. /// - ~AssetReferenceBase() - { - if (_asset) - { - _asset->OnLoaded.Unbind(this); - _asset->OnUnloaded.Unbind(this); - _asset->RemoveReference(); - _asset = nullptr; - } - } + ~AssetReferenceBase(); public: /// /// Gets the asset ID or Guid::Empty if not set. /// - /// The asset ID or Guid::Empty if not set. FORCE_INLINE Guid GetID() const { return _asset ? _asset->GetID() : Guid::Empty; @@ -72,7 +60,6 @@ public: /// /// Gets managed instance object (or null if no asset set). /// - /// Mono managed object FORCE_INLINE MonoObject* GetManagedInstance() const { return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; @@ -81,55 +68,13 @@ public: /// /// Gets the asset property value as string. /// - /// The string. - String ToString() const - { - static String NullStr = TEXT(""); - return _asset ? _asset->ToString() : NullStr; - } + String ToString() const; protected: - void OnSet(Asset* asset) - { - auto e = _asset; - if (e != asset) - { - if (e) - { - e->OnLoaded.Unbind(this); - e->OnUnloaded.Unbind(this); - e->RemoveReference(); - } - _asset = e = asset; - if (e) - { - e->AddReference(); - e->OnLoaded.Bind(this); - e->OnUnloaded.Bind(this); - } - Changed(); - if (e && e->IsLoaded()) - Loaded(); - } - } - - void OnAssetLoaded(Asset* asset) - { - if (_asset == asset) - { - Loaded(); - } - } - - void OnAssetUnloaded(Asset* asset) - { - if (_asset == asset) - { - Unload(); - OnSet(nullptr); - } - } + void OnSet(Asset* asset); + void OnLoaded(Asset* asset); + void OnUnloaded(Asset* asset); }; /// @@ -170,6 +115,22 @@ public: OnSet(other.Get()); } + AssetReference(AssetReference&& other) + { + OnSet(other.Get()); + other.OnSet(nullptr); + } + + AssetReference& operator=(AssetReference&& other) + { + if (&other != this) + { + OnSet(other.Get()); + other.OnSet(nullptr); + } + return *this; + } + /// /// Finalizes an instance of the class. /// @@ -220,7 +181,6 @@ public: /// /// Implicit conversion to the bool. /// - /// The asset. FORCE_INLINE operator T*() const { return (T*)_asset; @@ -229,7 +189,6 @@ public: /// /// Implicit conversion to the asset. /// - /// True if asset has been set, otherwise false. FORCE_INLINE operator bool() const { return _asset != nullptr; @@ -238,7 +197,6 @@ public: /// /// Implicit conversion to the asset. /// - /// The asset. FORCE_INLINE T* operator->() const { return (T*)_asset; @@ -247,7 +205,6 @@ public: /// /// Gets the asset. /// - /// The asset. FORCE_INLINE T* Get() const { return (T*)_asset; @@ -256,7 +213,6 @@ public: /// /// Gets the asset as a given type (static cast). /// - /// The asset. template FORCE_INLINE U* As() const { diff --git a/Source/Engine/Content/Assets/MaterialBase.cpp b/Source/Engine/Content/Assets/MaterialBase.cpp index 3b2a039db..f879f0512 100644 --- a/Source/Engine/Content/Assets/MaterialBase.cpp +++ b/Source/Engine/Content/Assets/MaterialBase.cpp @@ -2,6 +2,7 @@ #include "MaterialBase.h" #include "MaterialInstance.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Content/Content.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 5ea8c9351..d432a5804 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "MaterialInstance.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Content/Content.h" #include "Engine/Content/Upgraders/MaterialInstanceUpgrader.h" diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index dd9c484e1..d337c62a2 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -570,7 +570,7 @@ bool Model::Init(const Span& meshesCountPerLod) } // Dispose previous data and disable streaming (will start data uploading tasks manually) - stopStreaming(); + StopStreaming(); // Setup MaterialSlots.Resize(1); @@ -827,7 +827,7 @@ Asset::LoadResult Model::load() #endif // Request resource streaming - startStreaming(true); + StartStreaming(true); return LoadResult::Ok; } diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index b98ba2e57..3860aeda8 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -670,7 +670,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) } // Dispose previous data and disable streaming (will start data uploading tasks manually) - stopStreaming(); + StopStreaming(); // Setup MaterialSlots.Resize(1); @@ -973,7 +973,7 @@ Asset::LoadResult SkinnedModel::load() } // Request resource streaming - startStreaming(true); + StartStreaming(true); return LoadResult::Ok; } diff --git a/Source/Engine/Content/Assets/Texture.cpp b/Source/Engine/Content/Assets/Texture.cpp index 23dbff0e2..08100d4b5 100644 --- a/Source/Engine/Content/Assets/Texture.cpp +++ b/Source/Engine/Content/Assets/Texture.cpp @@ -17,6 +17,16 @@ Texture::Texture(const SpawnParams& params, const AssetInfo* info) { } +TextureFormatType Texture::GetFormatType() const +{ + return _texture.GetFormatType(); +} + +bool Texture::IsNormalMap() const +{ + return _texture.GetFormatType() == TextureFormatType::NormalMap; +} + #if USE_EDITOR bool Texture::Save(const StringView& path, const InitData* customData) diff --git a/Source/Engine/Content/Assets/Texture.h b/Source/Engine/Content/Assets/Texture.h index afefc59d2..2b0a6c127 100644 --- a/Source/Engine/Content/Assets/Texture.h +++ b/Source/Engine/Content/Assets/Texture.h @@ -10,23 +10,16 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Texture : public TextureBase { DECLARE_BINARY_ASSET_HEADER(Texture, TexturesSerializedVersion); -public: /// /// Gets the texture format type. /// - FORCE_INLINE TextureFormatType GetFormatType() const - { - return _texture.GetFormatType(); - } + TextureFormatType GetFormatType() const; /// /// Returns true if texture is a normal map. /// - API_PROPERTY() FORCE_INLINE bool IsNormalMap() const - { - return GetFormatType() == TextureFormatType::NormalMap; - } + API_PROPERTY() bool IsNormalMap() const; public: diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 2da76a840..0d523b67f 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -19,6 +19,7 @@ #include "Engine/Serialization/JsonWriter.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Utilities/StringConverter.h" +#include "Engine/Threading/MainThreadTask.h" #include "FlaxEngine.Gen.h" namespace @@ -83,6 +84,41 @@ VisualScriptExecutor::VisualScriptExecutor() _perGroupProcessCall[17] = (ProcessBoxHandler)&VisualScriptExecutor::ProcessGroupFlow; } +void VisualScriptExecutor::Invoke(const Guid& scriptId, int32 nodeId, int32 boxId, const Guid& instanceId, Variant& result) const +{ + auto script = Content::Load(scriptId); + if (!script) + return; + const auto node = script->Graph.GetNode(nodeId); + if (!node) + return; + const auto box = node->GetBox(boxId); + if (!box) + return; + auto instance = Scripting::FindObject(instanceId); + + // Add to the calling stack + VisualScripting::ScopeContext scope; + auto& stack = ThreadStacks.Get(); + VisualScripting::StackFrame frame; + frame.Script = script; + frame.Node = node; + frame.Box = box; + frame.Instance = instance; + frame.PreviousFrame = stack.Stack; + frame.Scope = &scope; + stack.Stack = &frame; + stack.StackFramesCount++; + + // Call per group custom processing event + const auto func = VisualScriptingExecutor._perGroupProcessCall[node->GroupID]; + (VisualScriptingExecutor.*func)(box, node, result); + + // Remove from the calling stack + stack.StackFramesCount--; + stack.Stack = frame.PreviousFrame; +} + VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box) { // Check if graph is looped or is too deep @@ -1273,6 +1309,46 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val } } break; + } + // Delay + case 6: + { + boxBase = node->GetBox(2); + if (!boxBase->HasConnection()) + break; + const float duration = (float)tryGetValue(node->GetBox(1), node->Values[0]); + if (duration > ZeroTolerance) + { + class DelayTask : public MainThreadTask + { + public: + Guid Script; + Guid Instance; + int32 Node; + int32 Box; + + protected: + bool Run() override + { + Variant result; + VisualScriptingExecutor.Invoke(Script, Node, Box, Instance, result); + return false; + } + }; + const auto& stack = ThreadStacks.Get().Stack; + auto task = New(); + task->Script = stack->Script->GetID();; + task->Instance = stack->Instance->GetID();; + task->Node = ((Node*)boxBase->FirstConnection()->Parent)->ID; + task->Box = boxBase->FirstConnection()->ID; + task->InitialDelay = duration; + task->Start(); + } + else + { + eatBox(node, boxBase->FirstConnection()); + } + break; } } } @@ -1990,7 +2066,7 @@ void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeSt auto& params = instanceParams->Value.Params; for (auto i = stream.MemberBegin(); i != stream.MemberEnd(); ++i) { - StringAnsiView idNameAnsi(i->name.GetString(), i->name.GetStringLength()); + StringAnsiView idNameAnsi(i->name.GetStringAnsiView()); Guid paramId; if (!Guid::Parse(idNameAnsi, paramId)) { diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 1a1201191..27ee21b18 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -36,6 +36,8 @@ public: /// VisualScriptExecutor(); + void Invoke(const Guid& scriptId, int32 nodeId, int32 boxId, const Guid& instanceId, Variant& result) const; + private: Value eatBox(Node* caller, Box* box) override; Graph* GetCurrentGraph() const override; diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 44baecb12..c9a07e665 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -75,7 +75,7 @@ void FindIds(ISerializable::DeserializeStream& node, Array& output) { // Try parse as Guid in format `N` (32 hex chars) Guid id; - if (!Guid::Parse(node.GetText(), id)) + if (!Guid::Parse(node.GetStringAnsiView(), id)) output.Add(id); } } diff --git a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h index a598538f9..ef25ad07c 100644 --- a/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/TextureAssetUpgrader.h @@ -480,7 +480,6 @@ private: newHeader.IsSRGB = textureHeader.IsSRGB; newHeader.NeverStream = textureHeader.NeverStream; newHeader.Type = textureHeader.Type; - newHeader.IsCubeMap = false; context.Output.CustomData.Copy(&newHeader); // Convert import options @@ -535,7 +534,6 @@ private: newHeader.MipLevels = textureHeader.MipLevels; newHeader.Format = PixelFormatOldToNew(textureHeader.Format); newHeader.IsSRGB = textureHeader.IsSRGB; - newHeader.NeverStream = false; newHeader.Type = TextureFormatType::ColorRGB; newHeader.IsCubeMap = true; context.Output.CustomData.Copy(&newHeader); @@ -617,7 +615,6 @@ private: newHeader.IsSRGB = textureHeader.IsSRGB; newHeader.NeverStream = textureHeader.NeverStream; newHeader.Type = textureHeader.Type; - newHeader.IsCubeMap = false; context.Output.CustomData.Copy(&newHeader); // Copy sprite atlas data from old header stream to chunk (chunk 15th) @@ -696,7 +693,6 @@ private: newHeader.IsSRGB = textureHeader.IsSRGB; newHeader.NeverStream = textureHeader.NeverStream; newHeader.Type = textureHeader.Type; - newHeader.IsCubeMap = false; auto data = (IESProfile::CustomDataLayout*)newHeader.CustomData; data->Brightness = brightness; data->TextureMultiplier = multiplier; @@ -765,7 +761,6 @@ private: newHeader.IsSRGB = textureHeader.IsSRGB; newHeader.NeverStream = textureHeader.NeverStream; newHeader.Type = textureHeader.Type; - newHeader.IsCubeMap = false; context.Output.CustomData.Copy(&newHeader); // Copy asset previews IDs mapping data from old chunk to new one diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index 49401bf25..678e2b998 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -15,7 +15,7 @@ public: protected: - Asset* _asset; + Asset* _asset = nullptr; public: @@ -25,33 +25,23 @@ public: EventType Unload; public: + NON_COPYABLE(WeakAssetReferenceBase); /// /// Initializes a new instance of the class. /// - WeakAssetReferenceBase() - : _asset(nullptr) - { - } + WeakAssetReferenceBase() = default; /// /// Finalizes an instance of the class. /// - ~WeakAssetReferenceBase() - { - if (_asset) - { - _asset->OnUnloaded.Unbind(this); - _asset = nullptr; - } - } + ~WeakAssetReferenceBase(); public: /// /// Gets the asset ID or Guid::Empty if not set. /// - /// The asset ID or Guid::Empty if not set. FORCE_INLINE Guid GetID() const { return _asset ? _asset->GetID() : Guid::Empty; @@ -60,7 +50,6 @@ public: /// /// Gets managed instance object (or null if no asset set). /// - /// Mono managed object FORCE_INLINE MonoObject* GetManagedInstance() const { return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; @@ -69,35 +58,12 @@ public: /// /// Gets the asset property value as string. /// - /// The string. - String ToString() const - { - static String NullStr = TEXT(""); - return _asset ? _asset->ToString() : NullStr; - } + String ToString() const; protected: - void OnSet(Asset* asset) - { - auto e = _asset; - if (e != asset) - { - if (e) - e->OnUnloaded.Unbind(this); - _asset = e = asset; - if (e) - e->OnUnloaded.Bind(this); - } - } - - void OnAssetUnloaded(Asset* asset) - { - ASSERT(_asset == asset); - Unload(); - asset->OnUnloaded.Unbind(this); - asset = nullptr; - } + void OnSet(Asset* asset); + void OnUnloaded(Asset* asset); }; /// @@ -135,6 +101,22 @@ public: OnSet(other.Get()); } + WeakAssetReference(WeakAssetReference&& other) + { + OnSet(other.Get()); + other.OnSet(nullptr); + } + + WeakAssetReference& operator=(WeakAssetReference&& other) + { + if (&other != this) + { + OnSet(other.Get()); + other.OnSet(nullptr); + } + return *this; + } + /// /// Finalizes an instance of the class. /// @@ -175,7 +157,6 @@ public: /// /// Implicit conversion to the bool. /// - /// The asset. FORCE_INLINE operator T*() const { return (T*)_asset; @@ -184,7 +165,6 @@ public: /// /// Implicit conversion to the asset. /// - /// True if asset has been set, otherwise false. FORCE_INLINE operator bool() const { return _asset != nullptr; @@ -193,7 +173,6 @@ public: /// /// Implicit conversion to the asset. /// - /// The asset. FORCE_INLINE T* operator->() const { return (T*)_asset; @@ -202,7 +181,6 @@ public: /// /// Gets the asset. /// - /// The asset. FORCE_INLINE T* Get() const { return (T*)_asset; @@ -211,7 +189,6 @@ public: /// /// Gets the asset as a given type (static cast). /// - /// The asset. template FORCE_INLINE U* As() const { diff --git a/Source/Engine/ContentImporters/ImportTexture.cpp b/Source/Engine/ContentImporters/ImportTexture.cpp index 7daa62d9d..99c752a31 100644 --- a/Source/Engine/ContentImporters/ImportTexture.cpp +++ b/Source/Engine/ContentImporters/ImportTexture.cpp @@ -168,6 +168,7 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu textureHeader.MipLevels = textureData.GetMipLevels(); textureHeader.IsSRGB = PixelFormatExtensions::IsSRGB(textureHeader.Format); textureHeader.IsCubeMap = isCubeMap; + textureHeader.TextureGroup = options.TextureGroup; ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS); // Save header @@ -306,6 +307,7 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu textureHeader.MipLevels = textureData.Mips.Count(); textureHeader.IsSRGB = PixelFormatExtensions::IsSRGB(textureHeader.Format); textureHeader.IsCubeMap = isCubeMap; + textureHeader.TextureGroup = options.TextureGroup; ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS); // Save header @@ -542,12 +544,10 @@ CreateAssetResult ImportTexture::ImportIES(class CreateAssetContext& context) textureHeader.Width = loader.GetWidth(); textureHeader.Height = loader.GetHeight(); textureHeader.MipLevels = 1; - textureHeader.NeverStream = false; textureHeader.Type = TextureFormatType::Unknown; textureHeader.Format = PixelFormat::R16_Float; - textureHeader.IsSRGB = false; - textureHeader.IsCubeMap = false; auto data = (IESProfile::CustomDataLayout*)textureHeader.CustomData; + static_assert(sizeof(IESProfile::CustomDataLayout) <= sizeof(textureHeader.CustomData), "Invalid Custom Data size in Texture Header."); data->Brightness = loader.GetBrightness(); data->TextureMultiplier = multiplier; ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS); diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 9b8c41245..5069c0ac2 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -493,17 +493,8 @@ public: /// Adds the other collection to the collection. /// /// The other collection to add. - FORCE_INLINE void Add(const Array& other) - { - Add(other.Get(), other.Count()); - } - - /// - /// Adds the other collection to the collection. - /// - /// The other collection to add. - template - FORCE_INLINE void Add(const Array& other) + template + FORCE_INLINE void Add(const Array& other) { Add(other.Get(), other.Count()); } diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h index 1ed78ffe2..0d24e980d 100644 --- a/Source/Engine/Core/Collections/ChunkedArray.h +++ b/Source/Engine/Core/Collections/ChunkedArray.h @@ -46,7 +46,6 @@ public: /// /// Gets the amount of the elements in the collection. /// - /// The amount of the elements in the collection. FORCE_INLINE int32 Count() const { return _count; @@ -55,7 +54,6 @@ public: /// /// Gets the amount of the elements that can be hold by collection without resizing. /// - /// The current capacity of the collection. FORCE_INLINE int32 Capacity() const { return _chunks.Count() * ChunkSize; @@ -64,7 +62,6 @@ public: /// /// Returns true if array isn't empty. /// - /// True if array has any elements added, otherwise it is empty. FORCE_INLINE bool HasItems() const { return _count != 0; @@ -73,7 +70,6 @@ public: /// /// Returns true if collection is empty. /// - /// True if array is empty, otherwise it has any elements added. FORCE_INLINE bool IsEmpty() const { return _count == 0; @@ -154,20 +150,12 @@ public: public: - /// - /// Checks if iterator is in the end of the collection. - /// - /// True if is in the end, otherwise false. bool IsEnd() const { ASSERT(_collection); return Index() == _collection->Count(); } - /// - /// Checks if iterator is not in the end of the collection. - /// - /// True if is not in the end, otherwise false. bool IsNotEnd() const { ASSERT(_collection); @@ -331,6 +319,36 @@ public: return &chunk->At(chunk->Count() - 1); } + /// + /// Adds the one item to the collection and returns the reference to it. + /// + /// The reference to the added item. + T& AddOne() + { + // Find first chunk with some space + Chunk* chunk = nullptr; + for (int32 i = 0; i < _chunks.Count(); i++) + { + if (_chunks[i]->Count() < ChunkSize) + { + chunk = _chunks[i]; + break; + } + } + + // Allocate chunk if missing + if (chunk == nullptr) + { + chunk = New(); + chunk->SetCapacity(ChunkSize); + _chunks.Add(chunk); + } + + // Add item + _count++; + return chunk->AddOne(); + } + /// /// Removes the element at specified iterator position. /// @@ -408,7 +426,6 @@ public: /// The new size. void Resize(int32 newSize) { - // Check if shrink if (newSize < Count()) { MISSING_CODE("shrinking ChunkedArray on Resize"); @@ -439,7 +456,6 @@ public: chunkIndex++; } } - ASSERT(newSize == Count()); } diff --git a/Source/Engine/Core/Collections/RingBuffer.h b/Source/Engine/Core/Collections/RingBuffer.h new file mode 100644 index 000000000..8a8deb86f --- /dev/null +++ b/Source/Engine/Core/Collections/RingBuffer.h @@ -0,0 +1,107 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Platform/Platform.h" +#include "Engine/Core/Memory/Memory.h" +#include "Engine/Core/Memory/Allocation.h" + +/// +/// Template for ring buffer with variable capacity. +/// +template +class RingBuffer +{ +public: + + typedef T ItemType; + typedef typename AllocationType::template Data AllocationData; + +private: + + int32 _front = 0, _back = 0, _count = 0, _capacity = 0; + AllocationData _allocation; + +public: + + ~RingBuffer() + { + Memory::DestructItems(Get() + Math::Min(_front, _back), _count); + } + + FORCE_INLINE T* Get() + { + return _allocation.Get(); + } + + FORCE_INLINE int32 Count() const + { + return _count; + } + + FORCE_INLINE int32 Capacity() const + { + return _capacity; + } + + void PushBack(const T& data) + { + if (_capacity == 0 || _capacity == _count) + { + const int32 capacity = _allocation.CalculateCapacityGrow(_capacity, _count + 1); + AllocationData alloc; + alloc.Allocate(capacity); + const int32 frontCount = Math::Min(_capacity - _front, _count); + Memory::MoveItems(alloc.Get(), _allocation.Get() + _front, frontCount); + Memory::DestructItems(_allocation.Get() + _front, frontCount); + const int32 backCount = _count - frontCount; + Memory::MoveItems(alloc.Get() + frontCount, _allocation.Get(), backCount); + Memory::DestructItems(_allocation.Get(), backCount); + _allocation.Swap(alloc); + _front = 0; + _back = _count; + _capacity = capacity; + } + Memory::ConstructItems(_allocation.Get() + _back, &data, 1); + _back = (_back + 1) % _capacity; + _count++; + } + + FORCE_INLINE T& PeekFront() + { + ASSERT(_front != _back); + return _allocation.Get()[_front]; + } + + FORCE_INLINE const T& PeekFront() const + { + ASSERT(_front != _back); + return _allocation.Get()[_front]; + } + + FORCE_INLINE T& operator[](int32 index) + { + ASSERT(index >= 0 && index < _count); + return _allocation.Get()[(_front + index) % _capacity]; + } + + FORCE_INLINE const T& operator[](int32 index) const + { + ASSERT(index >= 0 && index < _count); + return _allocation.Get()[(_front + index) % _capacity]; + } + + void PopFront() + { + ASSERT(_front != _back); + Memory::DestructItems(_allocation.Get() + _front, 1); + _front = (_front + 1) % _capacity; + _count--; + } + + void Clear() + { + Memory::DestructItems(Get() + Math::Min(_front, _back), _count); + _front = _back = _count = 0; + } +}; diff --git a/Source/Engine/Core/Collections/SamplesBuffer.h b/Source/Engine/Core/Collections/SamplesBuffer.h index 95a68bcd0..b6058701e 100644 --- a/Source/Engine/Core/Collections/SamplesBuffer.h +++ b/Source/Engine/Core/Collections/SamplesBuffer.h @@ -13,27 +13,14 @@ class SamplesBuffer { protected: - int32 _count; + int32 _count = 0; T _data[Size]; - // Note: first element is _data[0] - -public: - - /// - /// Initializes a new instance of the class. - /// - SamplesBuffer() - : _count(0) - { - } - public: /// /// Gets amount of elements in the collection. /// - /// The collection size. FORCE_INLINE int32 Count() const { return _count; @@ -42,7 +29,6 @@ public: /// /// Gets amount of elements that can be added to the collection. /// - /// The collection capacity. FORCE_INLINE int32 Capacity() const { return Size; @@ -51,7 +37,6 @@ public: /// /// Returns true if collection has any elements added. /// - /// True if collection has any elements added, otherwise false. FORCE_INLINE bool HasItems() const { return _count > 0; @@ -60,7 +45,6 @@ public: /// /// Returns true if collection is empty. /// - /// True if collection is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _count < 1; @@ -69,7 +53,6 @@ public: /// /// Gets pointer to the first element in the collection. /// - /// Pointer to the first element in the collection. FORCE_INLINE T* Get() const { return _data; @@ -111,7 +94,6 @@ public: /// /// Gets the first element value. /// - /// The first element. FORCE_INLINE T First() const { ASSERT(HasItems()); @@ -121,7 +103,6 @@ public: /// /// Gets last element value. /// - /// The last element. FORCE_INLINE T Last() const { ASSERT(HasItems()); @@ -136,15 +117,13 @@ public: /// The value to add. void Add(const T& value) { - // Move previous elements - for (int32 i = _count - 1; i > 0; i--) - _data[i] = _data[i - 1]; - - // Update elements count if (_count != Size) _count++; - - // Insert element + if (_count > 1) + { + for (int32 i = _count - 1; i > 0; i--) + _data[i] = _data[i - 1]; + } _data[0] = value; } @@ -171,7 +150,6 @@ public: /// /// Gets the minimum value in the buffer. /// - /// The minimum value. T Minimum() const { ASSERT(HasItems()); @@ -187,7 +165,6 @@ public: /// /// Gets the maximum value in the buffer. /// - /// The maximum value. T Maximum() const { ASSERT(HasItems()); @@ -203,7 +180,6 @@ public: /// /// Gets the average value in the buffer. /// - /// The average value. T Average() const { ASSERT(HasItems()); diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index 529171639..211bad866 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -18,19 +18,19 @@ public: /// /// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to. /// - API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, 32, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")") + API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, 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)\")") + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, 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\")") + API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), EditorDisplay(\"General\")") int32 ContentKey = 0; /// diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 5b333b227..26d9175b0 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -19,6 +19,7 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" +#include "Engine/Streaming/StreamingSettings.h" class GameSettingsService : public EngineService { @@ -42,6 +43,7 @@ IMPLEMENT_SETTINGS_GETTER(TimeSettings, Time); IMPLEMENT_SETTINGS_GETTER(AudioSettings, Audio); IMPLEMENT_SETTINGS_GETTER(PhysicsSettings, Physics); IMPLEMENT_SETTINGS_GETTER(InputSettings, Input); +IMPLEMENT_SETTINGS_GETTER(StreamingSettings, Streaming); #if !USE_EDITOR #if PLATFORM_WINDOWS @@ -130,6 +132,7 @@ bool GameSettings::Load() PRELOAD_SETTINGS(Navigation); PRELOAD_SETTINGS(Localization); PRELOAD_SETTINGS(GameCooking); + PRELOAD_SETTINGS(Streaming); #undef PRELOAD_SETTINGS // Apply the game settings to the engine @@ -157,6 +160,7 @@ void GameSettings::Apply() APPLY_SETTINGS(AudioSettings); APPLY_SETTINGS(LayersAndTagsSettings); APPLY_SETTINGS(PhysicsSettings); + APPLY_SETTINGS(StreamingSettings); APPLY_SETTINGS(InputSettings); APPLY_SETTINGS(GraphicsSettings); APPLY_SETTINGS(NavigationSettings); @@ -202,6 +206,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(Navigation); DESERIALIZE(Localization); DESERIALIZE(GameCooking); + DESERIALIZE(Streaming); // Per-platform settings containers DESERIALIZE(WindowsPlatform); diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index ed96ef4e6..b9ba4e110 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -91,6 +91,12 @@ namespace FlaxEditor.Content.Settings [EditorOrder(1050), EditorDisplay("Other Settings"), AssetReference(typeof(BuildSettings), true), Tooltip("Reference to Build Settings asset")] public JsonAsset GameCooking; + /// + /// Reference to asset. + /// + [EditorOrder(1060), EditorDisplay("Other Settings"), AssetReference(typeof(StreamingSettings), true), Tooltip("Reference to Streaming Settings asset")] + public JsonAsset Streaming; + /// /// 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). /// @@ -229,6 +235,8 @@ namespace FlaxEditor.Content.Settings return LoadAsset(gameSettings.Localization) as T; if (type == typeof(BuildSettings)) return LoadAsset(gameSettings.GameCooking) as T; + if (type == typeof(StreamingSettings)) + return LoadAsset(gameSettings.Streaming) as T; if (type == typeof(InputSettings)) return LoadAsset(gameSettings.Input) as T; if (type == typeof(AudioSettings)) @@ -333,6 +341,8 @@ namespace FlaxEditor.Content.Settings return SaveAsset(gameSettings, ref gameSettings.Localization, obj); if (type == typeof(BuildSettings)) return SaveAsset(gameSettings, ref gameSettings.GameCooking, obj); + if (type == typeof(StreamingSettings)) + return SaveAsset(gameSettings, ref gameSettings.Streaming, obj); if (type == typeof(InputSettings)) return SaveAsset(gameSettings, ref gameSettings.Input, obj); if (type == typeof(WindowsPlatformSettings)) diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 8016bdbe4..0737272a3 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -70,6 +70,7 @@ public: Guid Navigation; Guid Localization; Guid GameCooking; + Guid Streaming; // Per-platform settings containers Guid WindowsPlatform; diff --git a/Source/Engine/Core/Math/BoundingBox.cpp b/Source/Engine/Core/Math/BoundingBox.cpp index 439b4341f..81ef146e6 100644 --- a/Source/Engine/Core/Math/BoundingBox.cpp +++ b/Source/Engine/Core/Math/BoundingBox.cpp @@ -26,6 +26,13 @@ void BoundingBox::FromPoints(const Vector3* points, int32 pointsCount, BoundingB result = BoundingBox(min, max); } +BoundingBox BoundingBox::FromPoints(const Vector3* points, int32 pointsCount) +{ + BoundingBox result; + FromPoints(points, pointsCount, result); + return result; +} + void BoundingBox::FromSphere(const BoundingSphere& sphere, BoundingBox& result) { result = BoundingBox( @@ -34,6 +41,38 @@ void BoundingBox::FromSphere(const BoundingSphere& sphere, BoundingBox& result) ); } +BoundingBox BoundingBox::FromSphere(const BoundingSphere& sphere) +{ + BoundingBox result; + FromSphere(sphere, result); + return result; +} + +BoundingBox BoundingBox::Transform(const BoundingBox& box, const Matrix& matrix) +{ + BoundingBox result; + Transform(box, matrix, result); + return result; +} + +BoundingBox BoundingBox::MakeOffsetted(const BoundingBox& box, const Vector3& offset) +{ + BoundingBox result; + result.Minimum = box.Minimum + offset; + result.Maximum = box.Maximum + offset; + return result; +} + +BoundingBox BoundingBox::MakeScaled(const BoundingBox& box, float scale) +{ + Vector3 size; + Vector3::Subtract(box.Maximum, box.Minimum, size); + Vector3 sizeHalf = size * 0.5f; + const Vector3 center = box.Minimum + sizeHalf; + sizeHalf = sizeHalf * scale; + return BoundingBox(center - sizeHalf, center + sizeHalf); +} + void BoundingBox::Transform(const BoundingBox& box, const Matrix& matrix, BoundingBox& result) { // Reference: http://dev.theomader.com/transform-bounding-boxes/ diff --git a/Source/Engine/Core/Math/BoundingBox.cs b/Source/Engine/Core/Math/BoundingBox.cs index 784f39930..6538f7995 100644 --- a/Source/Engine/Core/Math/BoundingBox.cs +++ b/Source/Engine/Core/Math/BoundingBox.cs @@ -469,6 +469,35 @@ namespace FlaxEngine */ } + /// + /// Creates the bounding box that is offseted by the given vector. Adds the offset value to minimum and maximum points. + /// + /// The box. + /// The bounds offset. + /// The offsetted bounds. + public static BoundingBox MakeOffsetted(ref BoundingBox box, ref Vector3 offset) + { + BoundingBox result; + Vector3.Add(ref box.Minimum, ref offset, out result.Minimum); + Vector3.Add(ref box.Maximum, ref offset, out result.Maximum); + return result; + } + + /// + /// Creates the bounding box that is scaled by the given factor. Applies scale to the size of the bounds. + /// + /// The box. + /// The bounds scale. + /// The scaled bounds. + public static BoundingBox MakeScaled(ref BoundingBox box, float scale) + { + Vector3.Subtract(ref box.Maximum, ref box.Minimum, out var size); + Vector3 sizeHalf = size * 0.5f; + Vector3 center = box.Minimum + sizeHalf; + sizeHalf = sizeHalf * scale; + return new BoundingBox(center - sizeHalf, center + sizeHalf); + } + /// /// Transforms bounding box using the given transformation matrix. /// diff --git a/Source/Engine/Core/Math/BoundingBox.h b/Source/Engine/Core/Math/BoundingBox.h index 8a349e022..92dd3bcc3 100644 --- a/Source/Engine/Core/Math/BoundingBox.h +++ b/Source/Engine/Core/Math/BoundingBox.h @@ -254,12 +254,7 @@ public: /// The points that will be contained by the box. /// The amount of points to use. /// The constructed bounding box. - static BoundingBox FromPoints(const Vector3* points, int32 pointsCount) - { - BoundingBox result; - FromPoints(points, pointsCount, result); - return result; - } + static BoundingBox FromPoints(const Vector3* points, int32 pointsCount); /// /// Constructs a Bounding Box from a given sphere. @@ -273,12 +268,7 @@ public: /// /// The sphere that will designate the extents of the box. /// The constructed bounding box. - static BoundingBox FromSphere(const BoundingSphere& sphere) - { - BoundingBox result; - FromSphere(sphere, result); - return result; - } + static BoundingBox FromSphere(const BoundingSphere& sphere); /// /// Constructs a Bounding Box that is as large as the total combined area of the two specified boxes. @@ -298,26 +288,23 @@ public: /// The box. /// The matrix. /// The result transformed box. - static BoundingBox Transform(const BoundingBox& box, const Matrix& matrix) - { - BoundingBox result; - Transform(box, matrix, result); - return result; - } + static BoundingBox Transform(const BoundingBox& box, const Matrix& matrix); /// /// Creates the bounding box that is offseted by the given vector. Adds the offset value to minimum and maximum points. /// /// The box. - /// The offset. - /// The result. - static BoundingBox MakeOffsetted(const BoundingBox& box, const Vector3& offset) - { - BoundingBox result; - result.Minimum = box.Minimum + offset; - result.Maximum = box.Maximum + offset; - return result; - } + /// The bounds offset. + /// The offsetted bounds. + static BoundingBox MakeOffsetted(const BoundingBox& box, const Vector3& offset); + + /// + /// Creates the bounding box that is scaled by the given factor. Applies scale to the size of the bounds. + /// + /// The box. + /// The bounds scale. + /// The scaled bounds. + static BoundingBox MakeScaled(const BoundingBox& box, float scale); /// /// Transforms the bounding box using the specified matrix. diff --git a/Source/Engine/Core/Math/BoundingFrustum.h b/Source/Engine/Core/Math/BoundingFrustum.h index 9052521fc..6056fd82e 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.h +++ b/Source/Engine/Core/Math/BoundingFrustum.h @@ -248,7 +248,7 @@ public: /// True if the current BoundingFrustum intersects a BoundingBox, otherwise false. FORCE_INLINE bool Intersects(const BoundingBox& box) const { - return CollisionsHelper::FrustumIntersectsBox(*this, box); + return CollisionsHelper::FrustumContainsBox(*this, box) != ContainmentType::Disjoint; } private: diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index d485e5fba..00ff0ea9b 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -1301,43 +1301,33 @@ bool CollisionsHelper::FrustumIntersectsBox(const BoundingFrustum& frustum, cons ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frustum, const BoundingBox& box) { - Vector3 p, n; - Plane plane; auto result = ContainmentType::Contains; - for (int32 i = 0; i < 6; i++) { - plane = frustum.GetPlane(i); - GetBoxToPlanePVertexNVertex(box, plane.Normal, p, n); - + Plane plane = frustum.GetPlane(i); + Vector3 p = box.Minimum; + if (plane.Normal.X >= 0) + p.X = box.Maximum.X; + if (plane.Normal.Y >= 0) + p.Y = box.Maximum.Y; + if (plane.Normal.Z >= 0) + p.Z = box.Maximum.Z; if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back) return ContainmentType::Disjoint; - if (PlaneIntersectsPoint(plane, n) == PlaneIntersectionType::Back) + p = box.Maximum; + if (plane.Normal.X >= 0) + p.X = box.Minimum.X; + if (plane.Normal.Y >= 0) + p.Y = box.Minimum.Y; + if (plane.Normal.Z >= 0) + p.Z = box.Minimum.Z; + if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back) result = ContainmentType::Intersects; } return result; } -void CollisionsHelper::GetBoxToPlanePVertexNVertex(const BoundingBox& box, const Vector3& planeNormal, Vector3& p, Vector3& n) -{ - p = box.Minimum; - if (planeNormal.X >= 0) - p.X = box.Maximum.X; - if (planeNormal.Y >= 0) - p.Y = box.Maximum.Y; - if (planeNormal.Z >= 0) - p.Z = box.Maximum.Z; - - n = box.Maximum; - if (planeNormal.X >= 0) - n.X = box.Minimum.X; - if (planeNormal.Y >= 0) - n.Y = box.Minimum.Y; - if (planeNormal.Z >= 0) - n.Z = box.Minimum.Z; -} - bool CollisionsHelper::LineIntersectsLine(const Vector2& l1p1, const Vector2& l1p2, const Vector2& l2p1, const Vector2& l2p2) { float q = (l1p1.Y - l2p1.Y) * (l2p2.X - l2p1.X) - (l1p1.X - l2p1.X) * (l2p2.Y - l2p1.Y); diff --git a/Source/Engine/Core/Math/CollisionsHelper.cs b/Source/Engine/Core/Math/CollisionsHelper.cs index d6e0392c8..7d85d2407 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cs +++ b/Source/Engine/Core/Math/CollisionsHelper.cs @@ -163,6 +163,38 @@ namespace FlaxEngine } } + /// + /// Determines the closest point between a point and a line. + /// + /// The point to test. + /// The line first point. + /// The line second point. + /// When the method completes, contains the closest point between the two objects. + public static void ClosestPointPointLine(ref Vector3 point, ref Vector3 p0, ref Vector3 p1, out Vector3 result) + { + Vector3 p = point - p0; + Vector3 n = p1 - p0; + float length = n.Length; + if (length < 1e-10f) + { + result = p0; + return; + } + n /= length; + float dot = Vector3.Dot(ref n, ref p); + if (dot <= 0.0f) + { + result = p0; + return; + } + if (dot >= length) + { + result = p1; + return; + } + result = p0 + n * dot; + } + /// /// Determines the closest point between a point and a triangle. /// diff --git a/Source/Engine/Core/Math/CollisionsHelper.h b/Source/Engine/Core/Math/CollisionsHelper.h index 692b86dbe..9ef940c3e 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.h +++ b/Source/Engine/Core/Math/CollisionsHelper.h @@ -550,8 +550,6 @@ public: static ContainmentType FrustumContainsBox(const BoundingFrustum& frustum, const BoundingBox& box); - static void GetBoxToPlanePVertexNVertex(const BoundingBox& box, const Vector3& planeNormal, Vector3& p, Vector3& n); - /// /// Determines whether a line intersects with the other line. /// diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs index 2a62df067..6d1521e85 100644 --- a/Source/Engine/Core/Math/Vector4.cs +++ b/Source/Engine/Core/Math/Vector4.cs @@ -153,6 +153,19 @@ namespace FlaxEngine W = w; } + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the values with which to initialize the X and Y components. + /// A vector containing the values with which to initialize the Z and W components. + public Vector4(Vector2 xy, Vector2 zw) + { + X = xy.X; + Y = xy.Y; + Z = zw.X; + W = zw.Y; + } + /// /// Initializes a new instance of the struct. /// diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index d7d42f211..6871cb287 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -5,7 +5,6 @@ #include "Engine/Platform/Platform.h" #include "Engine/Platform/StringUtils.h" #include "Engine/Core/Formatting.h" -#include "Engine/Core/Templates.h" /// /// Represents text as a sequence of characters. Container uses a single dynamic memory allocation to store the characters data. Characters sequence is always null-terminated. diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index e267b760e..b630c429c 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -17,23 +17,14 @@ protected: const T* _data; int32 _length; -public: - - /// - /// Copies the data. - /// - /// The other object. - /// The reference to this object. - FORCE_INLINE StringViewBase& operator=(const StringViewBase& other) + constexpr StringViewBase() { - if (this != &other) - { - _data = other._data; - _length = other._length; - } - return *this; + _data = nullptr; + _length = 0; } +public: + /// /// Gets the specific const character from this string. /// @@ -82,7 +73,7 @@ public: /// /// Gets the length of the string. /// - FORCE_INLINE int32 Length() const + FORCE_INLINE constexpr int32 Length() const { return _length; } @@ -90,7 +81,7 @@ public: /// /// Gets the pointer to the string. /// - FORCE_INLINE const T* operator*() const + FORCE_INLINE constexpr const T* operator*() const { return _data; } @@ -98,7 +89,7 @@ public: /// /// Gets the pointer to the string. /// - FORCE_INLINE const T* Get() const + FORCE_INLINE constexpr const T* Get() const { return _data; } @@ -202,10 +193,8 @@ public: /// /// Initializes a new instance of the class. /// - StringView() + constexpr StringView() { - _data = nullptr; - _length = 0; } /// @@ -218,7 +207,7 @@ public: /// Initializes a new instance of the class. /// /// The reference to the static string. - StringView(const StringView& str) + constexpr StringView(const StringView& str) { _data = str._data; _length = str._length; @@ -239,7 +228,7 @@ public: /// /// The characters sequence. /// The characters sequence length (excluding null-terminator character). - StringView(const Char* str, int32 length) + constexpr StringView(const Char* str, int32 length) { _data = str; _length = length; @@ -259,6 +248,21 @@ public: return *this; } + /// + /// Assigns the static string. + /// + /// The other object. + /// The reference to this object. + FORCE_INLINE constexpr StringView& operator=(const StringView& other) + { + if (this != &other) + { + _data = other._data; + _length = other._length; + } + return *this; + } + /// /// Lexicographically test whether this string is equivalent to the other given string (case sensitive). /// @@ -394,10 +398,8 @@ public: /// /// Initializes a new instance of the class. /// - StringAnsiView() + constexpr StringAnsiView() { - _data = nullptr; - _length = 0; } /// @@ -410,7 +412,7 @@ public: /// Initializes a new instance of the class. /// /// The reference to the static string. - StringAnsiView(const StringAnsiView& str) + constexpr StringAnsiView(const StringAnsiView& str) { _data = str._data; _length = str._length; @@ -431,7 +433,7 @@ public: /// /// The characters sequence. /// The characters sequence length (excluding null-terminator character). - StringAnsiView(const char* str, int32 length) + constexpr StringAnsiView(const char* str, int32 length) { _data = str; _length = length; @@ -451,6 +453,21 @@ public: return *this; } + /// + /// Assigns the static string. + /// + /// The other object. + /// The reference to this object. + FORCE_INLINE constexpr StringAnsiView& operator=(const StringAnsiView& other) + { + if (this != &other) + { + _data = other._data; + _length = other._length; + } + return *this; + } + /// /// Lexicographically test whether this string is equivalent to the other given string (case sensitive). /// diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index b443577fb..ac2b744bc 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/GPUPipelineState.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/DynamicBuffer.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/Shaders/GPUShader.h" @@ -31,9 +32,11 @@ // Debug draw service configuration #define DEBUG_DRAW_INITIAL_VB_CAPACITY (4 * 1024) // -#define DEBUG_DRAW_SPHERE_RESOLUTION 64 -#define DEBUG_DRAW_SPHERE_LINES_COUNT (DEBUG_DRAW_SPHERE_RESOLUTION + 1) * 3 -#define DEBUG_DRAW_SPHERE_VERTICES DEBUG_DRAW_SPHERE_LINES_COUNT * 2 +#define DEBUG_DRAW_SPHERE_LOD0_RESOLUTION 64 +#define DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE 0.2f +#define DEBUG_DRAW_SPHERE_LOD1_RESOLUTION 16 +#define DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE 0.08f +#define DEBUG_DRAW_SPHERE_LOD2_RESOLUTION 8 // #define DEBUG_DRAW_CIRCLE_RESOLUTION 32 #define DEBUG_DRAW_CIRCLE_LINES_COUNT DEBUG_DRAW_CIRCLE_RESOLUTION @@ -44,6 +47,39 @@ // #define DEBUG_DRAW_TRIANGLE_SPHERE_RESOLUTION 12 +struct DebugSphereCache +{ + Array> Vertices; + + void Init(int32 resolution) + { + const int32 verticesCount = (resolution + 1) * 3 * 2; + Vertices.Resize(verticesCount); + int32 index = 0; + const float step = TWO_PI / (float)resolution; + for (float a = 0.0f; a < TWO_PI; a += step) + { + // Calculate sines and cosines + const float sinA = Math::Sin(a); + const float cosA = Math::Cos(a); + const float sinB = Math::Sin(a + step); + const float cosB = Math::Cos(a + step); + + // XY loop + Vertices[index++] = Vector3(cosA, sinA, 0.0f); + Vertices[index++] = Vector3(cosB, sinB, 0.0f); + + // XZ loop + Vertices[index++] = Vector3(cosA, 0.0f, sinA); + Vertices[index++] = Vector3(cosB, 0.0f, sinB); + + // YZ loop + Vertices[index++] = Vector3(0.0f, cosA, sinA); + Vertices[index++] = Vector3(0.0f, cosB, sinB); + } + } +}; + struct DebugLine { Vector3 Start; @@ -154,7 +190,7 @@ void UpdateList(float dt, Array& list) struct DebugDrawData { Array DefaultLines; - Array OneFrameLines; + Array OneFrameLines; Array DefaultTriangles; Array OneFrameTriangles; Array DefaultWireTriangles; @@ -184,14 +220,6 @@ struct DebugDrawData return DefaultText2D.Count() + OneFrameText2D.Count() + DefaultText3D.Count() + OneFrameText3D.Count(); } - inline void Add(const DebugLine& l) - { - if (l.TimeLeft > 0) - DefaultLines.Add(l); - else - OneFrameLines.Add(l); - } - inline void Add(const DebugTriangle& t) { if (t.TimeLeft > 0) @@ -256,6 +284,8 @@ struct DebugDrawContext { DebugDrawData DebugDrawDefault; DebugDrawData DebugDrawDepthTest; + Vector3 LastViewPos = Vector3::Zero; + Matrix LastViewProj = Matrix::Identity; }; namespace @@ -271,19 +301,47 @@ namespace PsData DebugDrawPsTrianglesDefault; PsData DebugDrawPsTrianglesDepthTest; DynamicVertexBuffer* DebugDrawVB = nullptr; - Vector3 SphereCache[DEBUG_DRAW_SPHERE_VERTICES]; Vector3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; Array SphereTriangleCache; + DebugSphereCache SphereCache[3]; }; extern int32 BoxTrianglesIndicesCache[]; +int32 BoxLineIndicesCache[] = +{ + // @formatter:off + 0, 1, + 0, 3, + 0, 4, + 1, 2, + 1, 5, + 2, 3, + 2, 6, + 3, 7, + 4, 5, + 4, 7, + 5, 6, + 6, 7, + // @formatter:on +}; + struct DebugDrawCall { int32 StartVertex; int32 VertexCount; }; +DebugDrawCall WriteList(int32& vertexCounter, const Array& list) +{ + DebugDrawCall drawCall; + drawCall.StartVertex = vertexCounter; + drawCall.VertexCount = list.Count(); + DebugDrawVB->Write(list.Get(), sizeof(Vertex) * drawCall.VertexCount); + vertexCounter += drawCall.VertexCount; + return drawCall; +} + DebugDrawCall WriteList(int32& vertexCounter, const Array& list) { DebugDrawCall drawCall; @@ -293,12 +351,10 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) for (int32 i = 0; i < list.Count(); i++) { const DebugLine& l = list[i]; - vv[0].Position = l.Start; vv[0].Color = l.Color; vv[1].Position = l.End; vv[1].Color = vv[0].Color; - DebugDrawVB->Write(vv, sizeof(Vertex) * 2); } @@ -317,14 +373,12 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) for (int32 i = 0; i < list.Count(); i++) { const DebugTriangle& l = list[i]; - vv[0].Position = l.V0; vv[0].Color = l.Color; vv[1].Position = l.V1; vv[1].Color = vv[0].Color; vv[2].Position = l.V2; vv[2].Color = vv[0].Color; - DebugDrawVB->Write(vv, sizeof(Vertex) * 3); } @@ -334,12 +388,11 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) return drawCall; } -template -DebugDrawCall WriteLists(int32& vertexCounter, const Array& listA, const Array& listB) +template +DebugDrawCall WriteLists(int32& vertexCounter, const Array& listA, const Array& listB) { const DebugDrawCall drawCallA = WriteList(vertexCounter, listA); const DebugDrawCall drawCallB = WriteList(vertexCounter, listB); - DebugDrawCall drawCall; drawCall.StartVertex = drawCallA.StartVertex; drawCall.VertexCount = drawCallA.VertexCount + drawCallB.VertexCount; @@ -364,7 +417,6 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext, class DebugDrawService : public EngineService { public: - DebugDrawService() : EngineService(TEXT("Debug Draw"), -80) { @@ -382,32 +434,13 @@ bool DebugDrawService::Init() Context = &GlobalContext; // Init wireframe sphere cache - int32 index = 0; - float step = TWO_PI / DEBUG_DRAW_SPHERE_RESOLUTION; - for (float a = 0.0f; a < TWO_PI; a += step) - { - // Calculate sines and cosines - 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); - SphereCache[index++] = Vector3(cosB, sinB, 0.0f); - - // XZ loop - SphereCache[index++] = Vector3(cosA, 0.0f, sinA); - SphereCache[index++] = Vector3(cosB, 0.0f, sinB); - - // YZ loop - SphereCache[index++] = Vector3(0.0f, cosA, sinA); - SphereCache[index++] = Vector3(0.0f, cosB, sinB); - } + SphereCache[0].Init(DEBUG_DRAW_SPHERE_LOD0_RESOLUTION); + SphereCache[1].Init(DEBUG_DRAW_SPHERE_LOD1_RESOLUTION); + SphereCache[2].Init(DEBUG_DRAW_SPHERE_LOD2_RESOLUTION); // Init wireframe circle cache - index = 0; - step = TWO_PI / DEBUG_DRAW_CIRCLE_RESOLUTION; + int32 index = 0; + float step = TWO_PI / (float)DEBUG_DRAW_CIRCLE_RESOLUTION; for (float a = 0.0f; a < TWO_PI; a += step) { // Calculate sines and cosines @@ -634,27 +667,30 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe if (renderContext.Buffers == nullptr || !DebugDrawVB) return; auto context = GPUDevice::Instance->GetMainContext(); + Context->LastViewPos = renderContext.View.Position; + Context->LastViewProj = renderContext.View.Projection; // Fallback to task buffers if (target == nullptr && renderContext.Task) target = renderContext.Task->GetOutputView(); // Fill vertex buffer and upload data -#if COMPILE_WITH_PROFILER - const auto updateBufferProfileKey = ProfilerCPU::BeginEvent(TEXT("Update Buffer")); -#endif - DebugDrawVB->Clear(); - int32 vertexCounter = 0; - const DebugDrawCall depthTestLines = WriteLists(vertexCounter, Context->DebugDrawDepthTest.DefaultLines, Context->DebugDrawDepthTest.OneFrameLines); - const DebugDrawCall defaultLines = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultLines, Context->DebugDrawDefault.OneFrameLines); - const DebugDrawCall depthTestTriangles = WriteLists(vertexCounter, Context->DebugDrawDepthTest.DefaultTriangles, Context->DebugDrawDepthTest.OneFrameTriangles); - const DebugDrawCall defaultTriangles = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultTriangles, Context->DebugDrawDefault.OneFrameTriangles); - const DebugDrawCall depthTestWireTriangles = WriteLists(vertexCounter, Context->DebugDrawDepthTest.DefaultWireTriangles, Context->DebugDrawDepthTest.OneFrameWireTriangles); - const DebugDrawCall defaultWireTriangles = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultWireTriangles, Context->DebugDrawDefault.OneFrameWireTriangles); - DebugDrawVB->Flush(context); -#if COMPILE_WITH_PROFILER - ProfilerCPU::EndEvent(updateBufferProfileKey); -#endif + DebugDrawCall depthTestLines, defaultLines, depthTestTriangles, defaultTriangles, depthTestWireTriangles, defaultWireTriangles; + { + PROFILE_CPU_NAMED("Update Buffer"); + DebugDrawVB->Clear(); + int32 vertexCounter = 0; + depthTestLines = WriteLists(vertexCounter, Context->DebugDrawDepthTest.DefaultLines, Context->DebugDrawDepthTest.OneFrameLines); + defaultLines = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultLines, Context->DebugDrawDefault.OneFrameLines); + depthTestTriangles = WriteLists(vertexCounter, Context->DebugDrawDepthTest.DefaultTriangles, Context->DebugDrawDepthTest.OneFrameTriangles); + defaultTriangles = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultTriangles, Context->DebugDrawDefault.OneFrameTriangles); + depthTestWireTriangles = WriteLists(vertexCounter, Context->DebugDrawDepthTest.DefaultWireTriangles, Context->DebugDrawDepthTest.OneFrameWireTriangles); + defaultWireTriangles = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultWireTriangles, Context->DebugDrawDefault.OneFrameWireTriangles); + { + PROFILE_CPU_NAMED("Flush"); + DebugDrawVB->Flush(context); + } + } // Update constant buffer const auto cb = DebugDrawShader->GetShader()->GetCB(0); @@ -785,11 +821,11 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe namespace { - bool DrawActorsTreeWalk(Actor* actor) + bool DrawActorsTreeWalk(Actor* a) { - if (actor->IsActiveInHierarchy()) + if (a->IsActiveInHierarchy()) { - actor->OnDebugDraw(); + a->OnDebugDraw(); return true; } return false; @@ -819,14 +855,19 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest) { - // Create draw call entry - DebugLine l = { start, end, Color32(color), duration }; - - // Add line - if (depthTest) - Context->DebugDrawDepthTest.Add(l); + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) + { + DebugLine l = { start, end, Color32(color), duration }; + debugDrawData.DefaultLines.Add(l); + } else - Context->DebugDrawDefault.Add(l); + { + Vertex l = { start, Color32(color) }; + debugDrawData.OneFrameLines.Add(l); + l.Position = end; + debugDrawData.OneFrameLines.Add(l); + } } void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration, bool depthTest) @@ -837,37 +878,36 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, c return; } - // Create draw call entry - DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; - - // Add lines + // Draw lines const Vector3* p = lines.Get(); - Array* list; - - if (depthTest) - list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultLines : &Context->DebugDrawDepthTest.OneFrameLines; - else - list = duration > 0 ? &Context->DebugDrawDefault.DefaultLines : &Context->DebugDrawDefault.OneFrameLines; - - list->EnsureCapacity(list->Count() + lines.Length()); - for (int32 i = 0; i < lines.Length(); i += 2) + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) { - Vector3::Transform(*p++, transform, l.Start); - Vector3::Transform(*p++, transform, l.End); - list->Add(l); + DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; + debugDrawData.DefaultLines.EnsureCapacity(debugDrawData.DefaultLines.Count() + lines.Length()); + for (int32 i = 0; i < lines.Length(); i += 2) + { + Vector3::Transform(*p++, transform, l.Start); + Vector3::Transform(*p++, transform, l.End); + debugDrawData.DefaultLines.Add(l); + } + } + else + { + Vertex l = { Vector3::Zero, Color32(color) }; + debugDrawData.OneFrameLines.EnsureCapacity(debugDrawData.OneFrameLines.Count() + lines.Length() * 2); + for (int32 i = 0; i < lines.Length(); i += 2) + { + Vector3::Transform(*p++, transform, l.Position); + debugDrawData.OneFrameLines.Add(l); + Vector3::Transform(*p++, transform, l.Position); + debugDrawData.OneFrameLines.Add(l); + } } } 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 - Array* list; - if (depthTest) - list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultLines : &Context->DebugDrawDepthTest.OneFrameLines; - else - list = duration > 0 ? &Context->DebugDrawDefault.DefaultLines : &Context->DebugDrawDefault.OneFrameLines; - DebugLine l = { p1, Vector3::Zero, Color32(color), duration }; - // Find amount of segments to use const Vector3 d1 = p2 - p1; const Vector3 d2 = p3 - p2; @@ -875,33 +915,35 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& 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 / (float)segmentCount; - list->EnsureCapacity(list->Count() + segmentCount + 2); - // Draw segmented curve - for (int32 i = 0; i <= segmentCount; i++) + // Draw segmented curve from lines + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) { - const float t = (float)i * segmentCountInv; - AnimationUtils::Bezier(p1, p2, p3, p4, t, l.End); - list->Add(l); - l.Start = l.End; + DebugLine l = { p1, Vector3::Zero, Color32(color), duration }; + debugDrawData.DefaultLines.EnsureCapacity(debugDrawData.DefaultLines.Count() + segmentCount + 2); + for (int32 i = 0; i <= segmentCount; i++) + { + const float t = (float)i * segmentCountInv; + AnimationUtils::Bezier(p1, p2, p3, p4, t, l.End); + debugDrawData.DefaultLines.Add(l); + l.Start = l.End; + } + } + else + { + Vertex l = { p1, Color32(color) }; + debugDrawData.OneFrameLines.EnsureCapacity(debugDrawData.OneFrameLines.Count() + segmentCount * 2 + 4); + for (int32 i = 0; i <= segmentCount; i++) + { + const float t = (float)i * segmentCountInv; + debugDrawData.OneFrameLines.Add(l); + AnimationUtils::Bezier(p1, p2, p3, p4, t, l.Position); + debugDrawData.OneFrameLines.Add(l); + } } } -#define DRAW_WIRE_BOX_LINE(i0, i1, list) l.Start = corners[i0]; l.End = corners[i1]; Context->list.Add(l) -#define DRAW_WIRE_BOX(list) \ - DRAW_WIRE_BOX_LINE(0, 1, list); \ - DRAW_WIRE_BOX_LINE(0, 3, list); \ - DRAW_WIRE_BOX_LINE(0, 4, list); \ - DRAW_WIRE_BOX_LINE(1, 2, list); \ - DRAW_WIRE_BOX_LINE(1, 5, list); \ - DRAW_WIRE_BOX_LINE(2, 3, list); \ - DRAW_WIRE_BOX_LINE(2, 6, list); \ - DRAW_WIRE_BOX_LINE(3, 7, list); \ - DRAW_WIRE_BOX_LINE(4, 5, list); \ - DRAW_WIRE_BOX_LINE(4, 7, list); \ - DRAW_WIRE_BOX_LINE(5, 6, list); \ - DRAW_WIRE_BOX_LINE(6, 7, list) - void DebugDraw::DrawWireBox(const BoundingBox& box, const Color& color, float duration, bool depthTest) { // Get corners @@ -909,16 +951,28 @@ void DebugDraw::DrawWireBox(const BoundingBox& box, const Color& color, float du box.GetCorners(corners); // Draw lines - DebugLine l; - l.Color = Color32(color); - l.TimeLeft = duration; - if (depthTest) + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) { - DRAW_WIRE_BOX(DebugDrawDepthTest); + DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; + for (uint32 i = 0; i < ARRAY_COUNT(BoxLineIndicesCache);) + { + l.Start = corners[BoxLineIndicesCache[i++]]; + l.End = corners[BoxLineIndicesCache[i++]]; + debugDrawData.DefaultLines.Add(l); + } } else { - DRAW_WIRE_BOX(DebugDrawDefault); + // TODO: draw lines as strips to reuse vertices with a single draw call + Vertex l = { Vector3::Zero, Color32(color) }; + for (uint32 i = 0; i < ARRAY_COUNT(BoxLineIndicesCache);) + { + l.Position = corners[BoxLineIndicesCache[i++]]; + debugDrawData.OneFrameLines.Add(l); + l.Position = corners[BoxLineIndicesCache[i++]]; + debugDrawData.OneFrameLines.Add(l); + } } } @@ -929,16 +983,28 @@ void DebugDraw::DrawWireFrustum(const BoundingFrustum& frustum, const Color& col frustum.GetCorners(corners); // Draw lines - DebugLine l; - l.Color = Color32(color); - l.TimeLeft = duration; - if (depthTest) + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) { - DRAW_WIRE_BOX(DebugDrawDepthTest); + DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; + for (uint32 i = 0; i < ARRAY_COUNT(BoxLineIndicesCache);) + { + l.Start = corners[BoxLineIndicesCache[i++]]; + l.End = corners[BoxLineIndicesCache[i++]]; + debugDrawData.DefaultLines.Add(l); + } } else { - DRAW_WIRE_BOX(DebugDrawDefault); + // TODO: draw lines as strips to reuse vertices with a single draw call + Vertex l = { Vector3::Zero, Color32(color) }; + for (uint32 i = 0; i < ARRAY_COUNT(BoxLineIndicesCache);) + { + l.Position = corners[BoxLineIndicesCache[i++]]; + debugDrawData.OneFrameLines.Add(l); + l.Position = corners[BoxLineIndicesCache[i++]]; + debugDrawData.OneFrameLines.Add(l); + } } } @@ -949,41 +1015,65 @@ void DebugDraw::DrawWireBox(const OrientedBoundingBox& box, const Color& color, box.GetCorners(corners); // Draw lines - DebugLine l; - l.Color = Color32(color); - l.TimeLeft = duration; - if (depthTest) + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) { - DRAW_WIRE_BOX(DebugDrawDepthTest); + DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; + for (uint32 i = 0; i < ARRAY_COUNT(BoxLineIndicesCache);) + { + l.Start = corners[BoxLineIndicesCache[i++]]; + l.End = corners[BoxLineIndicesCache[i++]]; + debugDrawData.DefaultLines.Add(l); + } } else { - DRAW_WIRE_BOX(DebugDrawDefault); + // TODO: draw lines as strips to reuse vertices with a single draw call + Vertex l = { Vector3::Zero, Color32(color) }; + for (uint32 i = 0; i < ARRAY_COUNT(BoxLineIndicesCache);) + { + l.Position = corners[BoxLineIndicesCache[i++]]; + debugDrawData.OneFrameLines.Add(l); + l.Position = corners[BoxLineIndicesCache[i++]]; + debugDrawData.OneFrameLines.Add(l); + } } } void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color, float duration, bool depthTest) { + // Select LOD + int32 index; + const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, Context->LastViewPos, Context->LastViewProj); + if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * 0.25f) + index = 0; + else if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * 0.25f) + index = 1; + else + index = 2; + auto& cache = SphereCache[index]; + // Draw lines of the unit sphere after linear transform - DebugLine l; - l.Color = Color32(color); - l.TimeLeft = duration; - if (depthTest) + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + if (duration > 0) { - for (int32 i = 0; i < DEBUG_DRAW_SPHERE_VERTICES;) + DebugLine l = { Vector3::Zero, Vector3::Zero, Color32(color), duration }; + for (int32 i = 0; i < cache.Vertices.Count();) { - l.Start = sphere.Center + SphereCache[i++] * sphere.Radius; - l.End = sphere.Center + SphereCache[i++] * sphere.Radius; - Context->DebugDrawDepthTest.Add(l); + l.Start = sphere.Center + cache.Vertices.Get()[i++] * sphere.Radius; + l.End = sphere.Center + cache.Vertices.Get()[i++] * sphere.Radius; + debugDrawData.DefaultLines.Add(l); } } else { - for (int32 i = 0; i < DEBUG_DRAW_SPHERE_VERTICES;) + Vertex l = { Vector3::Zero, Color32(color) }; + for (int32 i = 0; i < cache.Vertices.Count();) { - l.Start = sphere.Center + SphereCache[i++] * sphere.Radius; - l.End = sphere.Center + SphereCache[i++] * sphere.Radius; - Context->DebugDrawDefault.Add(l); + l.Position = sphere.Center + cache.Vertices.Get()[i++] * sphere.Radius; + debugDrawData.OneFrameLines.Add(l); + l.Position = sphere.Center + cache.Vertices.Get()[i++] * sphere.Radius; + debugDrawData.OneFrameLines.Add(l); } } } @@ -1190,7 +1280,8 @@ void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientat else { // Set up - const float step = TWO_PI / DEBUG_DRAW_SPHERE_RESOLUTION; + const int32 resolution = 64; + const float step = TWO_PI / (float)resolution; radius = Math::Max(radius, 0.05f); length = Math::Max(length, 0.05f); const float halfLength = length / 2.0f; diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index d6c03b070..9f5078c64 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -102,6 +102,23 @@ bool CommandLine::Parse(const Char* cmdLine) *(end - len) = 0; \ end -= len; \ } + +#define PARSE_ARG_OPT_SWITCH(text, field) \ + pos = (Char*)StringUtils::FindIgnoreCase(buffer.Get(), TEXT(text)); \ + if (pos) \ + { \ + len = ARRAY_COUNT(text) - 1; \ + if (ParseArg(pos + len, argStart, argEnd)) \ + Options.field = String::Empty; \ + else \ + { \ + Options.field = String(argStart, static_cast(argEnd - argStart)); \ + len = static_cast((argEnd - pos) + 1); \ + Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \ + *(end - len) = 0; \ + end -= len; \ + } \ + } PARSE_BOOL_SWITCH("-windowed ", Windowed); PARSE_BOOL_SWITCH("-fullscreen ", Fullscreen); @@ -137,6 +154,7 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_ARG_SWITCH("-build ", Build); PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug); + PARSE_ARG_OPT_SWITCH("-play ", Play); #endif diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 62b38ca99..d9d3547d3 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -164,6 +164,11 @@ public: /// Nullable ShaderDebug; + /// + /// -play !guid! ( Scene to play, can be empty to use default ) + /// + Nullable Play; + #endif }; diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 24d691c56..020f53faa 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -10,18 +10,11 @@ #include "FlaxEngine.Gen.h" #include "Engine/Core/Core.h" #include "Engine/Core/Log.h" -#include "Engine/Core/Utilities.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Types/String.h" #include "Engine/Platform/Platform.h" -#include "Engine/Platform/CPUInfo.h" -#include "Engine/Platform/MemoryStats.h" #include "Engine/Platform/Window.h" #include "Engine/Platform/FileSystem.h" -#include "Engine/Serialization/FileWriteStream.h" -#include "Engine/Serialization/FileReadStream.h" -#include "Engine/Level/Level.h" -#include "Engine/Renderer/Renderer.h" #include "Engine/Physics/Physics.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/MainThreadTask.h" @@ -34,6 +27,7 @@ #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Profiler/Profiler.h" +#include "Engine/Threading/TaskGraph.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/ProjectInfo.h" @@ -69,6 +63,7 @@ bool Engine::HasFocus = false; uint64 Engine::FrameCount = 0; Action Engine::FixedUpdate; Action Engine::Update; +TaskGraph* Engine::UpdateGraph = nullptr; Action Engine::LateUpdate; Action Engine::Draw; Action Engine::Pause; @@ -129,6 +124,7 @@ int32 Engine::Main(const Char* cmdLine) #endif // Initialize engine + UpdateGraph = New(); EngineService::OnInit(); if (Application::Init()) return -10; @@ -146,26 +142,22 @@ int32 Engine::Main(const Char* cmdLine) EngineImpl::IsReady = true; // Main engine loop - bool canDraw = true; // Prevent drawing 2 or more frames in a row without update or fixed update (nothing will change) + const bool useSleep = true; // TODO: this should probably be a platform setting while (!ShouldExit()) { -#if 0 - // TODO: test it more and maybe use in future to reduce CPU usage - // Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend - { - float tickFps; - auto tick = Time->GetHighestFrequency(tickFps); - double tickTargetStepTime = 1.0 / tickFps; - double nextTick = tick->LastEnd + tickTargetStepTime; - double timeToTick = nextTick - Platform::GetTimeSeconds(); - int32 sleepTimeMs = Math::Min(4, Math::FloorToInt(timeToTick * (1000.0 * 0.8))); // Convert seconds to milliseconds and apply adjustment with limit - if (!Device->WasVSyncUsed() && sleepTimeMs > 0) - { - PROFILE_CPU_NAMED("Idle"); - Platform::Sleep(sleepTimeMs); - } - } -#endif + // Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend + if ((useSleep && Time::UpdateFPS > 0) || !Platform::GetHasFocus()) + { + double nextTick = Time::GetNextTick(); + double timeToTick = nextTick - Platform::GetTimeSeconds(); + + // Sleep less than needed, some platforms may sleep slightly more than requested + if (timeToTick > 0.002) + { + PROFILE_CPU_NAMED("Idle"); + Platform::Sleep(1); + } + } // App paused logic if (Platform::GetIsPaused()) @@ -187,7 +179,6 @@ int32 Engine::Main(const Char* cmdLine) OnUpdate(); OnLateUpdate(); Time::OnEndUpdate(); - canDraw = true; } // Start physics simulation @@ -195,16 +186,14 @@ int32 Engine::Main(const Char* cmdLine) { OnFixedUpdate(); Time::OnEndPhysics(); - canDraw = true; } // Draw frame - if (canDraw && Time::OnBeginDraw()) + if (Time::OnBeginDraw()) { OnDraw(); Time::OnEndDraw(); FrameMark; - canDraw = false; } // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) @@ -299,10 +288,11 @@ void Engine::OnUpdate() // Simulate lags //Platform::Sleep(100); - MainThreadTask::RunAll(); + MainThreadTask::RunAll(Time::Update.UnscaledDeltaTime.GetTotalSeconds()); // Call event Update(); + UpdateGraph->Execute(); // Update services EngineService::OnUpdate(); @@ -450,6 +440,7 @@ void Engine::OnExit() // Unload Engine services EngineService::OnDispose(); + Delete(UpdateGraph); LOG_FLUSH(); diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 13f3e1fe8..51860e5a2 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -6,6 +6,7 @@ #include "Engine/Core/Types/DateTime.h" #include "Engine/Scripting/ScriptingType.h" +class TaskGraph; class JsonAsset; /// @@ -43,6 +44,11 @@ public: /// static Action Update; + /// + /// Task graph for engine update. + /// + API_FIELD(ReadOnly) static TaskGraph* UpdateGraph; + /// /// Event called after engine update. /// diff --git a/Source/Engine/Engine/Linux/LinuxGame.cpp b/Source/Engine/Engine/Linux/LinuxGame.cpp index 3fe7c6a92..48b4044d9 100644 --- a/Source/Engine/Engine/Linux/LinuxGame.cpp +++ b/Source/Engine/Engine/Linux/LinuxGame.cpp @@ -5,6 +5,7 @@ #include "LinuxGame.h" #include "Engine/Platform/Window.h" #include "Engine/Platform/FileSystem.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Config/PlatformSettings.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/Globals.h" diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 2dd348207..36db6a236 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -70,6 +70,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime) LastLength = static_cast(DeltaTime.Ticks) / Constants::TicksPerSecond; LastBegin = currentTime - LastLength; LastEnd = currentTime; + NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0; } void Time::TickData::OnReset(float targetFps, double currentTime) @@ -91,10 +92,18 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime) } else { - deltaTime = Math::Clamp(time - LastBegin, 0.0, (double)maxDeltaTime); - const double minDeltaTime = targetFps > ZeroTolerance ? 1.0 / (double)targetFps : 0.0; - if (deltaTime < minDeltaTime) + if (time < NextBegin) return false; + + deltaTime = Math::Max((time - LastBegin), 0.0); + if (deltaTime > maxDeltaTime) + { + deltaTime = (double)maxDeltaTime; + NextBegin = time; + } + + if (targetFps > ZeroTolerance) + NextBegin += (1.0 / targetFps); } // Update data @@ -135,10 +144,19 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime) } else { - deltaTime = Math::Clamp(time - LastBegin, 0.0, (double)maxDeltaTime); - minDeltaTime = targetFps > ZeroTolerance ? 1.0 / (double)targetFps : 0.0; - if (deltaTime < minDeltaTime) + if (time < NextBegin) return false; + + minDeltaTime = targetFps > ZeroTolerance ? 1.0 / targetFps : 0.0; + deltaTime = Math::Max((time - LastBegin), 0.0); + if (deltaTime > maxDeltaTime) + { + deltaTime = (double)maxDeltaTime; + NextBegin = time; + } + + if (targetFps > ZeroTolerance) + NextBegin += (1.0 / targetFps); } Samples.Add(deltaTime); @@ -158,26 +176,23 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime) return true; } -Time::TickData* Time::GetHighestFrequency(float& fps) +double Time::GetNextTick() { - const auto updateFps = UpdateFPS; - const auto drawFps = DrawFPS; - const auto physicsFps = PhysicsFPS; + const double nextUpdate = Time::Update.NextBegin; + const double nextPhysics = Time::Physics.NextBegin; + const double nextDraw = Time::Draw.NextBegin; - if (physicsFps >= drawFps && physicsFps >= updateFps) - { - fps = physicsFps; - return &Physics; - } + double nextTick = MAX_double; + if (UpdateFPS > 0 && nextUpdate < nextTick) + nextTick = nextUpdate; + if (PhysicsFPS > 0 && nextPhysics < nextTick) + nextTick = nextPhysics; + if (DrawFPS > 0 && nextDraw < nextTick) + nextTick = nextDraw; - if (drawFps >= physicsFps && drawFps >= updateFps) - { - fps = drawFps; - return &Draw; - } - - fps = updateFps; - return &Update; + if (nextTick == MAX_double) + return 0.0; + return nextTick; } void Time::SetGamePaused(bool value) diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index af0545184..d4cf673e9 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -48,6 +48,11 @@ public: /// double LastLength; + /// + /// The next tick start time. + /// + double NextBegin; + /// /// The delta time. /// @@ -167,11 +172,10 @@ public: } /// - /// Gets the tick data that uses the highest frequency for the ticking. + /// Gets the time of next upcoming tick data of ticking group with defined update frequency. /// - /// The FPS rate of the highest frequency ticking group. - /// The tick data. - static TickData* GetHighestFrequency(float& fps); + /// The time of next tick. + static double GetNextTick(); /// /// Gets the value indicating whenever game logic is paused (physics, script updates, etc.). @@ -185,7 +189,7 @@ public: /// /// Sets the value indicating whenever game logic is paused (physics, script updates, etc.). /// - /// >True if pause game logic, otherwise false. + /// True if pause game logic, otherwise false. API_PROPERTY() static void SetGamePaused(bool value); /// diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index bb809d4e0..84b0573d8 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -3,6 +3,7 @@ #include "Foliage.h" #include "FoliageType.h" #include "FoliageCluster.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Random.h" #include "Engine/Engine/Engine.h" #include "Engine/Graphics/RenderTask.h" diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index 10afb52b8..e33a3420d 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -2,12 +2,13 @@ #pragma once +#include "Engine/Core/Collections/Array.h" #include "GPUBuffer.h" /// /// Dynamic GPU buffer that allows to update and use GPU data (index/vertex/other) during single frame (supports dynamic resizing) /// -class FLAXENGINE_API DynamicBuffer : public NonCopyable +class FLAXENGINE_API DynamicBuffer { protected: @@ -16,6 +17,7 @@ protected: uint32 _stride; public: + NON_COPYABLE(DynamicBuffer); /// /// Init @@ -40,14 +42,11 @@ public: /// /// Gets buffer (may be null since it's using 'late init' feature) /// - /// Buffer FORCE_INLINE GPUBuffer* GetBuffer() const { return _buffer; } -public: - /// /// Clear data (begin for writing) /// @@ -71,7 +70,7 @@ public: /// /// Pointer to data to write /// Amount of data to write (in bytes) - FORCE_INLINE void Write(void* bytes, int32 size) + FORCE_INLINE void Write(const void* bytes, int32 size) { Data.Add((byte*)bytes, size); } diff --git a/Source/Engine/Graphics/GPUContext.cpp b/Source/Engine/Graphics/GPUContext.cpp index cff2c4766..a1955fb4c 100644 --- a/Source/Engine/Graphics/GPUContext.cpp +++ b/Source/Engine/Graphics/GPUContext.cpp @@ -13,6 +13,7 @@ GPUContext::GPUContext(GPUDevice* device) void GPUContext::FrameBegin() { + _lastRenderTime = Platform::GetTimeSeconds(); } void GPUContext::FrameEnd() diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index ed2417c96..5e1219fa2 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -121,18 +121,14 @@ private: protected: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. + double _lastRenderTime = -1; GPUContext(GPUDevice* device); public: /// - /// Gets the graphics device handle + /// Gets the graphics device. /// - /// Graphics device FORCE_INLINE GPUDevice* GetDevice() const { return _device; diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 8f30dc85a..18c1b5da0 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -31,6 +31,39 @@ GPUPipelineState* GPUPipelineState::New() return GPUDevice::Instance->CreatePipelineState(); } +bool GPUPipelineState::Init(const Description& desc) +{ + // Cache description in debug builds +#if BUILD_DEBUG + DebugDesc = desc; +#endif + + // Cache shader stages usage flags for pipeline state + _meta.InstructionsCount = 0; + _meta.UsedCBsMask = 0; + _meta.UsedSRsMask = 0; + _meta.UsedUAsMask = 0; +#define CHECK_STAGE(stage) \ + if (desc.stage) { \ + _meta.UsedCBsMask |= desc.stage->GetBindings().UsedCBsMask; \ + _meta.UsedSRsMask |= desc.stage->GetBindings().UsedSRsMask; \ + _meta.UsedUAsMask |= desc.stage->GetBindings().UsedUAsMask; \ + } + CHECK_STAGE(VS); + CHECK_STAGE(HS); + CHECK_STAGE(DS); + CHECK_STAGE(GS); + CHECK_STAGE(PS); +#undef CHECK_STAGE + + return false; +} + +GPUResource::ResourceType GPUPipelineState::GetResourceType() const +{ + return ResourceType::PipelineState; +} + GPUPipelineState::Description GPUPipelineState::Description::Default = { // Enable/disable depth write @@ -122,6 +155,80 @@ GPUPipelineState::Description GPUPipelineState::Description::DefaultFullscreenTr BlendingMode::Opaque, }; +GPUResource::GPUResource() + : PersistentScriptingObject(SpawnParams(Guid::New(), GPUResource::TypeInitializer)) +{ +} + +GPUResource::GPUResource(const SpawnParams& params) + : PersistentScriptingObject(params) +{ +} + +GPUResource::~GPUResource() +{ +#if !BUILD_RELEASE + ASSERT(_memoryUsage == 0); +#endif +} + +GPUResource::ObjectType GPUResource::GetObjectType() const +{ + return ObjectType::Other; +} + +uint64 GPUResource::GetMemoryUsage() const +{ + return _memoryUsage; +} + +#if GPU_ENABLE_RESOURCE_NAMING + +String GPUResource::GetName() const +{ + return String::Empty; +} + +#endif + +void GPUResource::ReleaseGPU() +{ + if (_memoryUsage != 0) + { + Releasing(); + OnReleaseGPU(); + _memoryUsage = 0; + } +} + +void GPUResource::OnDeviceDispose() +{ + // By default we want to release resource data but keep it alive + ReleaseGPU(); +} + +void GPUResource::OnReleaseGPU() +{ +} + +String GPUResource::ToString() const +{ +#if GPU_ENABLE_RESOURCE_NAMING + return GetName(); +#else + return TEXT("GPU Resource"); +#endif +} + +void GPUResource::OnDeleteObject() +{ + ReleaseGPU(); + + PersistentScriptingObject::OnDeleteObject(); +} + +double GPUResourceView::DummyLastRenderTime = -1; + struct GPUDevice::PrivateData { AssetReference QuadShader; @@ -229,6 +336,11 @@ bool GPUDevice::LoadContent() return false; } +bool GPUDevice::CanDraw() +{ + return true; +} + void GPUDevice::preDispose() { RenderTargetPool::Flush(); @@ -380,6 +492,11 @@ void GPUDevice::Dispose() VideoOutputModes.Resize(0); } +uint64 GPUDevice::GetMemoryUsage() const +{ + return Resources.GetMemoryUsage(); +} + MaterialBase* GPUDevice::GetDefaultMaterial() const { return _res->DefaultMaterial; diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index e1deb16cc..8290df1aa 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -224,10 +224,7 @@ public: /// /// Gets the amount of memory usage by all the GPU resources (in bytes). /// - API_PROPERTY() FORCE_INLINE uint64 GetMemoryUsage() const - { - return Resources.GetMemoryUsage(); - } + API_PROPERTY() uint64 GetMemoryUsage() const; /// /// Gets the default material. @@ -287,10 +284,7 @@ public: /// Checks if GPU can render frame now (all data is ready), otherwise will skip frame rendering. /// /// True if skip rendering, otherwise false. - virtual bool CanDraw() - { - return true; - } + virtual bool CanDraw(); /// /// Call frame rendering and process data using GPU @@ -389,7 +383,7 @@ public: /// /// Utility structure to safety graphics device locking. /// -struct GPUDeviceLock : NonCopyable +struct FLAXENGINE_API GPUDeviceLock : NonCopyable { const GPUDevice* Device; diff --git a/Source/Engine/Graphics/GPULimits.h b/Source/Engine/Graphics/GPULimits.h index b50714fcd..401ce5178 100644 --- a/Source/Engine/Graphics/GPULimits.h +++ b/Source/Engine/Graphics/GPULimits.h @@ -275,6 +275,11 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(GPULimits); /// API_FIELD() bool HasMultisampleDepthAsSRV; + /// + /// True if device supports reading from typed UAV in shader (common types such as R32G32B32A32, R16G16B16A16, R16, R8). This doesn't apply to single-component 32-bit formats. + /// + API_FIELD() bool HasTypedUAVLoad; + /// /// The maximum amount of texture mip levels. /// diff --git a/Source/Engine/Graphics/GPUPipelineState.h b/Source/Engine/Graphics/GPUPipelineState.h index 5e16799c8..5e7609876 100644 --- a/Source/Engine/Graphics/GPUPipelineState.h +++ b/Source/Engine/Graphics/GPUPipelineState.h @@ -115,12 +115,10 @@ protected: public: #if BUILD_DEBUG - /// /// The description of the pipeline state cached on creation in debug builds. Can be used to help with rendering crashes or issues and validation. /// Description DebugDesc; - #endif public: @@ -128,7 +126,6 @@ public: /// /// Gets constant buffers usage mask (each set bit marks usage of the constant buffer at the bit index slot). Combined from all the used shader stages. /// - /// The constant buffers usage mask. FORCE_INLINE uint32 GetUsedCBsMask() const { return _meta.UsedCBsMask; @@ -137,7 +134,6 @@ public: /// /// Gets shader resources usage mask (each set bit marks usage of the shader resource slot at the bit index slot). Combined from all the used shader stages. /// - /// The shader resource usage mask. FORCE_INLINE uint32 GetUsedSRsMask() const { return _meta.UsedSRsMask; @@ -146,7 +142,6 @@ public: /// /// Gets unordered access usage mask (each set bit marks usage of the unordered access slot at the bit index slot). Combined from all the used shader stages. /// - /// The unordered access usage mask. FORCE_INLINE uint32 GetUsedUAsMask() const { return _meta.UsedUAsMask; @@ -157,7 +152,6 @@ public: /// /// Returns true if pipeline state is valid and ready to use /// - /// True if is valid API_PROPERTY() virtual bool IsValid() const = 0; /// @@ -165,39 +159,9 @@ public: /// /// Full pipeline state description /// True if cannot create state, otherwise false - API_FUNCTION() virtual bool Init(API_PARAM(Ref) const Description& desc) - { - // Cache description in debug builds -#if BUILD_DEBUG - DebugDesc = desc; -#endif - - // Cache shader stages usage flags for pipeline state - _meta.InstructionsCount = 0; - _meta.UsedCBsMask = 0; - _meta.UsedSRsMask = 0; - _meta.UsedUAsMask = 0; -#define CHECK_STAGE(stage) \ - if(desc.stage) { \ - _meta.UsedCBsMask |= desc.stage->GetBindings().UsedCBsMask; \ - _meta.UsedSRsMask |= desc.stage->GetBindings().UsedSRsMask; \ - _meta.UsedUAsMask |= desc.stage->GetBindings().UsedUAsMask; \ - } - CHECK_STAGE(VS); - CHECK_STAGE(HS); - CHECK_STAGE(DS); - CHECK_STAGE(GS); - CHECK_STAGE(PS); -#undef CHECK_STAGE - - return false; - } + API_FUNCTION() virtual bool Init(API_PARAM(Ref) const Description& desc); public: - // [GPUResource] - ResourceType GetResourceType() const final override - { - return ResourceType::PipelineState; - } + ResourceType GetResourceType() const final override; }; diff --git a/Source/Engine/Graphics/GPUResource.h b/Source/Engine/Graphics/GPUResource.h index 288da6579..5d4eff81c 100644 --- a/Source/Engine/Graphics/GPUResource.h +++ b/Source/Engine/Graphics/GPUResource.h @@ -2,8 +2,7 @@ #pragma once -#include "Engine/Core/Common.h" -#include "Engine/Core/NonCopyable.h" +#include "Engine/Core/Enums.h" #include "Engine/Scripting/ScriptingObject.h" #include "Config.h" @@ -15,7 +14,7 @@ /// /// The base class for all GPU resources. /// -API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API GPUResource : public PersistentScriptingObject, public NonCopyable +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API GPUResource : public PersistentScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResource); public: @@ -35,36 +34,29 @@ protected: uint64 _memoryUsage = 0; public: + NON_COPYABLE(GPUResource); /// /// Initializes a new instance of the class. /// - GPUResource() - : PersistentScriptingObject(SpawnParams(Guid::New(), GPUResource::TypeInitializer)) - { - } + GPUResource(); /// /// Initializes a new instance of the class. /// /// The object initialization parameters. - GPUResource(const SpawnParams& params) - : PersistentScriptingObject(params) - { - } + GPUResource(const SpawnParams& params); /// /// Finalizes an instance of the class. /// - virtual ~GPUResource() - { -#if !BUILD_RELEASE - ASSERT(_memoryUsage == 0); -#endif - } + virtual ~GPUResource(); public: + // Points to the cache used by the resource for the resource visibility/usage detection. Written during rendering when resource is used. + double LastRenderTime = -1; + /// /// Action fired when resource GPU state gets released. All objects and async tasks using this resource should release references to this object nor use its data. /// @@ -75,90 +67,49 @@ public: /// /// Gets the resource type. /// - /// The type. virtual ResourceType GetResourceType() const = 0; /// /// Gets resource object type. /// - /// The object type. - virtual ObjectType GetObjectType() const - { - return ObjectType::Other; - } + virtual ObjectType GetObjectType() const; /// - /// Gets amount of GPU memory used by this resource (in bytes). - /// It's a rough estimation. GPU memory may be fragmented, compressed or sub-allocated so the actual memory pressure from this resource may vary (also depends on the current graphics backend). + /// Gets amount of GPU memory used by this resource (in bytes). It's a rough estimation. GPU memory may be fragmented, compressed or sub-allocated so the actual memory pressure from this resource may vary (also depends on the current graphics backend). /// - /// The current memory usage. - API_PROPERTY() FORCE_INLINE uint64 GetMemoryUsage() const - { - return _memoryUsage; - } + API_PROPERTY() uint64 GetMemoryUsage() const; #if GPU_ENABLE_RESOURCE_NAMING /// /// Gets the resource name. /// - /// The name of the resource. - virtual String GetName() const - { - return String::Empty; - } + virtual String GetName() const; #endif /// /// Releases GPU resource data. /// - API_FUNCTION() void ReleaseGPU() - { - if (_memoryUsage != 0) - { - Releasing(); - OnReleaseGPU(); - _memoryUsage = 0; - } - } + API_FUNCTION() void ReleaseGPU(); /// /// Action called when GPU device is disposing. /// - virtual void OnDeviceDispose() - { - // By default we want to release resource data but keep it alive - ReleaseGPU(); - } + virtual void OnDeviceDispose(); protected: /// /// Releases GPU resource data (implementation). /// - virtual void OnReleaseGPU() - { - } + virtual void OnReleaseGPU(); public: // [PersistentScriptingObject] - String ToString() const override - { -#if GPU_ENABLE_RESOURCE_NAMING - return GetName(); -#else - return TEXT("GPU Resource"); -#endif - } - - void OnDeleteObject() override - { - ReleaseGPU(); - - PersistentScriptingObject::OnDeleteObject(); - } + String ToString() const override; + void OnDeleteObject() override; }; /// @@ -193,9 +144,6 @@ public: , _name(name.Get(), name.Length()) #endif { - ASSERT(device); - - // Register device->Resources.Add(this); } @@ -204,7 +152,6 @@ public: /// virtual ~GPUResourceBase() { - // Unregister if (_device) _device->Resources.Remove(this); } @@ -214,7 +161,6 @@ public: /// /// Gets the graphics device. /// - /// The device. FORCE_INLINE DeviceType* GetDevice() const { return _device; @@ -231,10 +177,7 @@ public: #endif void OnDeviceDispose() override { - // Base GPUResource::OnDeviceDispose(); - - // Unlink device handle _device = nullptr; } }; @@ -246,17 +189,21 @@ API_CLASS(Abstract, NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPU { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResourceView); protected: + static double DummyLastRenderTime; explicit GPUResourceView(const SpawnParams& params) : PersistentScriptingObject(params) + , LastRenderTime(&DummyLastRenderTime) { } public: + // Points to the cache used by the resource for the resource visibility/usage detection. Written during rendering when resource view is used. + double* LastRenderTime; + /// /// Gets the native pointer to the underlying view. It's a platform-specific handle. /// - /// The pointer. virtual void* GetNativePtr() const = 0; }; diff --git a/Source/Engine/Graphics/GPUResourceState.h b/Source/Engine/Graphics/GPUResourceState.h index c5083b952..5ea11c3c3 100644 --- a/Source/Engine/Graphics/GPUResourceState.h +++ b/Source/Engine/Graphics/GPUResourceState.h @@ -45,12 +45,8 @@ public: void Initialize(uint32 subresourceCount, StateType initialState, bool usePerSubresourceTracking) { ASSERT(_subresourceState.IsEmpty() && subresourceCount > 0); - - // Initialize state _allSubresourcesSame = true; _resourceState = initialState; - - // Allocate space for per-subresource state tracking if (usePerSubresourceTracking && subresourceCount > 1) _subresourceState.Resize(subresourceCount, false); #if BUILD_DEBUG @@ -82,29 +78,19 @@ public: bool CheckResourceState(StateType state) const { if (_allSubresourcesSame) - { return state == _resourceState; - } - - // Check all subresources for (int32 i = 0; i < _subresourceState.Count(); i++) { if (_subresourceState[i] != state) - { return false; - } } - return true; } StateType GetSubresourceState(uint32 subresourceIndex) const { if (_allSubresourcesSame) - { return _resourceState; - } - ASSERT(subresourceIndex >= 0 && subresourceIndex < static_cast(_subresourceState.Count())); return _subresourceState[subresourceIndex]; } @@ -113,12 +99,9 @@ public: { _allSubresourcesSame = 1; _resourceState = state; - #if BUILD_DEBUG for (int32 i = 0; i < _subresourceState.Count(); i++) - { _subresourceState[i] = InvalidState; - } #endif } @@ -137,9 +120,7 @@ public: if (_allSubresourcesSame) { for (int32 i = 0; i < _subresourceState.Count(); i++) - { _subresourceState[i] = _resourceState; - } _allSubresourcesSame = 0; #if BUILD_DEBUG _resourceState = InvalidState; diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h index 7edf7da54..f3fc4fa67 100644 --- a/Source/Engine/Graphics/Materials/MaterialInfo.h +++ b/Source/Engine/Graphics/Materials/MaterialInfo.h @@ -77,15 +77,6 @@ API_ENUM() enum class MaterialBlendMode : byte Multiply = 3, }; -// Old material blending mode used before introducing MaterialShadingModel -// [Deprecated on 10.09.2018, expires on 10.05.2019] -enum class OldMaterialBlendMode : byte -{ - Opaque = 0, - Transparent = 1, - Unlit = 2, -}; - /// /// Material shading modes. Defines how material inputs and properties are combined to result the final surface color. /// @@ -417,243 +408,6 @@ API_ENUM() enum class MaterialSceneTextures ShadingModel = 10, }; -/// -/// Material info structure - version 1 -/// [Deprecated on 10.09.2018, expires on 10.05.2019] -/// -struct MaterialInfo1 -{ - int32 Version; - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo1& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && Flags == other.Flags; - } -}; - -/// -/// Material info structure - version 2 -/// [Deprecated on 10.09.2018, expires on 10.05.2019] -/// -struct MaterialInfo2 -{ - int32 Version; - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - MaterialTransparentLighting_Deprecated TransparentLighting; - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo2& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && TransparentLighting == other.TransparentLighting - && Flags == other.Flags; - } -}; - -/// -/// Material info structure - version 3 -/// [Deprecated on 10.09.2018, expires on 10.05.2019] -/// -struct MaterialInfo3 -{ - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - MaterialTransparentLighting_Deprecated TransparentLighting; - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo3& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && TransparentLighting == other.TransparentLighting - && Flags == other.Flags; - } -}; - -/// -/// Material info structure - version 4 -/// [Deprecated on 10.09.2018, expires on 10.05.2019] -/// -struct MaterialInfo4 -{ - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - MaterialTransparentLighting_Deprecated TransparentLighting; - MaterialPostFxLocation PostFxLocation; - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo4& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && TransparentLighting == other.TransparentLighting - && PostFxLocation == other.PostFxLocation - && Flags == other.Flags; - } -}; - -/// -/// Material info structure - version 5 -/// [Deprecated on 10.09.2018, expires on 10.05.2019] -/// -struct MaterialInfo5 -{ - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - MaterialTransparentLighting_Deprecated TransparentLighting; - MaterialPostFxLocation PostFxLocation; - float MaskThreshold; - float OpacityThreshold; - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo5& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && TransparentLighting == other.TransparentLighting - && PostFxLocation == other.PostFxLocation - && Math::NearEqual(MaskThreshold, other.MaskThreshold) - && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) - && Flags == other.Flags; - } -}; - -/// -/// Material info structure - version 6 -/// [Deprecated on 10.09.2018, expires on 10.05.2019] -/// -struct MaterialInfo6 -{ - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - MaterialTransparentLighting_Deprecated TransparentLighting; - MaterialDecalBlendingMode DecalBlendingMode; - MaterialPostFxLocation PostFxLocation; - float MaskThreshold; - float OpacityThreshold; - - MaterialInfo6() - { - } - - MaterialInfo6(const MaterialInfo5& other) - { - Domain = other.Domain; - BlendMode = other.BlendMode; - Flags = other.Flags; - TransparentLighting = other.TransparentLighting; - DecalBlendingMode = MaterialDecalBlendingMode::Translucent; - PostFxLocation = other.PostFxLocation; - MaskThreshold = other.MaskThreshold; - OpacityThreshold = other.OpacityThreshold; - } - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo6& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && TransparentLighting == other.TransparentLighting - && DecalBlendingMode == other.DecalBlendingMode - && PostFxLocation == other.PostFxLocation - && Math::NearEqual(MaskThreshold, other.MaskThreshold) - && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) - && Flags == other.Flags; - } -}; - -/// -/// Material info structure - version 7 -/// [Deprecated on 13.09.2018, expires on 13.12.2018] -/// -struct MaterialInfo7 -{ - MaterialDomain Domain; - OldMaterialBlendMode BlendMode; - MaterialFlags_Deprecated Flags; - MaterialTransparentLighting_Deprecated TransparentLighting; - MaterialDecalBlendingMode DecalBlendingMode; - MaterialPostFxLocation PostFxLocation; - float MaskThreshold; - float OpacityThreshold; - TessellationMethod TessellationMode; - int32 MaxTessellationFactor; - - MaterialInfo7() - { - } - - MaterialInfo7(const MaterialInfo6& other) - { - Domain = other.Domain; - BlendMode = other.BlendMode; - Flags = other.Flags; - TransparentLighting = other.TransparentLighting; - DecalBlendingMode = other.DecalBlendingMode; - PostFxLocation = other.PostFxLocation; - MaskThreshold = other.MaskThreshold; - OpacityThreshold = other.OpacityThreshold; - TessellationMode = TessellationMethod::None; - MaxTessellationFactor = 15; - } - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo7& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && TransparentLighting == other.TransparentLighting - && DecalBlendingMode == other.DecalBlendingMode - && PostFxLocation == other.PostFxLocation - && Math::NearEqual(MaskThreshold, other.MaskThreshold) - && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) - && Flags == other.Flags - && TessellationMode == other.TessellationMode - && MaxTessellationFactor == other.MaxTessellationFactor; - } -}; - /// /// Material info structure - version 8 /// [Deprecated on 24.07.2019, expires on 10.05.2021] @@ -676,53 +430,7 @@ struct MaterialInfo8 { } - MaterialInfo8(const MaterialInfo7& other) - { - Domain = other.Domain; - switch (other.BlendMode) - { - case OldMaterialBlendMode::Opaque: - BlendMode = MaterialBlendMode::Opaque; - ShadingModel = MaterialShadingModel::Lit; - break; - case OldMaterialBlendMode::Transparent: - BlendMode = MaterialBlendMode::Transparent; - ShadingModel = MaterialShadingModel::Lit; - break; - case OldMaterialBlendMode::Unlit: - BlendMode = MaterialBlendMode::Opaque; - ShadingModel = MaterialShadingModel::Unlit; - break; - } - Flags = other.Flags; - TransparentLighting = other.TransparentLighting; - DecalBlendingMode = other.DecalBlendingMode; - PostFxLocation = other.PostFxLocation; - MaskThreshold = other.MaskThreshold; - OpacityThreshold = other.OpacityThreshold; - TessellationMode = other.TessellationMode; - MaxTessellationFactor = other.MaxTessellationFactor; - } - - /// - /// Compare structure with other one - /// - /// Other structure to compare - /// True if both structures are equal - bool operator==(const MaterialInfo8& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && ShadingModel == other.ShadingModel - && TransparentLighting == other.TransparentLighting - && DecalBlendingMode == other.DecalBlendingMode - && PostFxLocation == other.PostFxLocation - && Math::NearEqual(MaskThreshold, other.MaskThreshold) - && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) - && Flags == other.Flags - && TessellationMode == other.TessellationMode - && MaxTessellationFactor == other.MaxTessellationFactor; - } + bool operator==(const MaterialInfo8& other) const; }; /// @@ -796,69 +504,8 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(MaterialInfo); { } - MaterialInfo(const MaterialInfo8& other) - { - Domain = other.Domain; - BlendMode = other.BlendMode; - ShadingModel = other.ShadingModel; - UsageFlags = MaterialUsageFlags::None; - if (other.Flags & MaterialFlags_Deprecated::UseMask) - UsageFlags |= MaterialUsageFlags::UseMask; - if (other.Flags & MaterialFlags_Deprecated::UseEmissive) - UsageFlags |= MaterialUsageFlags::UseEmissive; - if (other.Flags & MaterialFlags_Deprecated::UsePositionOffset) - UsageFlags |= MaterialUsageFlags::UsePositionOffset; - if (other.Flags & MaterialFlags_Deprecated::UseVertexColor) - UsageFlags |= MaterialUsageFlags::UseVertexColor; - if (other.Flags & MaterialFlags_Deprecated::UseNormal) - UsageFlags |= MaterialUsageFlags::UseNormal; - if (other.Flags & MaterialFlags_Deprecated::UseDisplacement) - UsageFlags |= MaterialUsageFlags::UseDisplacement; - if (other.Flags & MaterialFlags_Deprecated::UseRefraction) - UsageFlags |= MaterialUsageFlags::UseRefraction; - FeaturesFlags = MaterialFeaturesFlags::None; - if (other.Flags & MaterialFlags_Deprecated::Wireframe) - FeaturesFlags |= MaterialFeaturesFlags::Wireframe; - if (other.Flags & MaterialFlags_Deprecated::TransparentDisableDepthTest && BlendMode != MaterialBlendMode::Opaque) - FeaturesFlags |= MaterialFeaturesFlags::DisableDepthTest; - if (other.Flags & MaterialFlags_Deprecated::TransparentDisableFog && BlendMode != MaterialBlendMode::Opaque) - FeaturesFlags |= MaterialFeaturesFlags::DisableFog; - if (other.Flags & MaterialFlags_Deprecated::TransparentDisableReflections && BlendMode != MaterialBlendMode::Opaque) - FeaturesFlags |= MaterialFeaturesFlags::DisableReflections; - if (other.Flags & MaterialFlags_Deprecated::DisableDepthWrite) - FeaturesFlags |= MaterialFeaturesFlags::DisableDepthWrite; - if (other.Flags & MaterialFlags_Deprecated::TransparentDisableDistortion && BlendMode != MaterialBlendMode::Opaque) - FeaturesFlags |= MaterialFeaturesFlags::DisableDistortion; - if (other.Flags & MaterialFlags_Deprecated::InputWorldSpaceNormal) - FeaturesFlags |= MaterialFeaturesFlags::InputWorldSpaceNormal; - if (other.Flags & MaterialFlags_Deprecated::UseDitheredLODTransition) - FeaturesFlags |= MaterialFeaturesFlags::DitheredLODTransition; - if (other.BlendMode != MaterialBlendMode::Opaque && other.TransparentLighting == MaterialTransparentLighting_Deprecated::None) - ShadingModel = MaterialShadingModel::Unlit; - DecalBlendingMode = other.DecalBlendingMode; - PostFxLocation = other.PostFxLocation; - CullMode = other.Flags & MaterialFlags_Deprecated::TwoSided ? ::CullMode::TwoSided : ::CullMode::Normal; - MaskThreshold = other.MaskThreshold; - OpacityThreshold = other.OpacityThreshold; - TessellationMode = other.TessellationMode; - MaxTessellationFactor = other.MaxTessellationFactor; - } - - bool operator==(const MaterialInfo& other) const - { - return Domain == other.Domain - && BlendMode == other.BlendMode - && ShadingModel == other.ShadingModel - && UsageFlags == other.UsageFlags - && FeaturesFlags == other.FeaturesFlags - && DecalBlendingMode == other.DecalBlendingMode - && PostFxLocation == other.PostFxLocation - && CullMode == other.CullMode - && Math::NearEqual(MaskThreshold, other.MaskThreshold) - && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) - && TessellationMode == other.TessellationMode - && MaxTessellationFactor == other.MaxTessellationFactor; - } + MaterialInfo(const MaterialInfo8& other); + bool operator==(const MaterialInfo& other) const; }; // The current material info descriptor version used by the material pipeline diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index b5ff66c3b..01483558d 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -12,6 +12,85 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" +bool MaterialInfo8::operator==(const MaterialInfo8& other) const +{ + return Domain == other.Domain + && BlendMode == other.BlendMode + && ShadingModel == other.ShadingModel + && TransparentLighting == other.TransparentLighting + && DecalBlendingMode == other.DecalBlendingMode + && PostFxLocation == other.PostFxLocation + && Math::NearEqual(MaskThreshold, other.MaskThreshold) + && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) + && Flags == other.Flags + && TessellationMode == other.TessellationMode + && MaxTessellationFactor == other.MaxTessellationFactor; +} + +MaterialInfo::MaterialInfo(const MaterialInfo8& other) +{ + Domain = other.Domain; + BlendMode = other.BlendMode; + ShadingModel = other.ShadingModel; + UsageFlags = MaterialUsageFlags::None; + if (other.Flags & MaterialFlags_Deprecated::UseMask) + UsageFlags |= MaterialUsageFlags::UseMask; + if (other.Flags & MaterialFlags_Deprecated::UseEmissive) + UsageFlags |= MaterialUsageFlags::UseEmissive; + if (other.Flags & MaterialFlags_Deprecated::UsePositionOffset) + UsageFlags |= MaterialUsageFlags::UsePositionOffset; + if (other.Flags & MaterialFlags_Deprecated::UseVertexColor) + UsageFlags |= MaterialUsageFlags::UseVertexColor; + if (other.Flags & MaterialFlags_Deprecated::UseNormal) + UsageFlags |= MaterialUsageFlags::UseNormal; + if (other.Flags & MaterialFlags_Deprecated::UseDisplacement) + UsageFlags |= MaterialUsageFlags::UseDisplacement; + if (other.Flags & MaterialFlags_Deprecated::UseRefraction) + UsageFlags |= MaterialUsageFlags::UseRefraction; + FeaturesFlags = MaterialFeaturesFlags::None; + if (other.Flags & MaterialFlags_Deprecated::Wireframe) + FeaturesFlags |= MaterialFeaturesFlags::Wireframe; + if (other.Flags & MaterialFlags_Deprecated::TransparentDisableDepthTest && BlendMode != MaterialBlendMode::Opaque) + FeaturesFlags |= MaterialFeaturesFlags::DisableDepthTest; + if (other.Flags & MaterialFlags_Deprecated::TransparentDisableFog && BlendMode != MaterialBlendMode::Opaque) + FeaturesFlags |= MaterialFeaturesFlags::DisableFog; + if (other.Flags & MaterialFlags_Deprecated::TransparentDisableReflections && BlendMode != MaterialBlendMode::Opaque) + FeaturesFlags |= MaterialFeaturesFlags::DisableReflections; + if (other.Flags & MaterialFlags_Deprecated::DisableDepthWrite) + FeaturesFlags |= MaterialFeaturesFlags::DisableDepthWrite; + if (other.Flags & MaterialFlags_Deprecated::TransparentDisableDistortion && BlendMode != MaterialBlendMode::Opaque) + FeaturesFlags |= MaterialFeaturesFlags::DisableDistortion; + if (other.Flags & MaterialFlags_Deprecated::InputWorldSpaceNormal) + FeaturesFlags |= MaterialFeaturesFlags::InputWorldSpaceNormal; + if (other.Flags & MaterialFlags_Deprecated::UseDitheredLODTransition) + FeaturesFlags |= MaterialFeaturesFlags::DitheredLODTransition; + if (other.BlendMode != MaterialBlendMode::Opaque && other.TransparentLighting == MaterialTransparentLighting_Deprecated::None) + ShadingModel = MaterialShadingModel::Unlit; + DecalBlendingMode = other.DecalBlendingMode; + PostFxLocation = other.PostFxLocation; + CullMode = other.Flags & MaterialFlags_Deprecated::TwoSided ? ::CullMode::TwoSided : ::CullMode::Normal; + MaskThreshold = other.MaskThreshold; + OpacityThreshold = other.OpacityThreshold; + TessellationMode = other.TessellationMode; + MaxTessellationFactor = other.MaxTessellationFactor; +} + +bool MaterialInfo::operator==(const MaterialInfo& other) const +{ + return Domain == other.Domain + && BlendMode == other.BlendMode + && ShadingModel == other.ShadingModel + && UsageFlags == other.UsageFlags + && FeaturesFlags == other.FeaturesFlags + && DecalBlendingMode == other.DecalBlendingMode + && PostFxLocation == other.PostFxLocation + && CullMode == other.CullMode + && Math::NearEqual(MaskThreshold, other.MaskThreshold) + && Math::NearEqual(OpacityThreshold, other.OpacityThreshold) + && TessellationMode == other.TessellationMode + && MaxTessellationFactor == other.MaxTessellationFactor; +} + const Char* ToString(MaterialParameterType value) { const Char* result; diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index d4fef1966..63ee16677 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -214,7 +214,6 @@ public: /// /// Gets the parameter ID (not the parameter instance Id but the original parameter ID). /// - /// The ID. API_PROPERTY() FORCE_INLINE Guid GetParameterID() const { return _paramId; @@ -223,7 +222,6 @@ public: /// /// Gets the parameter type. /// - /// The type. API_PROPERTY() FORCE_INLINE MaterialParameterType GetParameterType() const { return _type; @@ -232,7 +230,6 @@ public: /// /// Gets the parameter name. /// - /// The name. API_PROPERTY() FORCE_INLINE const String& GetName() const { return _name; @@ -241,7 +238,6 @@ public: /// /// Returns true is parameter is public visible. /// - /// True if parameter has public access, otherwise false. API_PROPERTY() FORCE_INLINE bool IsPublic() const { return _isPublic; @@ -250,7 +246,6 @@ public: /// /// Returns true is parameter is overriding the value. /// - /// True if parameter is overriding the value, otherwise false. API_PROPERTY() FORCE_INLINE bool IsOverride() const { return _override; @@ -259,7 +254,6 @@ public: /// /// Sets the value override mode. /// - /// The value. API_PROPERTY() void SetIsOverride(bool value) { _override = value; @@ -268,7 +262,6 @@ public: /// /// Gets the parameter resource graphics pipeline binding register index. /// - /// The binding register. FORCE_INLINE byte GetRegister() const { return _registerIndex; @@ -277,7 +270,6 @@ public: /// /// Gets the parameter binding offset since the start of the constant buffer. /// - /// The binding data offset (in bytes). FORCE_INLINE uint16 GetBindOffset() const { return _offset; diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index e897c6258..1c332641d 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -64,7 +64,7 @@ MaterialShader::MaterialShader(const StringView& name) MaterialShader::~MaterialShader() { - ASSERT(!_isLoaded); + ASSERT(!_isLoaded && _shader); SAFE_DELETE_GPU_RESOURCE(_shader); } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 9879c87ec..4196b6de4 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -3,6 +3,7 @@ #pragma once #include "IMaterial.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Graphics/GPUPipelineState.h" #include "Engine/Renderer/Config.h" diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index dddbd6a3e..35b5fe5fb 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -363,7 +363,8 @@ void Mesh::GetDrawCallGeometry(DrawCall& drawCall) const void Mesh::Render(GPUContext* context) const { - ASSERT(IsInitialized()); + if (!IsInitialized()) + return; context->BindVB(ToSpan((GPUBuffer**)_vertexBuffers, 3)); context->BindIB(_indexBuffer); @@ -372,7 +373,7 @@ void Mesh::Render(GPUContext* context) const void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom) const { - if (!material || !material->IsSurface()) + if (!material || !material->IsSurface() || !IsInitialized()) return; // Submit draw call diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp index 25c29b11a..a8fc48889 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp @@ -6,6 +6,10 @@ #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Matrix3x4.h" +SkinnedMeshDrawData::SkinnedMeshDrawData() +{ +} + SkinnedMeshDrawData::~SkinnedMeshDrawData() { SAFE_DELETE_GPU_RESOURCE(BoneMatrices); @@ -37,10 +41,27 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) { if (!bones) return; - ASSERT(BonesCount > 0); - ANIM_GRAPH_PROFILE_EVENT("SetSkinnedMeshData"); + // Copy bones to the buffer + const int32 count = BonesCount; + const int32 preFetchStride = 2; + const Matrix* input = bones; + const auto output = (Matrix3x4*)Data.Get(); + ASSERT(Data.Count() == count * sizeof(Matrix3x4)); + for (int32 i = 0; i < count; i++) + { + Matrix3x4* bone = output + i; + Platform::Prefetch(bone + preFetchStride); + Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE); + bone->SetMatrixTranspose(input[i]); + } + + OnDataChanged(dropHistory); +} + +void SkinnedMeshDrawData::OnDataChanged(bool dropHistory) +{ // Setup previous frame bone matrices if needed if (_hasValidData && !dropHistory) { @@ -60,20 +81,6 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices); } - // Copy bones to the buffer - const int32 count = BonesCount; - const int32 preFetchStride = 2; - const Matrix* input = bones; - const auto output = (Matrix3x4*)Data.Get(); - ASSERT(Data.Count() == count * sizeof(Matrix3x4)); - for (int32 i = 0; i < count; i++) - { - Matrix3x4* bone = output + i; - Platform::Prefetch(bone + preFetchStride); - Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE); - bone->SetMatrixTranspose(input[i]); - } - _isDirty = true; _hasValidData = true; } diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h index b5d86a34d..a9a882684 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h @@ -42,9 +42,7 @@ public: /// /// Initializes a new instance of the class. /// - SkinnedMeshDrawData() - { - } + SkinnedMeshDrawData(); /// /// Finalizes an instance of the class. @@ -56,7 +54,6 @@ public: /// /// Determines whether this instance is ready for rendering. /// - /// True if has valid data and can be rendered, otherwise false. FORCE_INLINE bool IsReady() const { return BoneMatrices != nullptr && BoneMatrices->IsAllocated(); @@ -75,6 +72,12 @@ public: /// True if drop previous update bones used for motion blur, otherwise will keep them and do the update. void SetData(const Matrix* bones, bool dropHistory); + /// + /// After bones Data has been modified externally. Updates the bone matrices data for the GPU buffer. Ensure to call Flush before rendering. + /// + /// True if drop previous update bones used for motion blur, otherwise will keep them and do the update. + void OnDataChanged(bool dropHistory); + /// /// Flushes the bones data buffer with the GPU by sending the data fro the CPU. /// diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index 31ce925ee..42ad32486 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -98,10 +98,8 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) uint64 RenderBuffers::GetMemoryUsage() const { uint64 result = 0; - for (int32 i = 0; i < _resources.Count(); i++) result += _resources[i]->GetMemoryUsage(); - return result; } diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 76ef50a05..6815d8d0c 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Math/Viewport.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Graphics/Textures/GPUTexture.h" diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index b4da776d7..f3e73c220 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -267,7 +267,7 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContext& renderContext) { for (int32 i = 0; i < CustomActors.Count(); i++) { - if (CustomActors[i]->GetIsActive()) + if (CustomActors[i] && CustomActors[i]->GetIsActive()) CustomActors[i]->DrawHierarchy(renderContext); } } diff --git a/Source/Engine/Graphics/RenderTask.cs b/Source/Engine/Graphics/RenderTask.cs index 5b6e2a1a7..11517c097 100644 --- a/Source/Engine/Graphics/RenderTask.cs +++ b/Source/Engine/Graphics/RenderTask.cs @@ -60,15 +60,7 @@ namespace FlaxEngine private static List _postFx; private static IntPtr[] _postFxPtr; - private static readonly PostFxComparer _postFxComparer = new PostFxComparer(); - - internal sealed class PostFxComparer : IComparer - { - public int Compare(PostProcessEffect x, PostProcessEffect y) - { - return x.Order - y.Order; - } - } + private static Comparison _postFxComparison = (x, y) => x.Order - y.Order; internal IntPtr[] GetPostFx(out int count) { @@ -103,7 +95,7 @@ namespace FlaxEngine } // Sort postFx - _postFx.Sort(_postFxComparer); + _postFx.Sort(_postFxComparison); // Convert into unmanaged objects count = _postFx.Count; diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp index d14a204a5..c57d825d3 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.cpp +++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp @@ -3,6 +3,7 @@ #include "GPUShader.h" #include "GPUConstantBuffer.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Math.h" #include "Engine/Serialization/MemoryReadStream.h" GPUShaderProgramsContainer::GPUShaderProgramsContainer() diff --git a/Source/Engine/Graphics/Shaders/GPUShader.h b/Source/Engine/Graphics/Shaders/GPUShader.h index 76c8df546..cc9ee8eb9 100644 --- a/Source/Engine/Graphics/Shaders/GPUShader.h +++ b/Source/Engine/Graphics/Shaders/GPUShader.h @@ -12,7 +12,7 @@ class GPUShaderProgram; /// /// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate). /// -#define GPU_SHADER_CACHE_VERSION 7 +#define GPU_SHADER_CACHE_VERSION 8 /// /// Represents collection of shader programs with permutations and custom names. @@ -167,7 +167,7 @@ public: /// The Constant Buffer object. API_FUNCTION() FORCE_INLINE GPUConstantBuffer* GetCB(int32 slot) const { - ASSERT_LOW_LAYER(Math::IsInRange(slot, 0, ARRAY_COUNT(_constantBuffers) - 1)); + ASSERT_LOW_LAYER(slot >= 0 && slot < ARRAY_COUNT(_constantBuffers)); return _constantBuffers[slot]; } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index 8b5385f9e..82171182b 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -31,11 +31,12 @@ protected: { } - void Init(GPUResource* parent, PixelFormat format, MSAALevel msaa) + FORCE_INLINE void Init(GPUResource* parent, PixelFormat format, MSAALevel msaa) { _parent = parent; _format = format; _msaa = msaa; + LastRenderTime = &parent->LastRenderTime; } public: diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index d5a1e9339..7637e67f0 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -9,6 +9,38 @@ #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h" +TextureHeader_Deprecated::TextureHeader_Deprecated() +{ + Platform::MemoryClear(this, sizeof(*this)); +} + +TextureHeader::TextureHeader() +{ + Platform::MemoryClear(this, sizeof(*this)); + TextureGroup = -1; +} + +TextureHeader::TextureHeader(TextureHeader_Deprecated& old) +{ + Platform::MemoryClear(this, sizeof(*this)); + Width = old.Width;; + Height = old.Height; + MipLevels = old.MipLevels; + Format = old.Format; + Type = old.Type; + if (old.IsCubeMap) + IsCubeMap = 1; + if (old.IsSRGB) + IsSRGB = 1; + if (old.NeverStream) + NeverStream = 1; + TextureGroup = -1; + Platform::MemoryCopy(CustomData, old.CustomData, sizeof(CustomData)); +} + +static_assert(sizeof(TextureHeader_Deprecated) == 10 * sizeof(int32), "Invalid TextureHeader size."); +static_assert(sizeof(TextureHeader) == 36, "Invalid TextureHeader size."); + StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name) : StreamableResource(StreamingGroups::Instance()->Textures()) , _owner(parent) @@ -78,6 +110,14 @@ bool StreamingTexture::Create(const TextureHeader& header) // That's one of the main advantages of the current resources streaming system. _header = header; _isBlockCompressed = PixelFormatExtensions::IsCompressed(_header.Format); + if (_isBlockCompressed) + { + // Ensure that streaming doesn't go too low because the hardware expects the texture to be min in size of compressed texture block + int32 lastMip = header.MipLevels - 1; + while (header.Width >> lastMip < 4 && header.Height >> lastMip < 4) + lastMip--; + _minMipCountBlockCompressed = header.MipLevels - lastMip + 1; + } // Request resource streaming #if GPU_ENABLE_TEXTURES_STREAMING @@ -85,7 +125,7 @@ bool StreamingTexture::Create(const TextureHeader& header) #else bool isDynamic = false; #endif - startStreaming(isDynamic); + StartStreaming(isDynamic); return false; } @@ -112,6 +152,11 @@ String StreamingTexture::ToString() const return _texture->ToString(); } +int32 StreamingTexture::GetMaxResidency() const +{ + return _header.MipLevels; +} + int32 StreamingTexture::GetCurrentResidency() const { return _texture->ResidentMipLevels(); diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.h b/Source/Engine/Graphics/Textures/StreamingTexture.h index 1dda71ba4..324040baf 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.h +++ b/Source/Engine/Graphics/Textures/StreamingTexture.h @@ -7,10 +7,12 @@ #include "Types.h" /// -/// GPU texture object which can change it's resolution (quality) at runtime +/// GPU texture object which can change it's resolution (quality) at runtime. /// class FLAXENGINE_API StreamingTexture : public Object, public StreamableResource { + friend class TextureBase; + friend class TexturesStreamingHandler; friend class StreamTextureMipTask; friend class StreamTextureResizeTask; protected: @@ -19,6 +21,7 @@ protected: GPUTexture* _texture; TextureHeader _header; volatile mutable int64 _streamingTasksCount; + int32 _minMipCountBlockCompressed; bool _isBlockCompressed; public: @@ -60,7 +63,6 @@ public: /// /// Gets total texture width (in texels) /// - /// Texture width FORCE_INLINE int32 TotalWidth() const { return _header.Width; @@ -122,14 +124,6 @@ public: return &_header; } - /// - /// Gets a boolean indicating whether this is a using a block compress format (BC1, BC2, BC3, BC4, BC5, BC6H, BC7). - /// - FORCE_INLINE bool IsBlockCompressed() const - { - return _isBlockCompressed; - } - public: /// @@ -180,10 +174,7 @@ public: String ToString() const override; // [StreamableResource] - int32 GetMaxResidency() const override - { - return _header.MipLevels; - } + int32 GetMaxResidency() const override; int32 GetCurrentResidency() const override; int32 GetAllocatedResidency() const override; bool CanBeUpdated() const override; diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index d21d61f2f..d9aa392cf 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -2,16 +2,60 @@ #include "TextureBase.h" #include "TextureData.h" +#include "Engine/Core/Math/Color32.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" -#include "Engine/Core/Math/Color32.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" -#include + +TextureMipData::TextureMipData() + : RowPitch(0) + , DepthPitch(0) + , Lines(0) +{ +} + +TextureMipData::TextureMipData(const TextureMipData& other) + : RowPitch(other.RowPitch) + , DepthPitch(other.DepthPitch) + , Lines(other.Lines) + , Data(other.Data) +{ +} + +TextureMipData::TextureMipData(TextureMipData&& other) noexcept + : RowPitch(other.RowPitch) + , DepthPitch(other.DepthPitch) + , Lines(other.Lines) + , Data(std::move(other.Data)) +{ +} + +TextureMipData& TextureMipData::operator=(const TextureMipData& other) +{ + if (this == &other) + return *this; + RowPitch = other.RowPitch; + DepthPitch = other.DepthPitch; + Lines = other.Lines; + Data = other.Data; + return *this; +} + +TextureMipData& TextureMipData::operator=(TextureMipData&& other) noexcept +{ + if (this == &other) + return *this; + RowPitch = other.RowPitch; + DepthPitch = other.DepthPitch; + Lines = other.Lines; + Data = MoveTemp(other.Data); + return *this; +} REGISTER_BINARY_ASSET_ABSTRACT(TextureBase, "FlaxEngine.TextureBase"); @@ -30,27 +74,41 @@ Vector2 TextureBase::Size() const int32 TextureBase::GetArraySize() const { - return StreamingTexture()->TotalArraySize(); + return _texture.TotalArraySize(); } int32 TextureBase::GetMipLevels() const { - return StreamingTexture()->TotalMipLevels(); + return _texture.TotalMipLevels(); } int32 TextureBase::GetResidentMipLevels() const { - return GetTexture()->ResidentMipLevels(); + return _texture.GetTexture()->ResidentMipLevels(); } uint64 TextureBase::GetCurrentMemoryUsage() const { - return GetTexture()->GetMemoryUsage(); + return _texture.GetTexture()->GetMemoryUsage(); } uint64 TextureBase::GetTotalMemoryUsage() const { - return StreamingTexture()->GetTotalMemoryUsage(); + return _texture.GetTotalMemoryUsage(); +} + +int32 TextureBase::GetTextureGroup() const +{ + return _texture._header.TextureGroup; +} + +void TextureBase::SetTextureGroup(int32 textureGroup) +{ + if (_texture._header.TextureGroup != textureGroup) + { + _texture._header.TextureGroup = textureGroup; + _texture.RequestStreamingUpdate(); + } } BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch) @@ -70,7 +128,6 @@ BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& s } else { - // Wait for the asset header to be loaded if (WaitForLoaded()) return result; @@ -83,7 +140,7 @@ BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& s slicePitch = slicePitch1; // Ensure to have chunk loaded - if (LoadChunk(calculateChunkIndex(mipIndex))) + if (LoadChunk(CalculateChunkIndex(mipIndex))) return result; } @@ -167,16 +224,15 @@ bool TextureBase::Init(InitData* initData) _customData = initData; // Create texture - TextureHeader header; - header.Format = initData->Format; - header.Width = initData->Width; - header.Height = initData->Height; - header.IsCubeMap = initData->ArraySize == 6; - header.MipLevels = initData->Mips.Count(); - header.IsSRGB = false; - header.Type = TextureFormatType::ColorRGBA; - header.NeverStream = true; - if (_texture.Create(header)) + TextureHeader textureHeader; + textureHeader.Format = initData->Format; + textureHeader.Width = initData->Width; + textureHeader.Height = initData->Height; + textureHeader.IsCubeMap = initData->ArraySize == 6; + textureHeader.MipLevels = initData->Mips.Count(); + textureHeader.Type = TextureFormatType::ColorRGBA; + textureHeader.NeverStream = true; + if (_texture.Create(textureHeader)) { LOG(Warning, "Cannot initialize texture."); return true; @@ -218,7 +274,7 @@ bool TextureBase::Init(void* ptr) return Init(initData); } -int32 TextureBase::calculateChunkIndex(int32 mipIndex) const +int32 TextureBase::CalculateChunkIndex(int32 mipIndex) const { // Mips are in 0-13 chunks return mipIndex; @@ -244,7 +300,7 @@ Task* TextureBase::RequestMipDataAsync(int32 mipIndex) if (_customData) return nullptr; - auto chunkIndex = calculateChunkIndex(mipIndex); + auto chunkIndex = CalculateChunkIndex(mipIndex); return (Task*)_parent->RequestChunkDataAsync(chunkIndex); } @@ -261,7 +317,7 @@ void TextureBase::GetMipData(int32 mipIndex, BytesContainer& data) const return; } - auto chunkIndex = calculateChunkIndex(mipIndex); + auto chunkIndex = CalculateChunkIndex(mipIndex); _parent->GetChunkData(chunkIndex, data); } @@ -273,7 +329,7 @@ void TextureBase::GetMipDataWithLoading(int32 mipIndex, BytesContainer& data) co return; } - const auto chunkIndex = calculateChunkIndex(mipIndex); + const auto chunkIndex = CalculateChunkIndex(mipIndex); _parent->LoadChunk(chunkIndex); _parent->GetChunkData(chunkIndex, data); } @@ -290,6 +346,41 @@ bool TextureBase::GetMipDataCustomPitch(int32 mipIndex, uint32& rowPitch, uint32 return result; } +bool TextureBase::init(AssetInitData& initData) +{ + if (IsVirtual()) + return false; + if (initData.SerializedVersion != TexturesSerializedVersion) + { + LOG(Error, "Invalid serialized texture version."); + return true; + } + + // Get texture header for asset custom data (fast access) + TextureHeader textureHeader; + if (initData.CustomData.Length() == sizeof(TextureHeader)) + { + Platform::MemoryCopy(&textureHeader, initData.CustomData.Get(), sizeof(textureHeader)); + } + else if (initData.CustomData.Length() == sizeof(TextureHeader_Deprecated)) + { + textureHeader = TextureHeader(*(TextureHeader_Deprecated*)initData.CustomData.Get()); + } + else + { + LOG(Error, "Missing texture header."); + return true; + } + + return _texture.Create(textureHeader); +} + +Asset::LoadResult TextureBase::load() +{ + // Loading textures is very fast xD + return LoadResult::Ok; +} + bool TextureBase::InitData::GenerateMip(int32 mipIndex, bool linear) { // Validate input @@ -321,8 +412,6 @@ bool TextureBase::InitData::GenerateMip(int32 mipIndex, bool linear) // Allocate data const int32 dstMipWidth = Math::Max(1, Width >> mipIndex); const int32 dstMipHeight = Math::Max(1, Height >> mipIndex); - const int32 srcMipWidth = Math::Max(1, Width >> (mipIndex - 1)); - const int32 srcMipHeight = Math::Max(1, Height >> (mipIndex - 1)); const int32 pixelStride = PixelFormatExtensions::SizeInBytes(Format); dstMip.RowPitch = dstMipWidth * pixelStride; dstMip.SlicePitch = dstMip.RowPitch * dstMipHeight; diff --git a/Source/Engine/Graphics/Textures/TextureBase.h b/Source/Engine/Graphics/Textures/TextureBase.h index 7839f940a..2bb0042df 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.h +++ b/Source/Engine/Graphics/Textures/TextureBase.h @@ -4,7 +4,6 @@ #include "Engine/Content/BinaryAsset.h" #include "StreamingTexture.h" -#include "Engine/Core/Log.h" class TextureData; @@ -15,12 +14,8 @@ class TextureData; API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API TextureBase : public BinaryAsset, public ITextureOwner { DECLARE_ASSET_HEADER(TextureBase); -public: - static const uint32 TexturesSerializedVersion = 4; -public: - /// /// The texture init data (external source). /// @@ -55,9 +50,6 @@ protected: StreamingTexture _texture; InitData* _customData; - -private: - BinaryAsset* _parent; public: @@ -131,6 +123,16 @@ public: /// Gets the total memory usage that texture may have in use (if loaded to the maximum quality). Exact value may differ due to memory alignment and resource allocation policy. /// API_PROPERTY() uint64 GetTotalMemoryUsage() const; + + /// + /// Gets the index of the texture group used by this texture. + /// + API_PROPERTY() int32 GetTextureGroup() const; + + /// + /// Sets the index of the texture group used by this texture. + /// + API_PROPERTY() void SetTextureGroup(int32 textureGroup); public: @@ -160,7 +162,7 @@ public: protected: - virtual int32 calculateChunkIndex(int32 mipIndex) const; + virtual int32 CalculateChunkIndex(int32 mipIndex) const; private: @@ -180,43 +182,7 @@ public: protected: // [BinaryAsset] - bool init(AssetInitData& initData) override - { - // Skip for virtual assets - if (IsVirtual()) - return false; - - // Validate - if (initData.SerializedVersion != 4) - { - LOG(Error, "Invalid serialized texture version."); - return true; - } - if (initData.CustomData.Length() != sizeof(TextureHeader)) - { - LOG(Error, "Missing texture header."); - return true; - } - - // Load header - TextureHeader header; - Platform::MemoryCopy(&header, initData.CustomData.Get(), sizeof(TextureHeader)); - - // Create texture - if (_texture.Create(header)) - { - LOG(Error, "Cannot initialize texture."); - return true; - } - - return false; - } - - LoadResult load() override - { - // Loading textures is very fast xD - return LoadResult::Ok; - } - + bool init(AssetInitData& initData) override; + LoadResult load() override; void unload(bool isReloading) override; }; diff --git a/Source/Engine/Graphics/Textures/TextureData.h b/Source/Engine/Graphics/Textures/TextureData.h index e5e1eba57..dcae204c8 100644 --- a/Source/Engine/Graphics/Textures/TextureData.h +++ b/Source/Engine/Graphics/Textures/TextureData.h @@ -19,6 +19,12 @@ public: uint32 Lines; BytesContainer Data; + TextureMipData(); + TextureMipData(const TextureMipData& other); + TextureMipData(TextureMipData&& other) noexcept; + TextureMipData& operator=(const TextureMipData& other); + TextureMipData& operator=(TextureMipData&& other) noexcept; + template T& Get(int32 x, int32 y) { @@ -120,7 +126,6 @@ public: /// /// Gets amount of textures in the array /// - /// Array size int32 GetArraySize() const { return Items.Count(); @@ -129,7 +134,6 @@ public: /// /// Gets amount of mip maps in the textures /// - /// Amount of mip levels int32 GetMipLevels() const { return Items.HasItems() ? Items[0].Mips.Count() : 0; diff --git a/Source/Engine/Graphics/Textures/Types.h b/Source/Engine/Graphics/Textures/Types.h index e20af2b6a..5f2a60203 100644 --- a/Source/Engine/Graphics/Textures/Types.h +++ b/Source/Engine/Graphics/Textures/Types.h @@ -12,25 +12,53 @@ DECLARE_ENUM_EX_7(TextureFormatType, byte, 0, Unknown, ColorRGB, ColorRGBA, NormalMap, GrayScale, HdrRGBA, HdrRGB); /// -/// Texture header structure +/// Old texture header structure (was not fully initialized to zero). +/// +struct TextureHeader_Deprecated +{ + int32 Width; + int32 Height; + int32 MipLevels; + PixelFormat Format; + TextureFormatType Type; + bool IsCubeMap; + bool NeverStream; + bool IsSRGB; + byte CustomData[17]; + + TextureHeader_Deprecated(); +}; + +/// +/// Texture header structure. /// struct FLAXENGINE_API TextureHeader { /// - /// Top mip width in pixels + /// Width in pixels /// int32 Width; /// - /// Top mip height in pixels + /// Height in pixels /// int32 Height; + /// + /// Depth in pixels + /// + int32 Depth; + /// /// Amount of mip levels /// int32 MipLevels; + /// + /// Texture group for streaming (negative if unused). + /// + int32 TextureGroup; + /// /// Texture pixels format /// @@ -44,22 +72,23 @@ struct FLAXENGINE_API TextureHeader /// /// True if texture is a cubemap (has 6 array slices per mip). /// - bool IsCubeMap; - - /// - /// True if disable dynamic texture streaming - /// - bool NeverStream; + byte IsCubeMap : 1; /// /// True if texture contains sRGB colors data /// - bool IsSRGB; + byte IsSRGB : 1; + + /// + /// True if disable dynamic texture streaming + /// + byte NeverStream : 1; /// /// The custom data to be used per texture storage layer (faster access). /// - byte CustomData[17]; -}; + byte CustomData[10]; -static_assert(sizeof(TextureHeader) == 10 * sizeof(int32), "Invalid TextureHeader size."); + TextureHeader(); + TextureHeader(TextureHeader_Deprecated& old); +}; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 9c9af3145..b0cc5cb63 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -118,7 +118,7 @@ void GPUContextDX11::FrameBegin() _context->VSSetSamplers(0, ARRAY_COUNT(samplers), samplers); _context->DSSetSamplers(0, ARRAY_COUNT(samplers), samplers); _context->PSSetSamplers(0, ARRAY_COUNT(samplers), samplers); - _context->CSSetSamplers(0, ARRAY_COUNT(samplers), samplers); // TODO: maybe we don't want to bind those static sampler always? + _context->CSSetSamplers(0, ARRAY_COUNT(samplers), samplers); } #if GPU_ALLOW_PROFILE_EVENTS @@ -327,26 +327,26 @@ void GPUContextDX11::BindCB(int32 slot, GPUConstantBuffer* cb) void GPUContextDX11::BindSR(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_SR_BINDED); - auto handle = view ? ((IShaderResourceDX11*)view->GetNativePtr())->SRV() : nullptr; - if (_srHandles[slot] != handle) { _srDirtyFlag = true; _srHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } void GPUContextDX11::BindUA(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED); - auto handle = view ? ((IShaderResourceDX11*)view->GetNativePtr())->UAV() : nullptr; - if (_uaHandles[slot] != handle) { _uaDirtyFlag = true; _uaHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } @@ -637,7 +637,20 @@ void GPUContextDX11::ClearState() FlushState(); - //_context->ClearState(); + _context->ClearState(); + ID3D11SamplerState* samplers[] = + { + _device->_samplerLinearClamp, + _device->_samplerPointClamp, + _device->_samplerLinearWrap, + _device->_samplerPointWrap, + _device->_samplerShadow, + _device->_samplerShadowPCF + }; + _context->VSSetSamplers(0, ARRAY_COUNT(samplers), samplers); + _context->DSSetSamplers(0, ARRAY_COUNT(samplers), samplers); + _context->PSSetSamplers(0, ARRAY_COUNT(samplers), samplers); + _context->CSSetSamplers(0, ARRAY_COUNT(samplers), samplers); } void GPUContextDX11::FlushState() diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 938570e9f..2f7560141 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -296,26 +296,16 @@ bool GPUDeviceDX11::Init() ASSERT(createdFeatureLevel == targetFeatureLevel); _state = DeviceState::Created; - // Verify compute shader is supported on DirectX 11 - if (createdFeatureLevel >= D3D_FEATURE_LEVEL_11_0) - { - D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS options = { 0 }; - _device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &options, sizeof(options)); - if (!options.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x) - { - _device->Release(); - _device = nullptr; - LOG(Fatal, "DirectCompute is not supported by this device (DirectX 11 level)."); - return true; - } - } - // Init device limits { auto& limits = Limits; if (createdFeatureLevel >= D3D_FEATURE_LEVEL_11_0) { - limits.HasCompute = true; + D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS d3D10XHardwareOptions = {}; + _device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &d3D10XHardwareOptions, sizeof(d3D10XHardwareOptions)); + D3D11_FEATURE_DATA_D3D11_OPTIONS2 featureDataD3D11Options2 = {}; + _device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2, &featureDataD3D11Options2, sizeof(featureDataD3D11Options2)); + limits.HasCompute = d3D10XHardwareOptions.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x != 0; limits.HasTessellation = true; limits.HasGeometryShaders = true; limits.HasInstancing = true; @@ -326,6 +316,7 @@ bool GPUDeviceDX11::Init() limits.HasDepthAsSRV = true; limits.HasReadOnlyDepth = true; limits.HasMultisampleDepthAsSRV = true; + limits.HasTypedUAVLoad = featureDataD3D11Options2.TypedUAVLoadAdditionalFormats != 0; limits.MaximumMipLevelsCount = D3D11_REQ_MIP_LEVELS; limits.MaximumTexture1DSize = D3D11_REQ_TEXTURE1D_U_DIMENSION; limits.MaximumTexture1DArraySize = D3D11_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION; @@ -347,6 +338,7 @@ bool GPUDeviceDX11::Init() limits.HasDepthAsSRV = false; limits.HasReadOnlyDepth = createdFeatureLevel == D3D_FEATURE_LEVEL_10_1; limits.HasMultisampleDepthAsSRV = false; + limits.HasTypedUAVLoad = false; limits.MaximumMipLevelsCount = D3D10_REQ_MIP_LEVELS; limits.MaximumTexture1DSize = D3D10_REQ_TEXTURE1D_U_DIMENSION; limits.MaximumTexture1DArraySize = D3D10_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.h index e92a3b819..882277125 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.h @@ -3,7 +3,6 @@ #pragma once #include "Engine/Core/Types/BaseTypes.h" -#include "Engine/Platform/Platform.h" #include "Engine/Platform/CriticalSection.h" #include "CommandAllocatorPoolDX12.h" #include "../IncludeDirectXHeaders.h" @@ -35,28 +34,16 @@ public: public: - /// - /// Gets the current fence value. - /// - /// The current fence value. FORCE_INLINE uint64 GetCurrentValue() const { return _currentValue; } - /// - /// Gets the last signaled fence value. - /// - /// The last signaled fence value. FORCE_INLINE uint64 GetLastSignaledValue() const { return _lastSignaledValue; } - /// - /// Gets the last completed fence value. - /// - /// The last completed fence value. FORCE_INLINE uint64 GetLastCompletedValue() const { return _lastCompletedValue; @@ -146,42 +133,21 @@ private: public: - /// - /// Init - /// - /// Graphics Device handle - /// Command queue type CommandQueueDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE type); - - /// - /// Destructor - /// ~CommandQueueDX12(); public: - /// - /// Checks if command queue is ready for work - /// - /// True if is ready for work FORCE_INLINE bool IsReady() const { return _commandQueue != nullptr; } - /// - /// Gets DirectX 12 command queue object - /// - /// DirectX 12 command queue FORCE_INLINE ID3D12CommandQueue* GetCommandQueue() const { return _commandQueue; } - /// - /// Gets the command lists allocator pool. - /// - /// The allocator. FORCE_INLINE CommandAllocatorPoolDX12& GetAllocatorPool() { return _allocatorPool; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp index f07f66edc..bc1e0d922 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.cpp @@ -128,23 +128,17 @@ void DescriptorHeapWithSlotsDX12::ReleaseSlot(uint32 index) value &= ~mask; } -DescriptorHeapPoolDX12::DescriptorHeapPoolDX12(GPUDeviceDX12* device, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 descriptorsCount, bool shaderVisible) - : _device(device) - , _type(type) - , _descriptorsCount(descriptorsCount) - , _shaderVisible(shaderVisible) +GPUResource::ResourceType DescriptorHeapWithSlotsDX12::GetResourceType() const { + return ResourceType::Descriptor; } -void DescriptorHeapPoolDX12::Init() +DescriptorHeapPoolDX12::DescriptorHeapPoolDX12(GPUDeviceDX12* device, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 descriptorsCountPerHeap, bool shaderVisible) + : _device(device) + , _type(type) + , _descriptorsCountPerHeap(descriptorsCountPerHeap) + , _shaderVisible(shaderVisible) { - // Allocate first page - auto heap = New(_device); - if (heap->Create(_type, _descriptorsCount, _shaderVisible)) - { - Platform::Fatal(TEXT("Failed to allocate descriptor heap.")); - } - _heaps.Add(heap); } void DescriptorHeapPoolDX12::AllocateSlot(DescriptorHeapWithSlotsDX12*& heap, uint32& slot) @@ -159,7 +153,7 @@ void DescriptorHeapPoolDX12::AllocateSlot(DescriptorHeapWithSlotsDX12*& heap, ui } heap = New(_device); - if (heap->Create(_type, _descriptorsCount, _shaderVisible)) + if (heap->Create(_type, _descriptorsCountPerHeap, _shaderVisible)) { Platform::Fatal(TEXT("Failed to allocate descriptor heap.")); } @@ -195,14 +189,12 @@ DescriptorHeapRingBufferDX12::DescriptorHeapRingBufferDX12(GPUDeviceDX12* device bool DescriptorHeapRingBufferDX12::Init() { - // Create description + // Create heap D3D12_DESCRIPTOR_HEAP_DESC desc; desc.Type = _type; desc.NumDescriptors = _descriptorsCount; desc.Flags = _shaderVisible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE : D3D12_DESCRIPTOR_HEAP_FLAG_NONE; desc.NodeMask = 0; - - // Create heap const HRESULT result = _device->GetDevice()->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_heap)); LOG_DIRECTX_RESULT_WITH_RETURN(result); @@ -214,7 +206,6 @@ bool DescriptorHeapRingBufferDX12::Init() else _beginGPU.ptr = 0; _incrementSize = _device->GetDevice()->GetDescriptorHandleIncrementSize(desc.Type); - _memoryUsage = 1; return false; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.h index 8123a1ead..262457e9c 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/DescriptorHeapDX12.h @@ -4,6 +4,7 @@ #if GRAPHICS_API_DIRECTX12 +#include "Engine/Core/Collections/Array.h" #include "Engine/Graphics/GPUResource.h" #include "../IncludeDirectXHeaders.h" @@ -37,26 +38,9 @@ public: D3D12_CPU_DESCRIPTOR_HANDLE CPU() const; D3D12_GPU_DESCRIPTOR_HANDLE GPU() const; - // Creates shader resource view - // @param index Descriptor index in the heap - // @param resource Shader Resource to create view for it - // @param desc View description void CreateSRV(GPUDeviceDX12* device, ID3D12Resource* resource, D3D12_SHADER_RESOURCE_VIEW_DESC* desc = nullptr); - - // Creates render target view - // @param index Descriptor index in the heap - // @param resource Render Target to create view for it void CreateRTV(GPUDeviceDX12* device, ID3D12Resource* resource, D3D12_RENDER_TARGET_VIEW_DESC* desc = nullptr); - - // Creates depth stencil view - // @param index Descriptor index in the heap - // @param resource Render Target Depth to create view for it void CreateDSV(GPUDeviceDX12* device, ID3D12Resource* resource, D3D12_DEPTH_STENCIL_VIEW_DESC* desc = nullptr); - - // Creates unordered access view - // @param index Descriptor index in the heap - // @param resource Unordered Access to create view for it - // @param desc View description void CreateUAV(GPUDeviceDX12* device, ID3D12Resource* resource, D3D12_UNORDERED_ACCESS_VIEW_DESC* desc = nullptr, ID3D12Resource* counterResource = nullptr); void Release(); @@ -80,36 +64,6 @@ public: public: - // Get heap - FORCE_INLINE operator ID3D12DescriptorHeap*() const - { - return _heap; - } - -public: - - // Create heap data - // @param type Heap data type - // @param descriptorsCount Amount of descriptors to use - // @param shaderVisible True if allow shaders to access heap data - bool Create(D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 descriptorsCount, bool shaderVisible = false); - -public: - - // Tries to find free descriptor slot - // @param index Result index to use - // @returns True if can assign descriptor to the heap - bool TryToGetUnusedSlot(uint32& index); - - // Release descriptor slot - // @param index Descriptor index in the heap - void ReleaseSlot(uint32 index); - -public: - - // Get handle to the CPU view at given index - // @param index Descriptor index - // @returns CPU address FORCE_INLINE D3D12_CPU_DESCRIPTOR_HANDLE CPU(uint32 index) { D3D12_CPU_DESCRIPTOR_HANDLE handle; @@ -117,9 +71,6 @@ public: return handle; } - // Get handle to the GPU view at given index - // @param index Descriptor index - // @returns GPU address FORCE_INLINE D3D12_GPU_DESCRIPTOR_HANDLE GPU(uint32 index) { D3D12_GPU_DESCRIPTOR_HANDLE handle; @@ -127,13 +78,16 @@ public: return handle; } +public: + + bool Create(D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 descriptorsCount, bool shaderVisible = false); + bool TryToGetUnusedSlot(uint32& index); + void ReleaseSlot(uint32 index); + public: // [GPUResourceDX12] - ResourceType GetResourceType() const final override - { - return ResourceType::Descriptor; - } + ResourceType GetResourceType() const final override; protected: @@ -150,17 +104,16 @@ private: GPUDeviceDX12* _device; D3D12_DESCRIPTOR_HEAP_TYPE _type; - uint32 _descriptorsCount; + uint32 _descriptorsCountPerHeap; bool _shaderVisible; - Array _heaps; + Array> _heaps; public: - DescriptorHeapPoolDX12(GPUDeviceDX12* device, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 descriptorsCount, bool shaderVisible); + DescriptorHeapPoolDX12(GPUDeviceDX12* device, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 descriptorsCountPerHeap, bool shaderVisible); public: - void Init(); void AllocateSlot(DescriptorHeapWithSlotsDX12*& heap, uint32& slot); void ReleaseGPU(); }; @@ -206,24 +159,12 @@ public: public: - /// - /// Gets DirectX 12 heap object - /// - /// Heap object FORCE_INLINE ID3D12DescriptorHeap* GetHeap() const { return _heap; } -public: - - // Setup heap - // @returns True if cannot setup heap, otherwise false bool Init(); - - // Allocate memory for descriptors table - // @param numDesc Amount of descriptors in table - // @returns Allocated data (GPU param is valid only for shader visible heaps) Allocation AllocateTable(uint32 numDesc); public: diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp index df8ff9606..9bcade16e 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp @@ -9,6 +9,16 @@ #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h" +void GPUBufferViewDX12::SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC& srvDesc) +{ + _srv.CreateSRV(_device, _owner->GetResource(), &srvDesc); +} + +void GPUBufferViewDX12::SetUAV(D3D12_UNORDERED_ACCESS_VIEW_DESC& uavDesc, ID3D12Resource* counterResource) +{ + _uav.CreateUAV(_device, _owner->GetResource(), &uavDesc, counterResource); +} + uint64 GPUBufferDX12::GetSizeInBytes() const { return _memoryUsage; @@ -125,13 +135,7 @@ bool GPUBufferDX12::OnInit() // Create resource ID3D12Resource* resource; D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COPY_DEST; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource( - &heapProperties, - D3D12_HEAP_FLAG_NONE, - &resourceDesc, - initialState, - nullptr, - IID_PPV_ARGS(&resource))); + VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, initialState, 1); @@ -207,7 +211,7 @@ bool GPUBufferDX12::OnInit() } if (_desc.Flags & GPUBufferFlags::RawBuffer) srvDesc.Buffer.Flags |= D3D12_BUFFER_SRV_FLAG_RAW; - _view.SetSRV(&srvDesc); + _view.SetSRV(srvDesc); } if (useUAV) { @@ -226,7 +230,7 @@ bool GPUBufferDX12::OnInit() uavDesc.Format = DXGI_FORMAT_UNKNOWN; else uavDesc.Format = RenderToolsDX::ToDxgiFormat(PixelFormatExtensions::FindUnorderedAccessFormat(_desc.Format)); - _view.SetUAV(&uavDesc, _counter ? _counter->GetResource() : nullptr); + _view.SetUAV(uavDesc, _counter ? _counter->GetResource() : nullptr); } return false; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.h index 7d607f960..4d9c367f8 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.h @@ -27,6 +27,8 @@ public: /// GPUBufferViewDX12() { + SrvDimension = D3D12_SRV_DIMENSION_BUFFER; + UavDimension = D3D12_UAV_DIMENSION_BUFFER; } /// @@ -65,34 +67,14 @@ public: /// Sets the shader resource view. /// /// The SRV desc. - void SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC* srvDesc) - { - if (srvDesc) - { - _srv.CreateSRV(_device, _owner->GetResource(), srvDesc); - } - else - { - _srv.Release(); - } - } + void SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC& srvDesc); /// /// Sets the unordered access view. /// /// The UAV desc. /// The counter buffer resource. - void SetUAV(D3D12_UNORDERED_ACCESS_VIEW_DESC* uavDesc, ID3D12Resource* counterResource = nullptr) - { - if (uavDesc) - { - _uav.CreateUAV(_device, _owner->GetResource(), uavDesc, counterResource); - } - else - { - _uav.Release(); - } - } + void SetUAV(D3D12_UNORDERED_ACCESS_VIEW_DESC& uavDesc, ID3D12Resource* counterResource = nullptr); public: @@ -107,17 +89,14 @@ public: { return false; } - D3D12_CPU_DESCRIPTOR_HANDLE SRV() const override { return _srv.CPU(); } - D3D12_CPU_DESCRIPTOR_HANDLE UAV() const override { return _uav.CPU(); } - ResourceOwnerDX12* GetResourceOwner() const override { return _owner; @@ -173,19 +152,16 @@ public: /// /// Gets buffer size in a GPU memory in bytes. /// - /// Size in bytes. uint64 GetSizeInBytes() const; /// /// Gets buffer location in a GPU memory. /// - /// GPU memory location. D3D12_GPU_VIRTUAL_ADDRESS GetLocation() const; /// /// Gets the counter resource. /// - /// The internal counter buffer. FORCE_INLINE GPUBufferDX12* GetCounter() const { return _counter; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 4d1bd3e17..ffa0a5550 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -70,8 +70,6 @@ GPUContextDX12::GPUContextDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE ty , _rbBufferSize(0) , _srMaskDirtyGraphics(0) , _srMaskDirtyCompute(0) - , _uaMaskDirtyGraphics(0) - , _uaMaskDirtyCompute(0) , _isCompute(0) , _rtDirtyFlag(0) , _psDirtyFlag(0) @@ -79,6 +77,8 @@ GPUContextDX12::GPUContextDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE ty , _rtDepth(nullptr) , _ibHandle(nullptr) { + FrameFenceValues[0] = 0; + FrameFenceValues[1] = 0; _currentAllocator = _device->GetCommandQueue()->RequestAllocator(); VALIDATE_DIRECTX_RESULT(device->GetDevice()->CreateCommandList(0, type, _currentAllocator, nullptr, IID_PPV_ARGS(&_commandList))); #if GPU_ENABLE_RESOURCE_NAMING @@ -93,7 +93,6 @@ GPUContextDX12::~GPUContextDX12() void GPUContextDX12::AddTransitionBarrier(ResourceOwnerDX12* resource, const D3D12_RESOURCE_STATES before, const D3D12_RESOURCE_STATES after, const int32 subresourceIndex) { - // Check if need to flush barriers if (_rbBufferSize == DX12_RB_BUFFER_SIZE) flushRBs(); @@ -108,20 +107,6 @@ void GPUContextDX12::AddTransitionBarrier(ResourceOwnerDX12* resource, const D3D Log::Logger::Write(LogType::Info, info); #endif -#if DX12_ENABLE_RESOURCE_BARRIERS_BATCHING - - // Enqueue barrier - D3D12_RESOURCE_BARRIER& barrier = _rbBuffer[_rbBufferSize]; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = resource->GetResource(); - barrier.Transition.StateBefore = before; - barrier.Transition.StateAfter = after; - barrier.Transition.Subresource = subresourceIndex; - _rbBufferSize++; - -#else - D3D12_RESOURCE_BARRIER barrier; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; @@ -129,40 +114,85 @@ void GPUContextDX12::AddTransitionBarrier(ResourceOwnerDX12* resource, const D3D barrier.Transition.StateBefore = before; barrier.Transition.StateAfter = after; barrier.Transition.Subresource = subresourceIndex; +#if DX12_ENABLE_RESOURCE_BARRIERS_BATCHING + _rbBuffer[_rbBufferSize++] = barrier; +#else _commandList->ResourceBarrier(1, &barrier); +#endif +} +void GPUContextDX12::AddUAVBarrier() +{ + if (_rbBufferSize == DX12_RB_BUFFER_SIZE) + flushRBs(); + +#if DX12_ENABLE_RESOURCE_BARRIERS_DEBUGGING + const auto info = String::Format(TEXT("[DX12 Resource Barrier]: UAV")); + Log::Logger::Write(LogType::Info, info); +#endif + + D3D12_RESOURCE_BARRIER barrier; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.UAV.pResource = nullptr; +#if DX12_ENABLE_RESOURCE_BARRIERS_BATCHING + _rbBuffer[_rbBufferSize++] = barrier; +#else + _commandList->ResourceBarrier(1, &barrier); #endif } void GPUContextDX12::SetResourceState(ResourceOwnerDX12* resource, D3D12_RESOURCE_STATES after, int32 subresourceIndex) { - // Check if resource is missing auto nativeResource = resource->GetResource(); if (nativeResource == nullptr) return; - auto& state = resource->State; - if (subresourceIndex == D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES && !state.AreAllSubresourcesSame()) + if (subresourceIndex == -1) { - // Slow path because we have to transition the entire resource with multiple subresources that aren't in the same state - const uint32 subresourceCount = resource->GetSubresourcesCount(); - for (uint32 i = 0; i < subresourceCount; i++) + if (state.AreAllSubresourcesSame()) { - const D3D12_RESOURCE_STATES before = state.GetSubresourceState(i); - if (before != after) + // Transition entire resource at once + const D3D12_RESOURCE_STATES before = state.GetSubresourceState(-1); + if (ResourceStateDX12::IsTransitionNeeded(before, after)) { - AddTransitionBarrier(resource, before, after, i); - state.SetSubresourceState(i, after); + AddTransitionBarrier(resource, before, after, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES); + state.SetResourceState(after); + } + } + else + { + // Slow path to transition each subresource + bool hadAnyTransition = false; + for (int32 i = 0; i < state.GetSubresourcesCount(); i++) + { + const D3D12_RESOURCE_STATES before = state.GetSubresourceState(i); + if (ResourceStateDX12::IsTransitionNeeded(before, after)) + { + AddTransitionBarrier(resource, before, after, i); + state.SetSubresourceState(i, after); + hadAnyTransition = true; + } + } + if (hadAnyTransition) + { + // Try merge all subresources states into a single state + after = state.GetSubresourceState(0); + for (int32 i = 1; i < state.GetSubresourcesCount(); i++) + { + if (state.GetSubresourceState(i) != after) + return; + } + state.SetResourceState(after); } } - ASSERT(state.CheckResourceState(after)); - state.SetResourceState(after); } else { const D3D12_RESOURCE_STATES before = state.GetSubresourceState(subresourceIndex); if (ResourceStateDX12::IsTransitionNeeded(before, after)) { + // Transition a single subresource AddTransitionBarrier(resource, before, after, subresourceIndex); state.SetSubresourceState(subresourceIndex, after); } @@ -187,8 +217,6 @@ void GPUContextDX12::Reset() _rtDepth = nullptr; _srMaskDirtyGraphics = 0; _srMaskDirtyCompute = 0; - _uaMaskDirtyGraphics = 0; - _uaMaskDirtyCompute = 0; _psDirtyFlag = false; _isCompute = false; _currentCompute = nullptr; @@ -265,7 +293,7 @@ void GPUContextDX12::flushSRVs() return; // Bind all required slots and mark them as not dirty - _srMaskDirtyCompute &= ~srMask; + //_srMaskDirtyCompute &= ~srMask; // TODO: this causes visual artifacts sometimes, maybe use binary SR-dirty flag for all slots? } else { @@ -274,7 +302,7 @@ void GPUContextDX12::flushSRVs() return; // Bind all required slots and mark them as not dirty - _srMaskDirtyGraphics &= ~srMask; + //_srMaskDirtyGraphics &= ~srMask; // TODO: this causes visual artifacts sometimes, maybe use binary SR-dirty flag for all slots? } // Count SRVs required to be bind to the pipeline (the index of the most significant bit that's set) @@ -282,23 +310,25 @@ void GPUContextDX12::flushSRVs() ASSERT(srCount <= GPU_MAX_SR_BINDED); // Fill table with source descriptors + DxShaderHeader& header = _currentCompute ? ((GPUShaderProgramCSDX12*)_currentCompute)->Header : _currentState->Header; D3D12_CPU_DESCRIPTOR_HANDLE srcDescriptorRangeStarts[GPU_MAX_SR_BINDED]; for (uint32 i = 0; i < srCount; i++) { const auto handle = _srHandles[i]; - if (handle != nullptr) + const auto dimensions = (D3D12_SRV_DIMENSION)header.SrDimensions[i]; + if (srMask & (1 << i) && handle != nullptr) { + ASSERT(handle->SrvDimension == dimensions); srcDescriptorRangeStarts[i] = handle->SRV(); - - D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + // TODO: for setup states based on binding mode + D3D12_RESOURCE_STATES states = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; if (handle->IsDepthStencilResource()) - state |= D3D12_RESOURCE_STATE_DEPTH_READ; - - SetResourceState(handle->GetResourceOwner(), state, handle->SubresourceIndex); + states |= D3D12_RESOURCE_STATE_DEPTH_READ; + SetResourceState(handle->GetResourceOwner(), states, handle->SubresourceIndex); } else { - srcDescriptorRangeStarts[i] = _device->NullSRV(); + srcDescriptorRangeStarts[i] = _device->NullSRV(dimensions); } } @@ -306,10 +336,7 @@ void GPUContextDX12::flushSRVs() auto allocation = _device->RingHeap_CBV_SRV_UAV.AllocateTable(srCount); // Copy descriptors - _device->GetDevice()->CopyDescriptors( - 1, &allocation.CPU, &srCount, - srCount, srcDescriptorRangeStarts, nullptr, - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + _device->GetDevice()->CopyDescriptors(1, &allocation.CPU, &srCount, srCount, srcDescriptorRangeStarts, nullptr, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); // Flush SRV descriptors table if (_isCompute) @@ -340,7 +367,23 @@ void GPUContextDX12::flushRTVs() if (_rtDepth) { depthBuffer = _rtDepth->DSV(); - SetResourceState(_rtDepth->GetResourceOwner(), D3D12_RESOURCE_STATE_DEPTH_WRITE, _rtDepth->SubresourceIndex); + auto states = _rtDepth->ReadOnlyDepthView ? D3D12_RESOURCE_STATE_DEPTH_READ : D3D12_RESOURCE_STATE_DEPTH_WRITE; + if (_currentState && _rtDepth->ReadOnlyDepthView) + { + // If read-only depth buffer is also binded as shader input then ensure it has proper state bit + const uint32 srMask = _currentState->GetUsedSRsMask(); + const uint32 srCount = Math::FloorLog2(srMask) + 1; + for (uint32 i = 0; i < srCount; i++) + { + const auto handle = _srHandles[i]; + if (srMask & (1 << i) && handle == _rtDepth) + { + states |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + break; + } + } + } + SetResourceState(_rtDepth->GetResourceOwner(), states, _rtDepth->SubresourceIndex); } else { @@ -358,35 +401,30 @@ void GPUContextDX12::flushUAVs() if (_isCompute) { // Skip if no compute shader binded or it doesn't use shader resources - if (_uaMaskDirtyCompute == 0 || _currentCompute == nullptr || (uaMask = _currentCompute->GetBindings().UsedUAsMask) == 0) + if ((uaMask = _currentCompute->GetBindings().UsedUAsMask) == 0) return; - - // Bind all dirty slots and all used slots - uaMask |= _uaMaskDirtyCompute; - _uaMaskDirtyCompute = 0; } else { // Skip if no state binded or it doesn't use shader resources - if (_uaMaskDirtyGraphics == 0 || _currentState == nullptr || (uaMask = _currentState->GetUsedUAsMask()) == 0) + if (_currentState == nullptr || (uaMask = _currentState->GetUsedUAsMask()) == 0) return; - - // Bind all dirty slots and all used slots - uaMask |= _uaMaskDirtyGraphics; - _uaMaskDirtyGraphics = 0; } // Count UAVs required to be bind to the pipeline (the index of the most significant bit that's set) const uint32 uaCount = Math::FloorLog2(uaMask) + 1; - ASSERT(uaCount <= GPU_MAX_UA_BINDED); + ASSERT(uaCount <= GPU_MAX_UA_BINDED + 1); // Fill table with source descriptors - D3D12_CPU_DESCRIPTOR_HANDLE srcDescriptorRangeStarts[GPU_MAX_UA_BINDED]; + DxShaderHeader& header = _currentCompute ? ((GPUShaderProgramCSDX12*)_currentCompute)->Header : _currentState->Header; + D3D12_CPU_DESCRIPTOR_HANDLE srcDescriptorRangeStarts[GPU_MAX_UA_BINDED + 1]; for (uint32 i = 0; i < uaCount; i++) { const auto handle = _uaHandles[i]; - if (handle != nullptr) + const auto dimensions = (D3D12_UAV_DIMENSION)header.UaDimensions[i]; + if (uaMask & (1 << i) && handle != nullptr) { + ASSERT(handle->UavDimension == dimensions); srcDescriptorRangeStarts[i] = handle->UAV(); SetResourceState(handle->GetResourceOwner(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS); } @@ -400,10 +438,7 @@ void GPUContextDX12::flushUAVs() auto allocation = _device->RingHeap_CBV_SRV_UAV.AllocateTable(uaCount); // Copy descriptors - _device->GetDevice()->CopyDescriptors( - 1, &allocation.CPU, &uaCount, - uaCount, srcDescriptorRangeStarts, nullptr, - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + _device->GetDevice()->CopyDescriptors(1, &allocation.CPU, &uaCount, uaCount, srcDescriptorRangeStarts, nullptr, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); // Flush UAV descriptors table if (_isCompute) @@ -444,8 +479,8 @@ void GPUContextDX12::flushRBs() if (_rbBufferSize > 0) { #if DX12_ENABLE_RESOURCE_BARRIERS_DEBUGGING - const auto info = String::Format(TEXT("[DX12 Resource Barrier]: Flush {0} barriers"), _rbBufferSize); - Log::Logger::Write(LogType::Info, info); + const auto info = String::Format(TEXT("[DX12 Resource Barrier]: Flush {0} barriers"), _rbBufferSize); + Log::Logger::Write(LogType::Info, info); #endif // Flush resource barriers @@ -474,7 +509,7 @@ void GPUContextDX12::flushPS() } } -void GPUContextDX12::onDrawCall() +void GPUContextDX12::OnDrawCall() { // Ensure state of the vertex and index buffers for (int32 i = 0; i < _vbCount; i++) @@ -490,6 +525,37 @@ void GPUContextDX12::onDrawCall() SetResourceState(_ibHandle, D3D12_RESOURCE_STATE_INDEX_BUFFER); } + if (_currentState) + { + // If SRV resource is not binded to RTV then transition it to the whole state (GPU-BASED VALIDATION complains about it) + const uint32 srMask = _currentState->GetUsedSRsMask(); + const uint32 srCount = Math::FloorLog2(srMask) + 1; + for (uint32 i = 0; i < srCount; i++) + { + const auto handle = _srHandles[i]; + if (srMask & (1 << i) && handle != nullptr && handle->GetResourceOwner()) + { + const auto resourceOwner = handle->GetResourceOwner(); + bool isRtv = _rtDepth == handle; + for (int32 j = 0; j < _rtCount; j++) + { + if (_rtHandles[j] && _rtHandles[j]->GetResourceOwner() == resourceOwner) + { + isRtv = true; + break; + } + } + if (!isRtv) + { + D3D12_RESOURCE_STATES states = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + if (handle->IsDepthStencilResource()) + states |= D3D12_RESOURCE_STATE_DEPTH_READ; + SetResourceState(handle->GetResourceOwner(), states); + } + } + } + } + // Flush flushSRVs(); flushRTVs(); @@ -497,6 +563,46 @@ void GPUContextDX12::onDrawCall() flushRBs(); flushPS(); flushCBs(); + +#if BUILD_DEBUG + // Additional verification of the state + if (_currentState) + { + for (int32 i = 0; i < _rtCount; i++) + { + const auto handle = _rtHandles[i]; + if (handle != nullptr && handle->GetResourceOwner()) + { + const auto& state = handle->GetResourceOwner()->State; + ASSERT((state.GetSubresourceState(handle->SubresourceIndex) & D3D12_RESOURCE_STATE_RENDER_TARGET) != 0); + } + } + const uint32 srMask = _currentState->GetUsedSRsMask(); + const uint32 srCount = Math::FloorLog2(srMask) + 1; + for (uint32 i = 0; i < srCount; i++) + { + const auto handle = _srHandles[i]; + if (srMask & (1 << i) && handle != nullptr && handle->GetResourceOwner()) + { + const auto& state = handle->GetResourceOwner()->State; + bool isRtv = false; + for (int32 j = 0; j < _rtCount; j++) + { + if (_rtHandles[j] && _rtHandles[j]->GetResourceOwner() == handle->GetResourceOwner()) + { + isRtv = true; + break; + } + } + ASSERT((state.GetSubresourceState(handle->SubresourceIndex) & D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) != 0); + if (!isRtv) + { + ASSERT(state.AreAllSubresourcesSame()); + } + } + } + } +#endif } void GPUContextDX12::FrameBegin() @@ -514,7 +620,8 @@ void GPUContextDX12::FrameEnd() GPUContext::FrameEnd(); // Execute command (but don't wait for them) - Execute(false); + FrameFenceValues[1] = FrameFenceValues[0]; + FrameFenceValues[0] = Execute(false); } #if GPU_ALLOW_PROFILE_EVENTS @@ -662,15 +769,8 @@ void GPUContextDX12::SetRenderTarget(GPUTextureView* rt, GPUBuffer* uaOutput) // Set render target normally SetRenderTarget(nullptr, rt); - // Bind UAV output to the last slot - const int32 slot = ARRAY_COUNT(_uaHandles) - 1; - IShaderResourceDX12** lastSlot = &_uaHandles[slot]; - if (*lastSlot != uaOutputDX12) - { - *lastSlot = uaOutputDX12; - _srMaskDirtyGraphics |= 1 << slot; - _srMaskDirtyCompute |= 1 << slot; - } + // Bind UAV output to the 2nd slot (after render target to match DX11 binding model) + _uaHandles[1] = uaOutputDX12; } void GPUContextDX12::ResetSR() @@ -709,9 +809,7 @@ void GPUContextDX12::ResetCB() void GPUContextDX12::BindCB(int32 slot, GPUConstantBuffer* cb) { ASSERT(slot >= 0 && slot < GPU_MAX_CB_BINDED); - auto cbDX12 = static_cast(cb); - if (_cbHandles[slot] != cbDX12) { _cbDirtyFlag = true; @@ -722,29 +820,23 @@ void GPUContextDX12::BindCB(int32 slot, GPUConstantBuffer* cb) void GPUContextDX12::BindSR(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_SR_BINDED); - auto handle = view ? (IShaderResourceDX12*)view->GetNativePtr() : nullptr; - - if (_srHandles[slot] != handle) + if (_srHandles[slot] != handle || !handle) { _srMaskDirtyGraphics |= 1 << slot; _srMaskDirtyCompute |= 1 << slot; _srHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } void GPUContextDX12::BindUA(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED); - - auto handle = view ? (IShaderResourceDX12*)view->GetNativePtr() : nullptr; - - if (_uaHandles[slot] != handle) - { - _uaMaskDirtyGraphics |= 1 << slot; - _uaMaskDirtyCompute |= 1 << slot; - _uaHandles[slot] = handle; - } + _uaHandles[slot] = view ? (IShaderResourceDX12*)view->GetNativePtr() : nullptr; + if (view) + *view->LastRenderTime = _lastRenderTime; } void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets) @@ -850,6 +942,9 @@ void GPUContextDX12::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCoun // Restore previous state on next draw call _psDirtyFlag = true; + + // Insert UAV barrier to ensure proper memory access for multiple sequential dispatches + AddUAVBarrier(); } void GPUContextDX12::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* bufferForArgs, uint32 offsetForArgs) @@ -880,6 +975,9 @@ void GPUContextDX12::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* buf // Restore previous state on next draw call _psDirtyFlag = true; + + // Insert UAV barrier to ensure proper memory access for multiple sequential dispatches + AddUAVBarrier(); } void GPUContextDX12::ResolveMultisample(GPUTexture* sourceMultisampleTexture, GPUTexture* destTexture, int32 sourceSubResource, int32 destSubResource, PixelFormat format) @@ -900,14 +998,14 @@ void GPUContextDX12::ResolveMultisample(GPUTexture* sourceMultisampleTexture, GP void GPUContextDX12::DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex) { - onDrawCall(); + OnDrawCall(); _commandList->DrawInstanced(verticesCount, instanceCount, startVertex, startInstance); RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3); } void GPUContextDX12::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex) { - onDrawCall(); + OnDrawCall(); _commandList->DrawIndexedInstanced(indicesCount, instanceCount, startIndex, startVertex, startInstance); RENDER_STAT_DRAW_CALL(0, indicesCount / 3 * instanceCount); } @@ -920,7 +1018,7 @@ void GPUContextDX12::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offs auto signature = _device->DrawIndirectCommandSignature->GetSignature(); SetResourceState(bufferForArgsDX12, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT); - onDrawCall(); + OnDrawCall(); _commandList->ExecuteIndirect(signature, 1, bufferForArgsDX12->GetResource(), (UINT64)offsetForArgs, nullptr, 0); RENDER_STAT_DRAW_CALL(0, 0); } @@ -933,7 +1031,7 @@ void GPUContextDX12::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint auto signature = _device->DrawIndexedIndirectCommandSignature->GetSignature(); SetResourceState(bufferForArgsDX12, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT); - onDrawCall(); + OnDrawCall(); _commandList->ExecuteIndirect(signature, 1, bufferForArgsDX12->GetResource(), (UINT64)offsetForArgs, nullptr, 0); RENDER_STAT_DRAW_CALL(0, 0); } @@ -986,10 +1084,10 @@ void GPUContextDX12::ClearState() void GPUContextDX12::FlushState() { // Flush - flushCBs(); - flushSRVs(); - flushRTVs(); - flushUAVs(); + //flushCBs(); + //flushSRVs(); + //flushRTVs(); + //flushUAVs(); flushRBs(); //flushPS(); } @@ -1000,7 +1098,7 @@ void GPUContextDX12::Flush() return; // Flush GPU commands - Execute(); + Execute(true); Reset(); } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h index 6fb670e7b..b102cfa6b 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h @@ -47,9 +47,6 @@ private: uint32 _srMaskDirtyGraphics; uint32 _srMaskDirtyCompute; - uint32 _uaMaskDirtyGraphics; - uint32 _uaMaskDirtyCompute; - int32 _isCompute : 1; int32 _rtDirtyFlag : 1; int32 _psDirtyFlag : 1; @@ -58,7 +55,7 @@ private: GPUTextureViewDX12* _rtDepth; GPUTextureViewDX12* _rtHandles[GPU_MAX_RT_BINDED]; IShaderResourceDX12* _srHandles[GPU_MAX_SR_BINDED]; - IShaderResourceDX12* _uaHandles[GPU_MAX_UA_BINDED + 1]; + IShaderResourceDX12* _uaHandles[GPU_MAX_UA_BINDED]; GPUBufferDX12* _ibHandle; GPUBufferDX12* _vbHandles[GPU_MAX_VB_BINDED]; D3D12_INDEX_BUFFER_VIEW _ibView; @@ -68,29 +65,18 @@ private: public: - /// - /// Init - /// - /// Graphics device - /// Context type GPUContextDX12(GPUDeviceDX12* device, D3D12_COMMAND_LIST_TYPE type); - - /// - /// Destructor - /// ~GPUContextDX12(); public: - /// - /// Gets command list - /// - /// Command list to use FORCE_INLINE ID3D12GraphicsCommandList* GetCommandList() const { return _commandList; } + uint64 FrameFenceValues[2]; + public: /// @@ -102,6 +88,11 @@ public: /// The index of the subresource. void AddTransitionBarrier(ResourceOwnerDX12* resource, const D3D12_RESOURCE_STATES before, const D3D12_RESOURCE_STATES after, const int32 subresourceIndex); + /// + /// Adds the UAV barrier. Supports batching barriers. + /// + void AddUAVBarrier(); + /// /// Set DirectX 12 resource state using resource barrier /// @@ -147,7 +138,7 @@ private: void flushCBs(); void flushRBs(); void flushPS(); - void onDrawCall(); + void OnDrawCall(); public: diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 944833ca3..b2af2e6d7 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -63,6 +63,28 @@ GPUDevice* GPUDeviceDX12::Create() debugLayer->EnableDebugLayer(); LOG(Info, "DirectX debugging layer enabled"); } +#if 0 +#ifdef __ID3D12Debug1_FWD_DEFINED__ + ComPtr debugLayer1; + D3D12GetDebugInterface(IID_PPV_ARGS(&debugLayer1)); + if (debugLayer1) + { + // GPU-based validation and synchronized validation for debugging only + debugLayer1->SetEnableGPUBasedValidation(true); + debugLayer1->SetEnableSynchronizedCommandQueueValidation(true); + } +#endif +#endif +#ifdef __ID3D12DeviceRemovedExtendedDataSettings_FWD_DEFINED__ + ComPtr dredSettings; + VALIDATE_DIRECTX_RESULT(D3D12GetDebugInterface(IID_PPV_ARGS(&dredSettings))); + if (dredSettings) + { + // Turn on AutoBreadcrumbs and Page Fault reporting + dredSettings->SetAutoBreadcrumbsEnablement(D3D12_DRED_ENABLEMENT_FORCED_ON); + dredSettings->SetPageFaultEnablement(D3D12_DRED_ENABLEMENT_FORCED_ON); + } +#endif #endif // Create DXGI factory (CreateDXGIFactory2 is supported on Windows 8.1 or newer) @@ -169,7 +191,7 @@ GPUDeviceDX12::GPUDeviceDX12(IDXGIFactory4* dxgiFactory, GPUAdapterDX* adapter) , Heap_CBV_SRV_UAV(this, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 4 * 1024, false) , Heap_RTV(this, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 1 * 1024, false) , Heap_DSV(this, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, 64, false) - , RingHeap_CBV_SRV_UAV(this, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 64 * 1024, true) + , RingHeap_CBV_SRV_UAV(this, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 512 * 1024, true) { } @@ -321,6 +343,7 @@ bool GPUDeviceDX12::Init() limits.HasDepthAsSRV = true; limits.HasReadOnlyDepth = true; limits.HasMultisampleDepthAsSRV = true; + limits.HasTypedUAVLoad = options.TypedUAVLoadAdditionalFormats != 0; limits.MaximumMipLevelsCount = D3D12_REQ_MIP_LEVELS; limits.MaximumTexture1DSize = D3D12_REQ_TEXTURE1D_U_DIMENSION; limits.MaximumTexture1DArraySize = D3D12_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION; @@ -350,32 +373,78 @@ bool GPUDeviceDX12::Init() _device->SetStablePowerState(TRUE); #endif - // Create commands queue + // Setup resources _commandQueue = New(this, D3D12_COMMAND_LIST_TYPE_DIRECT); if (_commandQueue->Init()) return true; - - // Create rendering main context _mainContext = New(this, D3D12_COMMAND_LIST_TYPE_DIRECT); - - // Create descriptors heaps - Heap_CBV_SRV_UAV.Init(); - Heap_RTV.Init(); - Heap_DSV.Init(); if (RingHeap_CBV_SRV_UAV.Init()) return true; // Create empty views + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + for (int32 i = 0; i < ARRAY_COUNT(_nullSrv); i++) { - D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; - srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Texture2D.MipLevels = 1; - srvDesc.Texture2D.PlaneSlice = 0; - srvDesc.Texture2D.ResourceMinLODClamp = 0.0f; - _nullSrv.CreateSRV(this, nullptr, &srvDesc); + srvDesc.ViewDimension = (D3D12_SRV_DIMENSION)i; + switch (srvDesc.ViewDimension) + { + case D3D12_SRV_DIMENSION_BUFFER: + srvDesc.Buffer.FirstElement = 0; + srvDesc.Buffer.NumElements = 0; + srvDesc.Buffer.StructureByteStride = 0; + srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + break; + case D3D12_SRV_DIMENSION_TEXTURE1D: + srvDesc.Texture1D.MostDetailedMip = 0; + srvDesc.Texture1D.MipLevels = 1; + srvDesc.Texture1D.ResourceMinLODClamp = 0.0f; + break; + case D3D12_SRV_DIMENSION_TEXTURE1DARRAY: + srvDesc.Texture1DArray.MostDetailedMip = 0; + srvDesc.Texture1DArray.MipLevels = 1; + srvDesc.Texture1DArray.FirstArraySlice = 0; + srvDesc.Texture1DArray.ArraySize = 1; + srvDesc.Texture1DArray.ResourceMinLODClamp = 0.0f; + break; + case D3D12_SRV_DIMENSION_UNKNOWN: // Map Unknown into Texture2D + case D3D12_SRV_DIMENSION_TEXTURE2D: + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Texture2D.MipLevels = 1; + srvDesc.Texture2D.PlaneSlice = 0; + srvDesc.Texture2D.ResourceMinLODClamp = 0.0f; + break; + case D3D12_SRV_DIMENSION_TEXTURE2DARRAY: + srvDesc.Texture2DArray.MostDetailedMip = 0; + srvDesc.Texture2DArray.MipLevels = 1; + srvDesc.Texture2DArray.FirstArraySlice = 0; + srvDesc.Texture2DArray.ArraySize = 0; + srvDesc.Texture2DArray.PlaneSlice = 0; + srvDesc.Texture2DArray.ResourceMinLODClamp = 0.0f; + break; + case D3D12_SRV_DIMENSION_TEXTURE3D: + srvDesc.Texture3D.MostDetailedMip = 0; + srvDesc.Texture3D.MipLevels = 1; + srvDesc.Texture3D.ResourceMinLODClamp = 0.0f; + break; + case D3D12_SRV_DIMENSION_TEXTURECUBE: + srvDesc.TextureCube.MostDetailedMip = 0; + srvDesc.TextureCube.MipLevels = 1; + srvDesc.TextureCube.ResourceMinLODClamp = 0.0f; + break; + case D3D12_SRV_DIMENSION_TEXTURECUBEARRAY: + srvDesc.TextureCubeArray.MostDetailedMip = 0; + srvDesc.TextureCubeArray.MipLevels = 1; + srvDesc.TextureCubeArray.First2DArrayFace = 0; + srvDesc.TextureCubeArray.NumCubes = 0; + srvDesc.TextureCubeArray.ResourceMinLODClamp = 0.0f; + break; + default: + continue; + } + _nullSrv[i].CreateSRV(this, nullptr, &srvDesc); } { D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc; @@ -403,12 +472,11 @@ bool GPUDeviceDX12::Init() { D3D12_DESCRIPTOR_RANGE& range = r[1]; range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; - range.NumDescriptors = GPU_MAX_UA_BINDED + 1; // the last (additional) UAV register is used as a UAV output (hidden internally) + range.NumDescriptors = GPU_MAX_UA_BINDED; range.BaseShaderRegister = 0; range.RegisterSpace = 0; range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; } - static_assert(GPU_MAX_UA_BINDED == 2, "DX12 backend uses hardcoded single UAV register slot. Update code to support more."); // Root parameters D3D12_ROOT_PARAMETER rootParameters[4]; @@ -574,15 +642,11 @@ bool GPUDeviceDX12::Init() void GPUDeviceDX12::DrawBegin() { - // Wait for the GPU to have at least one backbuffer to render to - /*{ - PROFILE_CPU_NAMED("Wait For Fence"); - const uint64 nextFenceValue = _commandQueue->GetNextFenceValue(); - if (nextFenceValue >= DX12_BACK_BUFFER_COUNT) - { - _commandQueue->WaitForFence(nextFenceValue - DX12_BACK_BUFFER_COUNT); - } - }*/ + { + PROFILE_CPU_NAMED("Wait For GPU"); + //_commandQueue->WaitForGPU(); + _commandQueue->WaitForFence(_mainContext->FrameFenceValues[1]); + } // Base GPUDeviceDX::DrawBegin(); @@ -606,9 +670,9 @@ GPUDeviceDX12::~GPUDeviceDX12() Dispose(); } -D3D12_CPU_DESCRIPTOR_HANDLE GPUDeviceDX12::NullSRV() const +D3D12_CPU_DESCRIPTOR_HANDLE GPUDeviceDX12::NullSRV(D3D12_SRV_DIMENSION dimension) const { - return _nullSrv.CPU(); + return _nullSrv[dimension].CPU(); } D3D12_CPU_DESCRIPTOR_HANDLE GPUDeviceDX12::NullUAV() const @@ -647,7 +711,8 @@ void GPUDeviceDX12::Dispose() updateRes2Dispose(); // Clear pipeline objects - _nullSrv.Release(); + for (auto& srv : _nullSrv) + srv.Release(); _nullUav.Release(); TimestampQueryHeap.Destroy(); DX_SAFE_RELEASE_CHECK(_rootSignature, 0); @@ -709,7 +774,6 @@ GPUSwapChain* GPUDeviceDX12::CreateSwapChain(Window* window) void GPUDeviceDX12::AddResourceToLateRelease(IGraphicsUnknown* resource, uint32 safeFrameCount) { - ASSERT(safeFrameCount < 32); if (resource == nullptr) return; @@ -735,7 +799,6 @@ void GPUDeviceDX12::updateRes2Dispose() for (int32 i = _res2Dispose.Count() - 1; i >= 0 && i < _res2Dispose.Count(); i--) { const DisposeResourceEntry& entry = _res2Dispose[i]; - if (entry.TargetFrame <= currentFrame) { auto refs = entry.Resource->Release(); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h index ee6e69002..940be58c3 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h @@ -55,7 +55,7 @@ private: GPUContextDX12* _mainContext; // Heaps - DescriptorHeapWithSlotsDX12::Slot _nullSrv; + DescriptorHeapWithSlotsDX12::Slot _nullSrv[D3D12_SRV_DIMENSION_TEXTURECUBEARRAY + 1]; DescriptorHeapWithSlotsDX12::Slot _nullUav; public: @@ -93,25 +93,22 @@ public: CommandSignatureDX12* DrawIndexedIndirectCommandSignature = nullptr; CommandSignatureDX12* DrawIndirectCommandSignature = nullptr; - D3D12_CPU_DESCRIPTOR_HANDLE NullSRV() const; - + D3D12_CPU_DESCRIPTOR_HANDLE NullSRV(D3D12_SRV_DIMENSION dimension) const; D3D12_CPU_DESCRIPTOR_HANDLE NullUAV() const; public: /// - /// Gets DX12 device + /// Gets DX12 device. /// - /// DirectX 12 device FORCE_INLINE ID3D12Device* GetDevice() const { return _device; } /// - /// Gets DXGI factory + /// Gets DXGI factory. /// - /// DXGI factory object FORCE_INLINE IDXGIFactory4* GetDXGIFactory() const { return _factoryDXGI; @@ -120,28 +117,24 @@ public: /// /// Gets DirectX 12 command list object /// - /// Command list object (DirectX 12) ID3D12GraphicsCommandList* GetCommandList() const; /// - /// Gets command queue + /// Gets command queue. /// - /// Command queue FORCE_INLINE CommandQueueDX12* GetCommandQueue() const { return _commandQueue; } /// - /// Gets DirectX 12 command queue object + /// Gets DirectX 12 command queue object. /// - /// Command queue object (DirectX 12) ID3D12CommandQueue* GetCommandQueueDX12() const; /// - /// Gets root signature of the graphics pipeline + /// Gets root signature of the graphics pipeline. /// - /// Root signature FORCE_INLINE ID3D12RootSignature* GetRootSignature() const { return _rootSignature; @@ -150,7 +143,6 @@ public: /// /// Gets main commands context (for DirectX 12) /// - /// Main context FORCE_INLINE GPUContextDX12* GetMainContextDX12() const { return _mainContext; @@ -171,9 +163,7 @@ public: static FORCE_INLINE uint32 GetMaxMSAAQuality(uint32 sampleCount) { if (sampleCount <= 8) - { return 0; - } return 0xffffffff; } @@ -196,12 +186,10 @@ public: { return reinterpret_cast(_mainContext); } - void* GetNativePtr() const override { return _device; } - bool Init() override; void DrawBegin() override; void RenderEnd() override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp index 5c18c2e2d..0491f8ca2 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.cpp @@ -3,6 +3,7 @@ #if GRAPHICS_API_DIRECTX12 #include "GPUPipelineStateDX12.h" +#include "GPUShaderProgramDX12.h" #include "GPUTextureDX12.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" @@ -63,6 +64,44 @@ ID3D12PipelineState* GPUPipelineStateDX12::GetState(GPUTextureViewDX12* depth, i LOG_DIRECTX_RESULT(result); if (FAILED(result)) return nullptr; +#if GPU_ENABLE_RESOURCE_NAMING && BUILD_DEBUG + char name[200]; + int32 nameLen = 0; + if (DebugDesc.VS) + { + Platform::MemoryCopy(name + nameLen, *DebugDesc.VS->GetName(), DebugDesc.VS->GetName().Length()); + nameLen += DebugDesc.VS->GetName().Length(); + name[nameLen++] = '+'; + } + if (DebugDesc.HS) + { + Platform::MemoryCopy(name + nameLen, *DebugDesc.HS->GetName(), DebugDesc.HS->GetName().Length()); + nameLen += DebugDesc.HS->GetName().Length(); + name[nameLen++] = '+'; + } + if (DebugDesc.DS) + { + Platform::MemoryCopy(name + nameLen, *DebugDesc.DS->GetName(), DebugDesc.DS->GetName().Length()); + nameLen += DebugDesc.DS->GetName().Length(); + name[nameLen++] = '+'; + } + if (DebugDesc.GS) + { + Platform::MemoryCopy(name + nameLen, *DebugDesc.GS->GetName(), DebugDesc.GS->GetName().Length()); + nameLen += DebugDesc.GS->GetName().Length(); + name[nameLen++] = '+'; + } + if (DebugDesc.PS) + { + Platform::MemoryCopy(name + nameLen, *DebugDesc.PS->GetName(), DebugDesc.PS->GetName().Length()); + nameLen += DebugDesc.PS->GetName().Length(); + name[nameLen++] = '+'; + } + if (nameLen && name[nameLen - 1] == '+') + nameLen--; + name[nameLen] = '\0'; + SetDebugObjectName(state, name); +#endif // Cache it _states.Add(key, state); @@ -89,16 +128,27 @@ bool GPUPipelineStateDX12::Init(const Description& desc) psDesc.pRootSignature = _device->GetRootSignature(); // Shaders + Platform::MemoryClear(&Header, sizeof(Header)); psDesc.InputLayout = { static_cast(desc.VS->GetInputLayout()), desc.VS->GetInputLayoutSize() }; - psDesc.VS = { desc.VS->GetBufferHandle(), desc.VS->GetBufferSize() }; - if (desc.HS) - psDesc.HS = { desc.HS->GetBufferHandle(), desc.HS->GetBufferSize() }; - if (desc.DS) - psDesc.DS = { desc.DS->GetBufferHandle(), desc.DS->GetBufferSize() }; - if (desc.GS) - psDesc.GS = { desc.GS->GetBufferHandle(), desc.GS->GetBufferSize() }; - if (desc.PS) - psDesc.PS = { desc.PS->GetBufferHandle(), desc.PS->GetBufferSize() }; +#define INIT_SHADER_STAGE(stage, type) \ + if (desc.stage) \ + { \ + psDesc.stage = { desc.stage->GetBufferHandle(), desc.stage->GetBufferSize() }; \ + auto shader = (type*)desc.stage; \ + auto srCount = Math::FloorLog2(shader->GetBindings().UsedSRsMask) + 1; \ + for (uint32 i = 0; i < srCount; i++) \ + if (shader->Header.SrDimensions[i]) \ + Header.SrDimensions[i] = shader->Header.SrDimensions[i]; \ + auto uaCount = Math::FloorLog2(shader->GetBindings().UsedUAsMask) + 1; \ + for (uint32 i = 0; i < uaCount; i++) \ + if (shader->Header.UaDimensions[i]) \ + Header.UaDimensions[i] = shader->Header.UaDimensions[i]; \ + } + INIT_SHADER_STAGE(HS, GPUShaderProgramHSDX12); + INIT_SHADER_STAGE(DS, GPUShaderProgramDSDX12); + INIT_SHADER_STAGE(GS, GPUShaderProgramGSDX12); + INIT_SHADER_STAGE(VS, GPUShaderProgramVSDX12); + INIT_SHADER_STAGE(PS, GPUShaderProgramPSDX12); const static D3D12_PRIMITIVE_TOPOLOGY_TYPE primTypes1[] = { D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED, diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h index 020d03d82..df6850d6e 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h @@ -6,6 +6,7 @@ #include "Engine/Graphics/GPUPipelineState.h" #include "GPUDeviceDX12.h" +#include "Types.h" #include "../IncludeDirectXHeaders.h" class GPUTextureViewDX12; @@ -50,18 +51,12 @@ private: public: - /// - /// Init - /// - /// Graphics Device GPUPipelineStateDX12(GPUDeviceDX12* device); public: - /// - /// Direct3D primitive topology - /// D3D_PRIMITIVE_TOPOLOGY PrimitiveTopologyType = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; + DxShaderHeader Header; /// /// Gets DirectX 12 graphics pipeline state object for the given rendering state. Uses depth buffer and render targets formats and multi-sample levels to setup a proper PSO. Uses caching. diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp index 61a52c0e7..cfcc43e8d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp @@ -5,10 +5,16 @@ #include "GPUShaderDX12.h" #include "Engine/Serialization/MemoryReadStream.h" #include "GPUShaderProgramDX12.h" +#include "Types.h" #include "../RenderToolsDX.h" GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) { + // Extract the DX shader header from the cache + DxShaderHeader* header = (DxShaderHeader*)cacheBytes; + cacheBytes += sizeof(DxShaderHeader); + cacheSize -= sizeof(DxShaderHeader); + GPUShaderProgram* shader = nullptr; switch (type) { @@ -87,34 +93,34 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const } // Create object - shader = New(initializer, cacheBytes, cacheSize, inputLayout, inputLayoutSize); + shader = New(initializer, header, cacheBytes, cacheSize, inputLayout, inputLayoutSize); break; } case ShaderStage::Hull: { int32 controlPointsCount; stream.ReadInt32(&controlPointsCount); - shader = New(initializer, cacheBytes, cacheSize, controlPointsCount); + shader = New(initializer, header, cacheBytes, cacheSize, controlPointsCount); break; } case ShaderStage::Domain: { - shader = New(initializer, cacheBytes, cacheSize); + shader = New(initializer, header, cacheBytes, cacheSize); break; } case ShaderStage::Geometry: { - shader = New(initializer, cacheBytes, cacheSize); + shader = New(initializer, header, cacheBytes, cacheSize); break; } case ShaderStage::Pixel: { - shader = New(initializer, cacheBytes, cacheSize); + shader = New(initializer, header, cacheBytes, cacheSize); break; } case ShaderStage::Compute: { - shader = New(_device, initializer, cacheBytes, cacheSize); + shader = New(_device, initializer, header, cacheBytes, cacheSize); break; } } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h index 11f16d5c2..1be80d842 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderProgramDX12.h @@ -6,6 +6,7 @@ #include "GPUDeviceDX12.h" #include "Engine/Graphics/Shaders/GPUShaderProgram.h" +#include "Types.h" #include "../IncludeDirectXHeaders.h" /// @@ -20,18 +21,18 @@ protected: public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader data. - /// The shader data size. - GPUShaderProgramDX12(const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize) + + GPUShaderProgramDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) + : Header(*header) { BaseType::Init(initializer); _data.Set(cacheBytes, cacheSize); } +public: + + DxShaderHeader Header; + public: // [BaseType] @@ -39,7 +40,6 @@ public: { return (void*)_data.Get(); } - uint32 GetBufferSize() const override { return _data.Count(); @@ -58,16 +58,8 @@ private: public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader data. - /// The shader data size. - /// The input layout description. - /// The input layout description size. - GPUShaderProgramVSDX12(const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, D3D12_INPUT_ELEMENT_DESC* inputLayout, byte inputLayoutSize) - : GPUShaderProgramDX12(initializer, cacheBytes, cacheSize) + GPUShaderProgramVSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize, D3D12_INPUT_ELEMENT_DESC* inputLayout, byte inputLayoutSize) + : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) , _inputLayoutSize(inputLayoutSize) { for (byte i = 0; i < inputLayoutSize; i++) @@ -81,7 +73,6 @@ public: { return (void*)_inputLayout; } - byte GetInputLayoutSize() const override { return _inputLayoutSize; @@ -95,15 +86,8 @@ class GPUShaderProgramHSDX12 : public GPUShaderProgramDX12 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader data. - /// The shader data size. - /// The control points used by the hull shader for processing. - GPUShaderProgramHSDX12(const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, int32 controlPointsCount) - : GPUShaderProgramDX12(initializer, cacheBytes, cacheSize) + GPUShaderProgramHSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize, int32 controlPointsCount) + : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) { _controlPointsCount = controlPointsCount; } @@ -116,13 +100,8 @@ class GPUShaderProgramDSDX12 : public GPUShaderProgramDX12 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader data size. - GPUShaderProgramDSDX12(const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, cacheBytes, cacheSize) + GPUShaderProgramDSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) + : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) { } }; @@ -134,14 +113,8 @@ class GPUShaderProgramGSDX12 : public GPUShaderProgramDX12 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader data. - /// The shader data size. - GPUShaderProgramGSDX12(const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, cacheBytes, cacheSize) + GPUShaderProgramGSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) + : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) { } }; @@ -153,14 +126,8 @@ class GPUShaderProgramPSDX12 : public GPUShaderProgramDX12 { public: - /// - /// Initializes a new instance of the class. - /// - /// The program initialization data. - /// The shader data. - /// The shader data size. - GPUShaderProgramPSDX12(const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, cacheBytes, cacheSize) + GPUShaderProgramPSDX12(const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) + : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) { } }; @@ -178,23 +145,13 @@ private: public: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The program initialization data. - /// The shader data. - /// The shader data size. - GPUShaderProgramCSDX12(GPUDeviceDX12* device, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize) - : GPUShaderProgramDX12(initializer, cacheBytes, cacheSize) + GPUShaderProgramCSDX12(GPUDeviceDX12* device, const GPUShaderProgramInitializer& initializer, DxShaderHeader* header, byte* cacheBytes, uint32 cacheSize) + : GPUShaderProgramDX12(initializer, header, cacheBytes, cacheSize) , _device(device) , _state(nullptr) { } - - /// - /// Destructor - /// + ~GPUShaderProgramCSDX12() { _device->AddResourceToLateRelease(_state); @@ -205,7 +162,6 @@ public: /// /// Gets DirectX 12 compute pipeline state object /// - /// DirectX 12 compute pipeline state object FORCE_INLINE ID3D12PipelineState* GetState() const { return _state; @@ -214,7 +170,6 @@ public: /// /// Gets or creates compute pipeline state for that compute shader. /// - /// DirectX 12 compute pipeline state object ID3D12PipelineState* GetOrCreateState(); }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp index b8ac22bba..7f7119038 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp @@ -21,7 +21,7 @@ void BackBufferDX12::Setup(GPUSwapChainDX12* window, ID3D12Resource* backbuffer) rtDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; rtDesc.Texture2D.MipSlice = 0; rtDesc.Texture2D.PlaneSlice = 0; - Handle.SetRTV(&rtDesc); + Handle.SetRTV(rtDesc); } #if GPU_USE_WINDOW_SRV @@ -35,7 +35,7 @@ void BackBufferDX12::Setup(GPUSwapChainDX12* window, ID3D12Resource* backbuffer) srDesc.Texture2D.MipLevels = 1; srDesc.Texture2D.ResourceMinLODClamp = 0; srDesc.Texture2D.PlaneSlice = 0; - Handle.SetSRV(&srDesc); + Handle.SetSRV(srDesc); } #endif } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp index 03ba98eaf..1044ff41b 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp @@ -237,7 +237,7 @@ void GPUTextureDX12::onResidentMipsChanged() // Change view if (_handlesPerSlice[0].GetParent() == nullptr) _handlesPerSlice[0].Init(this, _device, this, Format(), MultiSampleLevel()); - _handlesPerSlice[0].SetSRV(&srDesc); + _handlesPerSlice[0].SetSRV(srDesc); } void GPUTextureDX12::OnReleaseGPU() @@ -254,6 +254,36 @@ void GPUTextureDX12::OnReleaseGPU() GPUTexture::OnReleaseGPU(); } +void GPUTextureViewDX12::Release() +{ + _rtv.Release(); + _srv.Release(); + _dsv.Release(); + _uav.Release(); +} + +void GPUTextureViewDX12::SetRTV(D3D12_RENDER_TARGET_VIEW_DESC& rtvDesc) +{ + _rtv.CreateRTV(_device, _owner->GetResource(), &rtvDesc); +} + +void GPUTextureViewDX12::SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC& srvDesc) +{ + SrvDimension = srvDesc.ViewDimension; + _srv.CreateSRV(_device, _owner->GetResource(), &srvDesc); +} + +void GPUTextureViewDX12::SetDSV(D3D12_DEPTH_STENCIL_VIEW_DESC& dsvDesc) +{ + _dsv.CreateDSV(_device, _owner->GetResource(), &dsvDesc); +} + +void GPUTextureViewDX12::SetUAV(D3D12_UNORDERED_ACCESS_VIEW_DESC& uavDesc, ID3D12Resource* counterResource) +{ + UavDimension = uavDesc.ViewDimension; + _uav.CreateUAV(_device, _owner->GetResource(), &uavDesc, counterResource); +} + void GPUTextureDX12::initHandles() { D3D12_RENDER_TARGET_VIEW_DESC rtDesc; @@ -318,7 +348,7 @@ void GPUTextureDX12::initHandles() srDesc.Texture3D.MostDetailedMip = 0; srDesc.Texture3D.MipLevels = MipLevels(); srDesc.Texture3D.ResourceMinLODClamp = 0; - _handleVolume.SetSRV(&srDesc); + _handleVolume.SetSRV(srDesc); } if (useRTV) { @@ -326,14 +356,14 @@ void GPUTextureDX12::initHandles() rtDesc.Texture3D.MipSlice = 0; rtDesc.Texture3D.FirstWSlice = 0; rtDesc.Texture3D.WSize = Depth(); - _handleVolume.SetRTV(&rtDesc); + _handleVolume.SetRTV(rtDesc); } if (useUAV) { uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; uavDesc.Texture2D.MipSlice = 0; uavDesc.Texture2D.PlaneSlice = 0; - _handleVolume.SetUAV(&uavDesc); + _handleVolume.SetUAV(uavDesc); } // Init per slice views @@ -348,7 +378,7 @@ void GPUTextureDX12::initHandles() { rtDesc.Texture3D.FirstWSlice = sliceIndex; _handlesPerSlice[sliceIndex].Init(this, _device, this, format, msaa); - _handlesPerSlice[sliceIndex].SetRTV(&rtDesc); + _handlesPerSlice[sliceIndex].SetRTV(rtDesc); } } } @@ -378,7 +408,7 @@ void GPUTextureDX12::initHandles() dsDesc.Texture2DArray.FirstArraySlice = arrayIndex; dsDesc.Texture2DArray.MipSlice = 0; } - _handlesPerSlice[arrayIndex].SetDSV(&dsDesc); + _handlesPerSlice[arrayIndex].SetDSV(dsDesc); } if (useRTV) { @@ -398,7 +428,7 @@ void GPUTextureDX12::initHandles() rtDesc.Texture2DArray.MipSlice = 0; rtDesc.Texture2DArray.PlaneSlice = 0; } - _handlesPerSlice[arrayIndex].SetRTV(&rtDesc); + _handlesPerSlice[arrayIndex].SetRTV(rtDesc); } if (useSRV) { @@ -420,7 +450,9 @@ void GPUTextureDX12::initHandles() srDesc.Texture2DArray.PlaneSlice = 0; srDesc.Texture2DArray.ResourceMinLODClamp = 0; } - _handlesPerSlice[arrayIndex].SetSRV(&srDesc); + _handlesPerSlice[arrayIndex].SetSRV(srDesc); + if (isCubeMap) + _handlesPerSlice[arrayIndex].SrvDimension = D3D12_SRV_DIMENSION_TEXTURE2D; // Hack xD (to reproduce the problem comment this line and use Spot Light with a shadow) } if (useUAV) { @@ -429,7 +461,7 @@ void GPUTextureDX12::initHandles() uavDesc.Texture2DArray.FirstArraySlice = arrayIndex; uavDesc.Texture2DArray.MipSlice = 0; uavDesc.Texture2DArray.PlaneSlice = 0; - _handlesPerSlice[arrayIndex].SetSRV(&srDesc); + _handlesPerSlice[arrayIndex].SetSRV(srDesc); } } @@ -442,7 +474,7 @@ void GPUTextureDX12::initHandles() dsDesc.Texture2DArray.ArraySize = arraySize; dsDesc.Texture2DArray.FirstArraySlice = 0; dsDesc.Texture2DArray.MipSlice = 0; - _handleArray.SetDSV(&dsDesc); + _handleArray.SetDSV(dsDesc); } if (useRTV) { @@ -451,7 +483,7 @@ void GPUTextureDX12::initHandles() rtDesc.Texture2DArray.FirstArraySlice = 0; rtDesc.Texture2DArray.MipSlice = 0; rtDesc.Texture2DArray.PlaneSlice = 0; - _handleArray.SetRTV(&rtDesc); + _handleArray.SetRTV(rtDesc); } if (useSRV) { @@ -472,7 +504,7 @@ void GPUTextureDX12::initHandles() srDesc.Texture2DArray.ResourceMinLODClamp = 0; srDesc.Texture2DArray.PlaneSlice = 0; } - _handleArray.SetSRV(&srDesc); + _handleArray.SetSRV(srDesc); } if (useUAV) { @@ -481,7 +513,7 @@ void GPUTextureDX12::initHandles() uavDesc.Texture2DArray.FirstArraySlice = 0; uavDesc.Texture2DArray.MipSlice = 0; uavDesc.Texture2DArray.PlaneSlice = 0; - _handleArray.SetUAV(&uavDesc); + _handleArray.SetUAV(uavDesc); } } } @@ -508,7 +540,7 @@ void GPUTextureDX12::initHandles() dsDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; dsDesc.Texture2D.MipSlice = 0; } - _handlesPerSlice[0].SetDSV(&dsDesc); + _handlesPerSlice[0].SetDSV(dsDesc); } if (useRTV) { @@ -530,7 +562,7 @@ void GPUTextureDX12::initHandles() rtDesc.Texture2D.MipSlice = 0; rtDesc.Texture2D.PlaneSlice = 0; } - _handlesPerSlice[0].SetRTV(&rtDesc); + _handlesPerSlice[0].SetRTV(rtDesc); } if (useSRV) { @@ -553,7 +585,7 @@ void GPUTextureDX12::initHandles() srDesc.Texture2D.ResourceMinLODClamp = 0; srDesc.Texture2D.PlaneSlice = 0; } - _handlesPerSlice[0].SetSRV(&srDesc); + _handlesPerSlice[0].SetSRV(srDesc); } if (useUAV) { @@ -571,7 +603,7 @@ void GPUTextureDX12::initHandles() uavDesc.Texture2D.MipSlice = 0; uavDesc.Texture2D.PlaneSlice = 0; } - _handlesPerSlice[0].SetUAV(&uavDesc); + _handlesPerSlice[0].SetUAV(uavDesc); } } @@ -593,35 +625,63 @@ void GPUTextureDX12::initHandles() // DSV if (useDSV) { - dsDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; - dsDesc.Texture2DArray.ArraySize = 1; - dsDesc.Texture2DArray.FirstArraySlice = arrayIndex; - dsDesc.Texture2DArray.MipSlice = mipIndex; - slice[mipIndex].SetDSV(&dsDesc); + if (isArray) + { + dsDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + dsDesc.Texture2DArray.ArraySize = 1; + dsDesc.Texture2DArray.FirstArraySlice = arrayIndex; + dsDesc.Texture2DArray.MipSlice = mipIndex; + } + else + { + dsDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + dsDesc.Texture2D.MipSlice = mipIndex; + } + slice[mipIndex].SetDSV(dsDesc); } // RTV if (useRTV) { - rtDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; - rtDesc.Texture2DArray.ArraySize = 1; - rtDesc.Texture2DArray.FirstArraySlice = arrayIndex; - rtDesc.Texture2DArray.MipSlice = mipIndex; - rtDesc.Texture2DArray.PlaneSlice = 0; - slice[mipIndex].SetRTV(&rtDesc); + if (isArray) + { + rtDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + rtDesc.Texture2DArray.ArraySize = 1; + rtDesc.Texture2DArray.FirstArraySlice = arrayIndex; + rtDesc.Texture2DArray.MipSlice = mipIndex; + rtDesc.Texture2DArray.PlaneSlice = 0; + } + else + { + rtDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rtDesc.Texture2D.MipSlice = mipIndex; + rtDesc.Texture2D.PlaneSlice = 0; + } + slice[mipIndex].SetRTV(rtDesc); } // SRV if (useSRV) { - srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; - srDesc.Texture2DArray.ArraySize = 1; - srDesc.Texture2DArray.FirstArraySlice = arrayIndex; - srDesc.Texture2DArray.MipLevels = 1; - srDesc.Texture2DArray.MostDetailedMip = mipIndex; - srDesc.Texture2DArray.ResourceMinLODClamp = 0; - srDesc.Texture2DArray.PlaneSlice = 0; - slice[mipIndex].SetSRV(&srDesc); + if (isArray) + { + srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + srDesc.Texture2DArray.ArraySize = 1; + srDesc.Texture2DArray.FirstArraySlice = arrayIndex; + srDesc.Texture2DArray.MipLevels = 1; + srDesc.Texture2DArray.MostDetailedMip = mipIndex; + srDesc.Texture2DArray.ResourceMinLODClamp = 0; + srDesc.Texture2DArray.PlaneSlice = 0; + } + else + { + srDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srDesc.Texture2D.MipLevels = 1; + srDesc.Texture2D.MostDetailedMip = mipIndex; + srDesc.Texture2D.ResourceMinLODClamp = 0; + srDesc.Texture2D.PlaneSlice = 0; + } + slice[mipIndex].SetSRV(srDesc); } } } @@ -631,6 +691,7 @@ void GPUTextureDX12::initHandles() if (_desc.Flags & GPUTextureFlags::ReadOnlyDepthView) { _handleReadOnlyDepth.Init(this, _device, this, format, msaa); + _handleReadOnlyDepth.ReadOnlyDepthView = true; if (useDSV) { if (isCubeMap) @@ -652,7 +713,7 @@ void GPUTextureDX12::initHandles() dsDesc.Flags = D3D12_DSV_FLAG_READ_ONLY_DEPTH; if (PixelFormatExtensions::HasStencil(format)) dsDesc.Flags |= D3D12_DSV_FLAG_READ_ONLY_STENCIL; - _handleReadOnlyDepth.SetDSV(&dsDesc); + _handleReadOnlyDepth.SetDSV(dsDesc); } ASSERT(!useRTV); if (useSRV) @@ -676,7 +737,7 @@ void GPUTextureDX12::initHandles() srDesc.Texture2D.ResourceMinLODClamp = 0; srDesc.Texture2D.PlaneSlice = 0; } - _handleReadOnlyDepth.SetSRV(&srDesc); + _handleReadOnlyDepth.SetSRV(srDesc); } } } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h index e38d5a712..b93828ed5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.h @@ -22,9 +22,6 @@ private: public: - /// - /// Initializes a new instance of the class. - /// GPUTextureViewDX12() { } @@ -45,9 +42,6 @@ public: return *this; } - /// - /// Finalizes an instance of the class. - /// ~GPUTextureViewDX12() { Release(); @@ -64,7 +58,7 @@ public: /// Parent texture format /// Parent texture multi-sample level /// Used subresource index or -1 to cover whole resource. - void Init(GPUResource* parent, GPUDeviceDX12* device, ResourceOwnerDX12* owner, PixelFormat format, MSAALevel msaa, int32 subresourceIndex = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES) + void Init(GPUResource* parent, GPUDeviceDX12* device, ResourceOwnerDX12* owner, PixelFormat format, MSAALevel msaa, int32 subresourceIndex = -1) { GPUTextureView::Init(parent, format, msaa); SubresourceIndex = subresourceIndex; @@ -75,88 +69,22 @@ public: /// /// Releases the view. /// - void Release() - { - _rtv.Release(); - _srv.Release(); - _dsv.Release(); - _uav.Release(); - } + void Release(); public: - /// - /// Sets the render target view. - /// - /// The RTV desc. - void SetRTV(D3D12_RENDER_TARGET_VIEW_DESC* rtvDesc) - { - if (rtvDesc) - { - _rtv.CreateRTV(_device, _owner->GetResource(), rtvDesc); - } - else - { - _rtv.Release(); - } - } - - /// - /// Sets the shader resource view. - /// - /// The SRV desc. - void SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC* srvDesc) - { - if (srvDesc) - { - _srv.CreateSRV(_device, _owner->GetResource(), srvDesc); - } - else - { - _srv.Release(); - } - } - - /// - /// Sets the depth stencil view. - /// - /// The DSV desc. - void SetDSV(D3D12_DEPTH_STENCIL_VIEW_DESC* dsvDesc) - { - if (dsvDesc) - { - _dsv.CreateDSV(_device, _owner->GetResource(), dsvDesc); - } - else - { - _dsv.Release(); - } - } - - /// - /// Sets the unordered access view. - /// - /// The UAV desc. - /// The counter buffer resource. - void SetUAV(D3D12_UNORDERED_ACCESS_VIEW_DESC* uavDesc, ID3D12Resource* counterResource = nullptr) - { - if (uavDesc) - { - _uav.CreateUAV(_device, _owner->GetResource(), uavDesc, counterResource); - } - else - { - _uav.Release(); - } - } + bool ReadOnlyDepthView = false; + void SetRTV(D3D12_RENDER_TARGET_VIEW_DESC& rtvDesc); + void SetSRV(D3D12_SHADER_RESOURCE_VIEW_DESC& srvDesc); + void SetDSV(D3D12_DEPTH_STENCIL_VIEW_DESC& dsvDesc); + void SetUAV(D3D12_UNORDERED_ACCESS_VIEW_DESC& uavDesc, ID3D12Resource* counterResource = nullptr); public: /// /// Gets the CPU handle to the render target view descriptor. /// - /// The CPU handle to the render target view descriptor. - D3D12_CPU_DESCRIPTOR_HANDLE RTV() const + FORCE_INLINE D3D12_CPU_DESCRIPTOR_HANDLE RTV() const { return _rtv.CPU(); } @@ -164,8 +92,7 @@ public: /// /// Gets the CPU handle to the depth stencil view descriptor. /// - /// The CPU handle to the depth stencil view descriptor. - D3D12_CPU_DESCRIPTOR_HANDLE DSV() const + FORCE_INLINE D3D12_CPU_DESCRIPTOR_HANDLE DSV() const { return _dsv.CPU(); } @@ -183,17 +110,14 @@ public: { return _dsv.IsValid(); } - D3D12_CPU_DESCRIPTOR_HANDLE SRV() const override { return _srv.CPU(); } - D3D12_CPU_DESCRIPTOR_HANDLE UAV() const override { return _uav.CPU(); } - ResourceOwnerDX12* GetResourceOwner() const override { return _owner; @@ -223,11 +147,6 @@ private: public: - /// - /// Initializes a new instance of the class. - /// - /// The device. - /// The name. GPUTextureDX12(GPUDeviceDX12* device, const StringView& name) : GPUResourceDX12(device, name) { @@ -244,35 +163,29 @@ public: { return (GPUTextureView*)&_handlesPerSlice[arrayOrDepthIndex]; } - GPUTextureView* View(int32 arrayOrDepthIndex, int32 mipMapIndex) const override { return (GPUTextureView*)&_handlesPerMip[arrayOrDepthIndex][mipMapIndex]; } - GPUTextureView* ViewArray() const override { ASSERT(ArraySize() > 1); return (GPUTextureView*)&_handleArray; } - GPUTextureView* ViewVolume() const override { ASSERT(IsVolume()); return (GPUTextureView*)&_handleVolume; } - GPUTextureView* ViewReadOnlyDepth() const override { ASSERT(_desc.Flags & GPUTextureFlags::ReadOnlyDepthView); return (GPUTextureView*)&_handleReadOnlyDepth; } - void* GetNativePtr() const override { - return (void*)nullptr; + return (void*)_resource; } - bool GetData(int32 arrayOrDepthSliceIndex, int32 mipMapIndex, TextureMipData& data, uint32 mipRowPitch) override; // [ResourceOwnerDX12] @@ -286,17 +199,14 @@ public: { return (_desc.Flags & GPUTextureFlags::DepthStencil) != 0; } - D3D12_CPU_DESCRIPTOR_HANDLE SRV() const override { return _srv.CPU(); } - D3D12_CPU_DESCRIPTOR_HANDLE UAV() const override { return _uav.CPU(); } - ResourceOwnerDX12* GetResourceOwner() const override { return (ResourceOwnerDX12*)this; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/IShaderResourceDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/IShaderResourceDX12.h index 155b018e3..64c6cf580 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/IShaderResourceDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/IShaderResourceDX12.h @@ -15,7 +15,7 @@ class IShaderResourceDX12 public: IShaderResourceDX12() - : SubresourceIndex(D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES) + : SubresourceIndex(-1) { } @@ -28,33 +28,33 @@ public: /// /// Affected subresource index or -1 if use whole resource. + /// This solves only resource states tracking per single subresource, not subresources range, if need to here should be range of subresources (for texture arrays, volume textures and cubemaps). /// - int32 SubresourceIndex; // Note: this solves only resource states tracking per single subresource, not subresources range, if need to here should be range of subresources (for texture arrays, volume textures and cubemaps) + int32 SubresourceIndex; + + D3D12_SRV_DIMENSION SrvDimension = D3D12_SRV_DIMENSION_UNKNOWN; + D3D12_UAV_DIMENSION UavDimension = D3D12_UAV_DIMENSION_UNKNOWN; public: /// /// Determines whether this resource is depth/stencil buffer. /// - /// True if this resource is depth/stencil buffer, otherwise false. virtual bool IsDepthStencilResource() const = 0; /// /// Gets CPU handle to the shader resource view descriptor. /// - /// SRV virtual D3D12_CPU_DESCRIPTOR_HANDLE SRV() const = 0; /// /// Gets CPU handle to the unordered access view descriptor. /// - /// UAV virtual D3D12_CPU_DESCRIPTOR_HANDLE UAV() const = 0; /// /// Gets the resource owner. /// - /// Owner object. virtual ResourceOwnerDX12* GetResourceOwner() const = 0; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp index a7bbb6f2c..b7916f9ca 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/QueryHeapDX12.cpp @@ -85,27 +85,15 @@ void QueryHeapDX12::Destroy() void QueryHeapDX12::EndQueryBatchAndResolveQueryData(GPUContextDX12* context) { ASSERT(_currentBatch.Open); - - // Skip empty batches if (_currentBatch.Count == 0) - { return; - } // Close the current batch _currentBatch.Open = false; // Resolve the batch const int32 offset = _currentBatch.Start * _resultSize; - const int32 size = _currentBatch.Count * _resultSize; - context->GetCommandList()->ResolveQueryData( - _queryHeap, - _queryType, - _currentBatch.Start, - _currentBatch.Count, - _resultBuffer, - offset - ); + context->GetCommandList()->ResolveQueryData(_queryHeap, _queryType, _currentBatch.Start, _currentBatch.Count, _resultBuffer, offset); _currentBatch.Sync = _device->GetCommandQueue()->GetSyncPoint(); // Begin a new query batch @@ -199,8 +187,7 @@ void* QueryHeapDX12::ResolveQuery(ElementHandle& handle) Platform::MemoryCopy(_resultData.Get() + range.Begin, (byte*)mapped + range.Begin, batch.Count * _resultSize); // Unmap with an empty range to indicate nothing was written by the CPU - range.Begin = range.End = 0; - _resultBuffer->Unmap(0, &range); + _resultBuffer->Unmap(0, nullptr); // All elements got its results so we can remove this batch _batches.RemoveAt(i); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/ResourceOwnerDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/ResourceOwnerDX12.h index c3e241f8d..f797c84c0 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/ResourceOwnerDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/ResourceOwnerDX12.h @@ -15,7 +15,7 @@ class GPUAsyncContextDX12; /// /// Default amount of frames to wait until resource delete. /// -#define DX12_RESOURCE_DELETE_SAFE_FRAMES_COUNT 10 +#define DX12_RESOURCE_DELETE_SAFE_FRAMES_COUNT 100 /// /// Custom resource state used to indicate invalid state (useful for debugging resource tracking issues). @@ -32,12 +32,21 @@ public: /// /// Returns true if resource state transition is needed in order to use resource in given state. /// - /// The current resource state. - /// the destination resource state. + /// The current resource state. + /// the destination resource state. /// True if need to perform a transition, otherwise false. - static bool IsTransitionNeeded(D3D12_RESOURCE_STATES currentState, D3D12_RESOURCE_STATES targetState) + FORCE_INLINE static bool IsTransitionNeeded(D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES& after) { - return currentState != targetState && ((currentState | targetState) != currentState || targetState == D3D12_RESOURCE_STATE_COMMON); + if (before == D3D12_RESOURCE_STATE_DEPTH_WRITE && after == D3D12_RESOURCE_STATE_DEPTH_READ) + return false; + if (after == D3D12_RESOURCE_STATE_COMMON) + return before != D3D12_RESOURCE_STATE_COMMON; + if (after == D3D12_RESOURCE_STATE_DEPTH_READ) + return ~(before & (D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)) == 0; + const D3D12_RESOURCE_STATES combined = before | after; + if ((combined & (D3D12_RESOURCE_STATE_GENERIC_READ | D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT)) == combined) + after = combined; + return before != after; } }; @@ -81,7 +90,6 @@ public: /// /// Gets the subresources count. /// - /// The subresources count. FORCE_INLINE uint32 GetSubresourcesCount() const { return _subresourcesCount; @@ -90,7 +98,6 @@ public: /// /// Gets DirectX 12 resource object handle /// - /// DirectX 12 resource object handle FORCE_INLINE ID3D12Resource* GetResource() const { return _resource; @@ -99,7 +106,6 @@ public: /// /// Gets resource owner object as a GPUResource type or returns null if cannot perform cast. /// - /// GPU Resource or null if cannot cast. virtual GPUResource* AsGPUResource() const = 0; protected: diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h b/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h new file mode 100644 index 000000000..2a39c400d --- /dev/null +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/Types.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if COMPILE_WITH_DX_SHADER_COMPILER || GRAPHICS_API_DIRECTX12 + +#include "../IncludeDirectXHeaders.h" + +struct DxShaderHeader +{ + /// + /// The SRV dimensions per-slot. + /// + byte SrDimensions[32]; + + /// + /// The UAV dimensions per-slot. + /// + byte UaDimensions[4]; + + // .. rest is just a actual data array +}; + +#endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp index c4680b57e..c00b5bf58 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.cpp @@ -60,6 +60,7 @@ DynamicAllocation UploadBufferDX12::Allocate(uint64 size, uint64 align) // Move in the page _currentOffset += size; + ASSERT(_currentPage->GetResource()); return result; } @@ -67,12 +68,8 @@ bool UploadBufferDX12::UploadBuffer(GPUContextDX12* context, ID3D12Resource* buf { // Allocate data const DynamicAllocation allocation = Allocate(size, 4); - - // Check if allocation is invalid if (allocation.IsInvalid()) - { return true; - } // Copy data Platform::MemoryCopy(allocation.CPUAddress, data, static_cast(size)); @@ -85,10 +82,8 @@ bool UploadBufferDX12::UploadBuffer(GPUContextDX12* context, ID3D12Resource* buf bool UploadBufferDX12::UploadTexture(GPUContextDX12* context, ID3D12Resource* texture, const void* srcData, uint32 srcRowPitch, uint32 srcSlicePitch, int32 mipIndex, int32 arrayIndex) { - // Cache resource info D3D12_RESOURCE_DESC resourceDesc = texture->GetDesc(); const UINT subresourceIndex = RenderToolsDX::CalcSubresourceIndex(mipIndex, arrayIndex, resourceDesc.MipLevels); - D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; uint32 numRows; uint64 rowPitchAligned, mipSizeAligned; @@ -96,16 +91,8 @@ bool UploadBufferDX12::UploadTexture(GPUContextDX12* context, ID3D12Resource* te rowPitchAligned = footprint.Footprint.RowPitch; mipSizeAligned = rowPitchAligned * footprint.Footprint.Height; - // Destination texture copy location description - D3D12_TEXTURE_COPY_LOCATION dstLocation; - dstLocation.pResource = texture; - dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - dstLocation.SubresourceIndex = subresourceIndex; - // Allocate data const DynamicAllocation allocation = Allocate(mipSizeAligned, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); - - // Check if allocation is invalid if (allocation.Size != mipSizeAligned) return true; @@ -131,6 +118,12 @@ bool UploadBufferDX12::UploadTexture(GPUContextDX12* context, ID3D12Resource* te } } + // Destination texture copy location description + D3D12_TEXTURE_COPY_LOCATION dstLocation; + dstLocation.pResource = texture; + dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLocation.SubresourceIndex = subresourceIndex; + // Source buffer copy location description D3D12_TEXTURE_COPY_LOCATION srcLocation; srcLocation.pResource = allocation.Page->GetResource(); @@ -235,13 +228,7 @@ UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ID3D12Resource* resource; - VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource( - &heapProperties, - D3D12_HEAP_FLAG_NONE, - &resourceDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, - nullptr, - IID_PPV_ARGS(&resource))); + VALIDATE_DIRECTX_RESULT(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, D3D12_RESOURCE_STATE_GENERIC_READ, 1); @@ -260,6 +247,8 @@ void UploadBufferPageDX12::OnReleaseGPU() { _resource->Unmap(0, nullptr); } + GPUAddress = 0; + CPUAddress = nullptr; // Release releaseResource(); diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h index 6995e5c65..7f1723cb5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/UploadBufferDX12.h @@ -13,7 +13,7 @@ #define DX12_UPLOAD_PAGE_GEN_TIMEOUT DX12_BACK_BUFFER_COUNT // Upload buffer pages that are not used for a few frames are disposed -#define DX12_UPLOAD_PAGE_NOT_USED_FRAME_TIMEOUT 8 +#define DX12_UPLOAD_PAGE_NOT_USED_FRAME_TIMEOUT 60 class GPUTextureDX12; @@ -183,7 +183,6 @@ public: /// /// Gets the current generation number. /// - /// The current generation number. FORCE_INLINE uint64 GetCurrentGeneration() const { return _currentGeneration; diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp index e20fe0ac0..fa266a34a 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp +++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp @@ -58,6 +58,7 @@ bool GPUDeviceNull::Init() limits.HasDepthAsSRV = false; limits.HasReadOnlyDepth = false; limits.HasMultisampleDepthAsSRV = false; + limits.HasTypedUAVLoad = false; limits.MaximumMipLevelsCount = 14; limits.MaximumTexture1DSize = 8192; limits.MaximumTexture1DArraySize = 512; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 69dd0134d..6e73ec157 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -33,10 +33,46 @@ static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartIndex) == OFFSET_OF(VkD static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartVertex) == OFFSET_OF(VkDrawIndexedIndirectCommand, vertexOffset), "Wrong offset for GPUDrawIndexedIndirectArgs::StartVertex"); static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartInstance) == OFFSET_OF(VkDrawIndexedIndirectCommand, firstInstance), "Wrong offset for GPUDrawIndexedIndirectArgs::StartInstance"); +#if VK_ENABLE_BARRIERS_DEBUG + +const Char* ToString(VkImageLayout layout) +{ + switch (layout) + { +#define TO_STR(type) case type: return TEXT(#type) + TO_STR(VK_IMAGE_LAYOUT_UNDEFINED); + TO_STR(VK_IMAGE_LAYOUT_GENERAL); + TO_STR(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_PREINITIALIZED); + TO_STR(VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL); + TO_STR(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + TO_STR(VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR); + TO_STR(VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV); + TO_STR(VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT); + TO_STR(VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL_KHR); + TO_STR(VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR); +#undef TO_STR + default: + return TEXT("?"); + } +} + +#endif + void PipelineBarrierVulkan::AddImageBarrier(VkImage image, const VkImageSubresourceRange& range, VkImageLayout srcLayout, VkImageLayout dstLayout, GPUTextureViewVulkan* handle) { #if VK_ENABLE_BARRIERS_DEBUG - ImageBarriersDebug.Add(handle); + ImageBarriersDebug.Add(handle); #endif VkImageMemoryBarrier& imageBarrier = ImageBarriers.AddOne(); RenderToolsVulkan::ZeroStruct(imageBarrier, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); @@ -44,21 +80,21 @@ void PipelineBarrierVulkan::AddImageBarrier(VkImage image, const VkImageSubresou imageBarrier.subresourceRange = range; imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - - RenderToolsVulkan::SetImageBarrierInfo(srcLayout, dstLayout, imageBarrier, SourceStage, DestStage); - + imageBarrier.oldLayout = srcLayout; + imageBarrier.newLayout = dstLayout; + SourceStage |= RenderToolsVulkan::GetImageBarrierFlags(srcLayout, imageBarrier.srcAccessMask); + DestStage |= RenderToolsVulkan::GetImageBarrierFlags(dstLayout, imageBarrier.dstAccessMask); #if VK_ENABLE_BARRIERS_DEBUG - LOG(Warning, "Image Barrier: 0x{0:x}, {1} -> {2} for baseMipLevel: {3}, baseArrayLayer: {4}, levelCount: {5}, layerCount: {6} ({7})", - (int)image, - srcLayout, - dstLayout, - range.baseMipLevel, - range.baseArrayLayer, - range.levelCount, - range.layerCount, - - handle && handle->Owner->AsGPUResource() ? handle->Owner->AsGPUResource()->ToString() : String::Empty - ); + LOG(Warning, "Image Barrier: 0x{0:x}, {1} -> {2} for baseMipLevel: {3}, baseArrayLayer: {4}, levelCount: {5}, layerCount: {6} ({7})", + (uintptr)image, + ToString(srcLayout), + ToString(dstLayout), + range.baseMipLevel, + range.baseArrayLayer, + range.levelCount, + range.layerCount, + handle && handle->Owner->AsGPUResource() ? handle->Owner->AsGPUResource()->ToString() : String::Empty + ); #endif } @@ -73,16 +109,14 @@ void PipelineBarrierVulkan::AddBufferBarrier(VkBuffer buffer, VkDeviceSize offse bufferBarrier.dstAccessMask = dstAccess; bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - - RenderToolsVulkan::SetBufferBarrierInfo(srcAccess, dstAccess, SourceStage, DestStage); + SourceStage |= RenderToolsVulkan::GetBufferBarrierFlags(srcAccess); + DestStage |= RenderToolsVulkan::GetBufferBarrierFlags(dstAccess); } void PipelineBarrierVulkan::Execute(CmdBufferVulkan* cmdBuffer) { ASSERT(cmdBuffer->IsOutsideRenderPass()); - vkCmdPipelineBarrier(cmdBuffer->GetHandle(), SourceStage, DestStage, 0, 0, nullptr, BufferBarriers.Count(), BufferBarriers.Get(), ImageBarriers.Count(), ImageBarriers.Get()); - Reset(); } @@ -127,11 +161,11 @@ void GPUContextVulkan::AddImageBarrier(VkImage image, VkImageLayout srcLayout, V _barriers.AddImageBarrier(image, subresourceRange, srcLayout, dstLayout, handle); #if !VK_ENABLE_BARRIERS_BATCHING - // Auto-flush without batching - const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - if (cmdBuffer->IsInsideRenderPass()) - EndRenderPass(); - _barriers.Execute(cmdBuffer); + // Auto-flush without batching + const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); + if (cmdBuffer->IsInsideRenderPass()) + EndRenderPass(); + _barriers.Execute(cmdBuffer); #endif } @@ -144,15 +178,19 @@ void GPUContextVulkan::AddImageBarrier(GPUTextureViewVulkan* handle, VkImageLayo const int32 mipLevels = state.GetSubresourcesCount() / handle->Owner->ArraySlices; if (state.AreAllSubresourcesSame()) { - // Transition entire resource at once - const VkImageLayout srcLayout = state.GetSubresourceState(0); - VkImageSubresourceRange range; - range.aspectMask = handle->Info.subresourceRange.aspectMask; - range.baseMipLevel = 0; - range.levelCount = mipLevels; - range.baseArrayLayer = 0; - range.layerCount = handle->Owner->ArraySlices; - AddImageBarrier(handle->Image, srcLayout, dstLayout, range, handle); + const VkImageLayout srcLayout = state.GetSubresourceState(-1); + if (srcLayout != dstLayout) + { + // Transition entire resource at once + VkImageSubresourceRange range; + range.aspectMask = handle->Info.subresourceRange.aspectMask; + range.baseMipLevel = 0; + range.levelCount = mipLevels; + range.baseArrayLayer = 0; + range.layerCount = handle->Owner->ArraySlices; + AddImageBarrier(handle->Image, srcLayout, dstLayout, range, handle); + state.SetResourceState(dstLayout); + } } else { @@ -160,7 +198,6 @@ void GPUContextVulkan::AddImageBarrier(GPUTextureViewVulkan* handle, VkImageLayo for (int32 i = 0; i < state.GetSubresourcesCount(); i++) { const VkImageLayout srcLayout = state.GetSubresourceState(i); - if (srcLayout != dstLayout) { VkImageSubresourceRange range; @@ -173,15 +210,13 @@ void GPUContextVulkan::AddImageBarrier(GPUTextureViewVulkan* handle, VkImageLayo state.SetSubresourceState(i, dstLayout); } } - ASSERT(state.CheckResourceState(dstLayout)); } - + ASSERT(state.CheckResourceState(dstLayout)); state.SetResourceState(dstLayout); } else { const VkImageLayout srcLayout = state.GetSubresourceState(subresourceIndex); - if (srcLayout != dstLayout) { // Transition a single subresource @@ -265,11 +300,11 @@ void GPUContextVulkan::AddBufferBarrier(GPUBufferVulkan* buffer, VkAccessFlags d buffer->Access = dstAccess; #if !VK_ENABLE_BARRIERS_BATCHING - // Auto-flush without batching - const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - if (cmdBuffer->IsInsideRenderPass()) - EndRenderPass(); - _barriers.Execute(cmdBuffer); + // Auto-flush without batching + const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); + if (cmdBuffer->IsInsideRenderPass()) + EndRenderPass(); + _barriers.Execute(cmdBuffer); #endif } @@ -631,6 +666,18 @@ void GPUContextVulkan::OnDrawCall() UpdateDescriptorSets(pipelineState); } + // Bind any missing vertex buffers to null if required by the current state + const auto vertexInputState = pipelineState->GetVertexInputState(); + const int32 missingVBs = vertexInputState->vertexBindingDescriptionCount - _vbCount; + if (missingVBs > 0) + { + VkBuffer buffers[GPU_MAX_VB_BINDED]; + VkDeviceSize offsets[GPU_MAX_VB_BINDED] = {}; + for (int32 i = 0; i < missingVBs; i++) + buffers[i] = _device->HelperResources.GetDummyVertexBuffer()->GetHandle(); + vkCmdBindVertexBuffers(cmdBuffer->GetHandle(), _vbCount, missingVBs, buffers, offsets); + } + // Start render pass if not during one if (cmdBuffer->IsOutsideRenderPass()) BeginRenderPass(); @@ -655,7 +702,7 @@ void GPUContextVulkan::OnDrawCall() _rtDirtyFlag = false; #if VK_ENABLE_BARRIERS_DEBUG - LOG(Warning, "Draw"); + LOG(Warning, "Draw"); #endif } @@ -671,6 +718,7 @@ void GPUContextVulkan::FrameBegin() _srDirtyFlag = 0; _uaDirtyFlag = 0; _rtCount = 0; + _vbCount = 0; _renderPass = nullptr; _currentState = nullptr; _rtDepth = nullptr; @@ -900,31 +948,32 @@ void GPUContextVulkan::BindCB(int32 slot, GPUConstantBuffer* cb) void GPUContextVulkan::BindSR(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_SR_BINDED); - const auto handle = view ? (DescriptorOwnerResourceVulkan*)view->GetNativePtr() : nullptr; - if (_srHandles[slot] != handle) { _srDirtyFlag = true; _srHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } void GPUContextVulkan::BindUA(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED); - const auto handle = view ? (DescriptorOwnerResourceVulkan*)view->GetNativePtr() : nullptr; - if (_uaHandles[slot] != handle) { _uaDirtyFlag = true; _uaHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } void GPUContextVulkan::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets) { + _vbCount = vertexBuffers.Length(); if (vertexBuffers.Length() == 0) return; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); @@ -958,7 +1007,7 @@ void GPUContextVulkan::UpdateCB(GPUConstantBuffer* cb, const void* data) const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); // Allocate bytes for the buffer - const auto allocation = _device->UniformBufferUploader->Allocate(size, 0, cmdBuffer); + const auto allocation = _device->UniformBufferUploader->Allocate(size, 0, this); // Copy data Platform::MemoryCopy(allocation.CPUAddress, data, allocation.Size); @@ -1005,7 +1054,7 @@ void GPUContextVulkan::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCo RENDER_STAT_DISPATCH_CALL(); #if VK_ENABLE_BARRIERS_DEBUG - LOG(Warning, "Dispatch"); + LOG(Warning, "Dispatch"); #endif } @@ -1039,7 +1088,7 @@ void GPUContextVulkan::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* b RENDER_STAT_DISPATCH_CALL(); #if VK_ENABLE_BARRIERS_DEBUG - LOG(Warning, "DispatchIndirect"); + LOG(Warning, "DispatchIndirect"); #endif } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h index a3d26f017..da2054ab3 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h @@ -35,8 +35,6 @@ class DescriptorSetLayoutVulkan; /// #define VK_BARRIER_BUFFER_SIZE 16 -#if VK_ENABLE_BARRIERS_BATCHING - /// /// The Vulkan pipeline resources layout barrier batching structure. /// @@ -81,8 +79,6 @@ public: void Execute(CmdBufferVulkan* cmdBuffer); }; -#endif - /// /// GPU Context for Vulkan backend. /// @@ -93,9 +89,7 @@ private: GPUDeviceVulkan* _device; QueueVulkan* _queue; CmdBufferManagerVulkan* _cmdBufferManager; -#if VK_ENABLE_BARRIERS_BATCHING PipelineBarrierVulkan _barriers; -#endif int32 _psDirtyFlag : 1; int32 _rtDirtyFlag : 1; @@ -104,6 +98,7 @@ private: int32 _uaDirtyFlag : 1; int32 _rtCount; + int32 _vbCount; RenderPassVulkan* _renderPass; GPUPipelineStateVulkan* _currentState; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 9ca399dad..5c9a7032a 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1684,6 +1684,7 @@ bool GPUDeviceVulkan::Init() limits.HasDepthAsSRV = true; limits.HasReadOnlyDepth = true; limits.HasMultisampleDepthAsSRV = !!PhysicalDeviceFeatures.sampleRateShading; + limits.HasTypedUAVLoad = true; limits.MaximumMipLevelsCount = Math::Min(static_cast(log2(PhysicalDeviceLimits.maxImageDimension2D)), GPU_MAX_TEXTURE_MIP_LEVELS); limits.MaximumTexture1DSize = PhysicalDeviceLimits.maxImageDimension1D; limits.MaximumTexture1DArraySize = PhysicalDeviceLimits.maxImageArrayLayers; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index cdb76e1cf..991bd5766 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -270,7 +270,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _descDynamic.pDynamicStates = _dynamicStates; _dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT; _dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; - _dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; + //_dynamicStates[_descDynamic.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; static_assert(ARRAY_COUNT(_dynamicStates) <= 3, "Invalid dynamic states array."); _desc.pDynamicState = &_descDynamic; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h index 29b71a3bc..142dd9f91 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h @@ -152,6 +152,11 @@ public: /// const SpirvShaderDescriptorInfo* DescriptorInfoPerStage[DescriptorSet::GraphicsStagesCount]; + const VkPipelineVertexInputStateCreateInfo* GetVertexInputState() const + { + return _desc.pVertexInputState; + } + DescriptorSetWriteContainerVulkan DSWriteContainer; DescriptorSetWriterVulkan DSWriter[DescriptorSet::GraphicsStagesCount]; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp index 5b38b926a..863ebdc46 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp @@ -3,6 +3,7 @@ #if GRAPHICS_API_VULKAN #include "GPUShaderVulkan.h" +#include "GPUContextVulkan.h" #include "GPUShaderProgramVulkan.h" #include "RenderToolsVulkan.h" #include "CmdBufferVulkan.h" @@ -12,7 +13,7 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #if PLATFORM_DESKTOP -#define VULKAN_UNIFORM_RING_BUFFER_SIZE 16 * 1024 * 1024 +#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024 #else #define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024 #endif @@ -45,7 +46,7 @@ UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device LOG_VULKAN_RESULT(result); } -UniformBufferUploaderVulkan::Allocation UniformBufferUploaderVulkan::Allocate(uint64 size, uint32 alignment, CmdBufferVulkan* cmdBuffer) +UniformBufferUploaderVulkan::Allocation UniformBufferUploaderVulkan::Allocate(uint64 size, uint32 alignment, GPUContextVulkan* context) { alignment = Math::Max(_minAlignment, alignment); uint64 offset = Math::AlignUp(_offset, alignment); @@ -53,16 +54,12 @@ UniformBufferUploaderVulkan::Allocation UniformBufferUploaderVulkan::Allocate(ui // Check if wrap around ring buffer if (offset + size >= _size) { - if (_fenceCmdBuffer) + auto cmdBuffer = context->GetCmdBufferManager()->GetActiveCmdBuffer(); + if (_fenceCmdBuffer && _fenceCounter == cmdBuffer->GetFenceSignaledCounter()) { - if (_fenceCmdBuffer == cmdBuffer && _fenceCounter == cmdBuffer->GetFenceSignaledCounter()) - { - LOG(Error, "Wrapped around the ring buffer. Requested more bytes than possible in the same cmd buffer!"); - } - else if (_fenceCounter == _fenceCmdBuffer->GetFenceSignaledCounter()) - { - LOG(Error, "Wrapped around the ring buffer! Need to wait on the GPU!!!"); - } + LOG(Error, "Wrapped around the ring buffer! Need to wait on the GPU!"); + context->Flush(); + cmdBuffer = context->GetCmdBufferManager()->GetActiveCmdBuffer(); } offset = 0; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h index 842dfc145..5158baefa 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h @@ -62,7 +62,7 @@ public: public: - Allocation Allocate(uint64 size, uint32 alignment, CmdBufferVulkan* cmdBuffer); + Allocation Allocate(uint64 size, uint32 alignment, GPUContextVulkan* context); public: diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index 1380d221d..c7c11fb0c 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -164,37 +164,6 @@ public: return stageFlags; } - static inline void SetBufferBarrierInfo(VkAccessFlags source, VkAccessFlags dest, VkPipelineStageFlags& sourceStage, VkPipelineStageFlags& destStage) - { - sourceStage |= GetBufferBarrierFlags(source); - destStage |= GetBufferBarrierFlags(dest); - } - - static inline void SetImageBarrierInfo(VkImageLayout source, VkImageLayout dest, VkImageMemoryBarrier& barrier, VkPipelineStageFlags& sourceStage, VkPipelineStageFlags& destStage) - { - barrier.oldLayout = source; - barrier.newLayout = dest; - - sourceStage |= GetImageBarrierFlags(source, barrier.srcAccessMask); - destStage |= GetImageBarrierFlags(dest, barrier.dstAccessMask); - } - - static void ImagePipelineBarrier(VkCommandBuffer cmdBuffer, VkImage img, VkImageLayout src, VkImageLayout dest, const VkImageSubresourceRange& subresourceRange) - { - VkImageMemoryBarrier imageBarrier; - ZeroStruct(imageBarrier, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); - imageBarrier.image = img; - imageBarrier.subresourceRange = subresourceRange; - imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - - VkPipelineStageFlags srcStages = (VkPipelineStageFlags)0; - VkPipelineStageFlags destStages = (VkPipelineStageFlags)0; - SetImageBarrierInfo(src, dest, imageBarrier, srcStages, destStages); - - vkCmdPipelineBarrier(cmdBuffer, srcStages, destStages, 0, 0, nullptr, 0, nullptr, 1, &imageBarrier); - } - template static FORCE_INLINE void ZeroStruct(T& data, VkStructureType type) { diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index d7796691b..44131504d 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -223,6 +223,27 @@ bool Mouse::Update(EventQueue& queue) return false; } +void Keyboard::State::Clear() +{ + Platform::MemoryClear(this, sizeof(State)); +} + +Keyboard::Keyboard() + : InputDevice(SpawnParams(Guid::New(), TypeInitializer), TEXT("Keyboard")) +{ + _state.Clear(); + _prevState.Clear(); +} + +bool Keyboard::IsAnyKeyDown() const +{ + // TODO: optimize with SIMD + bool result = false; + for (auto e : _state.Keys) + result |= e; + return result; +} + void Keyboard::OnCharInput(Char c, Window* target) { // Skip control characters diff --git a/Source/Engine/Input/Keyboard.h b/Source/Engine/Input/Keyboard.h index f182f9731..7877b3f13 100644 --- a/Source/Engine/Input/Keyboard.h +++ b/Source/Engine/Input/Keyboard.h @@ -10,48 +10,20 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Keyboard : public InputDevice { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Keyboard); -public: +protected: - /// - /// The keyboard state. - /// struct State { - /// - /// The input text length (characters count). - /// uint16 InputTextLength; - - /// - /// The input text. - /// Char InputText[32]; - - /// - /// The keys. - /// bool Keys[(int32)KeyboardKeys::MAX]; - - /// - /// Clears the state. - /// - void Clear() - { - Platform::MemoryClear(this, sizeof(State)); - } + void Clear(); }; -protected: - State _state; State _prevState; - explicit Keyboard() - : InputDevice(SpawnParams(Guid::New(), TypeInitializer), TEXT("Keyboard")) - { - _state.Clear(); - _prevState.Clear(); - } + explicit Keyboard(); public: @@ -94,6 +66,11 @@ public: return !_state.Keys[static_cast(key)] && _prevState.Keys[static_cast(key)]; } + /// + /// Checks if any keyboard key is currently pressed. + /// + API_PROPERTY() bool IsAnyKeyDown() const; + public: /// diff --git a/Source/Engine/Input/KeyboardKeys.h b/Source/Engine/Input/KeyboardKeys.h index fe6daa12b..12f79ea1f 100644 --- a/Source/Engine/Input/KeyboardKeys.h +++ b/Source/Engine/Input/KeyboardKeys.h @@ -745,7 +745,7 @@ API_ENUM() enum class KeyboardKeys LeftBracket = 0xDB, /// - /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard the '\\|' key + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard the '\|' key /// Backslash = 0xDC, diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index a62aad592..45bc701c6 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -2,7 +2,8 @@ #include "AnimatedModel.h" #include "BoneSocket.h" -#include "Engine/Animations/AnimationManager.h" +#include "Engine/Core/Math/Matrix3x4.h" +#include "Engine/Animations/Animations.h" #include "Engine/Engine/Engine.h" #if USE_EDITOR #include "Editor/Editor.h" @@ -13,8 +14,6 @@ #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Serialization/Serialization.h" -extern Array UpdateBones; - AnimatedModel::AnimatedModel(const SpawnParams& params) : ModelInstanceActor(params) , _actualMode(AnimationUpdateMode::Never) @@ -52,7 +51,7 @@ void AnimatedModel::UpdateAnimation() if (AnimationGraph && AnimationGraph->IsLoaded() && AnimationGraph->Graph.IsReady()) { // Request an animation update - AnimationManager::AddToUpdate(this); + Animations::AddToUpdate(this); } else { @@ -114,16 +113,6 @@ void AnimatedModel::PreInitSkinningData() UpdateSockets(); } -void AnimatedModel::UpdateSockets() -{ - for (int32 i = 0; i < Children.Count(); i++) - { - auto socket = dynamic_cast(Children[i]); - if (socket) - socket->UpdateTransformation(); - } -} - void AnimatedModel::GetCurrentPose(Array& nodesTransformation, bool worldSpace) const { nodesTransformation = GraphInstance.NodesPose; @@ -389,7 +378,7 @@ void AnimatedModel::BeginPlay(SceneBeginData* data) void AnimatedModel::EndPlay() { - AnimationManager::RemoveFromUpdate(this); + Animations::RemoveFromUpdate(this); SetMasterPoseModel(nullptr); // Base @@ -439,8 +428,7 @@ void AnimatedModel::UpdateLocalBounds() } // Scale bounds - box.Minimum *= BoundsScale; - box.Maximum *= BoundsScale; + box = box.MakeScaled(box, BoundsScale); _boxLocal = box; } @@ -453,9 +441,19 @@ void AnimatedModel::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void AnimatedModel::OnAnimationUpdated() +void AnimatedModel::UpdateSockets() { - ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated"); + for (int32 i = 0; i < Children.Count(); i++) + { + auto socket = dynamic_cast(Children[i]); + if (socket) + socket->UpdateTransformation(); + } +} + +void AnimatedModel::OnAnimationUpdated_Async() +{ + // Update asynchronous stuff auto& skeleton = SkinnedModel->Skeleton; // Copy pose from the master @@ -471,22 +469,37 @@ void AnimatedModel::OnAnimationUpdated() // Calculate the final bones transformations and update skinning { ANIM_GRAPH_PROFILE_EVENT("Final Pose"); - UpdateBones.Resize(skeleton.Bones.Count(), false); - for (int32 boneIndex = 0; boneIndex < skeleton.Bones.Count(); boneIndex++) + const int32 bonesCount = skeleton.Bones.Count(); + Matrix3x4* output = (Matrix3x4*)_skinningData.Data.Get(); + ASSERT(_skinningData.Data.Count() == bonesCount * sizeof(Matrix3x4)); + for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { auto& bone = skeleton.Bones[boneIndex]; - UpdateBones[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; + Matrix matrix = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; + output[boneIndex].SetMatrixTranspose(matrix); } + _skinningData.OnDataChanged(!PerBoneMotionBlur); } - _skinningData.SetData(UpdateBones.Get(), !PerBoneMotionBlur); UpdateBounds(); + _blendShapes.Update(SkinnedModel.Get()); +} + +void AnimatedModel::OnAnimationUpdated_Sync() +{ + // Update synchronous stuff UpdateSockets(); ApplyRootMotion(GraphInstance.RootMotion); - _blendShapes.Update(SkinnedModel.Get()); AnimationUpdated(); } +void AnimatedModel::OnAnimationUpdated() +{ + ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated"); + OnAnimationUpdated_Async(); + OnAnimationUpdated_Sync(); +} + void AnimatedModel::OnSkinnedModelChanged() { Entries.Release(); @@ -502,7 +515,6 @@ void AnimatedModel::OnSkinnedModelLoaded() { Entries.SetupIfInvalid(SkinnedModel); - UpdateBounds(); GraphInstance.Invalidate(); if (_blendShapes.Weights.HasItems()) _blendShapes.WeightsDirty = true; @@ -617,7 +629,7 @@ void AnimatedModel::DrawGeneric(RenderContext& renderContext) void AnimatedModel::OnDebugDrawSelected() { - DEBUG_DRAW_WIRE_BOX(_box, Color::Violet * 0.8f, 0, true); + DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true); // Base ModelInstanceActor::OnDebugDrawSelected(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index df218b897..771833f2c 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -15,7 +15,7 @@ API_CLASS() class FLAXENGINE_API AnimatedModel : public ModelInstanceActor { DECLARE_SCENE_OBJECT(AnimatedModel); - friend class AnimationManagerService; + friend class AnimationsSystem; public: /// @@ -306,6 +306,8 @@ private: void UpdateLocalBounds(); void UpdateBounds(); void UpdateSockets(); + void OnAnimationUpdated_Async(); + void OnAnimationUpdated_Sync(); void OnAnimationUpdated(); void OnSkinnedModelChanged(); diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 5f9d4bdb3..9341002c1 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -136,8 +136,11 @@ void PointLight::Draw(RenderContext& renderContext) void PointLight::OnDebugDraw() { - // Draw source tube - DEBUG_DRAW_WIRE_TUBE(GetPosition(), GetOrientation(), SourceRadius, SourceLength, Color::Orange, 0, true); + if (SourceRadius > ZeroTolerance || SourceLength > ZeroTolerance) + { + // Draw source tube + DEBUG_DRAW_WIRE_TUBE(GetPosition(), GetOrientation(), SourceRadius, SourceLength, Color::Orange, 0, true); + } // Base LightWithShadow::OnDebugDraw(); diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index da7efbf31..94ccf6c29 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "SkyLight.h" +#include "Engine/Core/Log.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index 3e874f07f..3c43114f0 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -480,3 +480,11 @@ void SplineModel::OnTransformChanged() OnSplineUpdated(); } + +void SplineModel::OnActiveInTreeChanged() +{ + // Base + ModelInstanceActor::OnActiveInTreeChanged(); + + OnSplineUpdated(); +} diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h index 6a0393cdc..5c08ea17f 100644 --- a/Source/Engine/Level/Actors/SplineModel.h +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -125,4 +125,5 @@ protected: // [ModelInstanceActor] void OnTransformChanged() override; + void OnActiveInTreeChanged() override; }; diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index c601506c4..ec25ffb5b 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -184,8 +184,11 @@ void SpotLight::Draw(RenderContext& renderContext) void SpotLight::OnDebugDraw() { - // Draw source tube - DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition(), SourceRadius), Color::Orange, 0, true); + if (SourceRadius > ZeroTolerance) + { + // Draw source tube + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition(), SourceRadius), Color::Orange, 0, true); + } // Base LightWithShadow::OnDebugDraw(); diff --git a/Source/Engine/Level/Scene/Lightmap.cpp b/Source/Engine/Level/Scene/Lightmap.cpp index 88fa88b5a..2559005a9 100644 --- a/Source/Engine/Level/Scene/Lightmap.cpp +++ b/Source/Engine/Level/Scene/Lightmap.cpp @@ -2,6 +2,7 @@ #include "Lightmap.h" #include "Scene.h" +#include "Engine/Core/Log.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Content/Content.h" #include "Engine/Level/Level.h" diff --git a/Source/Engine/Level/Scene/SceneCSGData.cpp b/Source/Engine/Level/Scene/SceneCSGData.cpp index 2ac5121b5..2e4ecc3b3 100644 --- a/Source/Engine/Level/Scene/SceneCSGData.cpp +++ b/Source/Engine/Level/Scene/SceneCSGData.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "SceneCSGData.h" +#include "Engine/Core/Log.h" #include "Engine/CSG/CSGBuilder.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/Scene.h" diff --git a/Source/Engine/Level/SceneObject.cpp b/Source/Engine/Level/SceneObject.cpp index aaf31a4f6..9267f171f 100644 --- a/Source/Engine/Level/SceneObject.cpp +++ b/Source/Engine/Level/SceneObject.cpp @@ -149,7 +149,7 @@ void SceneObject::Deserialize(DeserializeStream& stream, ISerializeModifier* mod // Handle C# objects data serialization if (Flags & ObjectFlags::IsManagedType) { - auto* const v = stream.FindMember("V"); + auto* const v = SERIALIZE_FIND_MEMBER(stream, "V"); if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) { ManagedSerialization::Deserialize(v->value, GetOrCreateManagedInstance()); @@ -159,7 +159,7 @@ void SceneObject::Deserialize(DeserializeStream& stream, ISerializeModifier* mod // Handle custom scripting objects data serialization if (Flags & ObjectFlags::IsCustomScriptingType) { - auto* const v = stream.FindMember("D"); + auto* const v = SERIALIZE_FIND_MEMBER(stream, "D"); if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) { _type.Module->DeserializeObject(v->value, this, modifier); diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 85cbc930f..321b0f6f1 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -74,7 +74,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream LOG(Warning, "Invalid object type (TypeName must be an object type full name string)."); return nullptr; } - const StringAnsiView typeName(typeNameMember->value.GetString(), typeNameMember->value.GetStringLength()); + const StringAnsiView typeName(typeNameMember->value.GetStringAnsiView()); const ScriptingTypeHandle type = Scripting::FindScriptingType(typeName); if (type) diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index fe7756049..32ba8c61f 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -5,6 +5,7 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" #if COMPILE_WITH_ASSETS_IMPORTER +#include "Engine/Core/Log.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index ae25e01e0..3f2c58df8 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -3,6 +3,12 @@ #include "ParticleEmitterGraph.CPU.h" #include "Engine/Core/Random.h" +// ReSharper disable CppCStyleCast +// ReSharper disable CppClangTidyClangDiagnosticCastAlign +// ReSharper disable CppDefaultCaseNotHandledInSwitchStatement +// ReSharper disable CppClangTidyCppcoreguidelinesMacroUsage +// ReSharper disable CppClangTidyClangDiagnosticOldStyleCast + #define RAND Random::Rand() #define RAND2 Vector2(RAND, RAND) #define RAND3 Vector3(RAND, RAND, RAND) @@ -62,7 +68,7 @@ namespace float scale = 1.0f; for (int32 i = 0; i < octaves; i++) { - const float curWeight = Math::Pow(1.0f - ((float)i / octaves), Math::Lerp(2.0f, 0.2f, roughness)); + const float curWeight = Math::Pow(1.0f - ((float)i / (float)octaves), Math::Lerp(2.0f, 0.2f, roughness)); noise += Noise3D(position * scale) * curWeight; weight += curWeight; @@ -97,7 +103,8 @@ namespace int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) { const auto node = _graph.SpawnModules[index]; - auto& data = _data->SpawnModulesData[index]; + auto& context = Context.Get(); + auto& data = context.Data->SpawnModulesData[index]; // Accumulate the previous frame fraction float spawnCount = data.SpawnCounter; @@ -109,13 +116,13 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) case 100: { const float rate = Math::Max((float)TryGetValue(node->GetBox(0), node->Values[2]), 0.0f); - spawnCount += rate * _deltaTime; + spawnCount += rate * context.DeltaTime; break; } // Single Burst case 101: { - const bool isFirstUpdate = (_data->Time - _deltaTime) <= 0.0f; + const bool isFirstUpdate = (context.Data->Time - context.DeltaTime) <= 0.0f; if (isFirstUpdate) { const float count = Math::Max((float)TryGetValue(node->GetBox(0), node->Values[2]), 0.0f); @@ -127,11 +134,11 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) case 102: { float& nextSpawnTime = data.NextSpawnTime; - if (nextSpawnTime - _data->Time <= 0.0f) + if (nextSpawnTime - context.Data->Time <= 0.0f) { const float count = Math::Max((float)TryGetValue(node->GetBox(0), node->Values[2]), 0.0f); const float delay = Math::Max((float)TryGetValue(node->GetBox(1), node->Values[3]), 0.0f); - nextSpawnTime = _data->Time + delay; + nextSpawnTime = context.Data->Time + delay; spawnCount += count; } break; @@ -140,13 +147,13 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) case 103: { float& nextSpawnTime = data.NextSpawnTime; - if (nextSpawnTime - _data->Time <= 0.0f) + if (nextSpawnTime - context.Data->Time <= 0.0f) { const Vector2 countMinMax = (Vector2)TryGetValue(node->GetBox(0), node->Values[2]); const Vector2 delayMinMax = (Vector2)TryGetValue(node->GetBox(1), node->Values[3]); const float count = Math::Max(countMinMax.X + RAND * (countMinMax.Y - countMinMax.X), 0.0f); const float delay = Math::Max(delayMinMax.X + RAND * (delayMinMax.Y - delayMinMax.X), 0.0f); - nextSpawnTime = _data->Time + delay; + nextSpawnTime = context.Data->Time + delay; spawnCount += count; } break; @@ -156,7 +163,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) // Calculate actual spawn amount spawnCount = Math::Max(spawnCount, 0.0f); const int32 result = Math::FloorToInt(spawnCount); - spawnCount -= result; + spawnCount -= (float)result; data.SpawnCounter = spawnCount; return result; @@ -164,8 +171,9 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd) { - auto stride = _data->Buffer->Stride; - auto start = _data->Buffer->GetParticleCPU(particlesStart); + auto& context = Context.Get(); + auto stride = context.Data->Buffer->Stride; + auto start = context.Data->Buffer->GetParticleCPU(particlesStart); switch (node->TypeID) { @@ -175,7 +183,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { auto spriteFacingMode = node->Values[2].AsInt; { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* spriteFacingModePtr = start + attribute.Offset; for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { @@ -186,14 +194,14 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* if ((ParticleSpriteFacingMode)spriteFacingMode == ParticleSpriteFacingMode::CustomFacingVector || (ParticleSpriteFacingMode)spriteFacingMode == ParticleSpriteFacingMode::FixedAxis) { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[1]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[1]]; byte* customFacingVectorPtr = start + attribute.Offset; auto box = node->GetBox(0); if (node->UsePerParticleDataResolve()) { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; const Vector3 vector = (Vector3)GetValue(box, 3); *((Vector3*)customFacingVectorPtr) = vector; customFacingVectorPtr += stride; @@ -217,7 +225,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { auto modelFacingMode = node->Values[2].AsInt; { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* modelFacingModePtr = start + attribute.Offset; for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { @@ -230,11 +238,11 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Update Age case 300: { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* agePtr = start + attribute.Offset; for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - *((float*)agePtr) += _deltaTime; + *((float*)agePtr) += context.DeltaTime; agePtr += stride; } break; @@ -243,16 +251,16 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* case 301: case 304: { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* velocityPtr = start + attribute.Offset; auto box = node->GetBox(0); if (node->UsePerParticleDataResolve()) { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; const Vector3 force = (Vector3)GetValue(box, 2); - *((Vector3*)velocityPtr) += force * _deltaTime; + *((Vector3*)velocityPtr) += force * context.DeltaTime; velocityPtr += stride; } } @@ -261,7 +269,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* const Vector3 force = (Vector3)GetValue(box, 2); for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - *((Vector3*)velocityPtr) += force * _deltaTime; + *((Vector3*)velocityPtr) += force * context.DeltaTime; velocityPtr += stride; } } @@ -270,9 +278,9 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Conform to Sphere case 305: { - auto& position = _data->Buffer->Layout->Attributes[node->Attributes[0]]; - auto& velocity = _data->Buffer->Layout->Attributes[node->Attributes[1]]; - auto& mass = _data->Buffer->Layout->Attributes[node->Attributes[2]]; + auto& position = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& velocity = context.Data->Buffer->Layout->Attributes[node->Attributes[1]]; + auto& mass = context.Data->Buffer->Layout->Attributes[node->Attributes[2]]; byte* positionPtr = start + position.Offset; byte* velocityPtr = start + velocity.Offset; @@ -302,7 +310,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* float ratio = Math::SmoothStep(0.0f, stickDistance * 2.0f, Math::Abs(distToSurface)); \ float tgtSpeed = Math::Sign(distToSurface) * attractionSpeed * ratio; \ float deltaSpeed = tgtSpeed - spdNormal; \ - Vector3 deltaVelocity = dir * (Math::Sign(deltaSpeed) * Math::Min(Math::Abs(deltaSpeed), _deltaTime * Math::Lerp(stickForce, attractionForce, ratio)) / Math::Max(*(float*)massPtr, ZeroTolerance)); \ + Vector3 deltaVelocity = dir * (Math::Sign(deltaSpeed) * Math::Min(Math::Abs(deltaSpeed), context.DeltaTime * Math::Lerp(stickForce, attractionForce, ratio)) / Math::Max(*(float*)massPtr, ZeroTolerance)); \ *(Vector3*)velocityPtr = velocity + deltaVelocity; \ positionPtr += stride; \ velocityPtr += stride; \ @@ -312,7 +320,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -332,7 +340,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Kill (sphere) case 306: { - auto& position = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& position = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + position.Offset; @@ -350,8 +358,8 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* if (sign * lengthSqr <= sign * sphereRadiusSqr) \ { \ particlesEnd--; \ - _data->Buffer->CPU.Count--; \ - Platform::MemoryCopy(_data->Buffer->GetParticleCPU(particleIndex), _data->Buffer->GetParticleCPU(_data->Buffer->CPU.Count), _data->Buffer->Stride); \ + context.Data->Buffer->CPU.Count--; \ + Platform::MemoryCopy(context.Data->Buffer->GetParticleCPU(particleIndex), context.Data->Buffer->GetParticleCPU(context.Data->Buffer->CPU.Count), context.Data->Buffer->Stride); \ particleIndex--; \ } \ positionPtr += stride @@ -360,7 +368,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -380,7 +388,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Kill (box) case 307: { - auto& position = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& position = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + position.Offset; @@ -403,8 +411,8 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* if (collision) \ { \ particlesEnd--; \ - _data->Buffer->CPU.Count--; \ - Platform::MemoryCopy(_data->Buffer->GetParticleCPU(particleIndex), _data->Buffer->GetParticleCPU(_data->Buffer->CPU.Count), _data->Buffer->Stride); \ + context.Data->Buffer->CPU.Count--; \ + Platform::MemoryCopy(context.Data->Buffer->GetParticleCPU(particleIndex), context.Data->Buffer->GetParticleCPU(context.Data->Buffer->CPU.Count), context.Data->Buffer->Stride); \ particleIndex--; \ } \ positionPtr += stride @@ -413,7 +421,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -441,8 +449,8 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* if (kill) \ { \ particlesEnd--; \ - _data->Buffer->CPU.Count--; \ - Platform::MemoryCopy(_data->Buffer->GetParticleCPU(particleIndex), _data->Buffer->GetParticleCPU(_data->Buffer->CPU.Count), _data->Buffer->Stride); \ + context.Data->Buffer->CPU.Count--; \ + Platform::MemoryCopy(context.Data->Buffer->GetParticleCPU(particleIndex), context.Data->Buffer->GetParticleCPU(context.Data->Buffer->CPU.Count), context.Data->Buffer->Stride); \ particleIndex--; \ } @@ -450,7 +458,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -473,9 +481,9 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* auto box = node->GetBox(0); const bool useSpriteSize = node->Values[3].AsBool; - auto& velocity = _data->Buffer->Layout->Attributes[node->Attributes[0]]; - auto& mass = _data->Buffer->Layout->Attributes[node->Attributes[1]]; - byte* spriteSizePtr = useSpriteSize ? start + _data->Buffer->Layout->Attributes[node->Attributes[2]].Offset : 0; + auto& velocity = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& mass = context.Data->Buffer->Layout->Attributes[node->Attributes[1]]; + byte* spriteSizePtr = useSpriteSize ? start + context.Data->Buffer->Layout->Attributes[node->Attributes[2]].Offset : nullptr; byte* velocityPtr = start + velocity.Offset; byte* massPtr = start + mass.Offset; @@ -486,7 +494,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* float particleDrag = drag; \ if (useSpriteSize) \ particleDrag *= ((Vector2*)spriteSizePtr)->MulValues(); \ - *((Vector3*)velocityPtr) *= Math::Max(0.0f, 1.0f - (particleDrag * _deltaTime) / Math::Max(*(float*)massPtr, ZeroTolerance)); \ + *((Vector3*)velocityPtr) *= Math::Max(0.0f, 1.0f - (particleDrag * context.DeltaTime) / Math::Max(*(float*)massPtr, ZeroTolerance)); \ velocityPtr += stride; \ massPtr += stride; \ spriteSizePtr += stride @@ -495,7 +503,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -515,9 +523,9 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Turbulence case 311: { - auto& position = _data->Buffer->Layout->Attributes[node->Attributes[0]]; - auto& velocity = _data->Buffer->Layout->Attributes[node->Attributes[1]]; - auto& mass = _data->Buffer->Layout->Attributes[node->Attributes[2]]; + auto& position = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& velocity = context.Data->Buffer->Layout->Attributes[node->Attributes[1]]; + auto& mass = context.Data->Buffer->Layout->Attributes[node->Attributes[2]]; byte* positionPtr = start + position.Offset; byte* velocityPtr = start + velocity.Offset; @@ -545,7 +553,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* Vector3 vectorFieldUVW = Vector3::Transform(*((Vector3*)positionPtr), invFieldTransformMatrix); \ Vector3 force = Noise3D(vectorFieldUVW + 0.5f, octavesCount, roughness); \ force = Vector3::Transform(force, fieldTransformMatrix) * intensity; \ - *((Vector3*)velocityPtr) += force * (_deltaTime / Math::Max(*(float*)massPtr, ZeroTolerance)); \ + *((Vector3*)velocityPtr) += force * (context.DeltaTime / Math::Max(*(float*)massPtr, ZeroTolerance)); \ positionPtr += stride; \ velocityPtr += stride; \ massPtr += stride @@ -554,7 +562,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -575,7 +583,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* case 200: case 302: { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* dataPtr = start + attribute.Offset; int32 dataSize = attribute.GetSize(); auto box = node->GetBox(0); @@ -584,7 +592,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; const Value value = GetValue(box, 4).Cast(type); Platform::MemoryCopy(dataPtr, &value.AsPointer, dataSize); dataPtr += stride; @@ -631,7 +639,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* case 362: case 363: { - auto& attribute = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& attribute = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* dataPtr = start + attribute.Offset; int32 dataSize = attribute.GetSize(); auto box = node->GetBox(0); @@ -640,7 +648,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; const Value value = GetValue(box, 2).Cast(type); Platform::MemoryCopy(dataPtr, &value.AsPointer, dataSize); dataPtr += stride; @@ -660,7 +668,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (sphere surface) case 202: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -685,7 +693,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -705,7 +713,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (plane) case 203: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -723,7 +731,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -743,7 +751,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (circle) case 204: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -766,7 +774,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -786,7 +794,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (disc) case 205: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -809,7 +817,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -829,7 +837,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (box surface) case 206: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -859,7 +867,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -879,7 +887,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (box volume) case 207: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -897,7 +905,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -917,7 +925,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (cylinder) case 208: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -942,7 +950,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -962,7 +970,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (line) case 209: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -980,7 +988,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1000,7 +1008,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (torus) case 210: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -1044,7 +1052,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1064,7 +1072,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (sphere volume) case 211: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; byte* positionPtr = start + positionAttr.Offset; @@ -1089,7 +1097,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1115,8 +1123,8 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Position (spiral) case 214: { - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; - auto& velocityAttr = _data->Buffer->Layout->Attributes[node->Attributes[1]]; + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; + auto& velocityAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[1]]; byte* positionPtr = start + positionAttr.Offset; byte* velocityPtr = start + velocityAttr.Offset; @@ -1125,7 +1133,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* auto rotationSpeedBox = node->GetBox(1); auto velocityScaleBox = node->GetBox(2); - auto& arc = node->SpiralModuleProgress; + auto& arc = *(float*)&context.Data->CustomData[node->CustomDataOffset]; #define INPUTS_FETCH() \ const Vector3 center = (Vector3)GetValue(centerBox, 2); \ @@ -1145,7 +1153,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1165,9 +1173,9 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* // Helper macros for collision modules to share the code #define COLLISION_BEGIN() \ - auto& positionAttr = _data->Buffer->Layout->Attributes[node->Attributes[0]]; \ - auto& velocityAttr = _data->Buffer->Layout->Attributes[node->Attributes[1]]; \ - auto& ageAttr = _data->Buffer->Layout->Attributes[node->Attributes[2]]; \ + auto& positionAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[0]]; \ + auto& velocityAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[1]]; \ + auto& ageAttr = context.Data->Buffer->Layout->Attributes[node->Attributes[2]]; \ byte* positionPtr = start + positionAttr.Offset; \ byte* velocityPtr = start + velocityAttr.Offset; \ byte* agePtr = start + ageAttr.Offset; \ @@ -1215,7 +1223,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #define LOGIC() \ Vector3 position = *(Vector3*)positionPtr; \ Vector3 velocity = *(Vector3*)velocityPtr; \ - Vector3 nextPos = position + velocity * _deltaTime; \ + Vector3 nextPos = position + velocity * context.DeltaTime; \ Vector3 n = planeNormal; \ float distToPlane = Vector3::Dot(nextPos, n) - Vector3::Dot(planePosition, n) - radius; \ if (distToPlane < 0.0f) \ @@ -1227,7 +1235,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1257,7 +1265,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #define LOGIC() \ Vector3 position = *(Vector3*)positionPtr; \ Vector3 velocity = *(Vector3*)velocityPtr; \ - Vector3 nextPos = position + velocity * _deltaTime; \ + Vector3 nextPos = position + velocity * context.DeltaTime; \ Vector3 dir = nextPos - spherePosition; \ float sqrLength = Vector3::Dot(dir, dir); \ float totalRadius = sphereRadius + sign * radius; \ @@ -1272,7 +1280,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1302,7 +1310,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #define LOGIC() \ Vector3 position = *(Vector3*)positionPtr; \ Vector3 velocity = *(Vector3*)velocityPtr; \ - Vector3 nextPos = position + velocity * _deltaTime; \ + Vector3 nextPos = position + velocity * context.DeltaTime; \ Vector3 dir = nextPos - boxPosition; \ Vector3 absDir = Vector3::Abs(dir); \ Vector3 halfBoxSize = boxSize * 0.5f + radius * sign; \ @@ -1332,7 +1340,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } @@ -1364,7 +1372,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* #define LOGIC() \ Vector3 position = *(Vector3*)positionPtr; \ Vector3 velocity = *(Vector3*)velocityPtr; \ - Vector3 nextPos = position + velocity * _deltaTime; \ + Vector3 nextPos = position + velocity * context.DeltaTime; \ Vector3 dir = nextPos - cylinderPosition; \ float halfHeight = cylinderHeight * 0.5f + radius * sign; \ float cylinderRadiusT = cylinderRadius + radius * sign; \ @@ -1397,7 +1405,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* { for (int32 particleIndex = particlesStart; particleIndex < particlesEnd; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; INPUTS_FETCH(); LOGIC(); } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index 019dc41f3..6d9ba3722 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -7,11 +7,12 @@ #include "Engine/Graphics/RenderTask.h" #define GET_VIEW() auto mainViewTask = MainRenderTask::Instance && MainRenderTask::Instance->LastUsedFrame != 0 ? MainRenderTask::Instance : nullptr -#define ACCESS_PARTICLE_ATTRIBUTE(index) (_data->Buffer->GetParticleCPU(_particleIndex) + _data->Buffer->Layout->Attributes[node->Attributes[index]].Offset) +#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[node->Attributes[index]].Offset) #define GET_PARTICLE_ATTRIBUTE(index, type) *(type*)ACCESS_PARTICLE_ATTRIBUTE(index) void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { + auto& context = Context.Get(); switch (node->TypeID) { // Get @@ -21,7 +22,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex); if (param) { - value = _data->Parameters[paramIndex]; + value = context.Data->Parameters[paramIndex]; switch (param->Type.Type) { case VariantType::Vector2: @@ -130,6 +131,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) { + auto& context = Context.Get(); switch (node->TypeID) { // Linearize Depth @@ -141,15 +143,13 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va } // Time case 8: - { - value = box->ID == 0 ? _data->Time : _deltaTime; + value = box->ID == 0 ? context.Data->Time : context.DeltaTime; break; - } // Transform Position To Screen UV case 9: { GET_VIEW(); - const Matrix viewProjection = _viewTask ? _viewTask->View.PrevViewProjection : Matrix::Identity; + const Matrix viewProjection = context.ViewTask ? context.ViewTask->View.PrevViewProjection : Matrix::Identity; const Vector3 position = (Vector3)TryGetValue(node->GetBox(0), Value::Zero); Vector4 projPos; Vector3::Transform(position, viewProjection); @@ -165,6 +165,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* nodeBase, Value& value) { + auto& context = Context.Get(); auto node = (ParticleEmitterGraphCPUNode*)nodeBase; switch (node->TypeID) { @@ -199,8 +200,8 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node // Particle Attribute (by index) case 303: { - const auto particleIndex = tryGetValue(node->GetBox(1), _particleIndex); - byte* ptr = (_data->Buffer->GetParticleCPU((uint32)particleIndex) + _data->Buffer->Layout->Attributes[node->Attributes[0]].Offset); + const auto particleIndex = tryGetValue(node->GetBox(1), context.ParticleIndex); + byte* ptr = (context.Data->Buffer->GetParticleCPU((uint32)particleIndex) + context.Data->Buffer->Layout->Attributes[node->Attributes[0]].Offset); switch ((ParticleAttribute::ValueTypes)node->Attributes[1]) { case ParticleAttribute::ValueTypes::Float: @@ -296,19 +297,19 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node // Effect Position case 200: { - value = _effect->GetPosition(); + value = context.Effect->GetPosition(); break; } // Effect Rotation case 201: { - value = _effect->GetOrientation(); + value = context.Effect->GetOrientation(); break; } // Effect Scale case 202: { - value = _effect->GetScale(); + value = context.Effect->GetScale(); break; } // Simulation Mode @@ -320,25 +321,25 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node // View Position case 204: { - value = _viewTask ? _viewTask->View.Position : Vector3::Zero; + value = context.ViewTask ? context.ViewTask->View.Position : Vector3::Zero; break; } // View Direction case 205: { - value = _viewTask ? _viewTask->View.Direction : Vector3::Forward; + value = context.ViewTask ? context.ViewTask->View.Direction : Vector3::Forward; break; } // View Far Plane case 206: { - value = _viewTask ? _viewTask->View.Far : 0.0f; + value = context.ViewTask ? context.ViewTask->View.Far : 0.0f; break; } // Screen Size case 207: { - const Vector4 size = _viewTask ? _viewTask->View.ScreenSize : Vector4::Zero; + const Vector4 size = context.ViewTask ? context.ViewTask->View.ScreenSize : Vector4::Zero; if (box->ID == 0) value = Vector2(size.X, size.Y); else @@ -358,11 +359,11 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node #if 0 // Prevent recursive calls - for (int32 i = _callStack.Count() - 1; i >= 0; i--) + for (int32 i = context.CallStack.Count() - 1; i >= 0; i--) { - if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300)) + if (context.CallStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300)) { - const auto callFunc = ((ParticleEmitterGraphCPUNode*)_callStack[i])->Assets[0].Get(); + const auto callFunc = context.CallStack[i]->Assets[0].Get(); if (callFunc == function) { value = Value::Zero; @@ -372,14 +373,9 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node } #endif - // Create a instanced version of the function graph - ParticleEmitterGraphCPU* graph; - if (!_functions.TryGet(nodeBase, graph)) - { - graph = New(); - function->LoadSurface((ParticleEmitterGraphCPU&)*graph); - _functions.Add(nodeBase, graph); - } + // Get function graph + Graph* graph = (Graph*)&function->Graph; + context.Functions[nodeBase] = graph; // Peek the function output (function->Outputs maps the functions outputs to output nodes indices) const int32 outputIndex = box->ID - 16; @@ -388,22 +384,22 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node value = Value::Zero; break; } - ParticleEmitterGraphCPU::Node* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]]; + Node* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]]; Box* functionOutputBox = functionOutputNode->TryGetBox(0); // Evaluate the function output - _graphStack.Push((Graph*)graph); + context.GraphStack.Push(graph); value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero; - _graphStack.Pop(); + context.GraphStack.Pop(); break; } // Particle Index case 301: - value = _particleIndex; + value = context.ParticleIndex; break; // Particles Count case 302: - value = (uint32)_data->Buffer->CPU.Count; + value = (uint32)context.Data->Buffer->CPU.Count; break; default: VisjectExecutor::ProcessGroupParticles(box, nodeBase, value); @@ -413,20 +409,21 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, Value& value) { + auto& context = Context.Get(); switch (node->TypeID) { // Function Input case 1: { // Find the function call - ParticleEmitterGraphCPUNode* functionCallNode = nullptr; - ASSERT(_graphStack.Count() >= 2); - ParticleEmitterGraphCPU* graph; - for (int32 i = _callStack.Count() - 1; i >= 0; i--) + Node* functionCallNode = nullptr; + ASSERT(context.GraphStack.Count() >= 2); + Graph* graph; + for (int32 i = context.CallStack.Count() - 1; i >= 0; i--) { - if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300) && _functions.TryGet(_callStack[i], graph) && _graphStack[_graphStack.Count() - 1] == (Graph*)graph) + if (context.CallStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300) && context.Functions.TryGet(context.CallStack[i], graph) && context.GraphStack[context.GraphStack.Count() - 1] == graph) { - functionCallNode = (ParticleEmitterGraphCPUNode*)_callStack[i]; + functionCallNode = context.CallStack[i]; break; } } @@ -436,7 +433,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, break; } const auto function = functionCallNode->Assets[0].As(); - if (!_functions.TryGet((Node*)functionCallNode, graph) || !function) + if (!context.Functions.TryGet(functionCallNode, graph) || !function) { value = Value::Zero; break; @@ -461,9 +458,9 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, if (functionCallBox && functionCallBox->HasConnection()) { // Use provided input value from the function call - _graphStack.Pop(); + context.GraphStack.Pop(); value = eatBox(node, functionCallBox->FirstConnection()); - _graphStack.Push((Graph*)graph); + context.GraphStack.Push(graph); } else { diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index 52b159947..396cbaee0 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -7,6 +7,8 @@ #include "Engine/Particles/ParticleEffect.h" #include "Engine/Engine/Time.h" +ThreadLocal ParticleEmitterGraphCPUExecutor::Context; + namespace { bool SortRibbonParticles(const int32& a, const int32& b, ParticleBufferCPUDataAccessor* data) @@ -35,8 +37,7 @@ void ParticleEmitterGraphCPU::CreateDefault() bool ParticleEmitterGraphCPU::Load(ReadStream* stream, bool loadMeta) { - // Base - if (ParticleEmitterGraph::Load(stream, loadMeta)) + if (Base::Load(stream, loadMeta)) return true; // Assign the offset in the sorted indices buffer to the rendering modules @@ -61,7 +62,7 @@ bool ParticleEmitterGraphCPU::Load(ReadStream* stream, bool loadMeta) for (int32 i = 0; i < RibbonRenderingModules.Count(); i++) { const auto module = RibbonRenderingModules[i]; - module->Ribbon.RibbonOrderOffset = ribbonOrderOffset; + module->RibbonOrderOffset = ribbonOrderOffset; ribbonOrderOffset += Capacity; } @@ -96,18 +97,16 @@ void ParticleEmitterGraphCPU::InitializeNode(Node* node) if (node->Used) return; - // Base - ParticleEmitterGraph::InitializeNode(node); + Base::InitializeNode(node); switch (node->Type) { // Position (spiral) case GRAPH_NODE_MAKE_TYPE(15, 214): - { - node->SpiralModuleProgress = 0.0f; + node->CustomDataOffset = CustomDataSize; + CustomDataSize += sizeof(float); break; } - } } ParticleEmitterGraphCPUExecutor::ParticleEmitterGraphCPUExecutor(ParticleEmitterGraphCPU& graph) @@ -120,9 +119,19 @@ ParticleEmitterGraphCPUExecutor::ParticleEmitterGraphCPUExecutor(ParticleEmitter _perGroupProcessCall[16] = (ProcessBoxHandler)&ParticleEmitterGraphCPUExecutor::ProcessGroupFunction; } -ParticleEmitterGraphCPUExecutor::~ParticleEmitterGraphCPUExecutor() +void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt) { - _functions.ClearDelete(); + auto& context = Context.Get(); + context.GraphStack.Clear(); + context.GraphStack.Push((Graph*)&_graph); + context.Data = &data; + context.Emitter = emitter; + context.Effect = effect; + context.DeltaTime = dt; + context.ParticleIndex = 0; + context.ViewTask = effect->GetRenderTask(); + context.CallStack.Clear(); + context.Functions.Clear(); } bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, BoundingBox& result) @@ -240,20 +249,14 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa case 401: { // Prepare graph data - _graphStack.Clear(); - _graphStack.Push((Graph*)&_graph); - _data = &data; - _emitter = emitter; - _effect = effect; - _deltaTime = 0.0f; - _viewTask = effect->GetRenderTask(); - _callStack.Clear(); + auto& context = Context.Get(); + Init(emitter, effect, data); // Find the maximum radius of the particle light float maxRadius = 0.0f; for (int32 particleIndex = 0; particleIndex < count; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; const float radius = (float)GetValue(module->GetBox(1), 3); if (radius > maxRadius) maxRadius = radius; @@ -370,14 +373,8 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff const int32 stride = buffer->Stride; // Prepare graph data - _graphStack.Clear(); - _graphStack.Push((Graph*)&_graph); - _data = &data; - _emitter = emitter; - _effect = effect; - _deltaTime = 0.0f; - _viewTask = effect->GetRenderTask(); - _callStack.Clear(); + Init(emitter, effect, data); + auto& context = Context.Get(); // Draw lights for (int32 moduleIndex = 0; moduleIndex < emitter->Graph.LightModules.Count(); moduleIndex++) @@ -405,7 +402,7 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff for (int32 particleIndex = 0; particleIndex < count; particleIndex++) { - _particleIndex = particleIndex; + context.ParticleIndex = particleIndex; const Vector4 color = (Vector4)GetValue(module->GetBox(0), 2); const float radius = (float)GetValue(module->GetBox(1), 3); @@ -429,15 +426,8 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt, bool canSpawn) { // Prepare data - _graphStack.Clear(); - _graphStack.Push((Graph*)&_graph); - _data = &data; - _emitter = emitter; - _effect = effect; - _particleIndex = 0; - _deltaTime = dt; - _viewTask = effect->GetRenderTask(); - _callStack.Clear(); + Init(emitter, effect, data, dt); + auto& context = Context.Get(); auto& cpu = data.Buffer->CPU; // Update particles @@ -452,20 +442,20 @@ void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleE // Dead particles removal if (_graph._attrAge != -1 && _graph._attrLifetime != -1) { - byte* agePtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrAge].Offset; - byte* lifetimePtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrLifetime].Offset; + byte* agePtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrAge].Offset; + byte* lifetimePtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrLifetime].Offset; for (int32 particleIndex = 0; particleIndex < cpu.Count; particleIndex++) { if (*(float*)agePtr >= *(float*)lifetimePtr) { cpu.Count--; - Platform::MemoryCopy(_data->Buffer->GetParticleCPU(particleIndex), _data->Buffer->GetParticleCPU(cpu.Count), _data->Buffer->Stride); + Platform::MemoryCopy(data.Buffer->GetParticleCPU(particleIndex), data.Buffer->GetParticleCPU(cpu.Count), data.Buffer->Stride); particleIndex--; } else { - agePtr += _data->Buffer->Stride; - lifetimePtr += _data->Buffer->Stride; + agePtr += data.Buffer->Stride; + lifetimePtr += data.Buffer->Stride; } } } @@ -474,12 +464,12 @@ void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleE // Debug validation for NANs in data if (_graph._attrPosition != -1) { - byte* positionPtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrPosition].Offset; + byte* positionPtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrPosition].Offset; for (int32 particleIndex = 0; particleIndex < cpu.Count; particleIndex++) { Vector3 pos = *((Vector3*)positionPtr); ASSERT(!pos.IsNanOrInfinity()); - positionPtr += _data->Buffer->Stride; + positionPtr += data.Buffer->Stride; } } #endif @@ -487,26 +477,26 @@ void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleE // Euler integration if (_graph._attrPosition != -1 && _graph._attrVelocity != -1) { - byte* positionPtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrPosition].Offset; - byte* velocityPtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrVelocity].Offset; + byte* positionPtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrPosition].Offset; + byte* velocityPtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrVelocity].Offset; for (int32 particleIndex = 0; particleIndex < cpu.Count; particleIndex++) { - *((Vector3*)positionPtr) += *((Vector3*)velocityPtr) * _deltaTime; - positionPtr += _data->Buffer->Stride; - velocityPtr += _data->Buffer->Stride; + *((Vector3*)positionPtr) += *((Vector3*)velocityPtr) * dt; + positionPtr += data.Buffer->Stride; + velocityPtr += data.Buffer->Stride; } } // Angular Euler Integration if (_graph._attrRotation != -1 && _graph._attrAngularVelocity != -1) { - byte* rotationPtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrRotation].Offset; - byte* angularVelocityPtr = cpu.Buffer.Get() + _data->Buffer->Layout->Attributes[_graph._attrAngularVelocity].Offset; + byte* rotationPtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrRotation].Offset; + byte* angularVelocityPtr = cpu.Buffer.Get() + data.Buffer->Layout->Attributes[_graph._attrAngularVelocity].Offset; for (int32 particleIndex = 0; particleIndex < cpu.Count; particleIndex++) { - *((Vector3*)rotationPtr) += *((Vector3*)angularVelocityPtr) * _deltaTime; - rotationPtr += _data->Buffer->Stride; - angularVelocityPtr += _data->Buffer->Stride; + *((Vector3*)rotationPtr) += *((Vector3*)angularVelocityPtr) * dt; + rotationPtr += data.Buffer->Stride; + angularVelocityPtr += data.Buffer->Stride; } } @@ -545,16 +535,14 @@ void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleE // Sort ribbon particles if (cpu.RibbonOrder.IsEmpty()) { - cpu.RibbonOrder.Resize(_graph.RibbonRenderingModules.Count() * _data->Buffer->Capacity); + cpu.RibbonOrder.Resize(_graph.RibbonRenderingModules.Count() * data.Buffer->Capacity); } - ASSERT(cpu.RibbonOrder.Count() == _graph.RibbonRenderingModules.Count() * _data->Buffer->Capacity); + ASSERT(cpu.RibbonOrder.Count() == _graph.RibbonRenderingModules.Count() * data.Buffer->Capacity); for (int32 i = 0; i < _graph.RibbonRenderingModules.Count(); i++) { const auto module = _graph.RibbonRenderingModules[i]; - - ParticleBufferCPUDataAccessor sortKeyData(_data->Buffer, emitter->Graph.Layout.GetAttributeOffset(module->Attributes[1])); - - int32* ribbonOrderData = cpu.RibbonOrder.Get() + module->Ribbon.RibbonOrderOffset; + ParticleBufferCPUDataAccessor sortKeyData(data.Buffer, emitter->Graph.Layout.GetAttributeOffset(module->Attributes[1])); + int32* ribbonOrderData = cpu.RibbonOrder.Get() + module->RibbonOrderOffset; for (int32 j = 0; j < cpu.Count; j++) { @@ -567,23 +555,13 @@ void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleE } } } - - // Cleanup - _data = nullptr; } int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt) { // Prepare data - _graphStack.Clear(); - _graphStack.Push((Graph*)&_graph); - _data = &data; - _emitter = emitter; - _effect = effect; - _particleIndex = 0; - _deltaTime = dt; - _viewTask = effect->GetRenderTask(); - _callStack.Clear(); + auto& context = Context.Get(); + Init(emitter, effect, data, dt); // Spawn particles int32 spawnCount = 0; @@ -592,16 +570,14 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par spawnCount += ProcessSpawnModule(i); } - // Cleanup - _data = nullptr; - return spawnCount; } VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box* box) { // Check if graph is looped or is too deep - if (_callStack.Count() >= PARTICLE_EMITTER_MAX_CALL_STACK) + auto& context = Context.Get(); + if (context.CallStack.Count() >= PARTICLE_EMITTER_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); return Value::Zero; @@ -615,7 +591,7 @@ VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box #endif // Add to the calling stack - _callStack.Add(caller); + context.CallStack.Add(caller); // Call per group custom processing event Value value; @@ -624,12 +600,13 @@ VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box (this->*func)(box, parentNode, value); // Remove from the calling stack - _callStack.RemoveLast(); + context.CallStack.RemoveLast(); return value; } VisjectExecutor::Graph* ParticleEmitterGraphCPUExecutor::GetCurrentGraph() const { - return _graphStack.Peek(); + auto& context = Context.Get(); + return context.GraphStack.Peek(); } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index 34d7cec2e..587999aae 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -7,6 +7,7 @@ #include "Engine/Particles/ParticlesData.h" #include "Engine/Visject/VisjectGraph.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Threading/ThreadLocal.h" struct RenderContext; class ParticleEffect; @@ -31,16 +32,6 @@ class ParticleEmitterGraphCPUExecutor; class ParticleEmitterGraphCPUBox : public VisjectGraphBox { -public: - - ParticleEmitterGraphCPUBox() - { - } - - ParticleEmitterGraphCPUBox(void* parent, byte id, VariantType type) - : VisjectGraphBox(parent, id, type) - { - } }; class ParticleEmitterGraphCPUNode : public ParticleEmitterGraphNode> @@ -54,23 +45,13 @@ public: union { - /// - /// The spiral position module progress value. - /// - float SpiralModuleProgress; - - struct - { - int32 RibbonOrderOffset; - } Ribbon; + int32 CustomDataOffset; + int32 RibbonOrderOffset; }; -public: - /// /// True if this node uses the per-particle data resolve instead of optimized whole-collection fetch. /// - /// True if use per particle data resolve, otherwise can optimize resolve pass. FORCE_INLINE bool UsePerParticleDataResolve() const { return UsesParticleData || !IsConstant; @@ -83,32 +64,31 @@ public: class ParticleEmitterGraphCPU : public ParticleEmitterGraph, ParticleEmitterGraphCPUNode, Variant> { friend ParticleEmitterGraphCPUExecutor; + typedef ParticleEmitterGraph, ParticleEmitterGraphCPUNode, Variant> Base; private: + struct NodeState + { + union + { + int32 SpiralProgress; + }; + }; Array _defaultParticleData; public: + // Size of the custom pre-node data buffer used for state tracking (eg. position on spiral arc progression). + int32 CustomDataSize = 0; + /// /// Creates the default surface graph (the main root node) for the particle emitter. Ensure to dispose the previous graph data before. /// void CreateDefault(); -public: - - /// - /// Determines whenever this emitter uses lights rendering. - /// - /// True if emitter uses lights rendering, otherwise false. - FORCE_INLINE bool UsesLightRendering() const - { - return LightModules.HasItems(); - } - /// /// Gets the position attribute offset from the particle data layout start (in bytes). /// - /// The offset in bytes. FORCE_INLINE int32 GetPositionAttributeOffset() const { return _attrPosition != -1 ? Layout.Attributes[_attrPosition].Offset : -1; @@ -117,7 +97,6 @@ public: /// /// Gets the age attribute offset from the particle data layout start (in bytes). /// - /// The offset in bytes. FORCE_INLINE int32 GetAgeAttributeOffset() const { return _attrAge != -1 ? Layout.Attributes[_attrAge].Offset : -1; @@ -127,37 +106,35 @@ public: // [ParticleEmitterGraph] bool Load(ReadStream* stream, bool loadMeta) override; - - bool onNodeLoaded(Node* n) override - { - ParticleEmitterGraph::onNodeLoaded(n); - return VisjectGraph::onNodeLoaded(n); - } - -protected: - - // [ParticleEmitterGraph] void InitializeNode(Node* node) override; }; +/// +/// The CPU particles emitter graph evaluation context. +/// +struct ParticleEmitterGraphCPUContext +{ + float DeltaTime; + uint32 ParticleIndex; + ParticleEmitterInstance* Data; + ParticleEmitter* Emitter; + ParticleEffect* Effect; + class SceneRenderTask* ViewTask; + Array> CallStack; + Array> GraphStack; + Dictionary Functions; +}; + /// /// The Particle Emitter Graph simulation on a CPU. /// class ParticleEmitterGraphCPUExecutor : public VisjectExecutor { private: - - // Runtime ParticleEmitterGraphCPU& _graph; - float _deltaTime; - uint32 _particleIndex; - ParticleEmitterInstance* _data; - ParticleEmitter* _emitter; - ParticleEffect* _effect; - class SceneRenderTask* _viewTask; - Array> _callStack; - Array> _graphStack; - Dictionary _functions; + + // Per-thread context to allow async execution + static ThreadLocal Context; public: @@ -167,11 +144,6 @@ public: /// The graph to execute. explicit ParticleEmitterGraphCPUExecutor(ParticleEmitterGraphCPU& graph); - /// - /// Finalizes an instance of the class. - /// - ~ParticleEmitterGraphCPUExecutor(); - /// /// Computes the local bounds of the particle emitter instance. /// @@ -214,6 +186,7 @@ public: private: + void Init(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt = 0.0f); Value eatBox(Node* caller, Box* box) override; Graph* GetCurrentGraph() const override; @@ -226,17 +199,7 @@ private: int32 ProcessSpawnModule(int32 index); void ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd); - Value TryGetValue(Box* box, int32 defaultValueBoxIndex, const Value& defaultValue) - { - const auto parentNode = box->GetParent(); - if (box->HasConnection()) - return eatBox(parentNode, box->FirstConnection()); - if (parentNode->Values.Count() > defaultValueBoxIndex) - return parentNode->Values[defaultValueBoxIndex]; - return defaultValue; - } - - Value GetValue(Box* box, int32 defaultValueBoxIndex) + FORCE_INLINE Value GetValue(Box* box, int32 defaultValueBoxIndex) { const auto parentNode = box->GetParent(); if (box->HasConnection()) diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 4d353b3ed..8ee2d20d0 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -52,36 +52,18 @@ public: /// /// The Particle Emitter Graph used to simulate particles. /// -template -class ParticleEmitterGraph : public Base +template +class ParticleEmitterGraph : public BaseType { public: typedef ValueType Value; - /// - /// The particle emitter module types. - /// enum class ModuleType { - /// - /// The spawn module. - /// Spawn, - - /// - /// The init module. - /// Initialize, - - /// - /// The update module. - /// Update, - - /// - /// The render module. - /// Render, }; @@ -172,8 +154,6 @@ public: bool UsesVolumetricFogRendering = false; -protected: - virtual void InitializeNode(NodeType* node) { // Skip if already initialized @@ -314,10 +294,8 @@ protected: } // Particle Emitter Function case GRAPH_NODE_MAKE_TYPE(14, 300): - { node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]); break; - } // Particle Index case GRAPH_NODE_MAKE_TYPE(14, 301): node->UsesParticleData = true; @@ -566,7 +544,7 @@ public: UsesVolumetricFogRendering = false; // Base - Base::Clear(); + BaseType::Clear(); } bool Load(ReadStream* stream, bool loadMeta) override @@ -575,7 +553,7 @@ public: Version++; // Base - if (Base::Load(stream, loadMeta)) + if (BaseType::Load(stream, loadMeta)) return true; // Compute particle data layout and initialize used nodes (for only used nodes, start depth searching rom the modules) @@ -678,6 +656,6 @@ public: } } - return Base::onNodeLoaded(n); + return BaseType::onNodeLoaded(n); } }; diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index f23aff28c..309b760fe 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleEffect.h" -#include "ParticleManager.h" +#include "Particles.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/SceneRendering.h" @@ -270,7 +270,7 @@ void ParticleEffect::UpdateSimulation() // Request update _lastUpdateFrame = Engine::FrameCount; _lastMinDstSqr = MAX_float; - ParticleManager::UpdateEffect(this); + Particles::UpdateEffect(this); } void ParticleEffect::UpdateBounds() @@ -493,7 +493,7 @@ bool ParticleEffect::HasContentLoaded() const void ParticleEffect::Draw(RenderContext& renderContext) { _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position)); - ParticleManager::DrawParticles(renderContext, this); + Particles::DrawParticles(renderContext, this); } void ParticleEffect::DrawGeneric(RenderContext& renderContext) @@ -679,7 +679,7 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier* void ParticleEffect::EndPlay() { CacheModifiedParameters(); - ParticleManager::OnEffectDestroy(this); + Particles::OnEffectDestroy(this); Instance.ClearState(); _parameters.Clear(); _parametersVersion = 0; diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp index 0b1d3fbf4..75de7b697 100644 --- a/Source/Engine/Particles/ParticleEmitter.cpp +++ b/Source/Engine/Particles/ParticleEmitter.cpp @@ -1,9 +1,9 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleEmitter.h" -#include "ParticleManager.h" #include "ParticleSystem.h" #include "ParticleEffect.h" +#include "Particles.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/ShaderAssetUpgrader.h" #include "Engine/Core/Log.h" @@ -105,7 +105,7 @@ Asset::LoadResult ParticleEmitter::load() EnablePooling = root->Values[3].AsBool; CustomBounds = (BoundingBox)root->Values[4]; UseAutoBounds = root->Values[5].AsBool; - IsUsingLights = Graph.UsesLightRendering(); + IsUsingLights = Graph.LightModules.HasItems(); } // Select simulation mode @@ -287,7 +287,7 @@ void ParticleEmitter::unload(bool isReloading) UnregisterForShaderReloads(this); #endif - ParticleManager::OnEmitterUnload(this); + Particles::OnEmitterUnload(this); Graph.Clear(); #if COMPILE_WITH_GPU_PARTICLES diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp index 24282f083..b02c42cbd 100644 --- a/Source/Engine/Particles/ParticleEmitterFunction.cpp +++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp @@ -24,6 +24,14 @@ Asset::LoadResult ParticleEmitterFunction::load() MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size()); if (Graph.Load(&stream, false)) return LoadResult::Failed; + for (int32 i = 0; i < Graph.Nodes.Count(); i++) + { + // Initialize all used nodes (starting from function output as roots) + if (Graph.Nodes[i].Type == GRAPH_NODE_MAKE_TYPE(16, 2)) + { + Graph.InitializeNode(&Graph.Nodes[i]); + } + } #if COMPILE_WITH_PARTICLE_GPU_GRAPH stream.SetPosition(0); if (GraphGPU.Load(&stream, false)) diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/Particles.cpp similarity index 86% rename from Source/Engine/Particles/ParticleManager.cpp rename to Source/Engine/Particles/Particles.cpp index 75d39cd13..c3f72270c 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1,11 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "ParticleManager.h" +#include "Particles.h" +#include "ParticleEffect.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Core/Collections/HashSet.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Time.h" +#include "Engine/Engine/Engine.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUPipelineStatePermutations.h" #include "Engine/Graphics/RenderTask.h" @@ -13,7 +15,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Renderer/RenderList.h" -#include "ParticleEffect.h" +#include "Engine/Threading/TaskGraph.h" #if COMPILE_WITH_GPU_PARTICLES #include "Engine/Content/Assets/Shader.h" #include "Engine/Profiler/ProfilerGPU.h" @@ -46,10 +48,8 @@ public: { if (VB) return false; - VB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer,VB")); IB = GPUDevice::Instance->CreateBuffer(TEXT("SpriteParticleRenderer.IB")); - static SpriteParticleVertex vertexBuffer[] = { { -0.5f, -0.5f, 0.0f, 0.0f }, @@ -57,20 +57,16 @@ public: { +0.5f, +0.5f, 1.0f, 1.0f }, { -0.5f, +0.5f, 0.0f, 1.0f }, }; - static uint16 indexBuffer[] = { 0, 1, 2, - 0, 2, 3, }; - - return VB->Init(GPUBufferDescription::Vertex(sizeof(SpriteParticleVertex), VertexCount, vertexBuffer)) || - IB->Init(GPUBufferDescription::Index(sizeof(uint16), IndexCount, indexBuffer)); + return VB->Init(GPUBufferDescription::Vertex(sizeof(SpriteParticleVertex), VertexCount, vertexBuffer)) || IB->Init(GPUBufferDescription::Index(sizeof(uint16), IndexCount, indexBuffer)); } void Dispose() @@ -103,17 +99,19 @@ namespace ParticleManagerImpl { CriticalSection PoolLocker; Dictionary> Pool; - HashSet UpdateList(256); + Array UpdateList; #if COMPILE_WITH_GPU_PARTICLES - HashSet GpuUpdateList(256); + CriticalSection GpuUpdateListLocker; + Array GpuUpdateList; RenderTask* GpuRenderTask = nullptr; #endif } using namespace ParticleManagerImpl; -bool ParticleManager::EnableParticleBufferPooling = true; -float ParticleManager::ParticleBufferRecycleTimeout = 10.0f; +TaskGraphSystem* Particles::System = nullptr; +bool Particles::EnableParticleBufferPooling = true; +float Particles::ParticleBufferRecycleTimeout = 10.0f; SpriteParticleRenderer SpriteRenderer; @@ -149,18 +147,28 @@ public: { } - void Update() override; + bool Init() override; void Dispose() override; }; +class ParticlesSystem : public TaskGraphSystem +{ +public: + float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime; + void Job(int32 index); + void Execute(TaskGraph* graph) override; + void PostExecute(TaskGraph* graph) override; +}; + ParticleManagerService ParticleManagerServiceInstance; -void ParticleManager::UpdateEffect(ParticleEffect* effect) +void Particles::UpdateEffect(ParticleEffect* effect) { + ASSERT_LOW_LAYER(!UpdateList.Contains(effect)); UpdateList.Add(effect); } -void ParticleManager::OnEffectDestroy(ParticleEffect* effect) +void Particles::OnEffectDestroy(ParticleEffect* effect) { UpdateList.Remove(effect); #if COMPILE_WITH_GPU_PARTICLES @@ -335,7 +343,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa break; int32 count = buffer->CPU.Count; ASSERT(buffer->CPU.RibbonOrder.Count() == emitter->Graph.RibbonRenderingModules.Count() * buffer->Capacity); - int32* ribbonOrderData = buffer->CPU.RibbonOrder.Get() + module->Ribbon.RibbonOrderOffset; + int32* ribbonOrderData = buffer->CPU.RibbonOrder.Get() + module->RibbonOrderOffset; ParticleBufferCPUDataAccessor positionData(buffer, emitter->Graph.Layout.GetAttributeOffset(module->Attributes[0])); @@ -483,7 +491,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa Vector2 uvOffset = module->Values[5].AsVector2(); ParticleBufferCPUDataAccessor sortKeyData(buffer, emitter->Graph.Layout.GetAttributeOffset(module->Attributes[1])); - int32* ribbonOrderData = buffer->CPU.RibbonOrder.Get() + module->Ribbon.RibbonOrderOffset; + int32* ribbonOrderData = buffer->CPU.RibbonOrder.Get() + module->RibbonOrderOffset; int32 count = buffer->CPU.Count; // Setup ribbon data @@ -895,7 +903,7 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa #endif -void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect* effect) +void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effect) { // Setup auto& view = renderContext.View; @@ -1027,14 +1035,14 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect void UpdateGPU(RenderTask* task, GPUContext* context) { + ScopeLock lock(GpuUpdateListLocker); if (GpuUpdateList.IsEmpty()) return; PROFILE_GPU("GPU Particles"); - for (auto i = GpuUpdateList.Begin(); i.IsNotEnd(); ++i) + for (ParticleEffect* effect : GpuUpdateList) { - ParticleEffect* effect = i->Item; auto& instance = effect->Instance; const auto particleSystem = effect->ParticleSystem.Get(); if (!particleSystem || !particleSystem->IsLoaded()) @@ -1066,13 +1074,14 @@ void UpdateGPU(RenderTask* task, GPUContext* context) #endif -ParticleBuffer* ParticleManager::AcquireParticleBuffer(ParticleEmitter* emitter) +ParticleBuffer* Particles::AcquireParticleBuffer(ParticleEmitter* emitter) { ParticleBuffer* result = nullptr; ASSERT(emitter && emitter->IsLoaded()); if (emitter->EnablePooling && EnableParticleBufferPooling) { + PoolLocker.Lock(); const auto entries = Pool.TryGet(emitter); if (entries) { @@ -1087,7 +1096,6 @@ ParticleBuffer* ParticleManager::AcquireParticleBuffer(ParticleEmitter* emitter) { Delete(result); result = nullptr; - if (entries->IsEmpty()) { Pool.Remove(emitter); @@ -1096,6 +1104,7 @@ ParticleBuffer* ParticleManager::AcquireParticleBuffer(ParticleEmitter* emitter) } } } + PoolLocker.Unlock(); } if (!result) @@ -1118,7 +1127,7 @@ ParticleBuffer* ParticleManager::AcquireParticleBuffer(ParticleEmitter* emitter) return result; } -void ParticleManager::RecycleParticleBuffer(ParticleBuffer* buffer) +void Particles::RecycleParticleBuffer(ParticleBuffer* buffer) { if (buffer->Emitter->EnablePooling && EnableParticleBufferPooling) { @@ -1138,7 +1147,7 @@ void ParticleManager::RecycleParticleBuffer(ParticleBuffer* buffer) } } -void ParticleManager::OnEmitterUnload(ParticleEmitter* emitter) +void Particles::OnEmitterUnload(ParticleEmitter* emitter) { PoolLocker.Lock(); const auto entries = Pool.TryGet(emitter); @@ -1154,218 +1163,22 @@ void ParticleManager::OnEmitterUnload(ParticleEmitter* emitter) PoolLocker.Unlock(); #if COMPILE_WITH_GPU_PARTICLES - for (auto i = GpuUpdateList.Begin(); i.IsNotEnd(); ++i) + GpuUpdateListLocker.Lock(); + for (int32 i = GpuUpdateList.Count() - 1; i >= 0; i--) { - if (i->Item->Instance.ContainsEmitter(emitter)) - GpuUpdateList.Remove(i); + if (GpuUpdateList[i]->Instance.ContainsEmitter(emitter)) + GpuUpdateList.RemoveAt(i); } + GpuUpdateListLocker.Unlock(); #endif } -void ParticleManagerService::Update() +bool ParticleManagerService::Init() { - PROFILE_CPU_NAMED("Particles"); - - // TODO: implement the thread jobs pipeline to run set of tasks at once (use it for multi threaded rendering and animations evaluation and CPU particles simulation) - - const auto timeSeconds = Platform::GetTimeSeconds(); - const auto& tickData = Time::Update; - const float deltaTimeUnscaled = tickData.UnscaledDeltaTime.GetTotalSeconds(); - const float timeUnscaled = tickData.UnscaledTime.GetTotalSeconds(); - const float deltaTime = tickData.DeltaTime.GetTotalSeconds(); - const float time = tickData.Time.GetTotalSeconds(); - - // Update particle effects - for (auto i = UpdateList.Begin(); i.IsNotEnd(); ++i) - { - ParticleEffect* effect = i->Item; - auto& instance = effect->Instance; - const auto particleSystem = effect->ParticleSystem.Get(); - if (!particleSystem || !particleSystem->IsLoaded()) - continue; - bool anyEmitterNotReady = false; - for (int32 j = 0; j < particleSystem->Tracks.Count(); j++) - { - const auto& track = particleSystem->Tracks[j]; - if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled) - continue; - auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get(); - if (!emitter || !emitter->IsLoaded()) - { - anyEmitterNotReady = true; - break; - } - } - if (anyEmitterNotReady) - continue; - -#if USE_EDITOR - // Lock in editor only (more reloads during asset live editing) - ScopeLock lock(particleSystem->Locker); -#endif - - // Prepare instance data - instance.Sync(particleSystem); - - bool updateBounds = false; - bool updateGpu = false; - - // Simulation delta time can be based on a time since last update or the current delta time - float dt = effect->UseTimeScale ? deltaTime : deltaTimeUnscaled; - float t = effect->UseTimeScale ? time : timeUnscaled; -#if USE_EDITOR - if (!Editor::IsPlayMode) - { - dt = deltaTimeUnscaled; - t = timeUnscaled; - } -#endif - const float lastUpdateTime = instance.LastUpdateTime; - if (lastUpdateTime > 0 && t > lastUpdateTime) - { - dt = t - lastUpdateTime; - } - else if (lastUpdateTime < 0) - { - // Update bounds after first system update - updateBounds = true; - } - // TODO: if using fixed timestep quantize the dt and accumulate remaining part for the next update? - if (dt <= 1.0f / 240.0f) - continue; - dt *= effect->SimulationSpeed; - instance.Time += dt; - const float fps = particleSystem->FramesPerSecond; - const float duration = particleSystem->DurationFrames / fps; - if (instance.Time > duration) - { - if (effect->IsLooping) - { - // Loop - // TODO: accumulate (duration - instance.Time) into next update dt - instance.Time = 0; - for (int32 j = 0; j < instance.Emitters.Count(); j++) - { - auto& e = instance.Emitters[j]; - e.Time = 0; - for (auto& s : e.SpawnModulesData) - { - s.NextSpawnTime = 0.0f; - } - } - } - else - { - // End - instance.Time = duration; - for (auto& emitterInstance : instance.Emitters) - { - if (emitterInstance.Buffer) - { - ParticleManager::RecycleParticleBuffer(emitterInstance.Buffer); - emitterInstance.Buffer = nullptr; - } - } - continue; - } - } - instance.LastUpdateTime = t; - - // Update all emitter tracks - for (int32 j = 0; j < particleSystem->Tracks.Count(); j++) - { - const auto& track = particleSystem->Tracks[j]; - if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled) - continue; - auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get(); - auto& data = instance.Emitters[track.AsEmitter.Index]; - ASSERT(emitter && emitter->IsLoaded()); - ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0); - - // Calculate new time position - const float startTime = track.AsEmitter.StartFrame / fps; - const float durationTime = track.AsEmitter.DurationFrames / fps; - const bool canSpawn = startTime <= instance.Time && instance.Time <= startTime + durationTime; - - // Update instance data - data.Sync(effect->Instance, particleSystem, track.AsEmitter.Index); - if (!data.Buffer) - { - data.Buffer = ParticleManager::AcquireParticleBuffer(emitter); - } - data.Time += dt; - - // Update particles simulation - switch (emitter->SimulationMode) - { - case ParticlesSimulationMode::CPU: - emitter->GraphExecutorCPU.Update(emitter, effect, data, dt, canSpawn); - updateBounds |= emitter->UseAutoBounds; - break; -#if COMPILE_WITH_GPU_PARTICLES - case ParticlesSimulationMode::GPU: - emitter->GPU.Update(emitter, effect, data, dt, canSpawn); - updateGpu = true; - break; -#endif - default: - CRASH; - break; - } - } - - // Update bounds if any of the emitters uses auto-bounds - if (updateBounds) - { - effect->UpdateBounds(); - } - -#if COMPILE_WITH_GPU_PARTICLES - // Register for GPU update - if (updateGpu) - { - GpuUpdateList.Add(effect); - } -#endif - } - UpdateList.Clear(); - -#if COMPILE_WITH_GPU_PARTICLES - // Create GPU render task if missing but required - if (GpuUpdateList.HasItems() && !GpuRenderTask) - { - GpuRenderTask = New(); - GpuRenderTask->Order = -10000000; - GpuRenderTask->Render.Bind(UpdateGPU); - ScopeLock lock(RenderTask::TasksLocker); - RenderTask::Tasks.Add(GpuRenderTask); - } - else if (GpuRenderTask) - { - ScopeLock lock(RenderTask::TasksLocker); - GpuRenderTask->Enabled = GpuUpdateList.HasItems(); - } -#endif - - // Recycle buffers - PoolLocker.Lock(); - for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) - { - auto& entries = i->Value; - for (int32 j = 0; j < entries.Count(); j++) - { - auto& e = entries[j]; - if (timeSeconds - e.LastTimeUsed >= ParticleManager::ParticleBufferRecycleTimeout) - { - Delete(e.Buffer); - entries.RemoveAt(j--); - } - } - - if (entries.IsEmpty()) - Pool.Remove(i); - } - PoolLocker.Unlock(); + Particles::System = New(); + Particles::System->Order = 10000; + Engine::UpdateGraph->AddSystem(Particles::System); + return false; } void ParticleManagerService::Dispose() @@ -1400,4 +1213,215 @@ void ParticleManagerService::Dispose() PoolLocker.Unlock(); SpriteRenderer.Dispose(); + SAFE_DELETE(Particles::System); +} + +void ParticlesSystem::Job(int32 index) +{ + PROFILE_CPU_NAMED("Particles.Job"); + auto effect = UpdateList[index]; + auto& instance = effect->Instance; + const auto particleSystem = effect->ParticleSystem.Get(); + if (!particleSystem || !particleSystem->IsLoaded()) + return; + bool anyEmitterNotReady = false; + for (int32 j = 0; j < particleSystem->Tracks.Count(); j++) + { + const auto& track = particleSystem->Tracks[j]; + if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled) + continue; + auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get(); + if (!emitter || !emitter->IsLoaded()) + { + anyEmitterNotReady = true; + break; + } + } + if (anyEmitterNotReady) + return; + + // Prepare instance data + instance.Sync(particleSystem); + + bool updateBounds = false; + bool updateGpu = false; + + // Simulation delta time can be based on a time since last update or the current delta time + bool useTimeScale = effect->UseTimeScale; +#if USE_EDITOR + if (!Editor::IsPlayMode) + useTimeScale = false; +#endif + float dt = useTimeScale ? DeltaTime : UnscaledDeltaTime; + float t = useTimeScale ? Time : UnscaledTime; + const float lastUpdateTime = instance.LastUpdateTime; + if (lastUpdateTime > 0 && t > lastUpdateTime) + { + dt = t - lastUpdateTime; + } + else if (lastUpdateTime < 0) + { + // Update bounds after first system update + updateBounds = true; + } + // TODO: if using fixed timestep quantize the dt and accumulate remaining part for the next update? + if (dt <= 1.0f / 240.0f) + return; + dt *= effect->SimulationSpeed; + instance.Time += dt; + const float fps = particleSystem->FramesPerSecond; + const float duration = (float)particleSystem->DurationFrames / fps; + if (instance.Time > duration) + { + if (effect->IsLooping) + { + // Loop + // TODO: accumulate (duration - instance.Time) into next update dt + instance.Time = 0; + for (int32 j = 0; j < instance.Emitters.Count(); j++) + { + auto& e = instance.Emitters[j]; + e.Time = 0; + for (auto& s : e.SpawnModulesData) + { + s.NextSpawnTime = 0.0f; + } + } + } + else + { + // End + instance.Time = duration; + for (auto& emitterInstance : instance.Emitters) + { + if (emitterInstance.Buffer) + { + Particles::RecycleParticleBuffer(emitterInstance.Buffer); + emitterInstance.Buffer = nullptr; + } + } + return; + } + } + instance.LastUpdateTime = t; + + // Update all emitter tracks + for (int32 j = 0; j < particleSystem->Tracks.Count(); j++) + { + const auto& track = particleSystem->Tracks[j]; + if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled) + continue; + auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get(); + auto& data = instance.Emitters[track.AsEmitter.Index]; + ASSERT(emitter && emitter->IsLoaded()); + ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0); + + // Calculate new time position + const float startTime = (float)track.AsEmitter.StartFrame / fps; + const float durationTime = (float)track.AsEmitter.DurationFrames / fps; + const bool canSpawn = startTime <= instance.Time && instance.Time <= startTime + durationTime; + + // Update instance data + data.Sync(effect->Instance, particleSystem, track.AsEmitter.Index); + if (!data.Buffer) + { + data.Buffer = Particles::AcquireParticleBuffer(emitter); + } + data.Time += dt; + + // Update particles simulation + switch (emitter->SimulationMode) + { + case ParticlesSimulationMode::CPU: + emitter->GraphExecutorCPU.Update(emitter, effect, data, dt, canSpawn); + updateBounds |= emitter->UseAutoBounds; + break; +#if COMPILE_WITH_GPU_PARTICLES + case ParticlesSimulationMode::GPU: + emitter->GPU.Update(emitter, effect, data, dt, canSpawn); + updateGpu = true; + break; +#endif + default: + break; + } + } + + // Update bounds if any of the emitters uses auto-bounds + if (updateBounds) + { + effect->UpdateBounds(); + } + +#if COMPILE_WITH_GPU_PARTICLES + // Register for GPU update + if (updateGpu) + { + ScopeLock lock(GpuUpdateListLocker); + GpuUpdateList.Add(effect); + } +#endif +} + +void ParticlesSystem::Execute(TaskGraph* graph) +{ + if (UpdateList.Count() == 0) + return; + + // Setup data for async update + const auto& tickData = Time::Update; + DeltaTime = tickData.DeltaTime.GetTotalSeconds(); + UnscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds(); + Time = tickData.Time.GetTotalSeconds(); + UnscaledTime = tickData.UnscaledTime.GetTotalSeconds(); + + // Schedule work to update all particles in async + Function job; + job.Bind(this); + graph->DispatchJob(job, UpdateList.Count()); +} + +void ParticlesSystem::PostExecute(TaskGraph* graph) +{ + PROFILE_CPU_NAMED("Particles.PostExecute"); + + UpdateList.Clear(); + +#if COMPILE_WITH_GPU_PARTICLES + // Create GPU render task if missing but required + if (GpuUpdateList.HasItems() && !GpuRenderTask) + { + GpuRenderTask = New(); + GpuRenderTask->Order = -10000000; + GpuRenderTask->Render.Bind(UpdateGPU); + ScopeLock lock(RenderTask::TasksLocker); + RenderTask::Tasks.Add(GpuRenderTask); + } + else if (GpuRenderTask) + { + ScopeLock lock(RenderTask::TasksLocker); + GpuRenderTask->Enabled = GpuUpdateList.HasItems(); + } +#endif + + // Recycle buffers + const auto timeSeconds = Platform::GetTimeSeconds(); + PoolLocker.Lock(); + for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) + { + auto& entries = i->Value; + for (int32 j = 0; j < entries.Count(); j++) + { + auto& e = entries[j]; + if (timeSeconds - e.LastTimeUsed >= Particles::ParticleBufferRecycleTimeout) + { + Delete(e.Buffer); + entries.RemoveAt(j--); + } + } + + if (entries.IsEmpty()) + Pool.Remove(i); + } + PoolLocker.Unlock(); } diff --git a/Source/Engine/Particles/ParticleManager.h b/Source/Engine/Particles/Particles.h similarity index 86% rename from Source/Engine/Particles/ParticleManager.h rename to Source/Engine/Particles/Particles.h index 4308b2ab8..276eeb5af 100644 --- a/Source/Engine/Particles/ParticleManager.h +++ b/Source/Engine/Particles/Particles.h @@ -2,6 +2,9 @@ #pragma once +#include "Engine/Scripting/ScriptingType.h" + +class TaskGraphSystem; struct RenderContext; struct RenderView; class ParticleEmitter; @@ -13,10 +16,17 @@ class SceneRenderTask; class Actor; /// -/// The particles service used for simulation and emitters data pooling. +/// The particles simulation service. /// -class FLAXENGINE_API ParticleManager +API_CLASS(Static) class FLAXENGINE_API Particles { +DECLARE_SCRIPTING_TYPE_NO_SPAWN(Particles); + + /// + /// The system for Particles update. + /// + API_FIELD(ReadOnly) static TaskGraphSystem* System; + public: /// diff --git a/Source/Engine/Particles/ParticlesData.h b/Source/Engine/Particles/ParticlesData.h index edda3c507..af1340ef8 100644 --- a/Source/Engine/Particles/ParticlesData.h +++ b/Source/Engine/Particles/ParticlesData.h @@ -14,7 +14,7 @@ #define PARTICLE_EMITTER_MAX_RIBBONS 4 class ParticleEmitter; -class ParticleManager; +class Particles; class GPUBuffer; class DynamicIndexBuffer; diff --git a/Source/Engine/Particles/ParticlesSimulation.cpp b/Source/Engine/Particles/ParticlesSimulation.cpp index fb0e9bf74..97c687685 100644 --- a/Source/Engine/Particles/ParticlesSimulation.cpp +++ b/Source/Engine/Particles/ParticlesSimulation.cpp @@ -1,9 +1,9 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticlesSimulation.h" -#include "ParticleManager.h" #include "ParticleSystem.h" #include "ParticleEmitter.h" +#include "Particles.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUDevice.h" @@ -15,7 +15,7 @@ ParticleEmitterInstance::~ParticleEmitterInstance() { if (Buffer) { - ParticleManager::RecycleParticleBuffer(Buffer); + Particles::RecycleParticleBuffer(Buffer); } } @@ -24,13 +24,14 @@ void ParticleEmitterInstance::ClearState() Version = 0; Time = 0; SpawnModulesData.Clear(); + CustomData.Clear(); #if COMPILE_WITH_GPU_PARTICLES GPU.DeltaTime = 0.0f; GPU.SpawnCount = 0; #endif if (Buffer) { - ParticleManager::RecycleParticleBuffer(Buffer); + Particles::RecycleParticleBuffer(Buffer); Buffer = nullptr; } } @@ -61,18 +62,22 @@ void ParticleEmitterInstance::Sync(ParticleSystemInstance& systemInstance, Parti if (SpawnModulesData.Count() != emitter->Graph.SpawnModules.Count()) { SpawnModulesData.Resize(emitter->Graph.SpawnModules.Count(), false); - SpawnerData data; data.SpawnCounter = 0; data.NextSpawnTime = 0; SpawnModulesData.SetAll(data); } + if (CustomData.Count() != emitter->Graph.CustomDataSize) + { + CustomData.Resize(emitter->Graph.CustomDataSize, false); + Platform::MemoryClear(CustomData.Get(), CustomData.Count()); + } } // Sync buffer version if (Buffer && Buffer->Version != Version) { - ParticleManager::RecycleParticleBuffer(Buffer); + Particles::RecycleParticleBuffer(Buffer); Buffer = nullptr; } } diff --git a/Source/Engine/Particles/ParticlesSimulation.h b/Source/Engine/Particles/ParticlesSimulation.h index c9230c1c6..7008957f0 100644 --- a/Source/Engine/Particles/ParticlesSimulation.h +++ b/Source/Engine/Particles/ParticlesSimulation.h @@ -83,8 +83,10 @@ public: /// Array SpawnModulesData; -#if COMPILE_WITH_GPU_PARTICLES + // Custom per-node data (eg. position on spiral module for arc progress tracking) + Array CustomData; +#if COMPILE_WITH_GPU_PARTICLES struct { /// @@ -97,7 +99,6 @@ public: /// int32 SpawnCount; } GPU; - #endif /// diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 558e4f98c..0fe53f570 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "WheeledVehicle.h" +#include "Engine/Core/Log.h" #include "Engine/Physics/Physics.h" #include "Engine/Physics/Utilities.h" #include "Engine/Level/Scene/Scene.h" diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index bb947d68d..1941038bf 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -156,7 +156,7 @@ public: // Left wheel of the rear axle. RearLeft, // Right wheel of the rear axle. - ReadRight, + RearRight, // Non-drivable wheel. NoDrive, }; diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index ef856bf79..68fd8bfbe 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -30,6 +30,11 @@ CharacterController::CharacterController(const SpawnParams& params) static_assert(sizeof(_filterData) == sizeof(PxFilterData), "Invalid filter data size."); } +float CharacterController::GetRadius() const +{ + return _radius; +} + void CharacterController::SetRadius(const float value) { if (Math::NearEqual(value, _radius)) @@ -41,6 +46,11 @@ void CharacterController::SetRadius(const float value) UpdateBounds(); } +float CharacterController::GetHeight() const +{ + return _height; +} + void CharacterController::SetHeight(const float value) { if (Math::NearEqual(value, _height)) @@ -52,6 +62,11 @@ void CharacterController::SetHeight(const float value) UpdateBounds(); } +float CharacterController::GetSlopeLimit() const +{ + return _slopeLimit; +} + void CharacterController::SetSlopeLimit(float value) { value = Math::Clamp(value, 0.0f, 89.0f); @@ -100,6 +115,11 @@ void CharacterController::SetUpDirection(const Vector3& up) _upDirection = up; } +float CharacterController::GetMinMoveDistance() const +{ + return _minMoveDistance; +} + Vector3 CharacterController::GetUpDirection() const { return _controller ? P2C(_controller->getUpDirection()) : _upDirection; @@ -115,6 +135,16 @@ Vector3 CharacterController::GetVelocity() const return _controller ? P2C(_controller->getActor()->getLinearVelocity()) : Vector3::Zero; } +bool CharacterController::IsGrounded() const +{ + return (static_cast(_lastFlags) & static_cast(CollisionFlags::Below)) != 0; +} + +CharacterController::CollisionFlags CharacterController::GetFlags() const +{ + return _lastFlags; +} + CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector3& speed) { const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); @@ -178,7 +208,7 @@ void CharacterController::OnDebugDrawSelected() #endif -void CharacterController::CreateActor() +void CharacterController::CreateController() { ASSERT(_controller == nullptr && _shape == nullptr); @@ -218,6 +248,18 @@ void CharacterController::CreateActor() UpdateBounds(); } +void CharacterController::DeleteController() +{ + if (_controller) + { + _shape->userData = nullptr; + _controller->getActor()->userData = nullptr; + _controller->release(); + _controller = nullptr; + } + _shape = nullptr; +} + void CharacterController::UpdateSize() const { if (_controller) @@ -313,7 +355,8 @@ void CharacterController::UpdateLayerBits() void CharacterController::BeginPlay(SceneBeginData* data) { - CreateActor(); + if (IsActiveInHierarchy()) + CreateController(); // Skip collider base Actor::BeginPlay(data); @@ -325,26 +368,28 @@ void CharacterController::EndPlay() Actor::EndPlay(); // Remove controller - if (_controller) - { - _shape->userData = nullptr; - _controller->getActor()->userData = nullptr; - _controller->release(); - _controller = nullptr; - } - _shape = nullptr; + DeleteController(); } void CharacterController::OnActiveInTreeChanged() { // Skip collider base Actor::OnActiveInTreeChanged(); +} - // Clear velocities and the forces on disabled - if (!IsActiveInHierarchy() && _controller) - { - // TODO: sleep actor? clear forces? - } +void CharacterController::OnEnable() +{ + if (_controller == nullptr) + CreateController(); + + Collider::OnEnable(); +} + +void CharacterController::OnDisable() +{ + Collider::OnDisable(); + + DeleteController(); } void CharacterController::OnParentChanged() diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 5b4dd3f49..5fece3195 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -71,15 +71,11 @@ private: uint32 _filterData[4]; public: - /// /// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale. /// API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") - FORCE_INLINE float GetRadius() const - { - return _radius; - } + float GetRadius() const; /// /// Sets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale. @@ -90,10 +86,7 @@ public: /// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. /// API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\")") - FORCE_INLINE float GetHeight() const - { - return _height; - } + float GetHeight() const; /// /// Sets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. @@ -104,10 +97,7 @@ public: /// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value. /// API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\")") - FORCE_INLINE float GetSlopeLimit() const - { - return _slopeLimit; - } + float GetSlopeLimit() const; /// /// Sets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value. @@ -151,10 +141,7 @@ public: /// 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 - { - return _minMoveDistance; - } + float GetMinMoveDistance() const; /// /// 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. @@ -171,18 +158,12 @@ public: /// /// Gets a value indicating whether this character was grounded during last move call grounded. /// - API_PROPERTY() FORCE_INLINE bool IsGrounded() const - { - return (static_cast(_lastFlags) & static_cast(CollisionFlags::Below)) != 0; - } + API_PROPERTY() bool IsGrounded() const; /// /// Gets the current collision flags. Tells which parts of the character capsule collided with the environment during the last move call. It can be used to trigger various character animations. /// - API_PROPERTY() FORCE_INLINE CollisionFlags GetFlags() const - { - return _lastFlags; - } + API_PROPERTY() CollisionFlags GetFlags() const; public: @@ -203,7 +184,6 @@ public: /// /// Gets the native PhysX rigid actor object. /// - /// The PhysX dynamic rigid actor. PxRigidDynamic* GetPhysXRigidActor() const; protected: @@ -211,7 +191,12 @@ protected: /// /// Creates the physics actor. /// - void CreateActor(); + void CreateController(); + + /// + /// Deletes the physics actor. + /// + void DeleteController(); /// /// Updates the character height and radius. @@ -248,6 +233,8 @@ protected: void DrawPhysicsDebug(RenderView& view) override; #endif void OnActiveInTreeChanged() override; + void OnEnable() override; + void OnDisable() override; void OnParentChanged() override; void OnTransformChanged() override; }; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index acf6af5d1..cc45f3f51 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -31,6 +31,11 @@ Collider::Collider(const SpawnParams& params) Material.Changed.Bind(this); } +PxShape* Collider::GetPxShape() const +{ + return _shape; +} + void Collider::SetIsTrigger(bool value) { if (value == _isTrigger || !CanBeTrigger()) diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index ca88ea66c..50ac3b8db 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -32,11 +32,7 @@ public: /// /// Gets the collider shape PhysX object. /// - /// The PhysX Shape object or null. - FORCE_INLINE PxShape* GetPxShape() const - { - return _shape; - } + PxShape* GetPxShape() const; /// /// Gets the 'IsTrigger' flag. diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index a8621da09..eeb0a43c1 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "CollisionData.h" +#include "Engine/Core/Log.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index 786df6654..5b9d02424 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -260,7 +260,7 @@ namespace String AppPackageName, DeviceManufacturer, DeviceModel, DeviceBuildNumber; String SystemVersion, SystemLanguage, CacheDir, ExecutablePath; byte MacAddress[6]; - AndroidKeyboard KeyboardImpl; + AndroidKeyboard* KeyboardImpl; AndroidDeviceGamepad* GamepadImpl; AndroidTouchScreen* TouchScreenImpl; ScreenOrientationType Orientation; @@ -496,9 +496,9 @@ namespace if (eventType.KeyboardKey != KeyboardKeys::None) { if (isDown) - KeyboardImpl.OnKeyDown(eventType.KeyboardKey); + KeyboardImpl->OnKeyDown(eventType.KeyboardKey); else - KeyboardImpl.OnKeyUp(eventType.KeyboardKey); + KeyboardImpl->OnKeyUp(eventType.KeyboardKey); } // Gamepad @@ -850,7 +850,7 @@ bool AndroidPlatform::Init() } // Setup native platform input devices - Input::Keyboard = &KeyboardImpl; + Input::Keyboard = KeyboardImpl = New(); Input::Gamepads.Add(GamepadImpl = New()); Input::OnGamepadsChanged(); Input::CustomDevices.Add(TouchScreenImpl = New()); diff --git a/Source/Engine/Platform/Base/FileSystemWatcherBase.h b/Source/Engine/Platform/Base/FileSystemWatcherBase.h index 395f0a340..6bfb38baf 100644 --- a/Source/Engine/Platform/Base/FileSystemWatcherBase.h +++ b/Source/Engine/Platform/Base/FileSystemWatcherBase.h @@ -22,61 +22,34 @@ enum class FileSystemAction /// class FLAXENGINE_API FileSystemWatcherBase : public NonCopyable { -protected: - - bool _isEnabled; - bool _withSubDirs; - String _directory; - public: FileSystemWatcherBase(const String& directory, bool withSubDirs) - : _isEnabled(true) - , _withSubDirs(withSubDirs) - , _directory(directory) + : Directory(directory) + , WithSubDirs(withSubDirs) + , Enabled(true) { } +public: + + /// + /// The watcher directory path. + /// + const String Directory; + + /// + /// The value whenever watcher is tracking changes in subdirectories. + /// + const bool WithSubDirs; + + /// + /// The current watcher enable state. + /// + bool Enabled; + /// /// Action fired when directory or file gets changed. Can be invoked from main or other thread depending on the platform. /// Delegate OnEvent; - -public: - - /// - /// Gets the watcher directory string. - /// - /// The target directory path. - const String& GetDirectory() const - { - return _directory; - } - - /// - /// Gets the value whenever watcher is tracking changes in subdirectories. - /// - /// True if watcher is tracking changes in subdirectories, otherwise false. - bool WithSubDirs() const - { - return _withSubDirs; - } - - /// - /// Gets the current watcher enable state. - /// - /// True if watcher is enabled, otherwise false. - bool GetEnabled() const - { - return _isEnabled; - } - - /// - /// Sets the current enable state. - /// - /// A state to assign. - void SetEnabled(bool value) - { - _isEnabled = value; - } }; diff --git a/Source/Engine/Platform/Base/ThreadBase.h b/Source/Engine/Platform/Base/ThreadBase.h index 8cd683d7c..6a56e33ad 100644 --- a/Source/Engine/Platform/Base/ThreadBase.h +++ b/Source/Engine/Platform/Base/ThreadBase.h @@ -45,7 +45,6 @@ public: /// /// Gets priority level of the thread. /// - /// The thread priority level. FORCE_INLINE ThreadPriority GetPriority() const { return _priority; @@ -60,7 +59,6 @@ public: /// /// Gets thread ID /// - /// Thread ID FORCE_INLINE uint64 GetID() const { return _id; @@ -69,7 +67,6 @@ public: /// /// Gets thread running state. /// - /// True if thread is running, otherwise false FORCE_INLINE bool IsRunning() const { return _isRunning; @@ -78,7 +75,6 @@ public: /// /// Gets name of the thread. /// - /// The thread name. FORCE_INLINE const String& GetName() const { return _name; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 90bd1784f..74d39df7e 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1195,8 +1195,8 @@ struct Property namespace Impl { - LinuxKeyboard Keyboard; - LinuxMouse Mouse; + LinuxKeyboard* Keyboard; + LinuxMouse* Mouse; StringAnsi ClipboardText; void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window) @@ -2193,8 +2193,8 @@ bool LinuxPlatform::Init() } } - Input::Mouse = &Impl::Mouse; - Input::Keyboard = &Impl::Keyboard; + Input::Mouse = Impl::Mouse = New(); + Input::Keyboard = Impl::Keyboard = New(); return false; } diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index f90be1129..e7ec0308c 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; -typedef NetworkBase Network; +class Win32Network; +typedef Win32Network Network; #elif PLATFORM_LINUX diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp index 522d5db15..b0fdf8f21 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.cpp +++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp @@ -22,8 +22,8 @@ UWPPlatformImpl* CUWPPlatform = nullptr; namespace Impl { - extern UWPWindow::UWPKeyboard Keyboard; - extern UWPWindow::UWPMouse Mouse; + extern UWPWindow::UWPKeyboard* Keyboard; + extern UWPWindow::UWPMouse* Mouse; extern UWPWindow* Window; } @@ -62,8 +62,8 @@ bool UWPPlatform::Init() UserName = String::Empty; SystemDpi = CUWPPlatform->GetDpi(); - Input::Mouse = &Impl::Mouse; - Input::Keyboard = &Impl::Keyboard; + Input::Mouse = Impl::Mouse = New(); + Input::Keyboard = Impl::Keyboard = New(); return false; } diff --git a/Source/Engine/Platform/UWP/UWPWindow.cpp b/Source/Engine/Platform/UWP/UWPWindow.cpp index ba509b089..716d038de 100644 --- a/Source/Engine/Platform/UWP/UWPWindow.cpp +++ b/Source/Engine/Platform/UWP/UWPWindow.cpp @@ -11,8 +11,8 @@ namespace Impl { - UWPWindow::UWPKeyboard Keyboard; - UWPWindow::UWPMouse Mouse; + UWPWindow::UWPKeyboard* Keyboard = nullptr; + UWPWindow::UWPMouse* Mouse = nullptr; UWPWindow* Window = nullptr; } @@ -325,7 +325,7 @@ UWPWindow::UWPWindow(const CreateWindowSettings& settings, UWPWindowImpl* impl) _dpiScale = _dpi / 96.0f; float x, y; _impl->GetBounds(&x, &y, &_logicalSize.X, &_logicalSize.Y); - _impl->GetMousePosition(&Impl::Mouse.MousePosition.X, &Impl::Mouse.MousePosition.Y); + _impl->GetMousePosition(&Impl::Mouse->MousePosition.X, &Impl::Mouse->MousePosition.Y); OnSizeChange(); } @@ -502,7 +502,7 @@ void UWPWindow::onKeyDown(int key) if (key < 7) return; - Impl::Keyboard.onKeyDown(key); + Impl::Keyboard->onKeyDown(key); } void UWPWindow::onKeyUp(int key) @@ -510,17 +510,17 @@ void UWPWindow::onKeyUp(int key) if (key < 7) return; - Impl::Keyboard.onKeyUp(key); + Impl::Keyboard->onKeyUp(key); } void UWPWindow::onCharacterReceived(int key) { - Impl::Keyboard.onCharacterReceived(key); + Impl::Keyboard->onCharacterReceived(key); } void UWPWindow::onMouseMoved(float x, float y) { - Impl::Mouse.onMouseMoved(x * _dpiScale, y * _dpiScale); + Impl::Mouse->onMouseMoved(x * _dpiScale, y * _dpiScale); } void UWPWindow::onPointerPressed(UWPWindowImpl::PointerData* pointer) @@ -529,7 +529,7 @@ void UWPWindow::onPointerPressed(UWPWindowImpl::PointerData* pointer) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; - Impl::Mouse.onPointerPressed(pointer); + Impl::Mouse->onPointerPressed(pointer); } // TODO: impl mobile touch support @@ -541,7 +541,7 @@ void UWPWindow::onPointerMoved(UWPWindowImpl::PointerData* pointer) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; - Impl::Mouse.onPointerMoved(pointer); + Impl::Mouse->onPointerMoved(pointer); } // TODO: impl mobile touch support @@ -553,7 +553,7 @@ void UWPWindow::onPointerWheelChanged(UWPWindowImpl::PointerData* pointer) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; - Impl::Mouse.onPointerWheelChanged(pointer); + Impl::Mouse->onPointerWheelChanged(pointer); } } @@ -563,7 +563,7 @@ void UWPWindow::onPointerReleased(UWPWindowImpl::PointerData* pointer) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; - Impl::Mouse.onPointerReleased(pointer); + Impl::Mouse->onPointerReleased(pointer); } // TODO: impl mobile touch support @@ -575,7 +575,7 @@ void UWPWindow::onPointerExited(UWPWindowImpl::PointerData* pointer) { pointer->PositionX *= _dpiScale; pointer->PositionY *= _dpiScale; - Impl::Mouse.onPointerExited(pointer); + Impl::Mouse->onPointerExited(pointer); } // TODO: impl mobile touch support diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 31c0f4a36..9d2041547 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -456,7 +456,21 @@ void Win32Platform::SetThreadAffinityMask(uint64 affinityMask) void Win32Platform::Sleep(int32 milliseconds) { - ::Sleep(static_cast(milliseconds)); + static thread_local HANDLE timer = NULL; + if (timer == NULL) + { + // Attempt to create high-resolution timer for each thread (Windows 10 build 17134 or later) + timer = CreateWaitableTimerEx(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + if (timer == NULL) // fallback for older versions of Windows + timer = CreateWaitableTimer(NULL, TRUE, NULL); + } + + // Negative value is relative to current time, minimum waitable time is 10 microseconds + LARGE_INTEGER dueTime; + dueTime.QuadPart = -int64_t(milliseconds) * 10000; + + SetWaitableTimerEx(timer, &dueTime, 0, NULL, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); } double Win32Platform::GetTimeSeconds() diff --git a/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp b/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp index 832fd9bb2..466ace2f6 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystemWatcher.cpp @@ -87,7 +87,7 @@ VOID CALLBACK NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTra { // Build path String path(notify->FileName, notify->FileNameLength / sizeof(WCHAR)); - path = watcher->GetDirectory() / path; + path = watcher->Directory / path; // Send event watcher->OnEvent(path, action); @@ -106,7 +106,7 @@ BOOL RefreshWatch(WindowsFileSystemWatcher* watcher) watcher->DirectoryHandle, watcher->Buffer[watcher->CurrentBuffer], FileSystemWatcher::BufferSize, - watcher->WithSubDirs() ? TRUE : FALSE, + watcher->WithSubDirs ? TRUE : FALSE, FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytesReturned, (OVERLAPPED*)&watcher->Overlapped, diff --git a/Source/Engine/Platform/Windows/WindowsInput.cpp b/Source/Engine/Platform/Windows/WindowsInput.cpp index 177eb067c..2838e2286 100644 --- a/Source/Engine/Platform/Windows/WindowsInput.cpp +++ b/Source/Engine/Platform/Windows/WindowsInput.cpp @@ -105,14 +105,14 @@ namespace WindowsInputImpl { float XInputLastUpdateTime = 0; bool XInputGamepads[XUSER_MAX_COUNT] = { false }; - WindowsMouse Mouse; - WindowsKeyboard Keyboard; + WindowsMouse* Mouse = nullptr; + WindowsKeyboard* Keyboard = nullptr; } void WindowsInput::Init() { - Input::Mouse = &WindowsInputImpl::Mouse; - Input::Keyboard = &WindowsInputImpl::Keyboard; + Input::Mouse = WindowsInputImpl::Mouse = New(); + Input::Keyboard = WindowsInputImpl::Keyboard = New(); } void WindowsInput::Update() @@ -142,9 +142,9 @@ void WindowsInput::Update() bool WindowsInput::WndProc(Window* window, Windows::UINT msg, Windows::WPARAM wParam, Windows::LPARAM lParam) { - if (WindowsInputImpl::Mouse.WndProc(window, msg, wParam, lParam)) + if (WindowsInputImpl::Mouse->WndProc(window, msg, wParam, lParam)) return true; - if (WindowsInputImpl::Keyboard.WndProc(window, msg, wParam, lParam)) + if (WindowsInputImpl::Keyboard->WndProc(window, msg, wParam, lParam)) return true; return false; } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 00a5c0d0f..52b0bdaa3 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -4,6 +4,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Platform/Window.h" +#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CreateWindowSettings.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/MemoryStats.h" @@ -17,6 +18,7 @@ #include "../Win32/IncludeWindowsHeaders.h" #include #include +#include #include #include #if CRASH_LOG_ENABLE @@ -27,16 +29,38 @@ const Char* WindowsPlatform::ApplicationWindowClass = TEXT("FlaxWindow"); void* WindowsPlatform::Instance = nullptr; +#if CRASH_LOG_ENABLE || TRACY_ENABLE +// Lock for symbols list, shared with Tracy +extern "C" { +static HANDLE dbgHelpLock; + +void DbgHelpInit() +{ + dbgHelpLock = CreateMutexW(nullptr, FALSE, nullptr); +} + +void DbgHelpLock() +{ + WaitForSingleObject(dbgHelpLock, INFINITE); +} + +void DbgHelpUnlock() +{ + ReleaseMutex(dbgHelpLock); +} +} +#endif + namespace { - String UserLocale, ComputerName, UserName; + String UserLocale, ComputerName, UserName, WindowsName; HANDLE EngineMutex = nullptr; Rectangle VirtualScreenBounds = Rectangle(0.0f, 0.0f, 0.0f, 0.0f); int32 VersionMajor = 0; int32 VersionMinor = 0; + int32 VersionBuild = 0; int32 SystemDpi = 96; #if CRASH_LOG_ENABLE - CriticalSection SymLocker; #if TRACY_ENABLE bool SymInitialized = true; #else @@ -125,6 +149,102 @@ LONG GetDWORDRegKey(HKEY hKey, const Char* strValueName, DWORD& nValue, DWORD nD return nError; } +void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionMinor, int32& versionBuild) +{ + // Get OS version + + HKEY hKey; + LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &hKey); + if (lRes == ERROR_SUCCESS) + { + GetStringRegKey(hKey, TEXT("ProductName"), windowsName, TEXT("Windows")); + + DWORD currentMajorVersionNumber; + DWORD currentMinorVersionNumber; + String currentBuildNumber; + GetDWORDRegKey(hKey, TEXT("CurrentMajorVersionNumber"), currentMajorVersionNumber, 0); + GetDWORDRegKey(hKey, TEXT("CurrentMinorVersionNumber"), currentMinorVersionNumber, 0); + GetStringRegKey(hKey, TEXT("CurrentBuildNumber"), currentBuildNumber, TEXT("0")); + VersionMajor = currentMajorVersionNumber; + VersionMinor = currentMinorVersionNumber; + StringUtils::Parse(currentBuildNumber.Get(), &VersionBuild); + + if (StringUtils::Compare(windowsName.Get(), TEXT("Windows 7"), 9) == 0) + { + VersionMajor = 6; + VersionMinor = 2; + } + + if (VersionMajor == 0 && VersionMinor == 0) + { + String windowsVersion; + GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); + + if (windowsVersion.HasChars()) + { + const int32 dot = windowsVersion.Find('.'); + if (dot != -1) + { + StringUtils::Parse(windowsVersion.Substring(0, dot).Get(), &VersionMajor); + StringUtils::Parse(windowsVersion.Substring(dot + 1).Get(), &VersionMinor); + } + } + } + } + else + { + if (IsWindowsServer()) + { + windowsName = TEXT("Windows Server"); + versionMajor = 6; + versionMinor = 3; + } + else if (IsWindows8Point1OrGreater()) + { + windowsName = TEXT("Windows 8.1"); + versionMajor = 6; + versionMinor = 3; + } + else if (IsWindows8OrGreater()) + { + windowsName = TEXT("Windows 8"); + versionMajor = 6; + versionMinor = 2; + } + else if (IsWindows7SP1OrGreater()) + { + windowsName = TEXT("Windows 7 SP1"); + versionMajor = 6; + versionMinor = 2; + } + else if (IsWindows7OrGreater()) + { + windowsName = TEXT("Windows 7"); + versionMajor = 6; + versionMinor = 1; + } + else if (IsWindowsVistaSP2OrGreater()) + { + windowsName = TEXT("Windows Vista SP2"); + versionMajor = 6; + versionMinor = 1; + } + else if (IsWindowsVistaSP1OrGreater()) + { + windowsName = TEXT("Windows Vista SP1"); + versionMajor = 6; + versionMinor = 1; + } + else if (IsWindowsVistaOrGreater()) + { + windowsName = TEXT("Windows Vista"); + versionMajor = 6; + versionMinor = 0; + } + } + RegCloseKey(hKey); +} + LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Find window to process that message @@ -402,7 +522,7 @@ void WindowsPlatform::PreInit(void* hInstance) #if CRASH_LOG_ENABLE TCHAR buffer[MAX_PATH] = { 0 }; - SymLocker.Lock(); + DbgHelpLock(); if (::GetModuleFileNameW(::GetModuleHandleW(nullptr), buffer, MAX_PATH)) SymbolsPath.Add(StringUtils::GetDirectoryName(buffer)); if (::GetEnvironmentVariableW(TEXT("_NT_SYMBOL_PATH"), buffer, MAX_PATH)) @@ -411,8 +531,17 @@ void WindowsPlatform::PreInit(void* hInstance) options |= SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS; SymSetOptions(options); OnSymbolsPathModified(); - SymLocker.Unlock(); + DbgHelpUnlock(); #endif + + GetWindowsVersion(WindowsName, VersionMajor, VersionMinor, VersionBuild); + + // Validate platform + if (VersionMajor < 6) + { + Error(TEXT("Not supported operating system version.")); + exit(-1); + } } bool WindowsPlatform::IsWindows10() @@ -472,6 +601,12 @@ bool WindowsPlatform::Init() return true; } + // Set lowest possible timer resolution for previous Windows versions + if (VersionMajor < 10 || (VersionMajor == 10 && VersionBuild < 17134)) + { + timeBeginPeriod(1); + } + DWORD tmp; Char buffer[256]; @@ -502,105 +637,7 @@ void WindowsPlatform::LogInfo() { Win32Platform::LogInfo(); - // Get OS version - { - String windowsName; - HKEY hKey; - LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &hKey); - if (lRes == ERROR_SUCCESS) - { - GetStringRegKey(hKey, TEXT("ProductName"), windowsName, TEXT("Windows")); - - DWORD currentMajorVersionNumber; - DWORD currentMinorVersionNumber; - GetDWORDRegKey(hKey, TEXT("CurrentMajorVersionNumber"), currentMajorVersionNumber, 0); - GetDWORDRegKey(hKey, TEXT("CurrentMinorVersionNumber"), currentMinorVersionNumber, 0); - VersionMajor = currentMajorVersionNumber; - VersionMinor = currentMinorVersionNumber; - - if (StringUtils::Compare(windowsName.Get(), TEXT("Windows 7"), 9) == 0) - { - VersionMajor = 6; - VersionMinor = 2; - } - - if (VersionMajor == 0 && VersionMinor == 0) - { - String windowsVersion; - GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); - - if (windowsVersion.HasChars()) - { - const int32 dot = windowsVersion.Find('.'); - if (dot != -1) - { - StringUtils::Parse(windowsVersion.Substring(0, dot).Get(), &VersionMajor); - StringUtils::Parse(windowsVersion.Substring(dot + 1).Get(), &VersionMinor); - } - } - } - } - else - { - if (IsWindowsServer()) - { - windowsName = TEXT("Windows Server"); - VersionMajor = 6; - VersionMinor = 3; - } - else if (IsWindows8Point1OrGreater()) - { - windowsName = TEXT("Windows 8.1"); - VersionMajor = 6; - VersionMinor = 3; - } - else if (IsWindows8OrGreater()) - { - windowsName = TEXT("Windows 8"); - VersionMajor = 6; - VersionMinor = 2; - } - else if (IsWindows7SP1OrGreater()) - { - windowsName = TEXT("Windows 7 SP1"); - VersionMajor = 6; - VersionMinor = 2; - } - else if (IsWindows7OrGreater()) - { - windowsName = TEXT("Windows 7"); - VersionMajor = 6; - VersionMinor = 1; - } - else if (IsWindowsVistaSP2OrGreater()) - { - windowsName = TEXT("Windows Vista SP2"); - VersionMajor = 6; - VersionMinor = 1; - } - else if (IsWindowsVistaSP1OrGreater()) - { - windowsName = TEXT("Windows Vista SP1"); - VersionMajor = 6; - VersionMinor = 1; - } - else if (IsWindowsVistaOrGreater()) - { - windowsName = TEXT("Windows Vista"); - VersionMajor = 6; - VersionMinor = 0; - } - } - RegCloseKey(hKey); - LOG(Info, "Microsoft {0} {1}-bit ({2}.{3})", windowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor); - } - - // Validate platform - if (VersionMajor < 6) - { - LOG(Error, "Not supported operating system version."); - exit(0); - } + LOG(Info, "Microsoft {0} {1}-bit ({2}.{3}.{4})", WindowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor, VersionBuild); // Check minimum amount of RAM auto memStats = Platform::GetMemoryStats(); @@ -638,7 +675,7 @@ void WindowsPlatform::BeforeExit() void WindowsPlatform::Exit() { #if CRASH_LOG_ENABLE - SymLocker.Lock(); + DbgHelpLock(); #if !TRACY_ENABLE if (SymInitialized) { @@ -647,7 +684,7 @@ void WindowsPlatform::Exit() } #endif SymbolsPath.Resize(0); - SymLocker.Unlock(); + DbgHelpUnlock(); #endif // Unregister app class @@ -1118,6 +1155,19 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) { ASSERT(filename); + // Add folder to search path to load dependency libraries + StringView folder = StringUtils::GetDirectoryName(filename); + if (folder.HasChars() && FileSystem::IsRelative(folder)) + folder = StringView::Empty; + if (folder.HasChars()) + { + Char& end = ((Char*)folder.Get())[folder.Length()]; + const Char c = end; + end = 0; + SetDllDirectoryW(*folder); + end = c; + } + // Avoiding windows dialog boxes if missing const DWORD errorMode = SEM_NOOPENFILEERRORBOX; DWORD prevErrorMode = 0; @@ -1134,17 +1184,20 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) { SetThreadErrorMode(prevErrorMode, nullptr); } + if (folder.HasChars()) + { + SetDllDirectoryW(nullptr); + } #if CRASH_LOG_ENABLE // Refresh modules info during next stack trace collecting to have valid debug symbols information - SymLocker.Lock(); - const auto folder = StringUtils::GetDirectoryName(filename); - if (!SymbolsPath.Contains(folder)) + DbgHelpLock(); + if (folder.HasChars() && !SymbolsPath.Contains(folder)) { SymbolsPath.Add(folder); OnSymbolsPathModified(); } - SymLocker.Unlock(); + DbgHelpUnlock(); #endif return handle; @@ -1154,7 +1207,7 @@ Array WindowsPlatform::GetStackFrames(int32 skipCount, { Array result; #if CRASH_LOG_ENABLE - SymLocker.Lock(); + DbgHelpLock(); // Initialize HANDLE process = GetCurrentProcess(); @@ -1278,7 +1331,7 @@ Array WindowsPlatform::GetStackFrames(int32 skipCount, } } - SymLocker.Unlock(); + DbgHelpUnlock(); #endif return result; } diff --git a/Source/Engine/Profiler/ProfilerCPU.cpp b/Source/Engine/Profiler/ProfilerCPU.cpp index 2119a25d7..e3b1caad4 100644 --- a/Source/Engine/Profiler/ProfilerCPU.cpp +++ b/Source/Engine/Profiler/ProfilerCPU.cpp @@ -12,7 +12,7 @@ bool ProfilerCPU::Enabled = false; ProfilerCPU::EventBuffer::EventBuffer() { - _capacity = Math::RoundUpToPowerOf2(10 * 1000); + _capacity = 8192; _capacityMask = _capacity - 1; _data = NewArray(_capacity); _head = 0; @@ -122,6 +122,14 @@ void ProfilerCPU::Thread::EndEvent(int32 index) e.End = time; } +void ProfilerCPU::Thread::EndEvent() +{ + const double time = Platform::GetTimeSeconds() * 1000.0; + _depth--; + Event& e = Buffer.Get(Buffer.GetCount() - 1); + e.End = time; +} + bool ProfilerCPU::IsProfilingCurrentThread() { return Enabled && Thread::Current != nullptr; @@ -194,11 +202,14 @@ int32 ProfilerCPU::BeginEvent(const char* name) void ProfilerCPU::EndEvent(int32 index) { - if (!Enabled) - return; + if (Enabled && Thread::Current) + Thread::Current->EndEvent(index); +} - ASSERT(Thread::Current); - Thread::Current->EndEvent(index); +void ProfilerCPU::EndEvent() +{ + if (Enabled && Thread::Current) + Thread::Current->EndEvent(); } void ProfilerCPU::Dispose() diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index feccdd79b..2a48e6c15 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -289,6 +289,11 @@ public: /// /// The event index returned by the BeginEvent method. void EndEvent(int32 index); + + /// + /// Ends the last event running on a this thread. + /// + void EndEvent(); }; public: @@ -341,6 +346,11 @@ public: /// The event index returned by the BeginEvent method. static void EndEvent(int32 index); + /// + /// Ends the last event. + /// + static void EndEvent(); + /// /// Releases resources. Calls to the profiling API after Dispose are not valid. /// diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp index 4afab6c04..daedf2a61 100644 --- a/Source/Engine/Profiler/ProfilingTools.cpp +++ b/Source/Engine/Profiler/ProfilingTools.cpp @@ -23,7 +23,7 @@ public: } void Update() override; - void BeforeExit() override; + void Dispose() override; }; ProfilingToolsService ProfilingToolsServiceInstance; @@ -166,7 +166,7 @@ void ProfilingToolsService::Update() #endif } -void ProfilingToolsService::BeforeExit() +void ProfilingToolsService::Dispose() { ProfilingTools::EventsCPU.Clear(); ProfilingTools::EventsCPU.SetCapacity(0); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 537c604d9..26b652de8 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -440,5 +440,5 @@ void Font::FlushFaceSize() const String Font::ToString() const { - return String::Format(TEXT("Font {0} {1}"), _asset->GetFamilyName(), _size); + return String::Format(TEXT("Font {0} {1}"), _asset ? _asset->GetFamilyName() : String::Empty, _size); } diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index a8e16f715..79ab045da 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -109,7 +109,7 @@ void FontManagerService::Dispose() FontTextureAtlas* FontManager::GetAtlas(int32 index) { - return index >= 0 && index < Atlases.Count() ? Atlases.Get()[index] : nullptr; + return index >= 0 && index < Atlases.Count() ? Atlases.Get()[index].Get() : nullptr; } bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) diff --git a/Source/Engine/Render2D/FontReference.cs b/Source/Engine/Render2D/FontReference.cs index 0d725a5a6..22b0e02c8 100644 --- a/Source/Engine/Render2D/FontReference.cs +++ b/Source/Engine/Render2D/FontReference.cs @@ -7,7 +7,7 @@ namespace FlaxEngine /// /// Font reference that defines the font asset and font size to use. /// - public struct FontReference + public class FontReference { [NoSerialize] private FontAsset _font; @@ -18,6 +18,16 @@ namespace FlaxEngine [NoSerialize] private Font _cachedFont; + /// + /// Initializes a new instance of the struct. + /// + public FontReference() + { + _font = null; + _size = 30; + _cachedFont = null; + } + /// /// Initializes a new instance of the struct. /// @@ -36,8 +46,16 @@ namespace FlaxEngine /// The font. public FontReference(Font font) { - _font = font?.Asset; - _size = font?.Size ?? 30; + if (font) + { + _font = font.Asset; + _size = font.Size; + } + else + { + _font = null; + _size = 30; + } _cachedFont = font; } @@ -53,9 +71,7 @@ namespace FlaxEngine if (_font != value) { _font = value; - - if (_cachedFont) - _cachedFont = null; + _cachedFont = null; } } } @@ -72,9 +88,7 @@ namespace FlaxEngine if (_size != value) { _size = value; - - if (_cachedFont) - _cachedFont = null; + _cachedFont = null; } } } @@ -85,20 +99,22 @@ namespace FlaxEngine /// Th font or null if descriptor is invalid. public Font GetFont() { - return _cachedFont ?? (_cachedFont = _font?.CreateFont(_size)); + if (_cachedFont) + return _cachedFont; + if (_font) + _cachedFont = _font.CreateFont(_size); + return _cachedFont; } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// + /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(ref FontReference other) + public bool Equals(FontReference other) { - return _font == other._font && _size == other._size; + return !(other is null) && _font == other._font && _size == other._size; } /// @@ -109,7 +125,9 @@ namespace FlaxEngine /// True if font references are equal, otherwise false. public static bool operator ==(FontReference lhs, FontReference rhs) { - return lhs.Equals(ref rhs); + if (lhs is null) + return rhs is null; + return lhs.Equals(rhs); } /// @@ -120,7 +138,9 @@ namespace FlaxEngine /// True if font references are not equal, otherwise false. public static bool operator !=(FontReference lhs, FontReference rhs) { - return !lhs.Equals(ref rhs); + if (lhs is null) + return !(rhs is null); + return !lhs.Equals(rhs); } /// @@ -129,7 +149,7 @@ namespace FlaxEngine if (!(other is FontReference)) return false; var fontReference = (FontReference)other; - return Equals(ref fontReference); + return Equals(fontReference); } /// @@ -137,7 +157,7 @@ namespace FlaxEngine { unchecked { - int hashCode = _font.GetHashCode(); + int hashCode = _font ? _font.GetHashCode() : 0; hashCode = (hashCode * 397) ^ _size; return hashCode; } diff --git a/Source/Engine/Render2D/FontTextureAtlas.h b/Source/Engine/Render2D/FontTextureAtlas.h index 5f622ada0..f9cca5530 100644 --- a/Source/Engine/Render2D/FontTextureAtlas.h +++ b/Source/Engine/Render2D/FontTextureAtlas.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Math/Vector2.h" #include "Engine/Content/Assets/Texture.h" #include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Utilities/RectPack.h" @@ -91,7 +92,6 @@ public: /// /// Gets the atlas width. /// - /// The width. FORCE_INLINE uint32 GetWidth() const { return _width; @@ -100,7 +100,6 @@ public: /// /// Gets the atlas height. /// - /// The height. FORCE_INLINE uint32 GetHeight() const { return _height; @@ -109,7 +108,6 @@ public: /// /// Gets the atlas size. /// - /// The size. FORCE_INLINE Vector2 GetSize() const { return Vector2(static_cast(_width), static_cast(_height)); @@ -118,9 +116,6 @@ public: /// /// Determines whether this atlas is dirty and data need to be flushed. /// - /// - /// true if this atlas is dirty; otherwise, false. - /// FORCE_INLINE bool IsDirty() const { return _isDirty; @@ -129,7 +124,6 @@ public: /// /// Gets padding style for textures in the atlas. /// - /// The padding style. FORCE_INLINE PaddingStyle GetPaddingStyle() const { return _paddingStyle; @@ -138,7 +132,6 @@ public: /// /// Gets amount of pixels to pad textures inside an atlas. /// - /// The padding amount. uint32 GetPaddingAmount() const; public: diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index a7602dce1..281b05ebc 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -25,19 +25,14 @@ #include "Engine/Engine/EngineService.h" #if USE_EDITOR - -// TODO: maybe we could also provide some stack trace for that? #define RENDER2D_CHECK_RENDERING_STATE \ if (!Render2D::IsRendering()) \ { \ LOG(Error, "Calling Render2D is only valid during rendering."); \ return; \ } - #else - #define RENDER2D_CHECK_RENDERING_STATE - #endif #if USE_EDITOR @@ -353,6 +348,57 @@ FORCE_INLINE void WriteRect(const Rectangle& rect, const Color& color) WriteRect(rect, color, Vector2::Zero, Vector2::One); } +void Write9SlicingRect(const Rectangle& rect, const Color& color, const Vector4& border, const Vector4& borderUVs) +{ + const Rectangle upperLeft(rect.Location.X, rect.Location.Y, border.X, border.Z); + const Rectangle upperRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y, border.Y, border.Z); + const Rectangle bottomLeft(rect.Location.X, rect.Location.Y + rect.Size.Y - border.W, border.X, border.W); + const Rectangle bottomRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y + rect.Size.Y - border.W, border.Y, border.W); + + const Vector2 upperLeftUV(borderUVs.X, borderUVs.Z); + const Vector2 upperRightUV(1.0f - borderUVs.Y, borderUVs.Z); + const Vector2 bottomLeftUV(borderUVs.X, 1.0f - borderUVs.W); + const Vector2 bottomRightUV(1.0f - borderUVs.Y, 1.0f - borderUVs.W); + + WriteRect(upperLeft, color, Vector2::Zero, upperLeftUV); + WriteRect(upperRight, color, Vector2(upperRightUV.X, 0), Vector2(1, upperLeftUV.Y)); + WriteRect(bottomLeft, color, Vector2(0, bottomLeftUV.Y), Vector2(bottomLeftUV.X, 1)); + WriteRect(bottomRight, color, bottomRightUV, Vector2::One); + + WriteRect(Rectangle(upperLeft.GetUpperRight(), upperRight.GetBottomLeft() - upperLeft.GetUpperRight()), color, Vector2(upperLeftUV.X, 0), upperRightUV); + WriteRect(Rectangle(upperLeft.GetBottomLeft(), bottomLeft.GetUpperRight() - upperLeft.GetBottomLeft()), color, Vector2(0, upperLeftUV.Y), bottomLeftUV); + WriteRect(Rectangle(bottomLeft.GetUpperRight(), bottomRight.GetBottomLeft() - bottomLeft.GetUpperRight()), color, bottomLeftUV, Vector2(bottomRightUV.X, 1)); + WriteRect(Rectangle(upperRight.GetBottomLeft(), bottomRight.GetUpperRight() - upperRight.GetBottomLeft()), color, upperRightUV, Vector2(1, bottomRightUV.Y)); + + WriteRect(Rectangle(upperLeft.GetBottomRight(), bottomRight.GetUpperLeft() - upperLeft.GetBottomRight()), color, upperRightUV, bottomRightUV); +} + +void Write9SlicingRect(const Rectangle& rect, const Color& color, const Vector4& border, const Vector4& borderUVs, const Vector2& uvLocation, const Vector2& uvSize) +{ + const Rectangle upperLeft(rect.Location.X, rect.Location.Y, border.X, border.Z); + const Rectangle upperRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y, border.Y, border.Z); + const Rectangle bottomLeft(rect.Location.X, rect.Location.Y + rect.Size.Y - border.W, border.X, border.W); + const Rectangle bottomRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y + rect.Size.Y - border.W, border.Y, border.W); + + const Vector2 upperLeftUV = Vector2(borderUVs.X, borderUVs.Z) * uvSize + uvLocation; + const Vector2 upperRightUV = Vector2(1.0f - borderUVs.Y, borderUVs.Z) * uvSize + uvLocation; + const Vector2 bottomLeftUV = Vector2(borderUVs.X, 1.0f - borderUVs.W) * uvSize + uvLocation; + const Vector2 bottomRightUV = Vector2(1.0f - borderUVs.Y, 1.0f - borderUVs.W) * uvSize + uvLocation; + const Vector2 uvEnd = uvLocation + uvSize; + + WriteRect(upperLeft, color, uvLocation, upperLeftUV); + WriteRect(upperRight, color, Vector2(upperRightUV.X, uvLocation.Y), Vector2(uvEnd.X, upperLeftUV.Y)); + WriteRect(bottomLeft, color, Vector2(uvLocation.X, bottomLeftUV.Y), Vector2(bottomLeftUV.X, uvEnd.Y)); + WriteRect(bottomRight, color, bottomRightUV, uvEnd); + + WriteRect(Rectangle(upperLeft.GetUpperRight(), upperRight.GetBottomLeft() - upperLeft.GetUpperRight()), color, Vector2(upperLeftUV.X, uvLocation.Y), upperRightUV); + WriteRect(Rectangle(upperLeft.GetBottomLeft(), bottomLeft.GetUpperRight() - upperLeft.GetBottomLeft()), color, Vector2(uvLocation.X, upperLeftUV.Y), bottomLeftUV); + WriteRect(Rectangle(bottomLeft.GetUpperRight(), bottomRight.GetBottomLeft() - bottomLeft.GetUpperRight()), color, bottomLeftUV, Vector2(bottomRightUV.X, uvEnd.Y)); + WriteRect(Rectangle(upperRight.GetBottomLeft(), bottomRight.GetUpperRight() - upperRight.GetBottomLeft()), color, upperRightUV, Vector2(uvEnd.X, bottomRightUV.Y)); + + WriteRect(Rectangle(upperLeft.GetBottomRight(), bottomRight.GetUpperLeft() - upperLeft.GetBottomRight()), color, upperRightUV, bottomRightUV); +} + typedef bool (*CanDrawCallCallback)(const Render2DDrawCall&, const Render2DDrawCall&); bool CanDrawCallCallbackTrue(const Render2DDrawCall& d1, const Render2DDrawCall& d2) @@ -1548,7 +1594,7 @@ void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rec drawCall.Type = DrawCallType::FillTexture; drawCall.StartIB = IBIndex; drawCall.CountIB = 6; - drawCall.AsTexture.Ptr = spriteHandle.GetAtlasTexture(); + drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight()); } @@ -1575,10 +1621,66 @@ void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle drawCall.Type = DrawCallType::FillTexturePoint; drawCall.StartIB = IBIndex; drawCall.CountIB = 6; - drawCall.AsTexture.Ptr = spriteHandle.GetAtlasTexture(); + drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight()); } +void Render2D::Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color) +{ + RENDER2D_CHECK_RENDERING_STATE; + + Render2DDrawCall drawCall; + drawCall.Type = DrawCallType::FillTexture; + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6 * 9; + drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr; + DrawCalls.Add(drawCall); + Write9SlicingRect(rect, color, border, borderUVs); +} + +void Render2D::Draw9SlicingTexturePoint(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color) +{ + RENDER2D_CHECK_RENDERING_STATE; + + Render2DDrawCall drawCall; + drawCall.Type = DrawCallType::FillTexturePoint; + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6 * 9; + drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr; + DrawCalls.Add(drawCall); + Write9SlicingRect(rect, color, border, borderUVs); +} + +void Render2D::Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color) +{ + RENDER2D_CHECK_RENDERING_STATE; + if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) + return; + + Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); + Render2DDrawCall& drawCall = DrawCalls.AddOne(); + drawCall.Type = DrawCallType::FillTexture; + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6 * 9; + drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); + Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size); +} + +void Render2D::Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color) +{ + RENDER2D_CHECK_RENDERING_STATE; + if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip()) + return; + + Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index); + Render2DDrawCall& drawCall = DrawCalls.AddOne(); + drawCall.Type = DrawCallType::FillTexturePoint; + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6 * 9; + drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture(); + Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size); +} + void Render2D::DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState* ps, const Color& color) { RENDER2D_CHECK_RENDERING_STATE; diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 36a5bcfa2..f1c9c816b 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -27,7 +27,6 @@ class TextureBase; API_CLASS(Static) class FLAXENGINE_API Render2D { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Render2D); -public: /// /// The rendering features and options flags. @@ -50,13 +49,11 @@ public: /// /// Checks if interface is during rendering phrase (Draw calls may be performed without failing). /// - /// True if is during rendering, otherwise false/ static bool IsRendering(); /// /// Gets the current rendering viewport. /// - /// The viewport static const Viewport& GetViewport(); /// @@ -64,8 +61,6 @@ public: /// API_FIELD() static RenderingFeatures Features; -public: - /// /// Called when frame rendering begins by the graphics device. /// @@ -302,6 +297,46 @@ public: /// The color to multiply all texture pixels. API_FUNCTION() static void DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color = Color::White); + /// + /// Draws the texture using 9-slicing. + /// + /// The texture to draw. + /// The rectangle to draw. + /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom). + /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom). + /// The color to multiply all texture pixels. + API_FUNCTION() static void Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White); + + /// + /// Draws the texture using 9-slicing (uses point sampler). + /// + /// The texture to draw. + /// The rectangle to draw. + /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom). + /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom). + /// The color to multiply all texture pixels. + API_FUNCTION() static void Draw9SlicingTexturePoint(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White); + + /// + /// Draws a sprite using 9-slicing. + /// + /// The sprite to draw. + /// The rectangle to draw. + /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom). + /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom). + /// The color to multiply all texture pixels. + API_FUNCTION() static void Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White); + + /// + /// Draws a sprite using 9-slicing (uses point sampler). + /// + /// The sprite to draw. + /// The rectangle to draw. + /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom). + /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom). + /// The color to multiply all texture pixels. + API_FUNCTION() static void Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White); + /// /// Performs custom rendering. /// diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index d084a6a67..3affcdfda 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -38,9 +38,6 @@ bool DepthOfFieldPass::Init() _platformSupportsDoF = limits.HasCompute; _platformSupportsBokeh = _platformSupportsDoF && limits.HasGeometryShaders && limits.HasDrawIndirect && limits.HasAppendConsumeBuffers; - _platformSupportsBokeh &= GPUDevice::Instance->GetRendererType() != RendererType::DirectX12; // TODO: fix bokeh crash on d3d12 (driver issue probably - started to happen recently) - _platformSupportsBokeh &= GPUDevice::Instance->GetRendererType() != RendererType::Vulkan; // TODO: add bokeh on Vulkan (draw indirect with UA output from PS) - // Create pipeline states if (_platformSupportsDoF) { diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 64219ef9c..b44f3f452 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -100,26 +100,28 @@ bool LightPass::setupResources() psDesc = GPUPipelineState::Description::DefaultNoDepth; psDesc.BlendMode = BlendingMode::Add; psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; - psDesc.CullMode = CullMode::Normal; psDesc.VS = shader->GetVS("VS_Model"); - if (_psLightPointNormal.Create(psDesc, shader, "PS_Point")) - return true; psDesc.CullMode = CullMode::Inverted; if (_psLightPointInverted.Create(psDesc, shader, "PS_Point")) return true; + psDesc.CullMode = CullMode::Normal; + psDesc.DepthTestEnable = true; + if (_psLightPointNormal.Create(psDesc, shader, "PS_Point")) + return true; } if (!_psLightSpotNormal.IsValid() || !_psLightSpotInverted.IsValid()) { psDesc = GPUPipelineState::Description::DefaultNoDepth; psDesc.BlendMode = BlendingMode::Add; psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; - psDesc.CullMode = CullMode::Normal; psDesc.VS = shader->GetVS("VS_Model"); - if (_psLightSpotNormal.Create(psDesc, shader, "PS_Spot")) - return true; psDesc.CullMode = CullMode::Inverted; if (_psLightSpotInverted.Create(psDesc, shader, "PS_Spot")) return true; + psDesc.CullMode = CullMode::Normal; + psDesc.DepthTestEnable = true; + if (_psLightSpotNormal.Create(psDesc, shader, "PS_Spot")) + return true; } if (!_psLightSkyNormal->IsValid() || !_psLightSkyInverted->IsValid()) { @@ -203,7 +205,11 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB PerFrame perFrame; // Bind output - context->SetRenderTarget(lightBuffer); + GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; + const bool depthBufferReadOnly = depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView; + GPUTextureView* depthBufferRTV = depthBufferReadOnly ? depthBuffer->ViewReadOnlyDepth() : nullptr; + GPUTextureView* depthBufferSRV = depthBufferReadOnly ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); + context->SetRenderTarget(depthBufferRTV, lightBuffer); // Set per frame data GBufferPass::SetInputs(renderContext.View, perFrame.GBuffer); @@ -221,7 +227,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB context->BindSR(0, renderContext.Buffers->GBuffer0); context->BindSR(1, renderContext.Buffers->GBuffer1); context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, renderContext.Buffers->DepthBuffer); + context->BindSR(3, depthBufferSRV); context->BindSR(4, renderContext.Buffers->GBuffer3); // Check if debug lights @@ -270,7 +276,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB ShadowsPass::Instance()->RenderShadow(renderContext, light, shadowMaskView); // Bind output - context->SetRenderTarget(lightBuffer); + context->SetRenderTarget(depthBufferRTV, lightBuffer); // Set shadow mask context->BindSR(5, shadowMaskView); @@ -325,7 +331,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB ShadowsPass::Instance()->RenderShadow(renderContext, light, shadowMaskView); // Bind output - context->SetRenderTarget(lightBuffer); + context->SetRenderTarget(depthBufferRTV, lightBuffer); // Set shadow mask context->BindSR(5, shadowMaskView); @@ -366,7 +372,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB ShadowsPass::Instance()->RenderShadow(renderContext, light, lightIndex, shadowMaskView); // Bind output - context->SetRenderTarget(lightBuffer); + context->SetRenderTarget(depthBufferRTV, lightBuffer); // Set shadow mask context->BindSR(5, shadowMaskView); diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 0a078b4b7..504416685 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -276,7 +276,7 @@ bool ProbesRenderer::Init() } // Init rendering pipeline - _output = GPUTexture::New(); + _output = GPUDevice::Instance->CreateTexture(TEXT("Output")); if (_output->Init(GPUTextureDescription::New2D(ENV_PROBES_RESOLUTION, ENV_PROBES_RESOLUTION, ENV_PROBES_FORMAT))) return true; _task = New(); diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 3f724dfaa..319e9f09a 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -314,11 +314,13 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererPointLightD context->ResetSR(); context->ResetRenderTarget(); const Viewport viewport = renderContext.Task->GetViewport(); + GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; + GPUTextureView* depthBufferSRV = depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); context->SetViewportAndScissors(viewport); context->BindSR(0, renderContext.Buffers->GBuffer0); context->BindSR(1, renderContext.Buffers->GBuffer1); context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, renderContext.Buffers->DepthBuffer); + context->BindSR(3, depthBufferSRV); context->BindSR(4, renderContext.Buffers->GBuffer3); // Setup shader data @@ -414,11 +416,13 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererSpotLightDa context->ResetSR(); context->ResetRenderTarget(); const Viewport viewport = renderContext.Task->GetViewport(); + GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; + GPUTextureView* depthBufferSRV = depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); context->SetViewportAndScissors(viewport); context->BindSR(0, renderContext.Buffers->GBuffer0); context->BindSR(1, renderContext.Buffers->GBuffer1); context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, renderContext.Buffers->DepthBuffer); + context->BindSR(3, depthBufferSRV); context->BindSR(4, renderContext.Buffers->GBuffer3); // Setup shader data @@ -688,11 +692,13 @@ void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererDirectional context->ResetSR(); context->ResetRenderTarget(); const Viewport viewport = renderContext.Task->GetViewport(); + GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; + GPUTextureView* depthBufferSRV = depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); context->SetViewportAndScissors(viewport); context->BindSR(0, renderContext.Buffers->GBuffer0); context->BindSR(1, renderContext.Buffers->GBuffer1); context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, renderContext.Buffers->DepthBuffer); + context->BindSR(3, depthBufferSRV); context->BindSR(4, renderContext.Buffers->GBuffer3); // Setup shader data diff --git a/Source/Engine/Renderer/Utils/MultiScaler.cpp b/Source/Engine/Renderer/Utils/MultiScaler.cpp index f38edab35..54995a9d4 100644 --- a/Source/Engine/Renderer/Utils/MultiScaler.cpp +++ b/Source/Engine/Renderer/Utils/MultiScaler.cpp @@ -182,8 +182,8 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 // Prepare Data data; - data.TexelSize.X = 1.0f / width; - data.TexelSize.Y = 1.0f / height; + data.TexelSize.X = 1.0f / (float)width; + data.TexelSize.Y = 1.0f / (float)height; auto cb = _shader->GetShader()->GetCB(0); context->UpdateCB(cb, &data); context->BindCB(0, cb); @@ -219,8 +219,8 @@ void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstH // Prepare Data data; - data.TexelSize.X = 2.0f / dstWidth; - data.TexelSize.Y = 2.0f / dstHeight; + data.TexelSize.X = 2.0f / (float)dstWidth; + data.TexelSize.Y = 2.0f / (float)dstHeight; auto cb = _shader->GetShader()->GetCB(0); context->UpdateCB(cb, &data); context->BindCB(0, cb); diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index 78781d3f9..8094b35aa 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -34,5 +34,10 @@ namespace FlaxEngine /// The spacing amount between collection items in the UI. /// public float Spacing; + + /// + /// The collection background color. + /// + public Color? BackgroundColor; } } diff --git a/Source/Engine/Scripting/Attributes/Editor/CategoryAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/CategoryAttribute.cs new file mode 100644 index 000000000..e5f4f9794 --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/CategoryAttribute.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Describes the category name for a type. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] + public sealed class CategoryAttribute : Attribute + { + /// + /// The category name. + /// + public string Name; + + /// + /// Initializes a new instance of the class. + /// + /// The category name. + public CategoryAttribute(string name) + { + Name = name; + } + } +} diff --git a/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp b/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp index 144eb6c86..814b20dcd 100644 --- a/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp +++ b/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp @@ -8,9 +8,13 @@ namespace UtilsInternal { - void MemoryCopy(void* source, void* destination, int32 length) + MonoObject* ExtractArrayFromList(MonoObject* obj) { - Platform::MemoryCopy(destination, source, length); + auto klass = mono_object_get_class(obj); + auto field = mono_class_get_field_from_name(klass, "_items"); + MonoObject* o; + mono_field_get_value(obj, field, &o); + return o; } } @@ -69,7 +73,10 @@ namespace FlaxLogWriterInternal void registerFlaxEngineInternalCalls() { AnimGraphExecutor::initRuntime(); - ADD_INTERNAL_CALL("FlaxEngine.Utils::MemoryCopy", &UtilsInternal::MemoryCopy); + ADD_INTERNAL_CALL("FlaxEngine.Utils::MemoryCopy", &Platform::MemoryCopy); + ADD_INTERNAL_CALL("FlaxEngine.Utils::MemoryClear", &Platform::MemoryClear); + ADD_INTERNAL_CALL("FlaxEngine.Utils::MemoryCompare", &Platform::MemoryCompare); + ADD_INTERNAL_CALL("FlaxEngine.Utils::Internal_ExtractArrayFromList", &UtilsInternal::ExtractArrayFromList); ADD_INTERNAL_CALL("FlaxEngine.DebugLogHandler::Internal_LogWrite", &DebugLogHandlerInternal::LogWrite); ADD_INTERNAL_CALL("FlaxEngine.DebugLogHandler::Internal_Log", &DebugLogHandlerInternal::Log); ADD_INTERNAL_CALL("FlaxEngine.DebugLogHandler::Internal_LogException", &DebugLogHandlerInternal::LogException); diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp index 21551afc0..b02580ab8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MClass.Mono.cpp @@ -80,11 +80,6 @@ MClass::~MClass() _events.ClearDelete(); } -String MClass::ToString() const -{ - return String(_fullname); -} - MonoClass* MClass::GetNative() const { return _monoClass; diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h index a8d083d7b..e4d0ba74c 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.h +++ b/Source/Engine/Scripting/ManagedCLR/MClass.h @@ -73,12 +73,6 @@ public: return _fullname; } - /// - /// Gets the class full name as string. - /// - /// The class full name. - String ToString() const; - #if USE_MONO /// diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp index 64abca055..5e4703d2d 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.Mono.cpp @@ -169,18 +169,20 @@ void OnGCAllocation(MonoProfiler* profiler, MonoObject* obj) //LOG(Info, "GC new: {0}.{1} ({2} bytes)", name_space, name, size); #if 0 - if (ProfilerCPU::IsProfilingCurrentThread()) - { - static int details = 0; - if (details) - { - StackWalkDataResult stackTrace; - stackTrace.Buffer.SetCapacity(1024); - mono_stack_walk(&OnStackWalk, &stackTrace); + if (ProfilerCPU::IsProfilingCurrentThread()) + { + static int details = 0; + if (details) + { + StackWalkDataResult stackTrace; + stackTrace.Buffer.SetCapacity(1024); + mono_stack_walk(&OnStackWalk, &stackTrace); - LOG(Info, "GC new: {0}.{1} ({2} bytes). Stack Trace:\n{3}", String(name_space), String(name), size, stackTrace.Buffer.ToStringView()); - } - } + const auto msg = String::Format(TEXT("GC new: {0}.{1} ({2} bytes). Stack Trace:\n{3}"), String(name_space), String(name), size, stackTrace.Buffer.ToStringView()); + Platform::Log(*msg); + //LOG_STR(Info, msg); + } + } #endif #if COMPILE_WITH_PROFILER diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index 79e578401..781dc161e 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -14,29 +14,21 @@ namespace ProfilerInternal { - /// - /// The managed events IDs. - /// - Array ManagedEvents; - - /// - /// The managed events IDs for GPU profiling. - /// +#if COMPILE_WITH_PROFILER Array ManagedEventsGPU; +#endif void BeginEvent(MonoString* nameObj) { #if COMPILE_WITH_PROFILER - const auto index = ProfilerCPU::BeginEvent((const Char*)mono_string_chars(nameObj)); - ManagedEvents.Push(index); + ProfilerCPU::BeginEvent((const Char*)mono_string_chars(nameObj)); #endif } void EndEvent() { #if COMPILE_WITH_PROFILER - const auto index = ManagedEvents.Pop(); - ProfilerCPU::EndEvent(index); + ProfilerCPU::EndEvent(); #endif } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 37a009ebc..55a60f799 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -639,9 +639,7 @@ MClass* Scripting::FindClass(MonoClass* monoClass) { if (monoClass == nullptr) return nullptr; - PROFILE_CPU(); - auto& modules = BinaryModule::GetModules(); for (auto module : modules) { @@ -653,7 +651,6 @@ MClass* Scripting::FindClass(MonoClass* monoClass) return result; } } - return nullptr; } @@ -661,9 +658,7 @@ MClass* Scripting::FindClass(const StringAnsiView& fullname) { if (fullname.IsEmpty()) return nullptr; - PROFILE_CPU(); - auto& modules = BinaryModule::GetModules(); for (auto module : modules) { @@ -675,7 +670,6 @@ MClass* Scripting::FindClass(const StringAnsiView& fullname) return result; } } - return nullptr; } @@ -683,9 +677,7 @@ MonoClass* Scripting::FindClassNative(const StringAnsiView& fullname) { if (fullname.IsEmpty()) return nullptr; - PROFILE_CPU(); - auto& modules = BinaryModule::GetModules(); for (auto module : modules) { @@ -697,7 +689,6 @@ MonoClass* Scripting::FindClassNative(const StringAnsiView& fullname) return result->GetNative(); } } - return nullptr; } @@ -705,9 +696,7 @@ ScriptingTypeHandle Scripting::FindScriptingType(const StringAnsiView& fullname) { if (fullname.IsEmpty()) return ScriptingTypeHandle(); - PROFILE_CPU(); - auto& modules = BinaryModule::GetModules(); for (auto module : modules) { @@ -717,7 +706,6 @@ ScriptingTypeHandle Scripting::FindScriptingType(const StringAnsiView& fullname) return ScriptingTypeHandle(module, typeIndex); } } - return ScriptingTypeHandle(); } @@ -865,7 +853,7 @@ bool Scripting::IsEveryAssemblyLoaded() bool Scripting::IsTypeFromGameScripts(MClass* type) { - const auto binaryModule = ManagedBinaryModule::GetModule(type->GetAssembly()); + const auto binaryModule = ManagedBinaryModule::GetModule(type ? type->GetAssembly() : nullptr); return binaryModule && binaryModule != GetBinaryModuleCorlib() && binaryModule != GetBinaryModuleFlaxEngine(); } diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index ae7821526..4d79324f9 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -221,6 +221,7 @@ namespace FlaxEngine TextBoxBackground = Color.FromBgra(0xFF333337), ProgressNormal = Color.FromBgra(0xFF0ad328), TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), + CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), SharedTooltip = new Tooltip(), }; style.DragWindow = style.BackgroundSelected * 0.7f; diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index 98fddfc7f..fcdd3746c 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -91,21 +91,21 @@ public: /// /// Finds the class with given fully qualified name within whole assembly. /// - /// The full name of the type eg: System.Int64.MaxInt. + /// The full name of the type eg: System.Int64. /// The MClass object or null if missing. static MClass* FindClass(const StringAnsiView& fullname); /// /// Finds the native class with given fully qualified name within whole assembly. /// - /// The full name of the type eg: System.Int64.MaxInt. + /// The full name of the type eg: System.Int64. /// The MClass object or null if missing. static MonoClass* FindClassNative(const StringAnsiView& fullname); /// /// Finds the scripting type of the given fullname by searching loaded scripting assemblies. /// - /// The full name of the type eg: System.Int64.MaxInt. + /// The full name of the type eg: System.Int64. /// The scripting type or invalid type if missing. static ScriptingTypeHandle FindScriptingType(const StringAnsiView& fullname); diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index dc9e27865..a66b8a103 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -53,6 +53,17 @@ MonoObject* ScriptingObject::GetManagedInstance() const return _gcHandle ? mono_gchandle_get_target(_gcHandle) : nullptr; } +MonoObject* ScriptingObject::GetOrCreateManagedInstance() const +{ + MonoObject* managedInstance = GetManagedInstance(); + if (!managedInstance) + { + const_cast(this)->CreateManaged(); + managedInstance = GetManagedInstance(); + } + return managedInstance; +} + MClass* ScriptingObject::GetClass() const { return _type ? _type.GetType().ManagedClass : nullptr; @@ -248,7 +259,7 @@ void ScriptingObject::OnDeleteObject() String ScriptingObject::ToString() const { - return _type ? _type.GetType().ManagedClass->ToString() : String::Empty; + return _type ? String(_type.GetType().ManagedClass->GetFullName()) : String::Empty; } ManagedScriptingObject::ManagedScriptingObject(const SpawnParams& params) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 9eb375fc5..1d2365d64 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -68,22 +68,11 @@ public: /// /// Gets the managed instance object or creates it if missing. /// - /// The Mono managed object. - FORCE_INLINE MonoObject* GetOrCreateManagedInstance() const - { - MonoObject* managedInstance = GetManagedInstance(); - if (!managedInstance) - { - const_cast(this)->CreateManaged(); - managedInstance = GetManagedInstance(); - } - return managedInstance; - } + MonoObject* GetOrCreateManagedInstance() const; /// /// Determines whether managed instance is alive. /// - /// True if managed object has been created and exists, otherwise false. FORCE_INLINE bool HasManagedInstance() const { return GetManagedInstance() != nullptr; @@ -92,7 +81,6 @@ public: /// /// Gets the unique object ID. /// - /// The unique object ID. FORCE_INLINE const Guid& GetID() const { return _id; @@ -101,7 +89,6 @@ public: /// /// Gets the scripting type handle of this object. /// - /// The scripting type handle. FORCE_INLINE const ScriptingTypeHandle& GetTypeHandle() const { return _type; @@ -110,7 +97,6 @@ public: /// /// Gets the scripting type of this object. /// - /// The scripting type. FORCE_INLINE const ScriptingType& GetType() const { return _type.GetType(); @@ -119,7 +105,6 @@ public: /// /// Gets the type class of this object. /// - /// The Mono class. MClass* GetClass() const; public: diff --git a/Source/Engine/Serialization/Json.h b/Source/Engine/Serialization/Json.h index 4d47d743e..880796345 100644 --- a/Source/Engine/Serialization/Json.h +++ b/Source/Engine/Serialization/Json.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" // TODO: config RAPIDJSON_SSE2 for rapidjson #define RAPIDJSON_ERROR_CHARTYPE Char diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 2ebf16a79..3130b597f 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -123,6 +123,13 @@ namespace FlaxEngine.Json writer.WriteStartObject(); { +#if FLAX_EDITOR + if ((serializer.TypeNameHandling & TypeNameHandling.Objects) == TypeNameHandling.Objects) + { + writer.WritePropertyName("$type"); + writer.WriteValue("FlaxEngine.GUI.Margin, FlaxEngine.CSharp"); + } +#endif writer.WritePropertyName("Left"); writer.WriteValue(valueMargin.Left); writer.WritePropertyName("Right"); @@ -177,22 +184,31 @@ namespace FlaxEngine.Json case JsonToken.PropertyName: { var propertyName = (string)reader.Value; - var propertyValue = (float)reader.ReadAsDouble(); - switch (propertyName) + reader.Read(); + switch (reader.TokenType) { - case "Left": - value.Left = propertyValue; - break; - case "Right": - value.Right = propertyValue; - break; - case "Top": - value.Top = propertyValue; - break; - case "Bottom": - value.Bottom = propertyValue; + case JsonToken.Integer: + case JsonToken.Float: + { + var propertyValue = Convert.ToSingle(reader.Value); + switch (propertyName) + { + case "Left": + value.Left = propertyValue; + break; + case "Right": + value.Right = propertyValue; + break; + case "Top": + value.Top = propertyValue; + break; + case "Bottom": + value.Bottom = propertyValue; + break; + } break; } + } break; } case JsonToken.Comment: break; diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index cc07597e8..2018015a9 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -47,6 +47,33 @@ namespace FlaxEngine.Json.JsonCustomSerializers return contract; } + /// + protected override JsonDictionaryContract CreateDictionaryContract(Type objectType) + { + var contract = base.CreateDictionaryContract(objectType); + + // Override contract to save enums keys as integer + if (contract.DictionaryKeyType?.IsEnum ?? false) + { + var enumType = contract.DictionaryKeyType; + contract.DictionaryKeyResolver = name => + { + try + { + var e = Enum.Parse(enumType, name); + name = Convert.ToInt32(e).ToString(); + } + catch + { + // Ignore errors + } + return name; + }; + } + + return contract; + } + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index bafc52077..1b0e0b45c 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -74,7 +74,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian v.Type = VariantType::Null; const auto mTypeName = SERIALIZE_FIND_MEMBER(stream, "TypeName"); if (mTypeName != stream.MemberEnd() && mTypeName->value.IsString()) - v.SetTypeName(StringAnsiView(mTypeName->value.GetString(), mTypeName->value.GetStringLength())); + v.SetTypeName(StringAnsiView(mTypeName->value.GetStringAnsiView())); } else { @@ -257,7 +257,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian break; case VariantType::String: CHECK(value.IsString()); - v.SetString(StringAnsiView(value.GetString(), value.GetStringLength())); + v.SetString(value.GetStringAnsiView()); break; case VariantType::Object: Deserialize(value, id, modifier); @@ -328,7 +328,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian break; case VariantType::Typename: CHECK(value.IsString()); - v.SetTypename(StringAnsiView(value.GetString(), value.GetStringLength())); + v.SetTypename(value.GetStringAnsiView()); break; default: Platform::CheckFailed("", __FILE__, __LINE__); diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 1413525e7..9fc49d8b7 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -18,6 +18,18 @@ struct VariantType; namespace Serialization { + inline int32 DeserializeInt(ISerializable::DeserializeStream& stream) + { + int32 result = 0; + if (stream.IsInt()) + result = stream.GetInt(); + else if (stream.IsFloat()) + result = (int32)stream.GetFloat(); + else if (stream.IsString()) + StringUtils::Parse(stream.GetString(), &result); + return result; + } + // In-build types inline bool ShouldSerialize(const bool& v, const void* otherObj) @@ -43,7 +55,7 @@ namespace Serialization } inline void Deserialize(ISerializable::DeserializeStream& stream, int8& v, ISerializeModifier* modifier) { - v = stream.GetInt(); + v = (int8)DeserializeInt(stream); } inline bool ShouldSerialize(const char& v, const void* otherObj) @@ -82,7 +94,7 @@ namespace Serialization } inline void Deserialize(ISerializable::DeserializeStream& stream, int16& v, ISerializeModifier* modifier) { - v = stream.GetInt(); + v = (int16)DeserializeInt(stream); } inline bool ShouldSerialize(const int32& v, const void* otherObj) @@ -95,7 +107,7 @@ namespace Serialization } inline void Deserialize(ISerializable::DeserializeStream& stream, int32& v, ISerializeModifier* modifier) { - v = stream.GetInt(); + v = DeserializeInt(stream); } inline bool ShouldSerialize(const int64& v, const void* otherObj) @@ -204,7 +216,7 @@ namespace Serialization template inline typename TEnableIf::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, T& v, ISerializeModifier* modifier) { - v = (T)stream.GetInt(); + v = (T)DeserializeInt(stream); } // Common types @@ -536,7 +548,7 @@ namespace Serialization else if (stream.IsString()) { // byte[] encoded as Base64 - const StringAnsiView streamView(stream.GetString(), stream.GetStringLength()); + const StringAnsiView streamView(stream.GetStringAnsiView()); v.Resize(Encryption::Base64DecodeLength(*streamView, streamView.Length())); Encryption::Base64Decode(*streamView, streamView.Length(), v.Get()); } @@ -577,21 +589,34 @@ namespace Serialization template inline void Deserialize(ISerializable::DeserializeStream& stream, Dictionary& v, ISerializeModifier* modifier) { - if (!stream.IsArray()) - return; - const auto& streamArray = stream.GetArray(); v.Clear(); - v.EnsureCapacity(streamArray.Size() * 3); - for (rapidjson::SizeType i = 0; i < streamArray.Size(); i++) + if (stream.IsArray()) { - auto& streamItem = streamArray[i]; - const auto mKey = SERIALIZE_FIND_MEMBER(streamItem, "Key"); - const auto mValue = SERIALIZE_FIND_MEMBER(streamItem, "Value"); - if (mKey != streamItem.MemberEnd() && mValue != streamItem.MemberEnd()) + const auto& streamArray = stream.GetArray(); + const int32 size = streamArray.Size(); + v.EnsureCapacity(size * 3); + for (int32 i = 0; i < size; i++) + { + auto& streamItem = streamArray[i]; + const auto mKey = SERIALIZE_FIND_MEMBER(streamItem, "Key"); + const auto mValue = SERIALIZE_FIND_MEMBER(streamItem, "Value"); + if (mKey != streamItem.MemberEnd() && mValue != streamItem.MemberEnd()) + { + KeyType key; + Deserialize(mKey->value, key, modifier); + Deserialize(mValue->value, v[key], modifier); + } + } + } + else if (stream.IsObject()) + { + const int32 size = stream.MemberCount(); + v.EnsureCapacity(size * 3); + for (auto i = stream.MemberBegin(); i != stream.MemberEnd(); ++i) { KeyType key; - Deserialize(mKey->value, key, modifier); - Deserialize(mValue->value, v[key], modifier); + Deserialize(i->name, key, modifier); + Deserialize(i->value, v[key], modifier); } } } diff --git a/Source/Engine/Serialization/UnmanagedMemoryStream.cs b/Source/Engine/Serialization/UnmanagedMemoryStream.cs index 12b7c4634..8e0067839 100644 --- a/Source/Engine/Serialization/UnmanagedMemoryStream.cs +++ b/Source/Engine/Serialization/UnmanagedMemoryStream.cs @@ -84,7 +84,7 @@ namespace FlaxEngine.Json if (toRead <= 0) return 0; fixed (byte* bufferPtr = buffer) - Utils.MemoryCopy(new IntPtr(_ptr + _pos), new IntPtr(bufferPtr), toRead); + Utils.MemoryCopy(new IntPtr(bufferPtr), new IntPtr(_ptr + _pos), (ulong)toRead); _pos += toRead; return toRead; } @@ -135,7 +135,7 @@ namespace FlaxEngine.Json if (newPos > _length) _length = newPos; fixed (byte* bufferPtr = buffer) - Utils.MemoryCopy(new IntPtr(_pos + _pos), new IntPtr(bufferPtr), count); + Utils.MemoryCopy(new IntPtr(bufferPtr), new IntPtr(_pos + _pos), (ulong)count); _pos = newPos; } diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index 1f58dce93..c03c10ae7 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "Engine/Graphics/Config.h" +#include "Engine/GraphicsDevice/DirectX/DX12/Types.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" #include "Engine/Platform/Windows/ComPtr.h" @@ -112,99 +113,6 @@ ShaderCompilerDX::~ShaderCompilerDX() containerReflection->Release(); } -namespace -{ - bool ProcessShader(ShaderCompilationContext* context, Array& constantBuffers, ID3D12ShaderReflection* shaderReflection, D3D12_SHADER_DESC& desc, ShaderBindings& bindings) - { - // Extract constant buffers usage information - for (uint32 a = 0; a < desc.ConstantBuffers; a++) - { - // Get CB - auto cb = shaderReflection->GetConstantBufferByIndex(a); - - // Get CB description - D3D12_SHADER_BUFFER_DESC cbDesc; - cb->GetDesc(&cbDesc); - - // Check buffer type - if (cbDesc.Type == D3D_CT_CBUFFER) - { - // Find CB slot index - int32 slot = INVALID_INDEX; - for (uint32 b = 0; b < desc.BoundResources; b++) - { - D3D12_SHADER_INPUT_BIND_DESC bDesc; - shaderReflection->GetResourceBindingDesc(b, &bDesc); - if (StringUtils::Compare(bDesc.Name, cbDesc.Name) == 0) - { - slot = bDesc.BindPoint; - break; - } - } - if (slot == INVALID_INDEX) - { - context->OnError("Missing bound resource."); - return true; - } - - // Set flag - bindings.UsedCBsMask |= 1 << slot; - - // Try to add CB to the list - for (int32 b = 0; b < constantBuffers.Count(); b++) - { - auto& cc = constantBuffers[b]; - if (cc.Slot == slot) - { - cc.IsUsed = true; - cc.Size = cbDesc.Size; - break; - } - } - } - } - - // Extract resources usage - for (uint32 i = 0; i < desc.BoundResources; i++) - { - // Get resource description - D3D12_SHADER_INPUT_BIND_DESC resDesc; - shaderReflection->GetResourceBindingDesc(i, &resDesc); - - switch (resDesc.Type) - { - // Sampler - case D3D_SIT_SAMPLER: - break; - - // Constant Buffer - case D3D_SIT_CBUFFER: - case D3D_SIT_TBUFFER: - break; - - // Shader Resource - case D3D_SIT_TEXTURE: - case D3D_SIT_STRUCTURED: - case D3D_SIT_BYTEADDRESS: - bindings.UsedSRsMask |= 1 << resDesc.BindPoint; - break; - - // Unordered Access - case D3D_SIT_UAV_RWTYPED: - case D3D_SIT_UAV_RWSTRUCTURED: - case D3D_SIT_UAV_RWBYTEADDRESS: - case D3D_SIT_UAV_APPEND_STRUCTURED: - case D3D_SIT_UAV_CONSUME_STRUCTURED: - case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER: - bindings.UsedUAsMask |= 1 << resDesc.BindPoint; - break; - } - } - - return false; - } -} - bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite) { if (WriteShaderFunctionBegin(_context, meta)) @@ -393,11 +301,113 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD shaderReflection->GetDesc(&desc); // Process shader reflection data + DxShaderHeader header; + Platform::MemoryClear(&header, sizeof(header)); ShaderBindings bindings = { desc.InstructionCount, 0, 0, 0 }; - if (ProcessShader(_context, _constantBuffers, shaderReflection.Get(), desc, bindings)) - return true; + for (uint32 a = 0; a < desc.ConstantBuffers; a++) + { + auto cb = shaderReflection->GetConstantBufferByIndex(a); + D3D12_SHADER_BUFFER_DESC cbDesc; + cb->GetDesc(&cbDesc); + if (cbDesc.Type == D3D_CT_CBUFFER) + { + // Find CB slot index + int32 slot = INVALID_INDEX; + for (uint32 b = 0; b < desc.BoundResources; b++) + { + D3D12_SHADER_INPUT_BIND_DESC bDesc; + shaderReflection->GetResourceBindingDesc(b, &bDesc); + if (StringUtils::Compare(bDesc.Name, cbDesc.Name) == 0) + { + slot = bDesc.BindPoint; + break; + } + } + if (slot == INVALID_INDEX) + { + _context->OnError("Missing bound resource."); + return true; + } - if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, shaderBuffer->GetBufferPointer(), (int32)shaderBuffer->GetBufferSize())) + // Set flag + bindings.UsedCBsMask |= 1 << slot; + + // Try to add CB to the list + for (int32 b = 0; b < _constantBuffers.Count(); b++) + { + auto& cc = _constantBuffers[b]; + if (cc.Slot == slot) + { + cc.IsUsed = true; + cc.Size = cbDesc.Size; + break; + } + } + } + } + for (uint32 i = 0; i < desc.BoundResources; i++) + { + D3D12_SHADER_INPUT_BIND_DESC resDesc; + shaderReflection->GetResourceBindingDesc(i, &resDesc); + switch (resDesc.Type) + { + // Sampler + case D3D_SIT_SAMPLER: + break; + + // Constant Buffer + case D3D_SIT_CBUFFER: + case D3D_SIT_TBUFFER: + break; + + // Shader Resource + case D3D_SIT_TEXTURE: + bindings.UsedSRsMask |= 1 << resDesc.BindPoint; + header.SrDimensions[resDesc.BindPoint] = resDesc.Dimension; + break; + case D3D_SIT_STRUCTURED: + case D3D_SIT_BYTEADDRESS: + bindings.UsedSRsMask |= 1 << resDesc.BindPoint; + header.SrDimensions[resDesc.BindPoint] = D3D_SRV_DIMENSION_BUFFER; + break; + + // Unordered Access + case D3D_SIT_UAV_RWTYPED: + case D3D_SIT_UAV_RWSTRUCTURED: + case D3D_SIT_UAV_RWBYTEADDRESS: + case D3D_SIT_UAV_APPEND_STRUCTURED: + case D3D_SIT_UAV_CONSUME_STRUCTURED: + case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER: + bindings.UsedUAsMask |= 1 << resDesc.BindPoint; + switch (resDesc.Dimension) + { + case D3D_SRV_DIMENSION_BUFFER: + header.UaDimensions[resDesc.BindPoint] = 1; // D3D12_UAV_DIMENSION_BUFFER; + break; + case D3D_SRV_DIMENSION_TEXTURE1D: + header.UaDimensions[resDesc.BindPoint] = 2; // D3D12_UAV_DIMENSION_TEXTURE1D; + break; + case D3D_SRV_DIMENSION_TEXTURE1DARRAY: + header.UaDimensions[resDesc.BindPoint] = 3; // D3D12_UAV_DIMENSION_TEXTURE1DARRAY; + break; + case D3D_SRV_DIMENSION_TEXTURE2D: + header.UaDimensions[resDesc.BindPoint] = 4; // D3D12_UAV_DIMENSION_TEXTURE2D; + break; + case D3D_SRV_DIMENSION_TEXTURE2DARRAY: + header.UaDimensions[resDesc.BindPoint] = 5; // D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + break; + case D3D_SRV_DIMENSION_TEXTURE3D: + header.UaDimensions[resDesc.BindPoint] = 8; // D3D12_UAV_DIMENSION_TEXTURE3D; + break; + default: + LOG(Error, "Unknown UAV resource {2} of type {0} at slot {1}", resDesc.Dimension, resDesc.BindPoint, String(resDesc.Name)); + return true; + } + break; + } + } + + if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), shaderBuffer->GetBufferPointer(), (int32)shaderBuffer->GetBufferSize())) return true; if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros)) diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index 32decb15f..b4db1a10b 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -80,7 +80,7 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) _constantBuffers.Add({ meta->CB[i].Slot, false, 0 }); // [Output] Version number - output->WriteInt32(7); + output->WriteInt32(8); // [Output] Additional data start const int32 additionalDataStartPos = output->GetPosition(); @@ -364,6 +364,21 @@ bool ShaderCompiler::WriteShaderFunctionBegin(ShaderCompilationContext* context, return false; } +bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* header, int32 headerSize, const void* cache, int32 cacheSize) +{ + auto output = context->Output; + + // [Output] Write compiled shader cache + output->WriteUint32(cacheSize + headerSize); + output->WriteBytes(header, headerSize); + output->WriteBytes(cache, cacheSize); + + // [Output] Shader bindings meta + output->Write(&bindings); + + return false; +} + bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* cache, int32 cacheSize) { auto output = context->Output; diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.h b/Source/Engine/ShadersCompilation/ShaderCompiler.h index 43947ab89..1e31ca21e 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.h +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.h @@ -96,6 +96,7 @@ protected: virtual bool OnCompileEnd(); static bool WriteShaderFunctionBegin(ShaderCompilationContext* context, ShaderFunctionMeta& meta); + static bool WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* header, int32 headerSize, const void* cache, int32 cacheSize); static bool WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* cache, int32 cacheSize); static bool WriteShaderFunctionEnd(ShaderCompilationContext* context, ShaderFunctionMeta& meta); static bool WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros); diff --git a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp index 998cc4291..597fd0b39 100644 --- a/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp +++ b/Source/Engine/ShadersCompilation/Vulkan/ShaderCompilerVulkan.cpp @@ -801,12 +801,8 @@ bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutat int32 spirvBytesCount = (int32)spirv.size() * sizeof(unsigned); header.Type = SpirvShaderHeader::Types::Raw; - Array data; - data.Resize(sizeof(header) + spirvBytesCount); - Platform::MemoryCopy(data.Get(), &header, sizeof(header)); - Platform::MemoryCopy(data.Get() + sizeof(header), &spirv[0], spirvBytesCount); - if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, data.Get(), data.Count())) + if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), &spirv[0], spirvBytesCount)) return true; if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros)) diff --git a/Source/Engine/ShadowsOfMordor/Builder.Debug.cpp b/Source/Engine/ShadowsOfMordor/Builder.Debug.cpp index 0e1236854..5be0072b4 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Debug.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Debug.cpp @@ -9,6 +9,8 @@ #if DEBUG_EXPORT_LIGHTMAPS_PREVIEW || DEBUG_EXPORT_CACHE_PREVIEW || DEBUG_EXPORT_HEMISPHERES_PREVIEW +#include "Engine/Engine/Globals.h" + String GetDebugDataPath() { auto result = Globals::ProjectCacheFolder / TEXT("ShadowsOfMordor_Debug"); @@ -165,7 +167,7 @@ void ShadowsOfMordor::Builder::downloadDebugHemisphereAtlases(SceneBuildCache* s GPUTexture* atlas = DebugExportHemispheresAtlases[atlasIndex]; TextureData textureData; - if (atlas->DownloadData(&textureData)) + if (atlas->DownloadData(textureData)) { LOG(Error, "Cannot download hemispheres atlas data."); continue; diff --git a/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp b/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp index fe388c039..74e46a03f 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.DoWork.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Builder.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Math.h" #include "Engine/Level/Actors/BoxBrush.h" @@ -135,7 +136,7 @@ bool ShadowsOfMordor::Builder::doWorkInner(DateTime buildStart) } } reportProgress(BuildProgressStep::RenderHemispheres, 1.0f); - return true; + return false; } #endif @@ -188,7 +189,12 @@ bool ShadowsOfMordor::Builder::doWorkInner(DateTime buildStart) tempDesc.Format = HemispheresFormatToPixelFormat[CACHE_NORMALS_FORMAT]; _cacheNormals = RenderTargetPool::Get(tempDesc); if (_cachePositions == nullptr || _cacheNormals == nullptr) + { + LOG(Warning, "Failed to get textures for cache."); + _wasBuildCalled = false; + _isActive = false; return true; + } generateHemispheres(); @@ -228,6 +234,8 @@ bool ShadowsOfMordor::Builder::doWorkInner(DateTime buildStart) if (bounceCount <= 0 || hemispheresCount <= 0) { LOG(Warning, "No data to render"); + _wasBuildCalled = false; + _isActive = false; return true; } @@ -284,8 +292,8 @@ bool ShadowsOfMordor::Builder::doWorkInner(DateTime buildStart) reportProgress(BuildProgressStep::RenderHemispheres, 1.0f); #if DEBUG_EXPORT_HEMISPHERES_PREVIEW - for (int32 sceneIndex = 0; sceneIndex < _scenes.Count(); sceneIndex++) - downloadDebugHemisphereAtlases(_scenes[sceneIndex]); + for (int32 sceneIndex = 0; sceneIndex < _scenes.Count(); sceneIndex++) + downloadDebugHemisphereAtlases(_scenes[sceneIndex]); #endif // References: @@ -391,6 +399,7 @@ int32 ShadowsOfMordor::Builder::doWork() buildFailed = doWorkInner(buildStart); if (buildFailed && !checkBuildCancelled()) { + IsBakingLightmaps = false; OnBuildFinished(buildFailed); return 0; } diff --git a/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp b/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp index aa66c955c..445555b69 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Builder.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Level/Actors/BoxBrush.h" #include "Engine/Level/Actors/StaticModel.h" @@ -63,7 +64,7 @@ bool cacheStaticGeometryTree(Actor* actor, ShadowsOfMordor::Builder::SceneBuildC model->GetPath(), meshIndex, lodIndex, - staticModel->GetName()); + staticModel->GetNamePath()); } } } diff --git a/Source/Engine/ShadowsOfMordor/Builder.Hemispheres.cpp b/Source/Engine/ShadowsOfMordor/Builder.Hemispheres.cpp index 560d922b9..edad95799 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Hemispheres.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Hemispheres.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Builder.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Level/SceneQuery.h" #include "Engine/Level/Actors/BoxBrush.h" @@ -78,6 +79,8 @@ void ShadowsOfMordor::Builder::generateHemispheres() // Fill cache if (runStage(RenderCache)) return; + if (waitForJobDataSync()) + return; // Post-process cache if (runStage(PostprocessCache)) diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 305cc71b8..c214c7e4b 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -17,6 +17,7 @@ #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/TerrainManager.h" #include "Engine/Foliage/Foliage.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Profiler/Profiler.h" namespace ShadowsOfMordor @@ -37,6 +38,8 @@ namespace ShadowsOfMordor void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) { + if (_workerActiveSceneIndex < 0 || _workerActiveSceneIndex >= _scenes.Count()) + return; auto scene = _scenes[_workerActiveSceneIndex]; int32 atlasSize = (int32)scene->GetSettings().AtlasSize; @@ -70,7 +73,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) { uint32 rowPitch, slicePitch; texture->ComputePitch(mipIndex, rowPitch, slicePitch); - context->UpdateTexture(textures[textureIndex], 0, 0, cleaner, rowPitch, slicePitch); + context->UpdateTexture(textures[textureIndex], 0, mipIndex, cleaner, rowPitch, slicePitch); } } } @@ -465,7 +468,9 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) Swap(scene->TempLightmapData, lightmapEntry.LightmapData); // Keep blurring the empty lightmap texels (from background) - const int32 blurPasses = 24; + int32 blurPasses = 24; + if (context->GetDevice()->GetRendererType() == RendererType::DirectX12) + blurPasses = 0; // TODO: fix CS_Dilate passes on D3D12 (probably UAV synchronization issue) for (int32 blurPassIndex = 0; blurPassIndex < blurPasses; blurPassIndex++) { context->UnBindSR(0); diff --git a/Source/Engine/ShadowsOfMordor/Builder.cpp b/Source/Engine/ShadowsOfMordor/Builder.cpp index 6225e36f6..dbbc8e14b 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.cpp @@ -432,7 +432,7 @@ bool ShadowsOfMordor::Builder::initResources() // TODO: remove this release and just create missing resources releaseResources(); - _output = GPUTexture::New(); + _output = GPUDevice::Instance->CreateTexture(TEXT("Output")); if (_output->Init(GPUTextureDescription::New2D(HEMISPHERES_RESOLUTION, HEMISPHERES_RESOLUTION, PixelFormat::R11G11B10_Float))) return true; _task = New(); @@ -521,7 +521,7 @@ void ShadowsOfMordor::Builder::releaseResources() bool ShadowsOfMordor::Builder::waitForJobDataSync() { bool wasCancelled = false; - const int32 framesToSyncCount = 3; + const int32 framesToSyncCount = 4; while (!wasCancelled) { diff --git a/Source/Engine/ShadowsOfMordor/Builder.h b/Source/Engine/ShadowsOfMordor/Builder.h index c8c84f5bf..6d07a0587 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.h +++ b/Source/Engine/ShadowsOfMordor/Builder.h @@ -10,6 +10,7 @@ #if COMPILE_WITH_GI_BAKING #include "Engine/Graphics/RenderTask.h" +#include "Engine/Core/Singleton.h" // Forward declarations #if COMPILE_WITH_ASSETS_IMPORTER diff --git a/Source/Engine/Streaming/Config.h b/Source/Engine/Streaming/Config.h deleted file mode 100644 index eba4e78a9..000000000 --- a/Source/Engine/Streaming/Config.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -// Resources streaming types -typedef float StreamingQuality; - -#define MIN_STREAMING_QUALITY 0.0f -#define MAX_STREAMING_QUALITY 1.0f - -/// -/// Enables/disables dynamic resources streaming feature -/// -#define ENABLE_RESOURCES_DYNAMIC_STREAMING 1 - -class StreamableResource; diff --git a/Source/Engine/Streaming/Handlers/AudioStreamingHandler.cpp b/Source/Engine/Streaming/Handlers/AudioStreamingHandler.cpp deleted file mode 100644 index ad5797f92..000000000 --- a/Source/Engine/Streaming/Handlers/AudioStreamingHandler.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "AudioStreamingHandler.h" -#include "Engine/Audio/AudioClip.h" -#include "Engine/Audio/Audio.h" -#include "Engine/Audio/AudioSource.h" - -StreamingQuality AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) -{ - // Audio clips don't use quality but only residency - return MAX_STREAMING_QUALITY; -} - -int32 AudioStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality) -{ - ASSERT(resource); - auto clip = static_cast(resource); - const int32 chunksCount = clip->Buffers.Count(); - bool chunksMask[ASSET_FILE_DATA_CHUNKS]; - Platform::MemoryClear(chunksMask, sizeof(chunksMask)); - - // Find audio chunks required for streaming - clip->StreamingQueue.Clear(); - for (int32 sourceIndex = 0; sourceIndex < Audio::Sources.Count(); sourceIndex++) - { - // TODO: collect refs to audio clip from sources and use faster iteration (but do it thread-safe) - - const auto src = Audio::Sources[sourceIndex]; - if (src->Clip == clip && src->GetState() == AudioSource::States::Playing) - { - // Stream the current and the next chunk if could be used in a while - const int32 chunk = src->_streamingFirstChunk; - ASSERT(Math::IsInRange(chunk, 0, chunksCount)); - chunksMask[chunk] = true; - - const float StreamingDstSec = 2.0f; - if (chunk + 1 < chunksCount && src->GetTime() + StreamingDstSec >= clip->GetBufferStartTime(src->_streamingFirstChunk)) - { - chunksMask[chunk + 1] = true; - } - } - } - - // Try to enqueue chunks to modify (load or unload) - for (int32 i = 0; i < chunksCount; i++) - { - if (chunksMask[i] != (clip->Buffers[i] != 0)) - { - clip->StreamingQueue.Add(i); - } - } - - return clip->StreamingQueue.Count(); -} - -int32 AudioStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) -{ - // No smoothing or slowdown in residency change - return targetResidency; -} - -bool AudioStreamingHandler::RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) -{ - // Audio clips use streaming queue buffer to detect streaming request start - const auto clip = static_cast(resource); - return clip->StreamingQueue.HasItems(); -} diff --git a/Source/Engine/Streaming/Handlers/AudioStreamingHandler.h b/Source/Engine/Streaming/Handlers/AudioStreamingHandler.h deleted file mode 100644 index c88481e22..000000000 --- a/Source/Engine/Streaming/Handlers/AudioStreamingHandler.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Streaming/IStreamingHandler.h" - -/// -/// Implementation of IStreamingHandler for audio resources. -/// -/// -class FLAXENGINE_API AudioStreamingHandler : public IStreamingHandler -{ -public: - - // [IStreamingHandler] - StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override; - int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override; - int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; - bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) override; -}; diff --git a/Source/Engine/Streaming/Handlers/ModelsStreamingHandler.cpp b/Source/Engine/Streaming/Handlers/ModelsStreamingHandler.cpp deleted file mode 100644 index 32626e071..000000000 --- a/Source/Engine/Streaming/Handlers/ModelsStreamingHandler.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "ModelsStreamingHandler.h" -#include "Engine/Content/Assets/Model.h" - -StreamingQuality ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) -{ - ASSERT(resource); - - // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options - - return MAX_STREAMING_QUALITY; -} - -int32 ModelsStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality) -{ - ASSERT(resource); - auto& model = *(Model*)resource; - auto lodCount = model.GetLODsCount(); - - if (quality < ZeroTolerance) - return 0; - - int32 lods = Math::CeilToInt(quality * lodCount); - - ASSERT(model.IsValidLODIndex(lods - 1)); - return lods; -} - -int32 ModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) -{ - ASSERT(resource); - auto& model = *(Model*)resource; - - // Always load only single LOD at once - int32 residency = targetResidency; - int32 currentResidency = model.GetCurrentResidency(); - if (currentResidency < targetResidency) - { - // Up - residency = currentResidency + 1; - } - else if (currentResidency > targetResidency) - { - // Down - residency = currentResidency - 1; - } - - return residency; -} diff --git a/Source/Engine/Streaming/Handlers/ModelsStreamingHandler.h b/Source/Engine/Streaming/Handlers/ModelsStreamingHandler.h deleted file mode 100644 index 02161f3ae..000000000 --- a/Source/Engine/Streaming/Handlers/ModelsStreamingHandler.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Streaming/IStreamingHandler.h" - -/// -/// Implementation of IStreamingHandler for streamable models. -/// -/// -class FLAXENGINE_API ModelsStreamingHandler : public IStreamingHandler -{ -public: - - // [IStreamingHandler] - StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override; - int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override; - int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; -}; diff --git a/Source/Engine/Streaming/Handlers/SkinnedModelsStreamingHandler.cpp b/Source/Engine/Streaming/Handlers/SkinnedModelsStreamingHandler.cpp deleted file mode 100644 index 3e4586dc2..000000000 --- a/Source/Engine/Streaming/Handlers/SkinnedModelsStreamingHandler.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "SkinnedModelsStreamingHandler.h" -#include "Engine/Content/Assets/SkinnedModel.h" - -StreamingQuality SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) -{ - ASSERT(resource); - - // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options - - return MAX_STREAMING_QUALITY; -} - -int32 SkinnedModelsStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality) -{ - ASSERT(resource); - auto& model = *(SkinnedModel*)resource; - auto lodCount = model.GetLODsCount(); - - if (quality < ZeroTolerance) - return 0; - - int32 lods = Math::CeilToInt(quality * lodCount); - - ASSERT(model.IsValidLODIndex(lods - 1)); - return lods; -} - -int32 SkinnedModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) -{ - ASSERT(resource); - auto& model = *(SkinnedModel*)resource; - - // Always load only single LOD at once - int32 residency = targetResidency; - int32 currentResidency = model.GetCurrentResidency(); - if (currentResidency < targetResidency) - { - // Up - residency = currentResidency + 1; - } - else if (currentResidency > targetResidency) - { - // Down - residency = currentResidency - 1; - } - - return residency; -} diff --git a/Source/Engine/Streaming/Handlers/SkinnedModelsStreamingHandler.h b/Source/Engine/Streaming/Handlers/SkinnedModelsStreamingHandler.h deleted file mode 100644 index 1ef929ec9..000000000 --- a/Source/Engine/Streaming/Handlers/SkinnedModelsStreamingHandler.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Streaming/IStreamingHandler.h" - -/// -/// Implementation of IStreamingHandler for streamable skinned models. -/// -/// -class FLAXENGINE_API SkinnedModelsStreamingHandler : public IStreamingHandler -{ -public: - - // [IStreamingHandler] - StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override; - int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override; - int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; -}; diff --git a/Source/Engine/Streaming/Handlers/TexturesStreamingHandler.cpp b/Source/Engine/Streaming/Handlers/TexturesStreamingHandler.cpp deleted file mode 100644 index d308124a8..000000000 --- a/Source/Engine/Streaming/Handlers/TexturesStreamingHandler.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "TexturesStreamingHandler.h" -#include "Engine/Core/Math/Math.h" -#include "Engine/Graphics/Textures/StreamingTexture.h" - -StreamingQuality TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) -{ - ASSERT(resource); - auto& texture = *(StreamingTexture*)resource; - - // TODO: calculate a proper quality levels for the textures - - //return 0.5f; - - return MAX_STREAMING_QUALITY; -} - -int32 TexturesStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality) -{ - ASSERT(resource); - auto& texture = *(StreamingTexture*)resource; - ASSERT(texture.IsInitialized()); - const auto totalMipLevels = texture.TotalMipLevels(); - - if (quality < ZeroTolerance) - { - return 0; - } - - int32 mipLevels = Math::CeilToInt(quality * totalMipLevels); - if (mipLevels > 0 && mipLevels < 3 && totalMipLevels > 1 && texture.IsBlockCompressed()) - { - // Block compressed textures require minimum size of 4 (3 mips or more) - mipLevels = 3; - } - - ASSERT(Math::IsInRange(mipLevels, 0, totalMipLevels)); - return mipLevels; -} - -int32 TexturesStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) -{ - ASSERT(resource); - auto& texture = *(StreamingTexture*)resource; - ASSERT(texture.IsInitialized()); - - int32 residency = targetResidency; - int32 currentResidency = texture.GetCurrentResidency(); - - // Check if go up or down - if (currentResidency < targetResidency) - { - // Up - residency = Math::Min(currentResidency + 2, targetResidency); - - // Stream first a few mips very fast (they are very small) - const int32 QuickStartMipsCount = 6; - if (currentResidency == 0) - { - residency = Math::Min(QuickStartMipsCount, targetResidency); - } - } - else if (currentResidency > targetResidency) - { - // Down at once - residency = targetResidency; - } - - return residency; -} diff --git a/Source/Engine/Streaming/Handlers/TexturesStreamingHandler.h b/Source/Engine/Streaming/Handlers/TexturesStreamingHandler.h deleted file mode 100644 index bb0b3a526..000000000 --- a/Source/Engine/Streaming/Handlers/TexturesStreamingHandler.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Streaming/IStreamingHandler.h" - -/// -/// Implementation of IStreamingHandler for streamable textures. -/// -/// -class FLAXENGINE_API TexturesStreamingHandler : public IStreamingHandler -{ -public: - - // [IStreamingHandler] - StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override; - int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override; - int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; -}; diff --git a/Source/Engine/Streaming/IStreamingHandler.h b/Source/Engine/Streaming/IStreamingHandler.h index fd221996d..299c530b4 100644 --- a/Source/Engine/Streaming/IStreamingHandler.h +++ b/Source/Engine/Streaming/IStreamingHandler.h @@ -2,13 +2,13 @@ #pragma once -#include "Config.h" -#include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/BaseTypes.h" class StreamingGroup; +class StreamableResource; /// -/// Base interface for all streamable resource handlers +/// Base interface for all streamable resource handlers that implement resource streaming policy. /// class FLAXENGINE_API IStreamingHandler { @@ -19,20 +19,21 @@ public: public: /// - /// Calculates target quality level for the given resource. + /// Calculates target quality level (0-1) for the given resource. /// /// The resource. - /// The current time. - /// Target Quality - virtual StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) = 0; + /// The current time and date. + /// The current platform time (seconds). + /// Target quality (0-1). + virtual float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) = 0; /// /// Calculates the residency level for a given resource and quality level. /// /// The resource. - /// The quality level. + /// The quality level (0-1). /// Residency level - virtual int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) = 0; + virtual int32 CalculateResidency(StreamableResource* resource, float quality) = 0; /// /// Calculates the residency level to stream for a given resource and target residency. diff --git a/Source/Engine/Streaming/StreamableResource.cpp b/Source/Engine/Streaming/StreamableResource.cpp deleted file mode 100644 index 3d6c825fd..000000000 --- a/Source/Engine/Streaming/StreamableResource.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "StreamableResource.h" -#include "StreamingManager.h" - -StreamableResource::StreamableResource(StreamingGroup* group) - : _group(group) - , _isDynamic(true) - , _isStreaming(false) - , _streamingQuality(MAX_STREAMING_QUALITY) -{ - ASSERT(_group != nullptr); -} - -StreamableResource::~StreamableResource() -{ - stopStreaming(); -} - -void StreamableResource::startStreaming(bool isDynamic) -{ - _isDynamic = isDynamic; - - if (_isStreaming == false) - { - StreamingManager::Resources.Add(this); - _isStreaming = true; - } -} - -void StreamableResource::stopStreaming() -{ - if (_isStreaming == true) - { - StreamingManager::Resources.Remove(this); - Streaming = StreamingCache(); - _isStreaming = false; - } -} diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index fab245791..a27833149 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -4,7 +4,6 @@ #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Collections/SamplesBuffer.h" -#include "Config.h" class StreamingGroup; class Task; @@ -18,7 +17,7 @@ protected: StreamingGroup* _group; bool _isDynamic, _isStreaming; - StreamingQuality _streamingQuality; + float _streamingQuality; StreamableResource(StreamingGroup* group); ~StreamableResource(); @@ -38,17 +37,13 @@ public: /// FORCE_INLINE bool IsDynamic() const { -#if ENABLE_RESOURCES_DYNAMIC_STREAMING return _isDynamic; -#else - return false; -#endif } /// /// Gets resource streaming quality level /// - FORCE_INLINE StreamingQuality GetStreamingQuality() const + FORCE_INLINE float GetStreamingQuality() const { return _streamingQuality; } @@ -108,27 +103,12 @@ public: public: - // Streaming Manager cached variables struct StreamingCache { - /// - /// The minimum usage distance since last update (eg. mesh draw distance from camera). - /// Used to calculate resource quality. - /// - //float MinDstSinceLastUpdate; - - DateTime LastUpdate; - int32 TargetResidency; - DateTime TargetResidencyChange; - SamplesBuffer QualitySamples; - - StreamingCache() - //: MinDstSinceLastUpdate(MAX_float) - : LastUpdate(0) - , TargetResidency(0) - , TargetResidencyChange(0) - { - } + DateTime LastUpdate = 0; + int32 TargetResidency = 0; + DateTime TargetResidencyChange = 0; + SamplesBuffer QualitySamples; }; StreamingCache Streaming; @@ -143,6 +123,6 @@ public: protected: - void startStreaming(bool isDynamic); - void stopStreaming(); + void StartStreaming(bool isDynamic); + void StopStreaming(); }; diff --git a/Source/Engine/Streaming/StreamableResourcesCollection.h b/Source/Engine/Streaming/StreamableResourcesCollection.h deleted file mode 100644 index a8231726d..000000000 --- a/Source/Engine/Streaming/StreamableResourcesCollection.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Core/Delegate.h" -#include "Engine/Core/Collections/Array.h" -#include "Engine/Platform/CriticalSection.h" - -class StreamableResource; - -/// -/// Container for collection of Streamable Resources. -/// -class StreamableResourcesCollection -{ -private: - - CriticalSection _locker; - Array _resources; - -public: - - /// - /// Initializes a new instance of the class. - /// - StreamableResourcesCollection() - : _resources(4096) - { - } - -public: - - /// - /// Event called on new resource added to the collection. - /// - Delegate Added; - - /// - /// Event called on new resource added from the collection. - /// - Delegate Removed; - -public: - - /// - /// Gets the amount of registered resources. - /// - /// The resources count. - int32 ResourcesCount() const - { - _locker.Lock(); - const int32 result = _resources.Count(); - _locker.Unlock(); - return result; - } - - /// - /// Gets the resource at the given index. - /// - /// The index. - /// The resource at the given index. - StreamableResource* operator[](int32 index) const - { - _locker.Lock(); - StreamableResource* result = _resources.At(index); - _locker.Unlock(); - return result; - } - -public: - - /// - /// Adds the resource to the collection. - /// - /// The resource to add. - void Add(StreamableResource* resource) - { - ASSERT(resource); - - _locker.Lock(); - ASSERT(_resources.Contains(resource) == false); - _resources.Add(resource); - _locker.Unlock(); - - Added(resource); - } - - /// - /// Removes resource from the collection. - /// - /// The resource to remove. - void Remove(StreamableResource* resource) - { - ASSERT(resource); - - _locker.Lock(); - ASSERT(_resources.Contains(resource) == true); - _resources.Remove(resource); - _locker.Unlock(); - - Removed(resource); - } -}; diff --git a/Source/Engine/Streaming/StreamingManager.cpp b/Source/Engine/Streaming/Streaming.cpp similarity index 73% rename from Source/Engine/Streaming/StreamingManager.cpp rename to Source/Engine/Streaming/Streaming.cpp index 3b557e85f..395a890b9 100644 --- a/Source/Engine/Streaming/StreamingManager.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -1,19 +1,22 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "StreamingManager.h" +#include "Streaming.h" #include "StreamableResource.h" #include "StreamingGroup.h" +#include "StreamingSettings.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/Task.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Serialization/Serialization.h" namespace StreamingManagerImpl { DateTime LastUpdateTime(0); - CriticalSection UpdateLocker; int32 LastUpdateResourcesIndex = 0; + CriticalSection ResourcesLock; + Array Resources; } using namespace StreamingManagerImpl; @@ -21,7 +24,6 @@ using namespace StreamingManagerImpl; class StreamingManagerService : public EngineService { public: - StreamingManagerService() : EngineService(TEXT("Streaming Manager"), 100) { @@ -32,9 +34,57 @@ public: StreamingManagerService StreamingManagerServiceInstance; -StreamableResourcesCollection StreamingManager::Resources; +Array> Streaming::TextureGroups; -void UpdateResource(StreamableResource* resource, DateTime now) +void StreamingSettings::Apply() +{ + Streaming::TextureGroups = TextureGroups; +} + +void StreamingSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(TextureGroups); +} + +StreamableResource::StreamableResource(StreamingGroup* group) + : _group(group) + , _isDynamic(true) + , _isStreaming(false) + , _streamingQuality(1.0f) +{ + ASSERT(_group != nullptr); +} + +StreamableResource::~StreamableResource() +{ + StopStreaming(); +} + +void StreamableResource::StartStreaming(bool isDynamic) +{ + _isDynamic = isDynamic; + if (!_isStreaming) + { + _isStreaming = true; + ResourcesLock.Lock(); + Resources.Add(this); + ResourcesLock.Unlock(); + } +} + +void StreamableResource::StopStreaming() +{ + if (_isStreaming) + { + ResourcesLock.Lock(); + Resources.Remove(this); + ResourcesLock.Unlock(); + Streaming = StreamingCache(); + _isStreaming = false; + } +} + +void UpdateResource(StreamableResource* resource, DateTime now, double currentTime) { ASSERT(resource && resource->CanBeUpdated()); @@ -43,18 +93,17 @@ void UpdateResource(StreamableResource* resource, DateTime now) auto handler = group->GetHandler(); // Calculate target quality for that asset - StreamingQuality targetQuality = MAX_STREAMING_QUALITY; + float targetQuality = 1.0f; if (resource->IsDynamic()) { - targetQuality = handler->CalculateTargetQuality(resource, now); - // TODO: here we should apply resources group master scale (based on game settings quality level and memory level) - targetQuality = Math::Clamp(targetQuality, MIN_STREAMING_QUALITY, MAX_STREAMING_QUALITY); + targetQuality = handler->CalculateTargetQuality(resource, now, currentTime); + targetQuality = Math::Saturate(targetQuality); } // Update quality smoothing resource->Streaming.QualitySamples.Add(targetQuality); targetQuality = resource->Streaming.QualitySamples.Maximum(); - targetQuality = Math::Clamp(targetQuality, MIN_STREAMING_QUALITY, MAX_STREAMING_QUALITY); + targetQuality = Math::Saturate(targetQuality); // Calculate target residency level (discrete value) auto maxResidency = resource->GetMaxResidency(); @@ -126,13 +175,14 @@ void StreamingManagerService::Update() // Configuration // TODO: use game settings static TimeSpan ManagerUpdatesInterval = TimeSpan::FromMilliseconds(30); - static TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(200); + static TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(100); static int32 MaxResourcesPerUpdate = 50; // Check if skip update auto now = DateTime::NowUTC(); auto delta = now - LastUpdateTime; - int32 resourcesCount = StreamingManager::Resources.ResourcesCount(); + ScopeLock lock(ResourcesLock); + const int32 resourcesCount = Resources.Count(); if (resourcesCount == 0 || delta < ManagerUpdatesInterval || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready) return; LastUpdateTime = now; @@ -140,8 +190,8 @@ void StreamingManagerService::Update() PROFILE_CPU(); // Start update - ScopeLock lock(UpdateLocker); int32 resourcesUpdates = Math::Min(MaxResourcesPerUpdate, resourcesCount); + double currentTime = Platform::GetTimeSeconds(); // Update high priority queue and then rest of the resources // Note: resources in the update queue are updated always, while others only between specified intervals @@ -154,15 +204,24 @@ void StreamingManagerService::Update() LastUpdateResourcesIndex = 0; // Peek resource - const auto resource = StreamingManager::Resources[LastUpdateResourcesIndex]; + const auto resource = Resources[LastUpdateResourcesIndex]; // Try to update it if (now - resource->Streaming.LastUpdate >= ResourceUpdatesInterval && resource->CanBeUpdated()) { - UpdateResource(resource, now); + UpdateResource(resource, now, currentTime); resourcesUpdates--; } } // TODO: add StreamingManager stats, update time per frame, updates per frame, etc. } + +void Streaming::RequestStreamingUpdate() +{ + PROFILE_CPU(); + ResourcesLock.Lock(); + for (auto e : Resources) + e->RequestStreamingUpdate(); + ResourcesLock.Unlock(); +} diff --git a/Source/Engine/Streaming/Streaming.h b/Source/Engine/Streaming/Streaming.h new file mode 100644 index 000000000..2afb404f6 --- /dev/null +++ b/Source/Engine/Streaming/Streaming.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Collections/Array.h" +#include "Engine/Scripting/ScriptingType.h" +#include "TextureGroup.h" + +/// +/// The content streaming service. +/// +API_CLASS(Static) class FLAXENGINE_API Streaming +{ +DECLARE_SCRIPTING_TYPE_NO_SPAWN(Streaming); + + /// + /// Textures streaming configuration (per-group). + /// + API_FIELD() static Array> TextureGroups; + + /// + /// Requests the streaming update for all the loaded resources. Use it to refresh content streaming after changing configuration. + /// + API_FUNCTION() static void RequestStreamingUpdate(); +}; diff --git a/Source/Engine/Streaming/StreamingGroup.cpp b/Source/Engine/Streaming/StreamingGroup.cpp index 01c3a6744..c0a3a928d 100644 --- a/Source/Engine/Streaming/StreamingGroup.cpp +++ b/Source/Engine/Streaming/StreamingGroup.cpp @@ -1,10 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "StreamingGroup.h" -#include "Handlers/TexturesStreamingHandler.h" -#include "Handlers/ModelsStreamingHandler.h" -#include "Handlers/SkinnedModelsStreamingHandler.h" -#include "Handlers/AudioStreamingHandler.h" +#include "StreamingHandlers.h" StreamingGroup::StreamingGroup(Type type, IStreamingHandler* handler) : _type(type) diff --git a/Source/Engine/Streaming/StreamingGroup.h b/Source/Engine/Streaming/StreamingGroup.h index 8f7cec045..8e3a78555 100644 --- a/Source/Engine/Streaming/StreamingGroup.h +++ b/Source/Engine/Streaming/StreamingGroup.h @@ -14,9 +14,6 @@ class FLAXENGINE_API StreamingGroup { public: - /// - /// Declares the Group type - /// DECLARE_ENUM_4(Type, Custom, Textures, Models, Audio); protected: @@ -38,7 +35,6 @@ public: /// /// Gets the group type. /// - /// Type FORCE_INLINE Type GetType() const { return _type; @@ -47,7 +43,6 @@ public: /// /// Gets the group type name. /// - /// Typename FORCE_INLINE const Char* GetTypename() const { return ToString(_type); @@ -56,7 +51,6 @@ public: /// /// Gets the group streaming handler used by this group. /// - /// Handler FORCE_INLINE IStreamingHandler* GetHandler() const { return _handler; @@ -88,7 +82,6 @@ public: /// /// Gets textures group. /// - /// Group FORCE_INLINE StreamingGroup* Textures() const { return _textures; @@ -97,7 +90,6 @@ public: /// /// Gets models group. /// - /// Group FORCE_INLINE StreamingGroup* Models() const { return _models; @@ -106,7 +98,6 @@ public: /// /// Gets skinned models group. /// - /// Group FORCE_INLINE StreamingGroup* SkinnedModels() const { return _skinnedModels; @@ -115,7 +106,6 @@ public: /// /// Gets audio group. /// - /// Group FORCE_INLINE StreamingGroup* Audio() const { return _audio; @@ -126,7 +116,6 @@ public: /// /// Gets all the groups. /// - /// Groups. FORCE_INLINE const Array& Groups() const { return _groups; @@ -135,7 +124,6 @@ public: /// /// Gets all the handlers. /// - /// Groups. FORCE_INLINE const Array& Handlers() const { return _handlers; diff --git a/Source/Engine/Streaming/StreamingHandlers.cpp b/Source/Engine/Streaming/StreamingHandlers.cpp new file mode 100644 index 000000000..169d94c10 --- /dev/null +++ b/Source/Engine/Streaming/StreamingHandlers.cpp @@ -0,0 +1,237 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "StreamingHandlers.h" +#include "Streaming.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Graphics/Textures/StreamingTexture.h" +#include "Engine/Graphics/Textures/GPUTexture.h" +#include "Engine/Content/Assets/Model.h" +#include "Engine/Content/Assets/SkinnedModel.h" +#include "Engine/Audio/AudioClip.h" +#include "Engine/Audio/Audio.h" +#include "Engine/Audio/AudioSource.h" + +float TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +{ + ASSERT(resource); + auto& texture = *(StreamingTexture*)resource; + const TextureHeader& header = *texture.GetHeader(); + float result = 1.0f; + if (header.TextureGroup >= 0 && header.TextureGroup < Streaming::TextureGroups.Count()) + { + // Quality based on texture group settings + const TextureGroup& group = Streaming::TextureGroups[header.TextureGroup]; + result = group.Quality; + + // Drop quality if invisible + const double lastRenderTime = texture.GetTexture()->LastRenderTime; + if (lastRenderTime < 0 || group.TimeToInvisible <= (float)(currentTime - lastRenderTime)) + { + result *= group.QualityIfInvisible; + } + } + return result; +} + +int32 TexturesStreamingHandler::CalculateResidency(StreamableResource* resource, float quality) +{ + if (quality < ZeroTolerance) + return 0; + ASSERT(resource); + auto& texture = *(StreamingTexture*)resource; + ASSERT(texture.IsInitialized()); + const TextureHeader& header = *texture.GetHeader(); + + const int32 totalMipLevels = texture.TotalMipLevels(); + int32 mipLevels = Math::CeilToInt(quality * (float)totalMipLevels); + + if (header.TextureGroup >= 0 && header.TextureGroup < Streaming::TextureGroups.Count()) + { + const TextureGroup& group = Streaming::TextureGroups[header.TextureGroup]; + mipLevels = Math::Clamp(mipLevels + group.MipLevelsBias, group.MipLevelsMin, group.MipLevelsMax); +#if USE_EDITOR + // Simulate per-platform limit in Editor + int32 max; + if (group.MipLevelsMaxPerPlatform.TryGet(PLATFORM_TYPE, max)) + mipLevels = Math::Min(mipLevels, max); +#endif + } + + if (mipLevels > 0 && mipLevels < texture._minMipCountBlockCompressed && texture._isBlockCompressed) + { + // Block compressed textures require minimum size of 4 + mipLevels = texture._minMipCountBlockCompressed; + } + + return Math::Clamp(mipLevels, 0, totalMipLevels); +} + +int32 TexturesStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) +{ + ASSERT(resource); + auto& texture = *(StreamingTexture*)resource; + ASSERT(texture.IsInitialized()); + int32 residency = targetResidency; + int32 currentResidency = texture.GetCurrentResidency(); + if (currentResidency < targetResidency) + { + // Up + residency = Math::Min(currentResidency + 2, targetResidency); + + // Stream first a few mips very fast (they are very small) + const int32 QuickStartMipsCount = 6; + if (currentResidency == 0) + { + residency = Math::Min(QuickStartMipsCount, targetResidency); + } + } + else if (currentResidency > targetResidency) + { + // Down at once + residency = targetResidency; + } + return residency; +} + +float ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +{ + // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options + return 1.0f; +} + +int32 ModelsStreamingHandler::CalculateResidency(StreamableResource* resource, float quality) +{ + if (quality < ZeroTolerance) + return 0; + ASSERT(resource); + auto& model = *(Model*)resource; + + const int32 lodCount = model.GetLODsCount(); + int32 lods = Math::CeilToInt(quality * (float)lodCount); + ASSERT(model.IsValidLODIndex(lods - 1)); + return lods; +} + +int32 ModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) +{ + ASSERT(resource); + auto& model = *(Model*)resource; + + // Always load only single LOD at once + int32 residency = targetResidency; + int32 currentResidency = model.GetCurrentResidency(); + if (currentResidency < targetResidency) + { + // Up + residency = currentResidency + 1; + } + else if (currentResidency > targetResidency) + { + // Down + residency = currentResidency - 1; + } + + return residency; +} + +float SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +{ + // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options + return 1.0f; +} + +int32 SkinnedModelsStreamingHandler::CalculateResidency(StreamableResource* resource, float quality) +{ + if (quality < ZeroTolerance) + return 0; + ASSERT(resource); + auto& model = *(SkinnedModel*)resource; + + const int32 lodCount = model.GetLODsCount(); + int32 lods = Math::CeilToInt(quality * (float)lodCount); + ASSERT(model.IsValidLODIndex(lods - 1)); + return lods; +} + +int32 SkinnedModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) +{ + ASSERT(resource); + auto& model = *(SkinnedModel*)resource; + + // Always load only single LOD at once + int32 residency = targetResidency; + int32 currentResidency = model.GetCurrentResidency(); + if (currentResidency < targetResidency) + { + // Up + residency = currentResidency + 1; + } + else if (currentResidency > targetResidency) + { + // Down + residency = currentResidency - 1; + } + + return residency; +} + +float AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +{ + // Audio clips don't use quality but only residency + return 1.0f; +} + +int32 AudioStreamingHandler::CalculateResidency(StreamableResource* resource, float quality) +{ + ASSERT(resource); + auto clip = static_cast(resource); + const int32 chunksCount = clip->Buffers.Count(); + bool chunksMask[ASSET_FILE_DATA_CHUNKS]; + Platform::MemoryClear(chunksMask, sizeof(chunksMask)); + + // Find audio chunks required for streaming + clip->StreamingQueue.Clear(); + for (int32 sourceIndex = 0; sourceIndex < Audio::Sources.Count(); sourceIndex++) + { + // TODO: collect refs to audio clip from sources and use faster iteration (but do it thread-safe) + + const auto src = Audio::Sources[sourceIndex]; + if (src->Clip == clip && src->GetState() == AudioSource::States::Playing) + { + // Stream the current and the next chunk if could be used in a while + const int32 chunk = src->_streamingFirstChunk; + ASSERT(Math::IsInRange(chunk, 0, chunksCount)); + chunksMask[chunk] = true; + + const float StreamingDstSec = 2.0f; + if (chunk + 1 < chunksCount && src->GetTime() + StreamingDstSec >= clip->GetBufferStartTime(src->_streamingFirstChunk)) + { + chunksMask[chunk + 1] = true; + } + } + } + + // Try to enqueue chunks to modify (load or unload) + for (int32 i = 0; i < chunksCount; i++) + { + if (chunksMask[i] != (clip->Buffers[i] != 0)) + { + clip->StreamingQueue.Add(i); + } + } + + return clip->StreamingQueue.Count(); +} + +int32 AudioStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) +{ + // No smoothing or slowdown in residency change + return targetResidency; +} + +bool AudioStreamingHandler::RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) +{ + // Audio clips use streaming queue buffer to detect streaming request start + const auto clip = static_cast(resource); + return clip->StreamingQueue.HasItems(); +} diff --git a/Source/Engine/Streaming/StreamingHandlers.h b/Source/Engine/Streaming/StreamingHandlers.h new file mode 100644 index 000000000..0c20dcfda --- /dev/null +++ b/Source/Engine/Streaming/StreamingHandlers.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Streaming/IStreamingHandler.h" + +/// +/// Implementation of IStreamingHandler for streamable textures. +/// +class FLAXENGINE_API TexturesStreamingHandler : public IStreamingHandler +{ +public: + // [IStreamingHandler] + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + int32 CalculateResidency(StreamableResource* resource, float quality) override; + int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; +}; + +/// +/// Implementation of IStreamingHandler for streamable models. +/// +class FLAXENGINE_API ModelsStreamingHandler : public IStreamingHandler +{ +public: + // [IStreamingHandler] + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + int32 CalculateResidency(StreamableResource* resource, float quality) override; + int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; +}; + +/// +/// Implementation of IStreamingHandler for streamable skinned models. +/// +class FLAXENGINE_API SkinnedModelsStreamingHandler : public IStreamingHandler +{ +public: + // [IStreamingHandler] + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + int32 CalculateResidency(StreamableResource* resource, float quality) override; + int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; +}; + +/// +/// Implementation of IStreamingHandler for audio clips. +/// +class FLAXENGINE_API AudioStreamingHandler : public IStreamingHandler +{ +public: + // [IStreamingHandler] + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + int32 CalculateResidency(StreamableResource* resource, float quality) override; + int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; + bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) override; +}; diff --git a/Source/Engine/Streaming/StreamingManager.h b/Source/Engine/Streaming/StreamingManager.h deleted file mode 100644 index 732ed3f96..000000000 --- a/Source/Engine/Streaming/StreamingManager.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "StreamableResourcesCollection.h" - -/// -/// Main class for dynamic resources streaming service -/// -class FLAXENGINE_API StreamingManager -{ -public: - - /// - /// List with all resources - /// - static StreamableResourcesCollection Resources; -}; diff --git a/Source/Engine/Streaming/StreamingSettings.h b/Source/Engine/Streaming/StreamingSettings.h new file mode 100644 index 000000000..7302696a0 --- /dev/null +++ b/Source/Engine/Streaming/StreamingSettings.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Config/Settings.h" +#include "TextureGroup.h" + +/// +/// Content streaming settings. +/// +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API StreamingSettings : public SettingsBase +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(StreamingSettings); +public: + + /// + /// Textures streaming configuration (per-group). + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Textures\")") + Array> TextureGroups; + +public: + + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static StreamingSettings* Get(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; +}; diff --git a/Source/Engine/Streaming/TextureGroup.h b/Source/Engine/Streaming/TextureGroup.h new file mode 100644 index 000000000..87ad7dc4d --- /dev/null +++ b/Source/Engine/Streaming/TextureGroup.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/String.h" +#include "Engine/Core/ISerializable.h" +#if USE_EDITOR +#include "Engine/Core/Collections/Dictionary.h" +#endif + +/// +/// Settings container for a group of textures. Defines the data streaming options and resource quality. +/// +API_STRUCT() struct TextureGroup : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup); + + /// + /// The name of the group. + /// + API_FIELD(Attributes="EditorOrder(10)") + String Name; + + /// + /// The quality scale factor applied to textures in this group. Can be used to increase or decrease textures resolution. In range 0-1 where 0 means lowest quality, 1 means full quality. + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 1)") + float Quality = 1.0f; + + /// + /// The quality scale factor applied when texture is invisible for some time (defined by TimeToInvisible). Used to decrease texture quality when it's not rendered. + /// + API_FIELD(Attributes="EditorOrder(25), Limit(0, 1)") + float QualityIfInvisible = 0.5f; + + /// + /// The time (in seconds) after which texture is considered to be invisible (if it's not rendered by a certain amount of time). + /// + API_FIELD(Attributes="EditorOrder(26), Limit(0)") + float TimeToInvisible = 20.0f; + + /// + /// The minimum amount of loaded mip levels for textures in this group. Defines the amount of the mips that should be always loaded. Higher values decrease streaming usage and keep more mips loaded. + /// + API_FIELD(Attributes="EditorOrder(30), Limit(0, 14)") + int32 MipLevelsMin = 0; + + /// + /// The maximum amount of loaded mip levels for textures in this group. Defines the maximum amount of the mips that can be loaded. Overriden per-platform. Lower values reduce textures quality and improve performance. + /// + API_FIELD(Attributes="EditorOrder(40), Limit(1, 14)") + int32 MipLevelsMax = 14; + + /// + /// The loaded mip levels bias for textures in this group. Can be used to increase or decrease quality of the streaming for textures in this group (eg. bump up the quality during cinematic sequence). + /// + API_FIELD(Attributes="EditorOrder(50), Limit(-14, 14)") + int32 MipLevelsBias = 0; + +#if USE_EDITOR + /// + /// The per-platform maximum amount of mip levels for textures in this group. Can be used to strip textures quality when cooking game for a target platform. + /// + API_FIELD(Attributes="EditorOrder(50)") + Dictionary MipLevelsMaxPerPlatform; +#endif +}; diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 2ff053432..8aff910f3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -2,6 +2,7 @@ #include "Terrain.h" #include "TerrainPatch.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/Ray.h" #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Serialization/Serialization.h" diff --git a/Source/Engine/Terrain/TerrainManager.cpp b/Source/Engine/Terrain/TerrainManager.cpp index caf620ff0..e0a27d50d 100644 --- a/Source/Engine/Terrain/TerrainManager.cpp +++ b/Source/Engine/Terrain/TerrainManager.cpp @@ -6,12 +6,13 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Core/Math/Color32.h" +#include "Engine/Core/Collections/ChunkedArray.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Content/AssetReference.h" #include "Engine/Renderer/DrawCall.h" -#include "Engine/Core/Collections/ChunkedArray.h" // Must match structure defined in Terrain.shader struct TerrainVertex diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp new file mode 100644 index 000000000..c89afed8f --- /dev/null +++ b/Source/Engine/Threading/JobSystem.cpp @@ -0,0 +1,302 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "JobSystem.h" +#include "IRunnable.h" +#include "Engine/Platform/CPUInfo.h" +#include "Engine/Platform/Thread.h" +#include "Engine/Platform/ConditionVariable.h" +#include "Engine/Engine/EngineService.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Scripting/ManagedCLR/MCore.h" +#if USE_MONO +#include "Engine/Scripting/ManagedCLR/MDomain.h" +#include +#include +#endif + +// Jobs storage perf info: +// (500 jobs, i7 9th gen) +// JOB_SYSTEM_USE_MUTEX=1, enqueue=130-280 cycles, dequeue=2-6 cycles +// JOB_SYSTEM_USE_MUTEX=0, enqueue=300-700 cycles, dequeue=10-16 cycles +// So using RingBuffer+Mutex+Signals is better than moodycamel::ConcurrentQueue + +#define JOB_SYSTEM_ENABLED 1 +#define JOB_SYSTEM_USE_MUTEX 1 +#define JOB_SYSTEM_USE_STATS 0 + +#if JOB_SYSTEM_USE_STATS +#include "Engine/Core/Log.h" +#endif +#if JOB_SYSTEM_USE_MUTEX +#include "Engine/Core/Collections/RingBuffer.h" +#else +#include "ConcurrentQueue.h" +#endif + +#if JOB_SYSTEM_ENABLED + +class JobSystemService : public EngineService +{ +public: + + JobSystemService() + : EngineService(TEXT("JobSystem"), -800) + { + } + + bool Init() override; + void BeforeExit() override; + void Dispose() override; +}; + +struct JobData +{ + Function Job; + int32 Index; +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; + +class JobSystemThread : public IRunnable +{ +public: + uint64 Index; + +public: + + // [IRunnable] + String ToString() const override + { + return TEXT("JobSystemThread"); + } + + int32 Run() override; + + void AfterWork(bool wasKilled) override + { + Delete(this); + } +}; + +namespace +{ + JobSystemService JobSystemInstance; + Thread* Threads[32] = {}; + int32 ThreadsCount = 0; + bool JobStartingOnDispatch = true; + volatile int64 ExitFlag = 0; + volatile int64 DoneLabel = 0; + volatile int64 NextLabel = 0; + ConditionVariable JobsSignal; + ConditionVariable WaitSignal; +#if JOB_SYSTEM_USE_MUTEX + CriticalSection JobsLocker; + RingBuffer> Jobs; +#else + ConcurrentQueue Jobs; +#endif +#if JOB_SYSTEM_USE_STATS + int64 DequeueCount = 0; + int64 DequeueSum = 0; +#endif +} + +bool JobSystemService::Init() +{ + ThreadsCount = Math::Min(Platform::GetCPUInfo().LogicalProcessorCount, ARRAY_COUNT(Threads)); + for (int32 i = 0; i < ThreadsCount; i++) + { + auto runnable = New(); + runnable->Index = (uint64)i; + auto thread = Thread::Create(runnable, String::Format(TEXT("Job System {0}"), i), ThreadPriority::AboveNormal); + if (thread == nullptr) + return true; + Threads[i] = thread; + } + return false; +} + +void JobSystemService::BeforeExit() +{ + Platform::AtomicStore(&ExitFlag, 1); + JobsSignal.NotifyAll(); +} + +void JobSystemService::Dispose() +{ + Platform::AtomicStore(&ExitFlag, 1); + JobsSignal.NotifyAll(); + Platform::Sleep(1); + + for (int32 i = 0; i < ThreadsCount; i++) + { + if (Threads[i] && Threads[i]->IsRunning()) + Threads[i]->Kill(true); + Threads[i] = nullptr; + } +} + +int32 JobSystemThread::Run() +{ + Platform::SetThreadAffinityMask(1ull << Index); + + JobData data; + CriticalSection mutex; + bool attachMonoThread = true; +#if !JOB_SYSTEM_USE_MUTEX + moodycamel::ConsumerToken consumerToken(Jobs); +#endif + while (Platform::AtomicRead(&ExitFlag) == 0) + { + // Try to get a job +#if JOB_SYSTEM_USE_STATS + const auto start = Platform::GetTimeCycles(); +#endif +#if JOB_SYSTEM_USE_MUTEX + JobsLocker.Lock(); + if (Jobs.Count() != 0) + { + data = Jobs.PeekFront(); + Jobs.PopFront(); + } + JobsLocker.Unlock(); +#else + if (!Jobs.try_dequeue(consumerToken, data)) + data.Job.Unbind(); +#endif +#if JOB_SYSTEM_USE_STATS + Platform::InterlockedIncrement(&DequeueCount); + Platform::InterlockedAdd(&DequeueSum, Platform::GetTimeCycles() - start); +#endif + + if (data.Job.IsBinded()) + { +#if USE_MONO + // Ensure to have C# thread attached to this thead (late init due to MCore being initialized after Job System) + if (attachMonoThread && !mono_domain_get()) + { + const auto domain = MCore::Instance()->GetActiveDomain(); + mono_thread_attach(domain->GetNative()); + attachMonoThread = false; + } +#endif + + // Run job + data.Job(data.Index); + + // Move forward with the job queue + Platform::InterlockedIncrement(&DoneLabel); + WaitSignal.NotifyAll(); + + data.Job.Unbind(); + } + else + { + // Wait for signal + mutex.Lock(); + JobsSignal.Wait(mutex); + mutex.Unlock(); + } + } + return 0; +} + +#endif + +int64 JobSystem::Dispatch(const Function& job, int32 jobCount) +{ + PROFILE_CPU(); + if (jobCount <= 0) + return 0; +#if JOB_SYSTEM_ENABLED +#if JOB_SYSTEM_USE_STATS + const auto start = Platform::GetTimeCycles(); +#endif + + JobData data; + data.Job = job; + +#if JOB_SYSTEM_USE_MUTEX + JobsLocker.Lock(); + for (data.Index = 0; data.Index < jobCount; data.Index++) + Jobs.PushBack(data); + JobsLocker.Unlock(); +#else + for (data.Index = 0; data.Index < jobCount; data.Index++) + Jobs.enqueue(data); +#endif + const auto label = Platform::InterlockedAdd(&NextLabel, (int64)jobCount) + jobCount; + +#if JOB_SYSTEM_USE_STATS + LOG(Info, "Job enqueue time: {0} cycles", (int64)(Platform::GetTimeCycles() - start)); +#endif + + if (JobStartingOnDispatch) + { + if (jobCount == 1) + JobsSignal.NotifyOne(); + else + JobsSignal.NotifyAll(); + } + + return label; +#else + for (int32 i = 0; i < jobCount; i++) + job(i); + return 0; +#endif +} + +void JobSystem::Wait() +{ +#if JOB_SYSTEM_ENABLED + Wait(Platform::AtomicRead(&NextLabel)); +#endif +} + +void JobSystem::Wait(int64 label) +{ +#if JOB_SYSTEM_ENABLED + PROFILE_CPU(); + + // Early out + if (label <= Platform::AtomicRead(&DoneLabel)) + return; + + // Wait on signal until input label is not yet done + CriticalSection mutex; + do + { + mutex.Lock(); + WaitSignal.Wait(mutex, 1); + mutex.Unlock(); + } while (label > Platform::AtomicRead(&DoneLabel) && Platform::AtomicRead(&ExitFlag) == 0); + +#if JOB_SYSTEM_USE_STATS + LOG(Info, "Job average dequeue time: {0} cycles", DequeueSum / DequeueCount); + DequeueSum = DequeueCount = 0; +#endif +#endif +} + +void JobSystem::SetJobStartingOnDispatch(bool value) +{ +#if JOB_SYSTEM_ENABLED + JobStartingOnDispatch = value; + + if (value) + { + JobsLocker.Lock(); + const int32 count = Jobs.Count(); + JobsLocker.Unlock(); + if (count == 1) + JobsSignal.NotifyOne(); + else if (count != 0) + JobsSignal.NotifyAll(); + } +#endif +} diff --git a/Source/Engine/Threading/JobSystem.h b/Source/Engine/Threading/JobSystem.h new file mode 100644 index 000000000..7a7e8cf7f --- /dev/null +++ b/Source/Engine/Threading/JobSystem.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Delegate.h" + +/// +/// Lightweight multi-threaded jobs execution scheduler. Uses a pool of threads and supports work-stealing concept. +/// +API_CLASS(Static) class FLAXENGINE_API JobSystem +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(JobSystem); + + /// + /// Dispatches the job for the execution. + /// + /// The job. Argument is an index of the job execution. + /// The job executions count. + /// The label identifying this dispatch. Can be used to wait for the execution end. + API_FUNCTION() static int64 Dispatch(const Function& job, int32 jobCount = 1); + + /// + /// Waits for all dispatched jobs to finish. + /// + API_FUNCTION() static void Wait(); + + /// + /// Waits for all dispatched jobs until a given label to finish (i.e. waits for a Dispatch that returned that label). + /// + /// The label. + API_FUNCTION() static void Wait(int64 label); + + /// + /// Sets whether automatically start jobs execution on Dispatch. If disabled jobs won't be executed until it gets re-enabled. Can be used to optimize execution of multiple dispatches that should overlap. + /// + API_FUNCTION() static void SetJobStartingOnDispatch(bool value); +}; diff --git a/Source/Engine/Threading/MainThreadTask.cpp b/Source/Engine/Threading/MainThreadTask.cpp index e6199986c..29bd60dc3 100644 --- a/Source/Engine/Threading/MainThreadTask.cpp +++ b/Source/Engine/Threading/MainThreadTask.cpp @@ -1,25 +1,68 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "MainThreadTask.h" -#include "ConcurrentTaskQueue.h" +#include "Engine/Platform/CriticalSection.h" #include "Engine/Profiler/ProfilerCPU.h" -ConcurrentTaskQueue MainThreadTasks; - -void MainThreadTask::RunAll() +namespace { - // TODO: use bulk dequeue + CriticalSection Locker; + Array Waiting; + Array Queue; +} +void MainThreadTask::RunAll(float dt) +{ PROFILE_CPU(); - - MainThreadTask* task; - while (MainThreadTasks.try_dequeue(task)) + Locker.Lock(); + for (int32 i = Waiting.Count() - 1; i >= 0; i--) { - task->Execute(); + auto task = Waiting[i]; + task->InitialDelay -= dt; + if (task->InitialDelay < ZeroTolerance) + { + Waiting.RemoveAt(i); + Queue.Add(task); + } } + for (int32 i = 0; i < Queue.Count(); i++) + { + Queue[i]->Execute(); + } + Queue.Clear(); + Locker.Unlock(); +} + +String MainThreadTask::ToString() const +{ + return String::Format(TEXT("Main Thread Task ({0})"), ::ToString(GetState())); } void MainThreadTask::Enqueue() { - MainThreadTasks.Add(this); + Locker.Lock(); + if (InitialDelay <= ZeroTolerance) + Queue.Add(this); + else + Waiting.Add(this); + Locker.Unlock(); +} + +bool MainThreadActionTask::Run() +{ + if (_action1.IsBinded()) + { + _action1(); + return false; + } + if (_action2.IsBinded()) + { + return _action2(); + } + return true; +} + +bool MainThreadActionTask::HasReference(Object* obj) const +{ + return obj == _target; } diff --git a/Source/Engine/Threading/MainThreadTask.h b/Source/Engine/Threading/MainThreadTask.h index 625c20c70..254733d9d 100644 --- a/Source/Engine/Threading/MainThreadTask.h +++ b/Source/Engine/Threading/MainThreadTask.h @@ -24,36 +24,20 @@ class FLAXENGINE_API MainThreadTask : public Task { friend class Engine; - -protected: - - /// - /// Initializes a new instance of the class. - /// - MainThreadTask() - : Task() - { - } - private: + static void RunAll(float dt); + +public: /// - /// Runs all main thread tasks. Called only by the Engine class. + /// The initial time delay (in seconds) before task execution. Use 0 to skip this feature. /// - static void RunAll(); + float InitialDelay = 0.0f; public: // [Task] - String ToString() const override - { - return String::Format(TEXT("Main Thread Task ({0})"), ::ToString(GetState())); - } - - bool HasReference(Object* obj) const override - { - return false; - } + String ToString() const override; protected: @@ -127,27 +111,10 @@ public: protected: // [MainThreadTask] - bool Run() override - { - if (_action1.IsBinded()) - { - _action1(); - return false; - } - - if (_action2.IsBinded()) - { - return _action2(); - } - - return true; - } + bool Run() override; public: // [MainThreadTask] - bool HasReference(Object* obj) const override - { - return obj == _target; - } + bool HasReference(Object* obj) const override; }; diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp index 1c4ad3e1e..f01d4b9e5 100644 --- a/Source/Engine/Threading/Task.cpp +++ b/Source/Engine/Threading/Task.cpp @@ -108,45 +108,6 @@ Task* Task::ContinueWith(Function action, Object* target) return ContinueWith(New(action, target)); } -Task* Task::Delay(int32 milliseconds) -{ - class DelayTask : public ThreadPoolTask - { - private: - - int32 _milliseconds; - DateTime _startTimeUTC; - - public: - - DelayTask(int32 milliseconds) - : _milliseconds(milliseconds) - { - } - - protected: - - // [ThreadPoolTask] - bool Run() override - { - // Take into account the different between task enqueue (OnStart event) and the actual task execution - auto diff = DateTime::NowUTC() - _startTimeUTC; - auto ms = Math::Max(0, _milliseconds - (int32)diff.GetTotalMilliseconds()); - - Platform::Sleep(ms); - - return false; - } - - void OnStart() override - { - _startTimeUTC = DateTime::NowUTC(); - } - }; - - return New(milliseconds); -} - Task* Task::StartNew(Task* task) { ASSERT(task); diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h index 119529f9f..7004c130e 100644 --- a/Source/Engine/Threading/Task.h +++ b/Source/Engine/Threading/Task.h @@ -45,36 +45,23 @@ protected: /// /// The cancel flag used to indicate that there is request to cancel task operation. /// - volatile int64 _cancelFlag; + volatile int64 _cancelFlag = 0; /// /// The current task state. /// - volatile TaskState _state; + volatile TaskState _state = TaskState::Created; /// /// The task to start after finish. /// - Task* _continueWith; - -protected: - - /// - /// Initializes a new instance of the class. - /// - Task() - : _cancelFlag(0) - , _state(TaskState::Created) - , _continueWith(nullptr) - { - } + Task* _continueWith = nullptr; public: /// - /// Gets work state + /// Gets the task state. /// - /// State FORCE_INLINE TaskState GetState() const { return static_cast(Platform::AtomicRead((int64 volatile*)&_state)); @@ -93,7 +80,6 @@ public: /// /// Gets the task to start after this one. /// - /// The next task. FORCE_INLINE Task* GetContinueWithTask() const { return _continueWith; @@ -102,45 +88,40 @@ public: public: /// - /// Checks if operation failed + /// Checks if operation failed. /// - /// True if operation failed, otherwise false FORCE_INLINE bool IsFailed() const { return GetState() == TaskState::Failed; } /// - /// Checks if operation has been canceled + /// Checks if operation has been canceled. /// - /// True if operation has been canceled, otherwise false FORCE_INLINE bool IsCanceled() const { return GetState() == TaskState::Canceled; } /// - /// Checks if operation has been queued + /// Checks if operation has been queued. /// - /// True if operation has been queued, otherwise false FORCE_INLINE bool IsQueued() const { return GetState() == TaskState::Queued; } /// - /// Checks if operation is running + /// Checks if operation is running. /// - /// True if operation is running, otherwise false FORCE_INLINE bool IsRunning() const { return GetState() == TaskState::Running; } /// - /// Checks if operation has been finished + /// Checks if operation has been finished. /// - /// True if operation has been finished, otherwise false FORCE_INLINE bool IsFinished() const { return GetState() == TaskState::Finished; @@ -149,7 +130,6 @@ public: /// /// Checks if operation has been ended (via cancel, fail or finish). /// - /// True if operation has been ended, otherwise false bool IsEnded() const { auto state = GetState(); @@ -159,7 +139,6 @@ public: /// /// Returns true if task has been requested to cancel it's operation. /// - /// True if task has been canceled and should stop it's work without calling child task start. FORCE_INLINE bool IsCancelRequested() { return Platform::AtomicRead(&_cancelFlag) != 0; @@ -244,25 +223,6 @@ public: /// Enqueued task. Task* ContinueWith(Function action, Object* target = nullptr); -public: - - /// - /// Creates a task that completes after a specified time interval (not started). - /// - /// The time span to wait before completing the returned task. - /// A task that represents the time delay (not started). - FORCE_INLINE static Task* Delay(const TimeSpan& delay) - { - return Delay(static_cast(delay.GetTotalMilliseconds())); - } - - /// - /// Creates a task that completes after a specified time interval (not started). - /// - /// The amount of milliseconds to wait before completing the returned task. - /// A task that represents the time delay (not started). - static Task* Delay(int32 milliseconds); - public: /// diff --git a/Source/Engine/Threading/TaskGraph.cpp b/Source/Engine/Threading/TaskGraph.cpp new file mode 100644 index 000000000..b36a85e31 --- /dev/null +++ b/Source/Engine/Threading/TaskGraph.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "TaskGraph.h" +#include "JobSystem.h" +#include "Engine/Core/Collections/Sorting.h" +#include "Engine/Profiler/ProfilerCPU.h" + +namespace +{ + bool SortTaskGraphSystem(TaskGraphSystem* const& a, TaskGraphSystem* const& b) + { + return b->Order > a->Order; + }; +} + +TaskGraphSystem::TaskGraphSystem(const SpawnParams& params) + : PersistentScriptingObject(params) +{ +} + +void TaskGraphSystem::AddDependency(TaskGraphSystem* system) +{ + _dependencies.Add(system); +} + +void TaskGraphSystem::PreExecute(TaskGraph* graph) +{ +} + +void TaskGraphSystem::Execute(TaskGraph* graph) +{ +} + +void TaskGraphSystem::PostExecute(TaskGraph* graph) +{ +} + +TaskGraph::TaskGraph(const SpawnParams& params) + : PersistentScriptingObject(params) +{ +} + +const Array>& TaskGraph::GetSystems() const +{ + return _systems; +} + +void TaskGraph::AddSystem(TaskGraphSystem* system) +{ + _systems.Add(system); +} + +void TaskGraph::RemoveSystem(TaskGraphSystem* system) +{ + _systems.Remove(system); +} + +void TaskGraph::Execute() +{ + PROFILE_CPU(); + + for (auto system : _systems) + system->PreExecute(this); + + _queue.Clear(); + _remaining.Clear(); + _remaining.Add(_systems); + + while (_remaining.HasItems()) + { + // Find systems without dependencies or with already executed dependencies + for (int32 i = _remaining.Count() - 1; i >= 0; i--) + { + auto e = _remaining[i]; + bool hasReadyDependencies = true; + for (auto d : e->_dependencies) + { + if (_remaining.Contains(d)) + { + hasReadyDependencies = false; + break; + } + } + if (hasReadyDependencies) + { + _queue.Add(e); + _remaining.RemoveAt(i); + } + } + + // End if no systems left + if (_queue.IsEmpty()) + break; + + // Execute in order + Sorting::QuickSort(_queue.Get(), _queue.Count(), &SortTaskGraphSystem); + JobSystem::SetJobStartingOnDispatch(false); + _currentLabel = 0; + for (int32 i = 0; i < _queue.Count(); i++) + { + _currentSystem = _queue[i]; + _currentSystem->Execute(this); + } + _currentSystem = nullptr; + _queue.Clear(); + + // Wait for async jobs to finish + JobSystem::SetJobStartingOnDispatch(true); + JobSystem::Wait(_currentLabel); + } + + for (auto system : _systems) + system->PostExecute(this); +} + +void TaskGraph::DispatchJob(const Function& job, int32 jobCount) +{ + ASSERT(_currentSystem); + _currentLabel = JobSystem::Dispatch(job, jobCount); +} diff --git a/Source/Engine/Threading/TaskGraph.h b/Source/Engine/Threading/TaskGraph.h new file mode 100644 index 000000000..142b0d2a1 --- /dev/null +++ b/Source/Engine/Threading/TaskGraph.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Core/Collections/Array.h" + +class TaskGraph; + +/// +/// System that can generate work into Task Graph for asynchronous execution. +/// +API_CLASS(Abstract) class FLAXENGINE_API TaskGraphSystem : public PersistentScriptingObject +{ +DECLARE_SCRIPTING_TYPE(TaskGraphSystem); + friend TaskGraph; +private: + Array> _dependencies; + +public: + /// + /// The execution order of the system (systems with higher order are executed later, lower first). + /// + API_FIELD() int32 Order = 0; + +public: + /// + /// Adds the dependency on the system execution. Before this system can be executed the given dependant system has to be executed first. + /// + /// The system to depend on. + API_FUNCTION() void AddDependency(TaskGraphSystem* system); + + /// + /// Called before executing any systems of the graph. Can be used to initialize data (synchronous). + /// + /// The graph executing the system. + API_FUNCTION() virtual void PreExecute(TaskGraph* graph); + + /// + /// Executes the system logic and schedules the asynchronous work. + /// + /// The graph executing the system. + API_FUNCTION() virtual void Execute(TaskGraph* graph); + + /// + /// Called after executing all systems of the graph. Can be used to cleanup data (synchronous). + /// + /// The graph executing the system. + API_FUNCTION() virtual void PostExecute(TaskGraph* graph); +}; + +/// +/// Graph-based asynchronous tasks scheduler for high-performance computing and processing. +/// +API_CLASS() class FLAXENGINE_API TaskGraph : public PersistentScriptingObject +{ +DECLARE_SCRIPTING_TYPE(TaskGraph); +private: + Array> _systems; + Array> _remaining; + Array> _queue; + TaskGraphSystem* _currentSystem = nullptr; + int64 _currentLabel = 0; + +public: + /// + /// Gets the list of systems. + /// + API_PROPERTY() const Array>& GetSystems() const; + + /// + /// Adds the system to the graph for the execution. + /// + /// The system to add. + API_FUNCTION() void AddSystem(TaskGraphSystem* system); + + /// + /// Removes the system from the graph. + /// + /// The system to add. + API_FUNCTION() void RemoveSystem(TaskGraphSystem* system); + + /// + /// Schedules the asynchronous systems execution including ordering and dependencies handling. + /// + API_FUNCTION() void Execute(); + + /// + /// Dispatches the job for the execution. + /// + /// Call only from system's Execute method to properly schedule job. + /// The job. Argument is an index of the job execution. + /// The job executions count. + API_FUNCTION() void DispatchJob(const Function& job, int32 jobCount = 1); +}; diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index b37b18661..c3395ff56 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -69,7 +69,8 @@ public: return result; } - void GetValues(Array& result) const + template + void GetValues(Array& result) const { result.EnsureCapacity(MaxThreads); for (int32 i = 0; i < MaxThreads; i++) @@ -134,7 +135,8 @@ public: } } - void GetNotNullValues(Array& result) const + template + void GetNotNullValues(Array& result) const { result.EnsureCapacity(MaxThreads); for (int32 i = 0; i < MaxThreads; i++) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index bd8970c59..be85b502d 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -16,6 +16,7 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #if USE_EDITOR +#include "Engine/Core/Collections/Dictionary.h" namespace { Dictionary TexturesHasAlphaCache; @@ -35,6 +36,7 @@ TextureTool::Options::Options() Resize = false; PreserveAlphaCoverage = false; PreserveAlphaCoverageReference = 0.5f; + TextureGroup = -1; Scale = 1.0f; SizeX = 1024; SizeY = 1024; @@ -97,6 +99,9 @@ void TextureTool::Options::Serialize(SerializeStream& stream, const void* otherO stream.JKEY("PreserveAlphaCoverageReference"); stream.Float(PreserveAlphaCoverageReference); + stream.JKEY("TextureGroup"); + stream.Int(TextureGroup); + stream.JKEY("Scale"); stream.Float(Scale); @@ -145,6 +150,7 @@ void TextureTool::Options::Deserialize(DeserializeStream& stream, ISerializeModi Resize = JsonTools::GetBool(stream, "Resize", Resize); PreserveAlphaCoverage = JsonTools::GetBool(stream, "PreserveAlphaCoverage", PreserveAlphaCoverage); PreserveAlphaCoverageReference = JsonTools::GetFloat(stream, "PreserveAlphaCoverageReference", PreserveAlphaCoverageReference); + TextureGroup = JsonTools::GetInt(stream, "TextureGroup", TextureGroup); Scale = JsonTools::GetFloat(stream, "Scale", Scale); SizeX = JsonTools::GetInt(stream, "SizeX", SizeX); SizeY = JsonTools::GetInt(stream, "SizeY", SizeY); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 6337406ae..a9f957f00 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -78,6 +78,11 @@ public: /// float PreserveAlphaCoverageReference; + /// + /// Texture group for streaming (negative if unused). See Streaming Settings. + /// + int32 TextureGroup; + /// /// The import texture scale. /// diff --git a/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs b/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs index 05c0bb2e2..eb426eac8 100644 --- a/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs @@ -48,4 +48,76 @@ namespace FlaxEngine.GUI Render2D.DrawSprite(Sprite, rect, color); } } + + /// + /// Implementation of for using 9-slicing. + /// + /// + public sealed class Sprite9SlicingBrush : IBrush + { + /// + /// The sprite. + /// + [ExpandGroups, EditorOrder(0), Tooltip("The sprite.")] + public SpriteHandle Sprite; + + /// + /// The texture sampling filter mode. + /// + [ExpandGroups, EditorOrder(1), Tooltip("The texture sampling filter mode.")] + public BrushFilter Filter = BrushFilter.Linear; + + /// + /// The border size. + /// + [ExpandGroups, EditorOrder(2), Limit(0), Tooltip("The border size.")] + public float BorderSize = 10.0f; + + /// + /// The sprite borders (in texture space, range 0-1). + /// + [ExpandGroups, EditorOrder(3), Limit(0, 1), Tooltip("The sprite borders (in texture space, range 0-1).")] + public Margin Border = new Margin(0.1f); + +#if FLAX_EDITOR + /// + /// Displays borders (editor only). + /// + [NoSerialize, EditorOrder(4), Tooltip("Displays borders (editor only).")] + public bool ShowBorders; +#endif + + /// + /// Initializes a new instance of the class. + /// + public Sprite9SlicingBrush() + { + } + + /// + public Vector2 Size => Sprite.IsValid ? Sprite.Size : Vector2.Zero; + + /// + public unsafe void Draw(Rectangle rect, Color color) + { + var border = Border; + var borderUV = *(Vector4*)&border; + var borderSize = borderUV * new Vector4(BorderSize, BorderSize, BorderSize, BorderSize); + if (Filter == BrushFilter.Point) + Render2D.Draw9SlicingSpritePoint(Sprite, rect, borderSize, borderUV, color); + else + Render2D.Draw9SlicingSprite(Sprite, rect, borderSize, borderUV, color); +#if FLAX_EDITOR + if (ShowBorders) + { + var bordersRect = rect; + bordersRect.Location.X += borderSize.X; + bordersRect.Location.Y += borderSize.Z; + bordersRect.Size.X -= borderSize.X + borderSize.Y; + bordersRect.Size.Y -= borderSize.Z + borderSize.W; + Render2D.DrawRectangle(bordersRect, Color.YellowGreen, 2.0f); + } +#endif + } + } } diff --git a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs index 1f772e0a6..7e63e75f2 100644 --- a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs +++ b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs @@ -1,7 +1,5 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System; - namespace FlaxEngine.GUI { /// @@ -50,4 +48,87 @@ namespace FlaxEngine.GUI Render2D.DrawTexture(Texture, rect, color); } } + + /// + /// Implementation of for using 9-slicing. + /// + /// + public sealed class Texture9SlicingBrush : IBrush + { + /// + /// The texture. + /// + [ExpandGroups, EditorOrder(0), Tooltip("The texture asset.")] + public Texture Texture; + + /// + /// The texture sampling filter mode. + /// + [ExpandGroups, EditorOrder(1), Tooltip("The texture sampling filter mode.")] + public BrushFilter Filter = BrushFilter.Linear; + + /// + /// The border size. + /// + [ExpandGroups, EditorOrder(2), Limit(0), Tooltip("The border size.")] + public float BorderSize = 10.0f; + + /// + /// The texture borders (in texture space, range 0-1). + /// + [ExpandGroups, EditorOrder(3), Limit(0, 1), Tooltip("The texture borders (in texture space, range 0-1).")] + public Margin Border = new Margin(0.1f); + +#if FLAX_EDITOR + /// + /// Displays borders (editor only). + /// + [NoSerialize, EditorOrder(4), Tooltip("Displays borders (editor only).")] + public bool ShowBorders; +#endif + + /// + /// Initializes a new instance of the class. + /// + public Texture9SlicingBrush() + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The texture. + public Texture9SlicingBrush(Texture texture) + { + Texture = texture; + } + + /// + public Vector2 Size => Texture?.Size ?? Vector2.Zero; + + /// + public unsafe void Draw(Rectangle rect, Color color) + { + if (Texture == null) + return; + var border = Border; + var borderUV = *(Vector4*)&border; + var borderSize = borderUV * new Vector4(BorderSize, BorderSize, BorderSize, BorderSize); + if (Filter == BrushFilter.Point) + Render2D.Draw9SlicingTexturePoint(Texture, rect, borderSize, borderUV, color); + else + Render2D.Draw9SlicingTexture(Texture, rect, borderSize, borderUV, color); +#if FLAX_EDITOR + if (ShowBorders) + { + var bordersRect = rect; + bordersRect.Location.X += borderSize.X; + bordersRect.Location.Y += borderSize.Z; + bordersRect.Size.X -= borderSize.X + borderSize.Y; + bordersRect.Size.Y -= borderSize.Z + borderSize.W; + Render2D.DrawRectangle(bordersRect, Color.YellowGreen, 2.0f); + } +#endif + } + } } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 1b06e1867..ba5763f31 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -185,8 +185,8 @@ namespace FlaxEngine.GUI AutoFocus = false; var style = Style.Current; Font = new FontReference(style.FontMedium); - TextColor = Style.Current.Foreground; - TextColorHighlighted = Style.Current.Foreground; + TextColor = style.Foreground; + TextColorHighlighted = style.Foreground; } /// @@ -196,8 +196,8 @@ namespace FlaxEngine.GUI AutoFocus = false; var style = Style.Current; Font = new FontReference(style.FontMedium); - TextColor = Style.Current.Foreground; - TextColorHighlighted = Style.Current.Foreground; + TextColor = style.Foreground; + TextColorHighlighted = style.Foreground; } /// diff --git a/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs b/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs index e2e1532f3..5e3978cd7 100644 --- a/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs +++ b/Source/Engine/UI/GUI/Common/RenderToTextureControl.cs @@ -95,6 +95,8 @@ namespace FlaxEngine.GUI return; } } + if (!_texture || !_texture.IsAllocated) + return; Profiler.BeginEventGPU("RenderToTextureControl"); var context = GPUDevice.Instance.MainContext; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index 837ec0ec7..3e89ed584 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -9,12 +9,7 @@ namespace FlaxEngine.GUI /// public class RichTextBox : RichTextBoxBase { - private TextBlockStyle _textStyle = new TextBlockStyle - { - Font = new FontReference(Style.Current.FontMedium), - Color = Style.Current.Foreground, - BackgroundSelectedBrush = new SolidColorBrush(Style.Current.BackgroundSelected), - }; + private TextBlockStyle _textStyle; /// /// The text style applied to the whole text. @@ -30,6 +25,20 @@ namespace FlaxEngine.GUI } } + /// + /// Initializes a new instance of the class. + /// + public RichTextBox() + { + var style = Style.Current; + _textStyle = new TextBlockStyle + { + Font = new FontReference(style.FontMedium), + Color = style.Foreground, + BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), + }; + } + /// protected override void OnParseTextBlocks() { diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 0dcfdd797..2cfe11fff 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -241,11 +241,12 @@ namespace FlaxEngine.GUI // Calculate text blocks for drawing var textBlocks = Utils.ExtractArrayFromList(_textBlocks); + var textBlocksCount = _textBlocks?.Count ?? 0; var hasSelection = HasSelection; var selection = new TextRange(SelectionLeft, SelectionRight); var viewRect = new Rectangle(_viewOffset, Size).MakeExpanded(10.0f); - var firstTextBlock = _textBlocks.Count; - for (int i = 0; i < _textBlocks.Count; i++) + var firstTextBlock = textBlocksCount; + for (int i = 0; i < textBlocksCount; i++) { ref TextBlock textBlock = ref textBlocks[i]; if (textBlock.Bounds.Intersects(ref viewRect)) @@ -254,8 +255,8 @@ namespace FlaxEngine.GUI break; } } - var endTextBlock = Mathf.Min(firstTextBlock + 1, _textBlocks.Count); - for (int i = _textBlocks.Count - 1; i > firstTextBlock; i--) + var endTextBlock = Mathf.Min(firstTextBlock + 1, textBlocksCount); + for (int i = textBlocksCount - 1; i > firstTextBlock; i--) { ref TextBlock textBlock = ref textBlocks[i]; if (textBlock.Bounds.Intersects(ref viewRect)) diff --git a/Source/Engine/UI/GUI/Margin.cs b/Source/Engine/UI/GUI/Margin.cs index 01c0ac3ab..cc6ba7ce9 100644 --- a/Source/Engine/UI/GUI/Margin.cs +++ b/Source/Engine/UI/GUI/Margin.cs @@ -29,21 +29,25 @@ namespace FlaxEngine.GUI /// /// Holds the margin to the left. /// + [EditorOrder(0)] public float Left; /// /// Holds the margin to the right. /// + [EditorOrder(1)] public float Right; /// /// Holds the margin to the top. /// + [EditorOrder(2)] public float Top; /// /// Holds the margin to the bottom. /// + [EditorOrder(3)] public float Bottom; /// diff --git a/Source/Engine/UI/GUI/RenderOutputControl.cs b/Source/Engine/UI/GUI/RenderOutputControl.cs index 9a7f6dfb1..bad38b3c3 100644 --- a/Source/Engine/UI/GUI/RenderOutputControl.cs +++ b/Source/Engine/UI/GUI/RenderOutputControl.cs @@ -96,7 +96,7 @@ namespace FlaxEngine.GUI { _task = task ?? throw new ArgumentNullException(); - _backBuffer = GPUDevice.Instance.CreateTexture(); + _backBuffer = GPUDevice.Instance.CreateTexture("RenderOutputControl.BackBuffer"); _resizeTime = ResizeCheckTime; _task.Output = _backBuffer; @@ -245,7 +245,7 @@ namespace FlaxEngine.GUI if (_backBufferOld == null && _backBuffer.IsAllocated) { _backBufferOld = _backBuffer; - _backBuffer = GPUDevice.Instance.CreateTexture(); + _backBuffer = GPUDevice.Instance.CreateTexture("RenderOutputControl.BackBuffer"); } // Set timeout to remove old buffer diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index 9b4f0f64e..5b77e8682 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -22,7 +22,7 @@ namespace FlaxEngine.GUI [EditorOrder(10)] public Font FontTitle { - get => _fontTitle.GetFont(); + get => _fontTitle?.GetFont(); set => _fontTitle = new FontReference(value); } @@ -36,7 +36,7 @@ namespace FlaxEngine.GUI [EditorOrder(20)] public Font FontLarge { - get => _fontLarge.GetFont(); + get => _fontLarge?.GetFont(); set => _fontLarge = new FontReference(value); } @@ -50,7 +50,7 @@ namespace FlaxEngine.GUI [EditorOrder(30)] public Font FontMedium { - get => _fontMedium.GetFont(); + get => _fontMedium?.GetFont(); set => _fontMedium = new FontReference(value); } @@ -64,7 +64,7 @@ namespace FlaxEngine.GUI [EditorOrder(40)] public Font FontSmall { - get => _fontSmall.GetFont(); + get => _fontSmall?.GetFont(); set => _fontSmall = new FontReference(value); } @@ -152,6 +152,12 @@ namespace FlaxEngine.GUI [EditorOrder(190)] public Color TextBoxBackgroundSelected; + /// + /// The collection background color. + /// + [EditorOrder(195)] + public Color CollectionBackgroundColor; + /// /// The progress normal color. /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 4541124e7..e6690c82d 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -72,19 +72,17 @@ namespace FlaxEngine.GUI Vector2 dpiSize = Size * dpiScale; Vector2 locationWS = target.PointToWindow(location); Vector2 locationSS = parentWin.PointToScreen(locationWS); - Vector2 screenSize = Platform.VirtualDesktopSize; - Vector2 parentWinLocationSS = parentWin.PointToScreen(Vector2.Zero); - float parentWinRightSS = parentWinLocationSS.Y + parentWin.Size.Y; - float parentWinBottomSS = parentWinLocationSS.X + parentWin.Size.X; + Rectangle monitorBounds = Platform.GetMonitorBounds(locationSS); + Vector2 rightBottomMonitorBounds = monitorBounds.BottomRight; Vector2 rightBottomLocationSS = locationSS + dpiSize; // Prioritize tooltip placement within parent window, fall back to virtual desktop - if (parentWinRightSS < rightBottomLocationSS.Y || screenSize.Y < rightBottomLocationSS.Y) + if (rightBottomMonitorBounds.Y < rightBottomLocationSS.Y) { // Direction: up locationSS.Y -= dpiSize.Y; } - if (parentWinBottomSS < rightBottomLocationSS.X || screenSize.X < rightBottomLocationSS.X) + if (rightBottomMonitorBounds.X < rightBottomLocationSS.X) { // Direction: left locationSS.X -= dpiSize.X; diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index fdb413837..3b84a42f3 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -77,7 +77,11 @@ namespace FlaxEngine GPUTexture depthBuffer = Canvas.IgnoreDepth ? null : renderContext.Buffers.DepthBuffer; // Render GUI in 3D + var features = Render2D.Features; + if (Canvas.RenderMode == CanvasRenderMode.WorldSpace || Canvas.RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) + Render2D.Features &= ~Render2D.RenderingFeatures.VertexSnapping; Render2D.CallDrawing(Canvas.GUI, context, input, depthBuffer, ref viewProjectionMatrix); + Render2D.Features = features; Profiler.EndEventGPU(); } diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index b9fef02f8..6afdd77b3 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -118,7 +118,7 @@ void UIControl::Deserialize(DeserializeStream& stream, ISerializeModifier* modif const auto controlMember = stream.FindMember("Control"); if (controlMember != stream.MemberEnd()) { - const StringAnsiView controlType(controlMember->value.GetString(), controlMember->value.GetStringLength()); + const StringAnsiView controlType(controlMember->value.GetStringAnsiView()); const auto type = Scripting::FindClass(controlType); if (type != nullptr) { diff --git a/Source/Engine/Utilities/Nameof.h b/Source/Engine/Utilities/Nameof.h new file mode 100644 index 000000000..8a50b7e39 --- /dev/null +++ b/Source/Engine/Utilities/Nameof.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/StringView.h" + +// Helper utility to get type name at compile time. Example: +// constexpr StringAnsiView name = Nameof::Get() + +namespace Nameof +{ + // [Reference: https://github.com/Neargye/nameof] + constexpr StringAnsiView PrettyName(StringAnsiView name) noexcept + { + if (name.Length() >= 1 && (name.Get()[0] == '"' || name.Get()[0] == '\'')) + return {}; + if (name.Length() >= 2 && name.Get()[0] == 'R' && (name.Get()[1] == '"' || name.Get()[1] == '\'')) + return {}; + if (name.Length() >= 2 && name.Get()[0] == 'L' && (name.Get()[1] == '"' || name.Get()[1] == '\'')) + return {}; + if (name.Length() >= 2 && name.Get()[0] == 'U' && (name.Get()[1] == '"' || name.Get()[1] == '\'')) + return {}; + if (name.Length() >= 2 && name.Get()[0] == 'u' && (name.Get()[1] == '"' || name.Get()[1] == '\'')) + return {}; + if (name.Length() >= 3 && name.Get()[0] == 'u' && name.Get()[1] == '8' && (name.Get()[2] == '"' || name.Get()[2] == '\'')) + return {}; + if (name.Length() >= 1 && (name.Get()[0] >= '0' && name.Get()[0] <= '9')) + return {}; + for (int32 i = name.Length(), h = 0, s = 0; i > 0; --i) + { + if (name.Get()[i - 1] == ')') + { + ++h; + ++s; + continue; + } + if (name.Get()[i - 1] == '(') + { + --h; + ++s; + continue; + } + if (h == 0) + { + name = StringAnsiView(*name, name.Length() - s); + break; + } + ++s; + } + int32 s = 0; + for (int32 i = name.Length(), h = 0; i > 0; --i) + { + if (name.Get()[i - 1] == '>') + { + ++h; + ++s; + continue; + } + if (name.Get()[i - 1] == '<') + { + --h; + ++s; + continue; + } + if (h == 0) + break; + ++s; + } + for (int32 i = name.Length() - s; i > 0; --i) + { + if (!((name.Get()[i - 1] >= '0' && name.Get()[i - 1] <= '9') || (name.Get()[i - 1] >= 'a' && name.Get()[i - 1] <= 'z') || (name.Get()[i - 1] >= 'A' && name.Get()[i - 1] <= 'Z') || (name.Get()[i - 1] == '_'))) + { + name = StringAnsiView(*name + i, name.Length() - i); + break; + } + } + name = StringAnsiView(*name, name.Length() - s); + if (name.Length() > 0 && ((name.Get()[0] >= 'a' && name.Get()[0] <= 'z') || (name.Get()[0] >= 'A' && name.Get()[0] <= 'Z') || (name.Get()[0] == '_'))) + return name; + return {}; + } + + // Gets the type name as constant expression (compile-time string) + template + constexpr StringAnsiView Get() noexcept + { +#if defined(__clang__) || defined(__GNUC__) + return PrettyName(StringAnsiView(__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2)); +#elif defined(_MSC_VER) + return PrettyName(StringAnsiView(__FUNCSIG__, sizeof(__FUNCSIG__) - 17)); +#else +#error "Unsupported compiler." +#endif + } +} diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index ab4fdf17b..f0c9d4452 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -17,14 +17,45 @@ namespace FlaxEngine /// /// Copies data from one memory location to another using an unmanaged memory pointers. /// - /// - /// Uses low-level memcpy call. - /// + /// Uses low-level platform impl. + /// The source location. + /// The destination location. + /// The length (amount of bytes to copy). + [Obsolete("Use MemoryCopy with long length and source/destination swapped to match C++ API.")] + public static void MemoryCopy(IntPtr source, IntPtr destination, int length) + { + // [Deprecated on 30.05.2021, expires on 30.05.2022] + MemoryCopy(destination, source, (ulong)length); + } + + /// + /// Copies data from one memory location to another using an unmanaged memory pointers. + /// + /// Uses low-level platform impl. /// The source location. /// The destination location. /// The length (amount of bytes to copy). [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void MemoryCopy(IntPtr source, IntPtr destination, int length); + public static extern void MemoryCopy(IntPtr destination, IntPtr source, ulong length); + + /// + /// Clears the memory region with zeros. + /// + /// Uses low-level platform impl. + /// Destination memory address + /// Size of the memory to clear in bytes + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void MemoryClear(IntPtr dst, ulong size); + + /// + /// Compares two blocks of the memory. + /// + /// Uses low-level platform impl. + /// The first buffer address. + /// The second buffer address. + /// Size of the memory to compare in bytes. + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern int MemoryCompare(IntPtr buf1, IntPtr buf2, ulong size); /// /// Rounds the floating point value up to 1 decimal place. @@ -180,16 +211,12 @@ namespace FlaxEngine internal static T[] ExtractArrayFromList(List list) { - T[] result = null; - if (list != null) - { - // TODO: move it to the native code - var field = list.GetType().GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic); - result = (T[])field.GetValue(list); - } - return result; + return list != null ? (T[])Internal_ExtractArrayFromList(list) : null; } + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern Array Internal_ExtractArrayFromList(object list); + /// /// Reads the color from the binary stream. /// @@ -249,7 +276,7 @@ namespace FlaxEngine { return new Int2(stream.ReadInt32(), stream.ReadInt32()); } - + /// /// Reads the Int3 from the binary stream. /// @@ -259,7 +286,7 @@ namespace FlaxEngine { return new Int3(stream.ReadInt32(), stream.ReadInt32(), stream.ReadInt32()); } - + /// /// Reads the Int4 from the binary stream. /// @@ -269,7 +296,7 @@ namespace FlaxEngine { return new Int4(stream.ReadInt32(), stream.ReadInt32(), stream.ReadInt32(), stream.ReadInt32()); } - + /// /// Reads the Quaternion from the binary stream. /// @@ -402,7 +429,7 @@ namespace FlaxEngine stream.Write(value.X); stream.Write(value.Y); } - + /// /// Writes the Int3 to the binary stream. /// diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 7be06566e..d38234c6d 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -18,13 +18,6 @@ class VisjectGraphNode; class VisjectGraphBox : public GraphBox { -public: - - /// - /// The cached value. - /// - Variant Cache; - public: VisjectGraphBox() diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index f7f535a40..2fe4bf65a 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")] -[assembly: AssemblyVersion("1.1.6219")] -[assembly: AssemblyFileVersion("1.1.6219")] +[assembly: AssemblyVersion("1.1.6221")] +[assembly: AssemblyFileVersion("1.1.6221")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 46eefd64f..add6b8f11 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 1, 6219) -#define FLAXENGINE_VERSION_TEXT "1.1.6219" +#define FLAXENGINE_VERSION Version(1, 1, 6221) +#define FLAXENGINE_VERSION_TEXT "1.1.6221" #define FLAXENGINE_VERSION_MAJOR 1 #define FLAXENGINE_VERSION_MINOR 1 -#define FLAXENGINE_VERSION_BUILD 6219 +#define FLAXENGINE_VERSION_BUILD 6221 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved." diff --git a/Source/Shaders/BakeLightmap.shader b/Source/Shaders/BakeLightmap.shader index 14f99b4f4..a1dc90f73 100644 --- a/Source/Shaders/BakeLightmap.shader +++ b/Source/Shaders/BakeLightmap.shader @@ -325,6 +325,8 @@ META_CS(true, FEATURE_LEVEL_SM5) [numthreads(1, 1, 1)] void CS_BlurEmpty(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadID) { + if (GroupID.x >= AtlasSize || GroupID.y > AtlasSize) + return; const int2 location = int2(GroupID.x, GroupID.y); const uint texelAdress = (location.y * AtlasSize + location.x) * NUM_SH_TARGETS; @@ -396,7 +398,7 @@ void CS_BlurEmpty(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThre #elif defined(_CS_Dilate) -Buffer InputBuffer : register(t0); +Buffer InputBuffer : register(t0); RWBuffer OutputBuffer : register(u0); // Fills the empty lightmap texels with blurred data of the surroundings texels (uses only valid ones) @@ -404,6 +406,8 @@ META_CS(true, FEATURE_LEVEL_SM5) [numthreads(1, 1, 1)] void CS_Dilate(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadID) { + if (GroupID.x >= AtlasSize || GroupID.y > AtlasSize) + return; const int2 location = int2(GroupID.x, GroupID.y); const uint texelAdress = (location.y * AtlasSize + location.x) * NUM_SH_TARGETS; @@ -431,11 +435,9 @@ void CS_Dilate(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadI for (int sampleIndex = 0; sampleIndex < 9; sampleIndex++) { int2 sampleLocation = location + int2(OffsetX[sampleIndex], OffsetY[sampleIndex]); - if (sampleLocation.x >= 0 && sampleLocation.x < AtlasSize && sampleLocation.y >= 0 && sampleLocation.y < AtlasSize) { uint sampleAdress = (sampleLocation.y * AtlasSize + sampleLocation.x) * NUM_SH_TARGETS; - float4 sample0 = InputBuffer[sampleAdress + 0]; float4 sample1 = InputBuffer[sampleAdress + 1]; float4 sample2 = InputBuffer[sampleAdress + 2]; @@ -454,7 +456,6 @@ void CS_Dilate(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadI if (total > 0) { total = 1.0f / total; - OutputBuffer[texelAdress + 0] = total0 * total; OutputBuffer[texelAdress + 1] = total1 * total; OutputBuffer[texelAdress + 2] = total2 * total; @@ -470,6 +471,8 @@ META_CS(true, FEATURE_LEVEL_SM5) [numthreads(1, 1, 1)] void CS_Finalize(uint3 GroupID : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadID) { + if (GroupID.x >= AtlasSize || GroupID.y > AtlasSize) + return; const int2 location = int2(GroupID.x, GroupID.y); const uint texelAdress = (location.y * AtlasSize + location.x) * NUM_SH_TARGETS; diff --git a/Source/ThirdParty/mono-2.0/mono.Build.cs b/Source/ThirdParty/mono-2.0/mono.Build.cs index 9fa592f35..873f368f4 100644 --- a/Source/ThirdParty/mono-2.0/mono.Build.cs +++ b/Source/ThirdParty/mono-2.0/mono.Build.cs @@ -87,7 +87,7 @@ public class mono : DepsModule options.Libraries.Add(Path.Combine(depsRoot, "libmonosgen-2.0.so")); break; case TargetPlatform.Switch: - // TODO: mono for Switch + options.OutputFiles.Add(Path.Combine(depsRoot, "libmonosgen-2.0.a")); break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/ThirdParty/rapidjson/document.h b/Source/ThirdParty/rapidjson/document.h index a6a6e4d9c..9d9fe2eb4 100644 --- a/Source/ThirdParty/rapidjson/document.h +++ b/Source/ThirdParty/rapidjson/document.h @@ -1648,6 +1648,7 @@ public: //@{ const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + ::StringAnsiView GetStringAnsiView() const { RAPIDJSON_ASSERT(IsString()); return data_.f.flags & kInlineStrFlag ? ::StringAnsiView(data_.ss.str, data_.ss.GetLength()) : ::StringAnsiView(GetStringPointer(), data_.s.length); } ::String GetText() const { ::String result; diff --git a/Source/ThirdParty/tracy/tracy.Build.cs b/Source/ThirdParty/tracy/tracy.Build.cs index aa10c514f..abcc83da4 100644 --- a/Source/ThirdParty/tracy/tracy.Build.cs +++ b/Source/ThirdParty/tracy/tracy.Build.cs @@ -33,6 +33,8 @@ public class tracy : ThirdPartyModule options.SourceFiles.Add(Path.Combine(FolderPath, "TracyClient.cpp")); options.PublicDefinitions.Add("TRACY_ENABLE"); + if (options.Platform.Target == TargetPlatform.Windows) + options.PrivateDefinitions.Add("TRACY_DBGHELP_LOCK=DbgHelp"); } /// diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 3e2ad1b31..6667aab9f 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -71,6 +71,13 @@ namespace Flax.Build.Bindings } } + public void EnsureInited(Builder.BuildData buildData) + { + if (IsInited) + return; + Init(buildData); + } + public virtual void Init(Builder.BuildData buildData) { IsInited = true; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index 27c902a5c..42f785e16 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -210,17 +210,15 @@ namespace Flax.Build.Bindings if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "Dictionary" || typeInfo.Type == "HashSet") && typeInfo.GenericArgs != null) return false; - // Skip for BytesContainer + // Skip for special types if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) return false; - - // Skip for Variant if (typeInfo.Type == "Variant" && typeInfo.GenericArgs == null) return false; - - // Skip for VariantType if (typeInfo.Type == "VariantType" && typeInfo.GenericArgs == null) return false; + if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) + return false; // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); @@ -261,7 +259,7 @@ namespace Flax.Build.Bindings { if (typeInfo == null) return null; - var result = FindApiTypeInfoInner(typeInfo, caller); + var result = FindApiTypeInfoInner(buildData, typeInfo, caller); if (result != null) return result; if (buildData.TypeCache.TryGetValue(typeInfo, out result)) @@ -274,7 +272,7 @@ namespace Flax.Build.Bindings // Find across all loaded modules for this build foreach (var e in buildData.ModulesInfo) { - result = FindApiTypeInfoInner(typeInfo, e.Value); + result = FindApiTypeInfoInner(buildData, typeInfo, e.Value); if (result != null) { buildData.TypeCache.Add(typeInfo, result); @@ -291,7 +289,7 @@ namespace Flax.Build.Bindings { if (result == null) return null; - result = FindApiTypeInfoInner(new TypeInfo { Type = nesting[i], }, result); + result = FindApiTypeInfoInner(buildData, new TypeInfo { Type = nesting[i], }, result); } return result; } @@ -301,14 +299,17 @@ namespace Flax.Build.Bindings return null; } - private static ApiTypeInfo FindApiTypeInfoInner(TypeInfo typeInfo, ApiTypeInfo parent) + private static ApiTypeInfo FindApiTypeInfoInner(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo parent) { foreach (var child in parent.Children) { if (child.Name == typeInfo.Type) + { + child.EnsureInited(buildData); return child; + } - var result = FindApiTypeInfoInner(typeInfo, child); + var result = FindApiTypeInfoInner(buildData, typeInfo, child); if (result != null) return result; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 26fa55231..a4952c7e0 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -14,7 +14,7 @@ namespace Flax.Build.Bindings private static readonly HashSet CSharpUsedNamespaces = new HashSet(); private static readonly List CSharpUsedNamespacesSorted = new List(); - private static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary() + internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary() { // Language types { "int8", "sbyte" }, @@ -32,7 +32,7 @@ namespace Flax.Build.Bindings { "double", "double" }, }; - private static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary() + internal static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary() { // Engine types { "String", "string" }, @@ -62,7 +62,39 @@ namespace Flax.Build.Bindings if (value.StartsWith("TEXT(\"") && value.EndsWith("\")")) return value.Substring(5, value.Length - 6); + // In-built constants + switch (value) + { + case "nullptr": + case "NULL": + case "String::Empty": + case "StringView::Empty": return "null"; + case "MAX_int8": return "sbyte.MaxValue"; + case "MAX_uint8": return "byte.MaxValue"; + case "MAX_int16": return "short.MaxValue"; + case "MAX_uint16": return "ushort.MaxValue"; + case "MAX_int32": return "int.MaxValue"; + case "MAX_uint32": return "uint.MaxValue"; + case "MAX_int64": return "long.MaxValue"; + case "MAX_uint64": return "ulong.MaxValue"; + case "MAX_float": return "float.MaxValue"; + case "MAX_double": return "double.MaxValue"; + case "true": + case "false": return value; + } + + // Numbers + if (float.TryParse(value, out _) || (value[value.Length - 1] == 'f' && float.TryParse(value.Substring(0, value.Length - 1), out _))) + return value; + value = value.Replace("::", "."); + var dot = value.LastIndexOf('.'); + ApiTypeInfo apiType = null; + if (dot != -1) + { + var type = new TypeInfo { Type = value.Substring(0, dot) }; + apiType = FindApiTypeInfo(buildData, type, caller); + } if (attribute) { @@ -95,45 +127,23 @@ namespace Flax.Build.Bindings case "Quaternion.Identity": return "typeof(Quaternion), \"0,0,0,1\""; } + // Enums + if (apiType != null && apiType.IsEnum) + return value; + return null; } } // Skip constants unsupported in C# - var dot = value.LastIndexOf('.'); - if (dot != -1) - { - var type = new TypeInfo { Type = value.Substring(0, dot) }; - var apiType = FindApiTypeInfo(buildData, type, caller); - if (apiType != null && apiType.IsStruct) - return null; - } + if (apiType != null && apiType.IsStruct) + return null; // Special case for value constructors if (value.Contains('(') && value.Contains(')')) return "new " + value; - // Convert from C++ to C# - switch (value) - { - case "nullptr": - case "NULL": - case "String.Empty": - case "StringView.Empty": return "null"; - - case "MAX_int8": return "sbyte.MaxValue"; - case "MAX_uint8": return "byte.MaxValue"; - case "MAX_int16": return "short.MaxValue"; - case "MAX_uint16": return "ushort.MaxValue"; - case "MAX_int32": return "int.MaxValue"; - case "MAX_uint32": return "uint.MaxValue"; - case "MAX_int64": return "long.MaxValue"; - case "MAX_uint64": return "ulong.MaxValue"; - case "MAX_float": return "float.MaxValue"; - case "MAX_double": return "double.MaxValue"; - - default: return value; - } + return value; } private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) @@ -197,14 +207,35 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) return "byte[]"; + // Function + if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) + { + if (typeInfo.GenericArgs.Count == 0) + throw new Exception("Missing function return type."); + if (typeInfo.GenericArgs.Count > 1) + { + var args = string.Empty; + args += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller); + for (int i = 2; i < typeInfo.GenericArgs.Count; i++) + args += ", " + GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + if (typeInfo.GenericArgs[0].Type == "void") + return string.Format("Action<{0}>", args); + return string.Format("Func<{0}, {1}>", args, GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + } + if (typeInfo.GenericArgs[0].Type == "void") + return "Action"; + return string.Format("Func<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + } + // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { + CSharpUsedNamespaces.Add(apiType.Namespace); + if (apiType.IsScriptingObject) return typeInfo.Type.Replace("::", "."); - - if (typeInfo.IsPtr && (apiType is LangType || apiType.IsPod)) + if (typeInfo.IsPtr && apiType.IsPod) return typeInfo.Type.Replace("::", ".") + '*'; } @@ -233,6 +264,10 @@ namespace Flax.Build.Bindings if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "IntPtr"; + // Function + if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) + return "IntPtr"; + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); } @@ -263,6 +298,9 @@ namespace Flax.Build.Bindings case "ManagedScriptingObject": // object return "FlaxEngine.Object.GetUnmanagedPtr"; + case "Function": + // delegate + return "Marshal.GetFunctionPointerForDelegate"; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -469,9 +507,8 @@ namespace Flax.Build.Bindings if (comment[i - 1].StartsWith("/// ")) tooltip += " " + comment[i - 1].Substring(4); } - if (tooltip.IndexOf('\"') != -1) - tooltip = tooltip.Replace("\"", "\\\""); - contents.Append(indent).Append("[Tooltip(\"").Append(tooltip).Append("\")]").AppendLine(); + tooltip = tooltip.Replace("\"", "\"\""); + contents.Append(indent).Append("[Tooltip(@\"").Append(tooltip).Append("\")]").AppendLine(); } } if (writeDefaultValue) @@ -527,7 +564,7 @@ namespace Flax.Build.Bindings contents.Append("abstract "); contents.Append("unsafe partial class ").Append(classInfo.Name); if (classInfo.BaseType != null && !classInfo.IsBaseTypeHidden) - contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, classInfo.BaseType, classInfo)); + contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = classInfo.BaseType.Name }, classInfo)); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; @@ -553,6 +590,50 @@ namespace Flax.Build.Bindings contents.AppendLine(); + var useCustomDelegateSignature = false; + for (var i = 0; i < paramsCount; i++) + { + var paramType = eventInfo.Type.GenericArgs[i]; + var result = GenerateCSharpNativeToManaged(buildData, paramType, classInfo); + if ((paramType.IsRef && !paramType.IsConst && paramType.IsPod(buildData, classInfo)) || result[result.Length - 1] == '*') + useCustomDelegateSignature = true; + CppParamsWrappersCache[i] = result; + } + + string eventSignature; + if (useCustomDelegateSignature) + { + contents.Append(indent).Append($"/// The delegate for event {eventInfo.Name}.").AppendLine(); + contents.Append(indent).Append("public delegate void ").Append(eventInfo.Name).Append("Delegate("); + for (var i = 0; i < paramsCount; i++) + { + var paramType = eventInfo.Type.GenericArgs[i]; + if (i != 0) + contents.Append(", "); + if (paramType.IsRef && !paramType.IsConst && paramType.IsPod(buildData, classInfo)) + contents.Append("ref "); + contents.Append(CppParamsWrappersCache[i]).Append(" arg").Append(i); + } + contents.Append(");").AppendLine().AppendLine(); + eventSignature = "event " + eventInfo.Name + "Delegate"; + } + else + { + eventSignature = "event Action"; + if (paramsCount != 0) + { + eventSignature += '<'; + for (var i = 0; i < paramsCount; i++) + { + if (i != 0) + eventSignature += ", "; + CppParamsWrappersCache[i] = GenerateCSharpNativeToManaged(buildData, eventInfo.Type.GenericArgs[i], classInfo); + eventSignature += CppParamsWrappersCache[i]; + } + eventSignature += '>'; + } + } + foreach (var comment in eventInfo.Comment) { if (comment.Contains("/// ")) @@ -570,19 +651,6 @@ namespace Flax.Build.Bindings contents.Append("private "); if (eventInfo.IsStatic) contents.Append("static "); - string eventSignature = "event Action"; - if (paramsCount != 0) - { - eventSignature += '<'; - for (var i = 0; i < paramsCount; i++) - { - if (i != 0) - eventSignature += ", "; - CppParamsWrappersCache[i] = GenerateCSharpNativeToManaged(buildData, eventInfo.Type.GenericArgs[i], classInfo); - eventSignature += CppParamsWrappersCache[i]; - } - eventSignature += '>'; - } contents.Append(eventSignature); contents.Append(' ').AppendLine(eventInfo.Name); contents.Append(indent).Append('{').AppendLine(); @@ -614,9 +682,12 @@ namespace Flax.Build.Bindings contents.Append($"void Internal_{eventInfo.Name}_Invoke("); for (var i = 0; i < paramsCount; i++) { + var paramType = eventInfo.Type.GenericArgs[i]; if (i != 0) contents.Append(", "); contents.Append(CppParamsWrappersCache[i]); + if (paramType.IsRef && !paramType.IsConst && paramType.IsPod(buildData, classInfo)) + contents.Append("*"); contents.Append(" arg").Append(i); } contents.Append(')').AppendLine(); @@ -625,8 +696,11 @@ namespace Flax.Build.Bindings contents.Append('('); for (var i = 0; i < paramsCount; i++) { + var paramType = eventInfo.Type.GenericArgs[i]; if (i != 0) contents.Append(", "); + if (paramType.IsRef && !paramType.IsConst && paramType.IsPod(buildData, classInfo)) + contents.Append("ref *"); contents.Append("arg").Append(i); } contents.Append(");").AppendLine(); @@ -861,7 +935,7 @@ namespace Flax.Build.Bindings contents.Append("private "); contents.Append("unsafe partial struct ").Append(structureInfo.Name); if (structureInfo.BaseType != null && structureInfo.IsPod) - contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, structureInfo.BaseType, structureInfo)); + contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo)); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; @@ -1048,30 +1122,6 @@ namespace Flax.Build.Bindings return true; } - private static void GenerateCSharpCollectNamespaces(BuildData buildData, ApiTypeInfo apiType, HashSet usedNamespaces) - { - if (apiType is ClassInfo classInfo) - { - foreach (var field in classInfo.Fields) - { - var fieldInfo = FindApiTypeInfo(buildData, field.Type, classInfo); - if (fieldInfo != null && !string.IsNullOrWhiteSpace(fieldInfo.Namespace) && fieldInfo.Namespace != apiType.Namespace) - usedNamespaces.Add(fieldInfo.Namespace); - } - } - else if (apiType is StructureInfo structureInfo) - { - foreach (var field in structureInfo.Fields) - { - var fieldInfo = FindApiTypeInfo(buildData, field.Type, structureInfo); - if (fieldInfo != null && !string.IsNullOrWhiteSpace(fieldInfo.Namespace) && fieldInfo.Namespace != apiType.Namespace) - usedNamespaces.Add(fieldInfo.Namespace); - } - } - foreach (var child in apiType.Children) - GenerateCSharpCollectNamespaces(buildData, child, usedNamespaces); - } - private static void GenerateCSharp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings) { var contents = new StringBuilder(); @@ -1084,29 +1134,17 @@ namespace Flax.Build.Bindings contents.Append("true").AppendLine(); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); + var headerPos = contents.Length; - // Using declarations CSharpUsedNamespaces.Clear(); + CSharpUsedNamespaces.Add(null); + CSharpUsedNamespaces.Add(string.Empty); CSharpUsedNamespaces.Add("System"); CSharpUsedNamespaces.Add("System.ComponentModel"); CSharpUsedNamespaces.Add("System.Globalization"); CSharpUsedNamespaces.Add("System.Runtime.CompilerServices"); CSharpUsedNamespaces.Add("System.Runtime.InteropServices"); CSharpUsedNamespaces.Add("FlaxEngine"); - foreach (var e in moduleInfo.Children) - { - foreach (var apiTypeInfo in e.Children) - { - GenerateCSharpCollectNamespaces(buildData, apiTypeInfo, CSharpUsedNamespaces); - } - } - CSharpUsedNamespacesSorted.Clear(); - CSharpUsedNamespacesSorted.AddRange(CSharpUsedNamespaces); - CSharpUsedNamespacesSorted.Sort(); - foreach (var e in CSharpUsedNamespacesSorted) - contents.AppendLine($"using {e};"); - // TODO: custom using declarations support - // TODO: generate using declarations based on references modules (eg. using FlaxEngine, using Plugin1 in game API) // Process all API types from the file var useBindings = false; @@ -1121,7 +1159,21 @@ namespace Flax.Build.Bindings if (!useBindings) return; + { + var header = new StringBuilder(); + + // Using declarations + CSharpUsedNamespacesSorted.Clear(); + CSharpUsedNamespacesSorted.AddRange(CSharpUsedNamespaces); + CSharpUsedNamespacesSorted.Sort(); + for (var i = 2; i < CSharpUsedNamespacesSorted.Count; i++) + header.AppendLine($"using {CSharpUsedNamespacesSorted[i]};"); + + contents.Insert(headerPos, header.ToString()); + } + // Save generated file + contents.AppendLine(); contents.AppendLine("#endif"); Utilities.WriteFileIfChanged(bindings.GeneratedCSharpFilePath, contents.ToString()); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 8fc7d18f2..0bb1f4cc7 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 7; + private const int CacheVersion = 8; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 78fd3df73..d0b615f66 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -73,26 +73,45 @@ namespace Flax.Build.Bindings return sb.ToString(); } - private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller) + private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isOut) { - var nativeToManaged = GenerateCppWrapperNativeToManaged(buildData, paramType, caller, out _, null); + var nativeToManaged = GenerateCppWrapperNativeToManaged(buildData, paramType, caller, out var managedTypeAsNative, null); + string result; if (!string.IsNullOrEmpty(nativeToManaged)) - nativeToManaged = string.Format(nativeToManaged, paramName); - else - nativeToManaged = paramName; - if ((!paramType.IsPtr && paramType.Type != "StringView" && paramType.Type != "StringAnsiView") || paramType.IsPod(buildData, caller)) { - if (nativeToManaged == paramName) + result = string.Format(nativeToManaged, paramName); + if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*') { - nativeToManaged = '&' + nativeToManaged; + // Pass pointer value } else { - contents.Append($" auto __param{paramName} = {nativeToManaged};").AppendLine(); - nativeToManaged = $"&__param{paramName}"; + // Pass as pointer to local variable converted for managed runtime + if (paramType.IsPtr) + result = string.Format(nativeToManaged, '*' + paramName); + contents.Append($" auto __param_{paramName} = {result};").AppendLine(); + result = $"&__param_{paramName}"; } } - return $"(void*){nativeToManaged}"; + else + { + result = paramName; + if (paramType.IsRef && !paramType.IsConst && !isOut) + { + // Pass reference as a pointer + result = '&' + result; + } + else if (paramType.IsPtr || managedTypeAsNative[managedTypeAsNative.Length - 1] == '*') + { + // Pass pointer value + } + else + { + // Pass as pointer to value + result = '&' + result; + } + } + return $"(void*){result}"; } public static string GenerateCppWrapperNativeToVariant(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value) @@ -420,6 +439,13 @@ namespace Flax.Build.Bindings return "ManagedBitArray::ToManaged({0})"; } + // Function + if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) + { + // TODO: automatic converting managed-native for Function + throw new NotImplementedException("TODO: converting native Function to managed"); + } + var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { @@ -606,6 +632,21 @@ namespace Flax.Build.Bindings return "MUtils::LinkArray({0})"; } + // Function + if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) + { + var args = string.Empty; + if (typeInfo.GenericArgs.Count > 1) + { + args += typeInfo.GenericArgs[1].GetFullNameNative(buildData, caller); + for (int i = 2; i < typeInfo.GenericArgs.Count; i++) + args += ", " + typeInfo.GenericArgs[i].GetFullNameNative(buildData, caller); + } + var T = $"Function<{typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller)}({args})>"; + type = T + "::Signature"; + return T + "({0})"; + } + if (apiType != null) { // Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) @@ -1002,7 +1043,7 @@ namespace Flax.Build.Bindings for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo); + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, parameterInfo.IsOut); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } @@ -1095,7 +1136,7 @@ namespace Flax.Build.Bindings var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; if (classInfo != null && classInfo.IsBaseTypeHidden) baseType = null; - if (baseType != null && (baseType.Type == "PersistentScriptingObject" || baseType.Type == "ScriptingObject")) + if (baseType != null && (baseType.Name == "PersistentScriptingObject" || baseType.Name == "ScriptingObject")) baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); @@ -1105,7 +1146,7 @@ namespace Flax.Build.Bindings contents.Append($"void {typeNameNative}::Serialize(SerializeStream& stream, const void* otherObj)").AppendLine(); contents.Append('{').AppendLine(); if (baseType != null) - contents.Append($" {baseType}::Serialize(stream, otherObj);").AppendLine(); + contents.Append($" {baseType.FullNameNative}::Serialize(stream, otherObj);").AppendLine(); contents.Append($" SERIALIZE_GET_OTHER_OBJ({typeNameNative});").AppendLine(); if (classInfo != null) @@ -1161,7 +1202,7 @@ namespace Flax.Build.Bindings contents.Append($"void {typeNameNative}::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)").AppendLine(); contents.Append('{').AppendLine(); if (baseType != null) - contents.Append($" {baseType}::Deserialize(stream, modifier);").AppendLine(); + contents.Append($" {baseType.FullNameNative}::Deserialize(stream, modifier);").AppendLine(); foreach (var fieldInfo in CppAutoSerializeFields) { @@ -1259,7 +1300,7 @@ namespace Flax.Build.Bindings { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; - var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo); + var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, false); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } if (eventInfo.IsStatic) @@ -1554,7 +1595,7 @@ namespace Flax.Build.Bindings else contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); if (classInfo.BaseType != null && useScripting) - contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); + contents.Append($"&{classInfo.BaseType.FullNameNative}::TypeInitializer, "); else contents.Append("nullptr, "); contents.Append(setupScriptVTable); @@ -1566,7 +1607,7 @@ namespace Flax.Build.Bindings else contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, "); if (classInfo.BaseType != null) - contents.Append($"&{classInfo.BaseType}::TypeInitializer"); + contents.Append($"&{classInfo.BaseType.FullNameNative}::TypeInitializer"); else contents.Append("nullptr"); } @@ -1884,16 +1925,6 @@ namespace Flax.Build.Bindings { var header = new StringBuilder(); - // Includes - CppReferencesFiles.Remove(null); - CppIncludeFilesList.Clear(); - foreach (var fileInfo in CppReferencesFiles) - CppIncludeFilesList.Add(fileInfo.Name); - CppIncludeFilesList.AddRange(CppIncludeFiles); - CppIncludeFilesList.Sort(); - foreach (var path in CppIncludeFilesList) - header.AppendFormat("#include \"{0}\"", path).AppendLine(); - // Variant converting helper methods foreach (var typeInfo in CppVariantToTypes) { @@ -2050,7 +2081,8 @@ namespace Flax.Build.Bindings header.Append(" for (int32 i = 0; i < data.Length(); i++)").AppendLine(); header.AppendFormat(" mono_array_set(result, {0}Managed, i, ToManaged(data[i]));", apiType.Name).AppendLine(); header.Append(" }").AppendLine(); - header.AppendFormat(" void ToNativeArray(Array<{0}>& result, MonoArray* data, int32 length)", fullName).AppendLine(); + header.Append(" template").AppendLine(); + header.AppendFormat(" void ToNativeArray(Array<{0}, AllocationType>& result, MonoArray* data, int32 length)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); header.AppendFormat(" result.Add(ToNative(mono_array_get(data, {0}Managed, i)));", apiType.Name).AppendLine(); @@ -2230,7 +2262,8 @@ namespace Flax.Build.Bindings header.Append(" mono_array_setref(result, i, Box(data[i]));").AppendLine(); header.Append(" }").AppendLine(); - header.AppendFormat(" void ToNativeArray(Array<{0}>& result, MonoArray* data, int32 length)", fullName).AppendLine(); + header.Append(" template").AppendLine(); + header.AppendFormat(" void ToNativeArray(Array<{0}, AllocationType>& result, MonoArray* data, int32 length)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); header.AppendFormat(" Unbox(result[i], (MonoObject*)mono_array_addr_with_size(data, sizeof({0}Managed), length));", fullName).AppendLine(); @@ -2241,6 +2274,18 @@ namespace Flax.Build.Bindings } contents.Insert(headerPos, header.ToString()); + + // Includes + header.Clear(); + CppReferencesFiles.Remove(null); + CppIncludeFilesList.Clear(); + foreach (var fileInfo in CppReferencesFiles) + CppIncludeFilesList.Add(fileInfo.Name); + CppIncludeFilesList.AddRange(CppIncludeFiles); + CppIncludeFilesList.Sort(); + foreach (var path in CppIncludeFilesList) + header.AppendFormat("#include \"{0}\"", path).AppendLine(); + contents.Insert(headerPos, header.ToString()); } Utilities.WriteFileIfChanged(bindings.GeneratedCppFilePath, contents.ToString()); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 67e90efa9..9d0bd10e3 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -76,6 +76,10 @@ namespace Flax.Build.Bindings if (commentLine.StartsWith("// ")) commentLine = "/// " + commentLine.Substring(3); + // Fix inlined summary + if (commentLine.StartsWith("/// ") && commentLine.EndsWith("")) + commentLine = "/// " + commentLine.Substring(13, commentLine.Length - 23); + context.StringCache.Insert(0, commentLine); break; } @@ -416,38 +420,38 @@ namespace Flax.Build.Bindings token = accessToken; break; } - - var baseTypeInfo = new TypeInfo + var inheritType = 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; + if (desc.Inheritance == null) + desc.Inheritance = new List(); + desc.Inheritance.Add(inheritType); token = context.Tokenizer.NextToken(); if (token.Type == TokenType.LeftCurlyBrace) { break; } + if (token.Type == TokenType.Colon) + { + token = context.Tokenizer.ExpectToken(TokenType.Colon); + token = context.Tokenizer.NextToken(); + inheritType.Type = token.Value; + token = context.Tokenizer.NextToken(); + continue; + } + if (token.Type == TokenType.DoubleColon) + { + token = context.Tokenizer.NextToken(); + inheritType.Type += token.Value; + token = context.Tokenizer.NextToken(); + continue; + } if (token.Type == TokenType.LeftAngleBracket) { var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List + inheritType.GenericArgs = new List { new TypeInfo { @@ -456,9 +460,9 @@ namespace Flax.Build.Bindings }; // TODO: find better way to resolve this (custom base type attribute?) - if (desc.BaseType.Type == "ShaderAssetTypeBase") + if (inheritType.Type == "ShaderAssetTypeBase") { - desc.BaseType = desc.BaseType.GenericArgs[0]; + desc.Inheritance[desc.Inheritance.Count - 1] = inheritType.GenericArgs[0]; } token = context.Tokenizer.NextToken(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index f8d26314f..fa219477a 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 System.Threading; using System.Threading.Tasks; using Flax.Build.NativeCpp; @@ -109,13 +108,6 @@ namespace Flax.Build.Bindings if (LoadCache(ref moduleInfo, moduleOptions, headerFiles)) { buildData.ModulesInfo[module] = moduleInfo; - - // Initialize API - using (new ProfileEventScope("Init")) - { - moduleInfo.Init(buildData); - } - return moduleInfo; } } @@ -164,12 +156,6 @@ namespace Flax.Build.Bindings } } - // Initialize API - using (new ProfileEventScope("Init")) - { - moduleInfo.Init(buildData); - } - return moduleInfo; } @@ -533,23 +519,10 @@ namespace Flax.Build.Bindings if (moduleInfo.IsFromCache) return; - // Process parsed API - using (new ProfileEventScope("Process")) + // Initialize parsed API + using (new ProfileEventScope("Init")) { - foreach (var child in moduleInfo.Children) - { - 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; - } - } + moduleInfo.Init(buildData); } // Generate bindings for scripting @@ -583,122 +556,5 @@ namespace Flax.Build.Bindings } } } - - private static void ProcessAndValidate(BuildData buildData, ApiTypeInfo apiTypeInfo) - { - if (apiTypeInfo is ClassInfo classInfo) - ProcessAndValidate(buildData, classInfo); - else if (apiTypeInfo is StructureInfo structureInfo) - ProcessAndValidate(buildData, structureInfo); - - foreach (var child in apiTypeInfo.Children) - ProcessAndValidate(buildData, child); - } - - private static void ProcessAndValidate(BuildData buildData, ClassInfo classInfo) - { - if (classInfo.UniqueFunctionNames == null) - classInfo.UniqueFunctionNames = new HashSet(); - - foreach (var fieldInfo in classInfo.Fields) - { - if (fieldInfo.Access == AccessLevel.Private) - continue; - - fieldInfo.Getter = new FunctionInfo - { - Name = "Get" + fieldInfo.Name, - Comment = fieldInfo.Comment, - IsStatic = fieldInfo.IsStatic, - Access = fieldInfo.Access, - Attributes = fieldInfo.Attributes, - ReturnType = fieldInfo.Type, - Parameters = new List(), - IsVirtual = false, - IsConst = true, - Glue = new FunctionInfo.GlueInfo() - }; - ProcessAndValidate(classInfo, fieldInfo.Getter); - fieldInfo.Getter.Name = fieldInfo.Name; - - if (!fieldInfo.IsReadOnly) - { - fieldInfo.Setter = new FunctionInfo - { - Name = "Set" + fieldInfo.Name, - Comment = fieldInfo.Comment, - IsStatic = fieldInfo.IsStatic, - Access = fieldInfo.Access, - Attributes = fieldInfo.Attributes, - ReturnType = new TypeInfo - { - Type = "void", - }, - Parameters = new List - { - new FunctionInfo.ParameterInfo - { - Name = "value", - Type = fieldInfo.Type, - }, - }, - IsVirtual = false, - IsConst = true, - Glue = new FunctionInfo.GlueInfo() - }; - ProcessAndValidate(classInfo, fieldInfo.Setter); - fieldInfo.Setter.Name = fieldInfo.Name; - } - } - - foreach (var propertyInfo in classInfo.Properties) - { - if (propertyInfo.Getter != null) - ProcessAndValidate(classInfo, propertyInfo.Getter); - if (propertyInfo.Setter != null) - ProcessAndValidate(classInfo, propertyInfo.Setter); - } - - foreach (var functionInfo in classInfo.Functions) - ProcessAndValidate(classInfo, functionInfo); - } - - private static void ProcessAndValidate(BuildData buildData, StructureInfo structureInfo) - { - foreach (var fieldInfo in structureInfo.Fields) - { - if (fieldInfo.Type.IsBitField) - throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {structureInfo.Name})"); - - // Pointers are fine - if (fieldInfo.Type.IsPtr) - continue; - - // In-build types - if (CSharpNativeToManagedBasicTypes.ContainsKey(fieldInfo.Type.Type)) - continue; - if (CSharpNativeToManagedDefault.ContainsKey(fieldInfo.Type.Type)) - continue; - - // Find API type info for this field type - var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); - if (apiType != null) - continue; - - throw new Exception($"Unknown field type '{fieldInfo.Type} {fieldInfo.Name}' in structure '{structureInfo.Name}'."); - } - } - - private static void ProcessAndValidate(ClassInfo classInfo, FunctionInfo functionInfo) - { - // Ensure that methods have unique names for bindings - if (classInfo.UniqueFunctionNames == null) - classInfo.UniqueFunctionNames = new HashSet(); - int idx = 1; - functionInfo.UniqueName = functionInfo.Name; - while (classInfo.UniqueFunctionNames.Contains(functionInfo.UniqueName)) - functionInfo.UniqueName = functionInfo.Name + idx++; - classInfo.UniqueFunctionNames.Add(functionInfo.UniqueName); - } } } diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index c68ff1a59..ff0bb077b 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -57,22 +57,87 @@ namespace Flax.Build.Bindings _isScriptingObject = true; else if (BaseType == null) _isScriptingObject = false; - else if (InBuildScriptingObjectTypes.Contains(BaseType.Type)) + else if (InBuildScriptingObjectTypes.Contains(BaseType.Name)) _isScriptingObject = true; else + _isScriptingObject = BaseType != null && BaseType.IsScriptingObject; + + if (UniqueFunctionNames == null) + UniqueFunctionNames = new HashSet(); + + foreach (var fieldInfo in Fields) { - var baseApiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, BaseType, this); - if (baseApiTypeInfo != null) + if (fieldInfo.Access == AccessLevel.Private) + continue; + + fieldInfo.Getter = new FunctionInfo { - if (!baseApiTypeInfo.IsInited) - baseApiTypeInfo.Init(buildData); - _isScriptingObject = baseApiTypeInfo.IsScriptingObject; - } - else + Name = "Get" + fieldInfo.Name, + Comment = fieldInfo.Comment, + IsStatic = fieldInfo.IsStatic, + Access = fieldInfo.Access, + Attributes = fieldInfo.Attributes, + ReturnType = fieldInfo.Type, + Parameters = new List(), + IsVirtual = false, + IsConst = true, + Glue = new FunctionInfo.GlueInfo() + }; + ProcessAndValidate(fieldInfo.Getter); + fieldInfo.Getter.Name = fieldInfo.Name; + + if (!fieldInfo.IsReadOnly) { - _isScriptingObject = false; + fieldInfo.Setter = new FunctionInfo + { + Name = "Set" + fieldInfo.Name, + Comment = fieldInfo.Comment, + IsStatic = fieldInfo.IsStatic, + Access = fieldInfo.Access, + Attributes = fieldInfo.Attributes, + ReturnType = new TypeInfo + { + Type = "void", + }, + Parameters = new List + { + new FunctionInfo.ParameterInfo + { + Name = "value", + Type = fieldInfo.Type, + }, + }, + IsVirtual = false, + IsConst = true, + Glue = new FunctionInfo.GlueInfo() + }; + ProcessAndValidate(fieldInfo.Setter); + fieldInfo.Setter.Name = fieldInfo.Name; } } + + foreach (var propertyInfo in Properties) + { + if (propertyInfo.Getter != null) + ProcessAndValidate(propertyInfo.Getter); + if (propertyInfo.Setter != null) + ProcessAndValidate(propertyInfo.Setter); + } + + foreach (var functionInfo in Functions) + ProcessAndValidate(functionInfo); + } + + private void ProcessAndValidate(FunctionInfo functionInfo) + { + // Ensure that methods have unique names for bindings + if (UniqueFunctionNames == null) + UniqueFunctionNames = new HashSet(); + int idx = 1; + functionInfo.UniqueName = functionInfo.Name; + while (UniqueFunctionNames.Contains(functionInfo.UniqueName)) + functionInfo.UniqueName = functionInfo.Name + idx++; + UniqueFunctionNames.Add(functionInfo.UniqueName); } public override void Write(BinaryWriter writer) @@ -113,7 +178,7 @@ namespace Flax.Build.Bindings { if (_scriptVTableSize == -1) { - if (BindingsGenerator.FindApiTypeInfo(buildData, BaseType, this) is ClassInfo baseApiTypeInfo) + if (BaseType is ClassInfo baseApiTypeInfo) { _scriptVTableOffset = baseApiTypeInfo.GetScriptVTableSize(buildData, out _); } diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs index 2f0dad003..ca1bb9257 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using System.IO; @@ -12,29 +13,37 @@ namespace Flax.Build.Bindings { public AccessLevel Access; public AccessLevel BaseTypeInheritance; - public TypeInfo BaseType; - public List Interfaces; // Optional - public List InterfaceNames; // Optional + public ClassStructInfo BaseType; + public List Interfaces; + public List Inheritance; // Data from parsing, used to interfaces and base type construct in Init public override void Init(Builder.BuildData buildData) { base.Init(buildData); - if (Interfaces == null && InterfaceNames != null && InterfaceNames.Count != 0) + if (BaseType == null && Interfaces == null && Inheritance != null) { - Interfaces = new List(); - for (var i = 0; i < InterfaceNames.Count; i++) + // Extract base class and interfaces from inheritance info + for (int i = 0; i < Inheritance.Count; i++) { - var interfaceName = InterfaceNames[i]; - var apiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, interfaceName, this); + var apiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, Inheritance[i], Parent); if (apiTypeInfo is InterfaceInfo interfaceInfo) { + if (Interfaces == null) + Interfaces = new List(); Interfaces.Add(interfaceInfo); } + else if (apiTypeInfo is ClassStructInfo otherInfo) + { + if (otherInfo == this) + throw new Exception($"Type '{Name}' inherits from itself."); + if (BaseType != null) + throw new Exception($"Invalid '{Name}' inheritance (only single base class is allowed for scripting types, excluding interfaces)."); + BaseType = otherInfo; + } } - if (Interfaces.Count == 0) - Interfaces = null; } + BaseType?.EnsureInited(buildData); } public override void Write(BinaryWriter writer) @@ -42,7 +51,7 @@ namespace Flax.Build.Bindings writer.Write((byte)Access); writer.Write((byte)BaseTypeInheritance); BindingsGenerator.Write(writer, BaseType); - BindingsGenerator.Write(writer, InterfaceNames); + BindingsGenerator.Write(writer, Inheritance); base.Write(writer); } @@ -52,7 +61,7 @@ namespace Flax.Build.Bindings Access = (AccessLevel)reader.ReadByte(); BaseTypeInheritance = (AccessLevel)reader.ReadByte(); BaseType = BindingsGenerator.Read(reader, BaseType); - InterfaceNames = BindingsGenerator.Read(reader, InterfaceNames); + Inheritance = BindingsGenerator.Read(reader, Inheritance); base.Read(reader); } diff --git a/Source/Tools/Flax.Build/Bindings/FileInfo.cs b/Source/Tools/Flax.Build/Bindings/FileInfo.cs index 6c4b4dcfe..7a38c8d87 100644 --- a/Source/Tools/Flax.Build/Bindings/FileInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FileInfo.cs @@ -17,6 +17,19 @@ namespace Flax.Build.Bindings base.AddChild(apiTypeInfo); } + public override void Init(Builder.BuildData buildData) + { + try + { + base.Init(buildData); + } + catch (Exception) + { + Log.Error($"Failed to init '{Name}' file scripting API."); + throw; + } + } + public int CompareTo(FileInfo other) { return Name.CompareTo(other.Name); diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index 5b664cc09..7e6888d91 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -26,14 +26,14 @@ namespace Flax.Build.Bindings { base.Init(buildData); - if (ForceNoPod || (InterfaceNames != null && InterfaceNames.Count != 0)) + if (ForceNoPod || (Interfaces != null && Interfaces.Count != 0)) { _isPod = false; return; } // Structure is POD (plain old data) only if all of it's fields are (and has no base type ro base type is also POD) - _isPod = BaseType == null || (BindingsGenerator.FindApiTypeInfo(buildData, BaseType, Parent)?.IsPod ?? false); + _isPod = BaseType == null || (BaseType?.IsPod ?? false); for (int i = 0; _isPod && i < Fields.Count; i++) { var field = Fields[i]; @@ -42,6 +42,29 @@ namespace Flax.Build.Bindings _isPod = false; } } + + foreach (var fieldInfo in Fields) + { + if (fieldInfo.Type.IsBitField) + throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {Name})"); + + // Pointers are fine + if (fieldInfo.Type.IsPtr) + continue; + + // In-build types + if (BindingsGenerator.CSharpNativeToManagedBasicTypes.ContainsKey(fieldInfo.Type.Type)) + continue; + if (BindingsGenerator.CSharpNativeToManagedDefault.ContainsKey(fieldInfo.Type.Type)) + continue; + + // Find API type info for this field type + var apiType = BindingsGenerator.FindApiTypeInfo(buildData, fieldInfo.Type, this); + if (apiType != null) + continue; + + throw new Exception($"Unknown field type '{fieldInfo.Type} {fieldInfo.Name}' in structure '{Name}'."); + } } public override void Write(BinaryWriter writer) diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 443b0687a..bfead9218 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -36,8 +36,7 @@ namespace Flax.Build.Bindings var apiType = BindingsGenerator.FindApiTypeInfo(buildData, this, caller); if (apiType != null) { - if (!apiType.IsInited) - apiType.Init(buildData); + apiType.EnsureInited(buildData); return apiType.IsPod; } @@ -45,7 +44,7 @@ namespace Flax.Build.Bindings return true; // Hardcoded cases - if (Type == "String" || Type == "Array") + if (Type == "String" || Type == "Array" || Type == "StringView" || Type == "StringAnsi" || Type == "StringAnsiView") return false; // Fallback to default diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs index 1bcfca18e..fd784fc3d 100644 --- a/Source/Tools/Flax.Build/Build/Builder.cs +++ b/Source/Tools/Flax.Build/Build/Builder.cs @@ -173,6 +173,7 @@ namespace Flax.Build //throw new Exception(string.Format("Platform {0} {1} is not supported.", targetPlatform, architecture)); var platform = Platform.GetPlatform(targetPlatform); + var toolchain = platform.GetToolchain(architecture); // Special case: building C# bindings only (eg. when building Linux game on Windows without C++ scripting or for C#-only projects) if (Configuration.BuildBindingsOnly || (project.IsCSharpOnlyProject && platform.HasModularBuildSupport)) @@ -186,7 +187,7 @@ namespace Flax.Build switch (target.Type) { case TargetType.NativeCpp: - BuildTargetNativeCppBindingsOnly(rules, graph, target, buildContext, platform, architecture, configuration); + BuildTargetNativeCppBindingsOnly(rules, graph, target, buildContext, toolchain, platform, architecture, configuration); break; case TargetType.DotNet: BuildTargetDotNet(rules, graph, target, platform, configuration); @@ -197,8 +198,6 @@ namespace Flax.Build continue; } - var toolchain = platform.GetToolchain(architecture); - using (new ProfileEventScope(target.Name)) { Log.Info(string.Format("Building target {0} in {1} for {2} {3}", target.Name, configuration, targetPlatform, architecture)); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index dd4b5d951..8df614774 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -553,7 +553,7 @@ namespace Flax.Build var graph = new TaskGraph(reference.Project.ProjectFolderPath); var skipBuild = target.IsPreBuilt || (Configuration.SkipTargets != null && Configuration.SkipTargets.Contains(target.Name)); target.PreBuild(); - referencedBuildData = BuildTargetNativeCppBindingsOnly(buildData.Rules, graph, target, buildContext, buildData.Platform, buildData.Architecture, buildData.Configuration); + referencedBuildData = BuildTargetNativeCppBindingsOnly(buildData.Rules, graph, target, buildContext, buildData.Toolchain, buildData.Platform, buildData.Architecture, buildData.Configuration, skipBuild); if (!skipBuild) { using (new ProfileEventScope("PrepareTasks")) @@ -935,7 +935,7 @@ namespace Flax.Build return buildData; } - private static BuildData BuildTargetNativeCppBindingsOnly(RulesAssembly rules, TaskGraph graph, Target target, Dictionary buildContext, Platform platform, TargetArchitecture architecture, TargetConfiguration configuration) + private static BuildData BuildTargetNativeCppBindingsOnly(RulesAssembly rules, TaskGraph graph, Target target, Dictionary buildContext, Toolchain toolchain, Platform platform, TargetArchitecture architecture, TargetConfiguration configuration, bool skipBuild = false) { if (buildContext.TryGetValue(target, out var buildData)) return buildData; @@ -954,24 +954,7 @@ namespace Flax.Build throw new Exception($"Cannot build target {target.Name}. The project file is missing (.flaxproj located in the folder above)."); // Setup build environment for the target - var platformName = platform.Target.ToString(); - var architectureName = architecture.ToString(); - var configurationName = configuration.ToString(); - var targetBuildOptions = new BuildOptions - { - Target = target, - Platform = platform, - Toolchain = null, - Architecture = architecture, - Configuration = configuration, - CompileEnv = new CompileEnvironment(), - LinkEnv = new LinkEnvironment(), - IntermediateFolder = Path.Combine(project.ProjectFolderPath, Configuration.IntermediateFolder, target.Name, platformName, architectureName, configurationName), - OutputFolder = Path.Combine(project.ProjectFolderPath, Configuration.BinariesFolder, target.Name, platformName, architectureName, configurationName), - WorkingDirectory = project.ProjectFolderPath, - HotReloadPostfix = Configuration.HotReloadPostfix, - }; - target.SetupTargetEnvironment(targetBuildOptions); + var targetBuildOptions = GetBuildOptions(target, toolchain.Platform, toolchain, toolchain.Architecture, configuration, project.ProjectFolderPath, skipBuild ? string.Empty : Configuration.HotReloadPostfix); using (new ProfileEventScope("PreBuild")) { @@ -1009,16 +992,13 @@ namespace Flax.Build { using (new ProfileEventScope(reference.Project.Name)) { + if (buildData.Toolchain == null) + buildData.Toolchain = platform.GetToolchain(architecture); + if (Configuration.BuildBindingsOnly || reference.Project.IsCSharpOnlyProject || !platform.HasRequiredSDKsInstalled) - { BuildTargetReferenceNativeCppBindingsOnly(buildContext, buildData, reference); - } else - { - if (buildData.Toolchain == null) - buildData.Toolchain = platform.GetToolchain(architecture); BuildTargetReferenceNativeCpp(buildContext, buildData, reference); - } } } } diff --git a/Source/Tools/Flax.Build/Build/Profiling.cs b/Source/Tools/Flax.Build/Build/Profiling.cs index c5cc89bcb..785badad3 100644 --- a/Source/Tools/Flax.Build/Build/Profiling.cs +++ b/Source/Tools/Flax.Build/Build/Profiling.cs @@ -5,6 +5,7 @@ using System.IO; using System.Collections.Generic; using System.Text; using System.Threading; +using System.Diagnostics; namespace Flax.Build { @@ -71,6 +72,8 @@ namespace Flax.Build private static int _depth; private static readonly List _events = new List(1024); + private static readonly DateTime _startTime = DateTime.Now; + private static readonly Stopwatch _stopwatch = Stopwatch.StartNew(); // https://stackoverflow.com/questions/1416139/how-to-get-timestamp-of-tick-precision-in-net-c /// /// Begins the profiling event. @@ -81,7 +84,7 @@ namespace Flax.Build { Event e; e.Name = name; - e.StartTime = DateTime.Now; + e.StartTime = _startTime.AddTicks(_stopwatch.Elapsed.Ticks); e.Duration = TimeSpan.Zero; e.Depth = _depth++; e.ThreadId = Thread.CurrentThread.ManagedThreadId; @@ -95,7 +98,7 @@ namespace Flax.Build /// The event identifier returned by . public static void End(int id) { - var endTime = DateTime.Now; + var endTime = _startTime.AddTicks(_stopwatch.Elapsed.Ticks); var e = _events[id]; e.Duration = endTime - e.StartTime; _events[id] = e; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs b/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs index 7cd7649b1..595d7ac0e 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/mono.cs @@ -33,6 +33,7 @@ namespace Flax.Deps.Dependencies TargetPlatform.XboxOne, TargetPlatform.PS4, TargetPlatform.XboxScarlett, + TargetPlatform.Switch, }; case TargetPlatform.Linux: return new[] @@ -614,7 +615,7 @@ namespace Flax.Deps.Dependencies { "ANDROID_API", apiLevel }, { "ANDROID_API_VERSION", apiLevel }, { "ANDROID_NATIVE_API_LEVEL", apiLevel }, - + { "CC", Path.Combine(ndkBin, archName + apiLevel + "-clang") }, { "CXX", Path.Combine(ndkBin, archName + apiLevel + "-clang++") }, { "AR", Path.Combine(ndkBin, archName + "-ar") }, @@ -705,6 +706,13 @@ namespace Flax.Deps.Dependencies Utilities.DirectoryDelete(Path.Combine(bclLibMonoOutput, "2.1", "Facades")); break; } + case TargetPlatform.Switch: + { + var type = Type.GetType("Flax.Build.Platforms.Switch.mono"); + var method = type.GetMethod("Build"); + method.Invoke(null, new object[] { root, options }); + break; + } } } } diff --git a/Source/Tools/Flax.Build/Deps/Dependency.cs b/Source/Tools/Flax.Build/Deps/Dependency.cs index c19687c2a..17d158d42 100644 --- a/Source/Tools/Flax.Build/Deps/Dependency.cs +++ b/Source/Tools/Flax.Build/Deps/Dependency.cs @@ -263,5 +263,36 @@ namespace Flax.Deps Utilities.Run("cmake", cmdLine, null, path, Utilities.RunOptions.None, envVars); } + + /// + /// Runs the bash script via Cygwin tool (native bash on platforms other than Windows). + /// + /// The path. + /// The workspace folder. + /// Custom environment variables to pass to the child process. + public static void RunCygwin(string path, string workspace = null, Dictionary envVars = null) + { + string app; + switch (BuildPlatform) + { + case TargetPlatform.Windows: + { + var cygwinFolder = Environment.GetEnvironmentVariable("CYGWIN"); + if (string.IsNullOrEmpty(cygwinFolder) || !Directory.Exists(cygwinFolder)) + { + cygwinFolder = "C:\\cygwin"; + if (!Directory.Exists(cygwinFolder)) + throw new Exception("Missing Cygwin. Install Cygwin64 to C:\\cygwin or set CYGWIN env variable to install location folder."); + } + app = Path.Combine(cygwinFolder, "bin\\bash.exe"); + break; + } + case TargetPlatform.Linux: + app = "bash"; + break; + default: throw new InvalidPlatformException(BuildPlatform); + } + Utilities.Run(app, path, null, workspace, Utilities.RunOptions.None, envVars); + } } } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index b59bcae39..8a1b3b43f 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -367,8 +367,11 @@ namespace Flax.Build.Platforms var vcToolChainDir = toolsets[Toolset]; switch (Toolset) { - case WindowsPlatformToolset.v141: return Path.Combine(vcToolChainDir, "lib", "x86", "store", "references"); - case WindowsPlatformToolset.v140: return Path.Combine(vcToolChainDir, "lib", "store", "references"); + case WindowsPlatformToolset.v142: + case WindowsPlatformToolset.v141: + return Path.Combine(vcToolChainDir, "lib", "x86", "store", "references"); + case WindowsPlatformToolset.v140: + return Path.Combine(vcToolChainDir, "lib", "store", "references"); default: return null; } } diff --git a/Source/Tools/Flax.Build/Program.cs b/Source/Tools/Flax.Build/Program.cs index 89e71ace0..7411edb3b 100644 --- a/Source/Tools/Flax.Build/Program.cs +++ b/Source/Tools/Flax.Build/Program.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Diagnostics; using System.IO; using System.Net; using System.Threading; @@ -25,7 +26,7 @@ namespace Flax.Build } Mutex singleInstanceMutex = null; - var startTime = DateTime.Now; + Stopwatch stopwatch = Stopwatch.StartNew(); bool failed = false; try @@ -166,9 +167,8 @@ namespace Flax.Build singleInstanceMutex.Dispose(); singleInstanceMutex = null; } - - var endTime = DateTime.Now; - Log.Info(string.Format("Total time: {0}", endTime - startTime)); + stopwatch.Stop(); + Log.Info(string.Format("Total time: {0}", stopwatch.Elapsed)); Log.Verbose("End."); Log.Dispose(); } diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 8390377a8..e08755175 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -326,7 +326,7 @@ namespace Flax.Build } } - var startTime = DateTime.UtcNow; + Stopwatch stopwatch = Stopwatch.StartNew(); if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand)) { Log.Verbose("Running: " + app + " " + (string.IsNullOrEmpty(commandLine) ? "" : commandLine)); @@ -397,11 +397,11 @@ namespace Flax.Build if (!options.HasFlag(RunOptions.NoWaitForExit)) { - var buildDuration = (DateTime.UtcNow - startTime).TotalMilliseconds; + stopwatch.Stop(); result = proc.ExitCode; if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand) || options.HasFlag(RunOptions.NoLoggingOfRunDuration)) { - Log.Info(string.Format("Took {0}s to run {1}, ExitCode={2}", buildDuration / 1000, Path.GetFileName(app), result)); + Log.Info(string.Format("Took {0}s to run {1}, ExitCode={2}", stopwatch.Elapsed.TotalSeconds, Path.GetFileName(app), result)); } }