diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 9844f3fda..1a308e08a 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -739,6 +739,8 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override void Initialize(LayoutElementsContainer layout) { + var style = FlaxEngine.GUI.Style.Current; + // Area for drag&drop scripts var dragArea = layout.CustomContainer(); dragArea.CustomControl.ScriptsEditor = this; @@ -800,17 +802,10 @@ namespace FlaxEditor.CustomEditors.Dedicated bool hasAllRequirements = true; if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) { - RequireScriptAttribute scriptAttribute = null; - foreach (var e in scriptType.GetAttributes(false)) + var attribute = (RequireScriptAttribute)scriptType.GetAttributes(false).FirstOrDefault(x => x is RequireScriptAttribute); + if (attribute != null) { - if (e is not RequireScriptAttribute requireScriptAttribute) - continue; - scriptAttribute = requireScriptAttribute; - } - - if (scriptAttribute != null) - { - foreach (var type in scriptAttribute.RequiredTypes) + foreach (var type in attribute.RequiredTypes) { if (!type.IsSubclassOf(typeof(Script))) continue; @@ -825,15 +820,7 @@ namespace FlaxEditor.CustomEditors.Dedicated } if (scriptType.HasAttribute(typeof(RequireActorAttribute), false)) { - RequireActorAttribute attribute = null; - foreach (var e in scriptType.GetAttributes(false)) - { - if (e is not RequireActorAttribute requireActorAttribute) - continue; - attribute = requireActorAttribute; - break; - } - + var attribute = (RequireActorAttribute)scriptType.GetAttributes(false).FirstOrDefault(x => x is RequireActorAttribute); if (attribute != null) { var actor = script.Actor; @@ -850,7 +837,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name); var group = layout.Group(title, editor); if (!hasAllRequirements) - group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed; + group.Panel.HeaderTextColor = style.Statusbar.Failed; if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { if (Editor.Instance.ProjectCache.IsGroupToggled(title)) @@ -863,9 +850,10 @@ namespace FlaxEditor.CustomEditors.Dedicated group.Panel.Open(); // Customize + float totalHeaderButtonsOffset = 0f; group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType); if (script.HasPrefabLink) - group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.ProgressNormal; + group.Panel.HeaderTextColor = style.ProgressNormal; // Add toggle button to the group var headerHeight = group.Panel.HeaderHeight; @@ -889,7 +877,7 @@ namespace FlaxEditor.CustomEditors.Dedicated TooltipText = "Script reference.", AutoFocus = true, IsScrollable = false, - Color = FlaxEngine.GUI.Style.Current.ForegroundGrey, + Color = style.ForegroundGrey, Parent = group.Panel, Bounds = new Rectangle(scriptToggle.Right, 0.5f, headerHeight, headerHeight), Margin = new Margin(1), @@ -908,10 +896,35 @@ namespace FlaxEditor.CustomEditors.Dedicated var settingsButton = group.AddSettingsButton(); settingsButton.Tag = script; settingsButton.Clicked += OnSettingsButtonClicked; + totalHeaderButtonsOffset += settingsButton.Width + FlaxEditor.Utilities.Constants.UIMargin; + + // Add script obsolete icon to the group + if (scriptType.HasAttribute(typeof(ObsoleteAttribute), false)) + { + var attribute = (ObsoleteAttribute)scriptType.GetAttributes(false).First(x => x is ObsoleteAttribute); + var tooltip = "Script marked as obsolete." + + (string.IsNullOrEmpty(attribute.Message) ? "" : $"\n{attribute.Message}") + + (string.IsNullOrEmpty(attribute.DiagnosticId) ? "" : $"\n{attribute.DiagnosticId}"); + var obsoleteButton = group.AddHeaderButton(tooltip, totalHeaderButtonsOffset, Editor.Instance.Icons.Info32); + obsoleteButton.Color = Color.Orange; + obsoleteButton.MouseOverColor = Color.DarkOrange; + totalHeaderButtonsOffset += obsoleteButton.Width; + } + + // Show visual indicator if script only exists in prefab instance and is not part of the prefab + bool isPrefabActor = scripts.Any(s => s.Actor.HasPrefabLink); + if (isPrefabActor && script.PrefabID == Guid.Empty) + { + var prefabInstanceButton = group.AddHeaderButton("Script only exists in this prefab instance.", totalHeaderButtonsOffset, Editor.Instance.Icons.Add32); + prefabInstanceButton.Color = style.ProgressNormal; + prefabInstanceButton.MouseOverColor = style.ProgressNormal * 0.9f; + totalHeaderButtonsOffset += prefabInstanceButton.Width; + } // Adjust margin to not overlap with other ui elements in the header group.Panel.HeaderTextMargin = group.Panel.HeaderTextMargin with { Left = scriptDrag.Right - 12, Right = settingsButton.Width + Utilities.Constants.UIMargin }; group.Object(values, editor); + // Remove drop down arrows and containment lines if no objects in the group if (group.Children.Count == 0) { diff --git a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs index 055c6a29d..a3397d10a 100644 --- a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs @@ -37,25 +37,35 @@ namespace FlaxEditor.CustomEditors.Elements public override ContainerControl ContainerControl => Panel; /// - /// Adds utility settings button to the group header. + /// Add utility settings button to the group header. /// /// The created control. public Image AddSettingsButton() + { + return AddHeaderButton("Settings", 0, Style.Current.Settings); + } + + /// + /// Adds a button to the group header. + /// + /// The created control. + public Image AddHeaderButton(string tooltipText, float xOffset, SpriteHandle sprite) { var style = Style.Current; + const float padding = 2.0f; var settingsButtonSize = Panel.HeaderHeight; Panel.HeaderTextMargin = Panel.HeaderTextMargin with { Right = settingsButtonSize + Utilities.Constants.UIMargin }; ; return new Image { - TooltipText = "Settings", + TooltipText = tooltipText, AutoFocus = true, AnchorPreset = AnchorPresets.TopRight, Parent = Panel, - Bounds = new Rectangle(Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize), + Bounds = new Rectangle(Panel.Width - settingsButtonSize - xOffset, padding * 0.5f, settingsButtonSize - padding, settingsButtonSize - padding), IsScrollable = false, Color = style.ForegroundGrey, Margin = new Margin(1), - Brush = new SpriteBrush(style.Settings), + Brush = new SpriteBrush(sprite), }; } } diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index dd38ea4ee..ad6b5f26f 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -161,7 +161,7 @@ namespace FlaxEditor.Options } /// - /// Options focus Game Window behaviour when play mode is entered. + /// Options for focus Game Window behaviour when play mode is entered. /// public enum PlayModeFocus { @@ -209,6 +209,22 @@ namespace FlaxEditor.Options ClientSide, } + /// + /// Generic options for a disabled or hidden state. Used for example in create content button. + /// + public enum DisabledHidden + { + /// + /// Disabled state. + /// + Disabled, + + /// + /// Hidden state. + /// + Hidden, + } + /// /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// @@ -562,6 +578,13 @@ namespace FlaxEditor.Options [EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)] public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true; + /// + /// Gets or sets a value indicating what should happen to unavaliable options in the content create menu. + /// + [DefaultValue(DisabledHidden.Hidden)] + [EditorDisplay("Content"), EditorOrder(600)] + public DisabledHidden UnavaliableContentCreateOptions { get; set; } = DisabledHidden.Hidden; + private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 6500b2955..56f594586 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -586,8 +586,9 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 13, Title = "Pre-skinned Local Position", Description = "Per vertex local position (before skinning)", + AlternativeTitles = new[] { "Vertex Position", "Pre skinning Local Vertex Position" }, Flags = NodeFlags.MaterialGraph, - Size = new Float2(230, 40), + Size = new Float2(270, 40), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0), @@ -598,8 +599,9 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 14, Title = "Pre-skinned Local Normal", Description = "Per vertex local normal (before skinning)", + AlternativeTitles = new[] { "Vertex Normal", "Pre skinning Local Normal" }, Flags = NodeFlags.MaterialGraph, - Size = new Float2(230, 40), + Size = new Float2(270, 40), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0), diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs index 5b8c05381..59af7af5e 100644 --- a/Source/Editor/Surface/Archetypes/Particles.cs +++ b/Source/Editor/Surface/Archetypes/Particles.cs @@ -436,10 +436,11 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 102, - Title = "Particle Lifetime", - Description = "Particle lifetime (in seconds).", + Title = "Particle Total Lifetime", + Description = "Total particle lifetime (in seconds) at the time when the particle was created. Always the same, no matter the particles age.", + AlternativeTitles = new[] { "Age" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(200, 30), + Size = new Float2(250, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), @@ -449,9 +450,10 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 103, Title = "Particle Age", - Description = "Particle age (in seconds).", + Description = "Particle age (in seconds). How long the particle has been alive since it was created.", + AlternativeTitles = new[] { "Lifetime" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(200, 30), + Size = new Float2(170, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), @@ -533,9 +535,10 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 110, Title = "Particle Normalized Age", - Description = "Particle normalized age to range 0-1 (age divided by lifetime).", + Description = "The normalized age of the particle, represented as 0 (max lifetime) to 1 (max age). (Same as age divided by lifetime.)", + AlternativeTitles = new[] { "Lifetime" }, Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph, - Size = new Float2(230, 30), + Size = new Float2(250, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0), diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 7c2cb23ad..3151f9b17 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -708,12 +708,30 @@ namespace FlaxEditor.Surface { var index = (int)label.Tag; menu.AddSeparator(); + menu.AddButton("Copy name", () => Clipboard.Text = ((IVisjectSurfaceWindow)Values[0]).VisjectSurface.Parameters[index].Name); + // TODO: move 'Copy all names' to context menu of the Properties category (as it's not item-specific) + menu.AddButton("Copy all names", CopyAllParameterNamesAsConstantCSharpCode); + menu.AddSeparator(); menu.AddButton("Rename", () => StartParameterRenaming(index, label)); menu.AddButton("Edit attributes...", () => EditAttributesParameter(index, label)); menu.AddButton("Delete", () => DeleteParameter(index)); OnParamContextMenu(index, menu); } + private void CopyAllParameterNamesAsConstantCSharpCode() + { + string allParamNames = ""; + foreach (var param in ((IVisjectSurfaceWindow)Values[0]).VisjectSurface.Parameters) + { + string cleanParamName = param.Name.Replace(" ", ""); + // Filter out headers and other non-parameter entries that can be present in the parameters list + if (string.IsNullOrEmpty(cleanParamName)) + continue; + allParamNames += $"private const string {cleanParamName}ParameterName = \"{param.Name}\";\n"; + } + Clipboard.Text = allParamNames; + } + private void StartParameterRenaming(int index, Control label) { var window = (IVisjectSurfaceWindow)Values[0]; diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 1ad225997..40dd9131b 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -39,8 +39,7 @@ namespace FlaxEditor.Windows folder = CurrentViewFolder; } Assert.IsNotNull(folder); - bool isRootFolder = CurrentViewFolder == _root.Folder; - + // Create context menu ContextMenuButton b; ContextMenu cm = new ContextMenu @@ -78,7 +77,7 @@ namespace FlaxEditor.Windows cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path))); if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text)) - { + { cm.AddButton("Show in Content Panel", () => { Editor.Instance.Windows.ContentWin.ClearItemsSearch(); @@ -178,19 +177,61 @@ namespace FlaxEditor.Windows cm.AddSeparator(); + CreateNewModuleMenu(cm, folder); + CreateNewFolderMenu(cm, folder, false, item); + CreateNewContentItemMenu(cm, folder); + + if (folder.CanHaveAssets) + { + cm.AddButton("Import file", () => + { + _view.ClearSelection(); + Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder); + }); + } + + // Remove any leftover separator + if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator) + cm.ItemsContainer.Children.Last().Dispose(); + + // Show it + cm.Show(this, location); + } + + private void CreateNewModuleMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false) + { // Check if is source folder to add new module if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source) { - var button = cm.AddButton("New module"); + var button = menu.AddButton("New module"); button.CloseMenuOnClick = false; button.Clicked += () => NewModule(button, parentFolderNode.Source.Path); } - - if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) + else if (disableUncreatable) { - cm.AddButton("New folder", NewFolder); + var button = menu.AddButton("New module"); + button.Enabled = false; } + } + private bool CanCreateFolder(ContentItem item = null) + { + bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode); + return canCreateFolder; + } + + private void CreateNewFolderMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false, ContentItem item = null) + { + bool canCreateFolder = CanCreateFolder(item); + if (canCreateFolder || disableUncreatable) + { + var b = menu.AddButton("New folder", NewFolder); + b.Enabled = canCreateFolder; + } + } + + private void CreateNewContentItemMenu(ContextMenu menu, ContentFolder folder, bool showNew = true, bool disableUncreatable = false) + { // Loop through each proxy and user defined json type and add them to the context menu var actorType = new ScriptType(typeof(Actor)); var scriptType = new ScriptType(typeof(Script)); @@ -230,7 +271,8 @@ namespace FlaxEditor.Windows if (p == null) continue; - if (p.CanCreate(folder)) + bool canCreate = p.CanCreate(folder); + if (canCreate || disableUncreatable) { var parts = attribute.Path.Split('/'); ContextMenuChildMenu childCM = null; @@ -238,16 +280,20 @@ namespace FlaxEditor.Windows for (int i = 0; i < parts?.Length; i++) { var part = parts[i].Trim(); + if (part == "New" && !showNew) + continue; if (i == parts.Length - 1) { if (mainCM) { - cm.AddButton(part, () => NewItem(p)); + var b = menu.AddButton(part, () => NewItem(p)); + b.Enabled = canCreate; mainCM = false; } else if (childCM != null) { - childCM.ContextMenu.AddButton(part, () => NewItem(p)); + var b = childCM.ContextMenu.AddButton(part, () => NewItem(p)); + b.Enabled = canCreate; childCM.ContextMenu.AutoSort = true; } } @@ -255,35 +301,21 @@ namespace FlaxEditor.Windows { if (mainCM) { - childCM = cm.GetOrAddChildMenu(part); + childCM = menu.GetOrAddChildMenu(part); childCM.ContextMenu.AutoSort = true; + childCM.Enabled = canCreate; mainCM = false; } else if (childCM != null) { childCM = childCM.ContextMenu.GetOrAddChildMenu(part); childCM.ContextMenu.AutoSort = true; + childCM.Enabled = canCreate; } } } } } - - if (folder.CanHaveAssets) - { - cm.AddButton("Import file", () => - { - _view.ClearSelection(); - Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder); - }); - } - - // Remove any leftover separator - if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator) - cm.ItemsContainer.Children.Last().Dispose(); - - // Show it - cm.Show(this, location); } private void OnExpandAllClicked(ContextMenuButton button) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 41382e60c..9b92b380f 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -37,6 +37,7 @@ namespace FlaxEditor.Windows private readonly ToolStrip _toolStrip; private readonly ToolStripButton _importButton; + private readonly ToolStripButton _createNewButton; private readonly ToolStripButton _navigateBackwardButton; private readonly ToolStripButton _navigateForwardButton; private readonly ToolStripButton _navigateUpButton; @@ -154,11 +155,12 @@ namespace FlaxEditor.Windows { Parent = this, }; - _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content"); + _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content."); + _createNewButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Add64, OnCreateNewItemButtonClicked).LinkTooltip("Create a new asset. Shift + left click to create a new folder."); _toolStrip.AddSeparator(); - _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward"); - _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward"); - _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up"); + _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward."); + _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward."); + _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up."); _toolStrip.AddSeparator(); // Navigation bar @@ -271,6 +273,42 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus()); } + private void OnCreateNewItemButtonClicked() + { + if (Input.GetKey(KeyboardKeys.Shift) && CanCreateFolder()) + { + NewFolder(); + return; + } + + var menu = new ContextMenu(); + + InterfaceOptions interfaceOptions = Editor.Instance.Options.Options.Interface; + bool disableUnavaliable = interfaceOptions.UnavaliableContentCreateOptions == InterfaceOptions.DisabledHidden.Disabled; + + CreateNewFolderMenu(menu, CurrentViewFolder, disableUnavaliable); + CreateNewModuleMenu(menu, CurrentViewFolder, disableUnavaliable); + menu.AddSeparator(); + CreateNewContentItemMenu(menu, CurrentViewFolder, false, disableUnavaliable); + // Hack: Show the menu once to get the direction, then show it above or below the button depending on the direction. + menu.Show(this, _createNewButton.UpperLeft); + var direction = menu.Direction; + menu.Hide(); + bool below = false; + switch (direction) + { + case ContextMenuDirection.RightDown: + case ContextMenuDirection.LeftDown: + below = true; + break; + case ContextMenuDirection.RightUp: + case ContextMenuDirection.LeftUp: + below = false; + break; + } + menu.Show(this, below ? _createNewButton.BottomLeft : _createNewButton.UpperLeft, direction); + } + private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) { var menu = new ContextMenu(); diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index 58903912c..07762be2f 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -207,16 +207,16 @@ public: API_PROPERTY() void SetAttenuation(float value); /// - /// Gets the doppler effect factor. Scale for source velocity. Default is 1. + /// Gets the doppler effect factor. Scale for source velocity. Default is 1. Used by spatial sources only. Cannot scale the effect up (only dim it). /// - API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, float.MaxValue, 0.1f), EditorDisplay(\"Audio Source\")") + API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, 1.0f, 0.1f), EditorDisplay(\"Audio Source\")") FORCE_INLINE float GetDopplerFactor() const { return _dopplerFactor; } /// - /// Sets the doppler effect factor. Scale for source velocity. Default is 1. + /// Sets the doppler effect factor. Scale for source velocity. Default is 1. Used by spatial sources only. Cannot scale the effect up (only dim it). /// API_PROPERTY() void SetDopplerFactor(float value); diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index 68ad4007f..f32c34d30 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -848,6 +848,13 @@ bool AudioBackendOAL::Base_Init() } // Init + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model + if (Audio::GetActiveDeviceIndex() == Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1)) + { + // Manually create context if SetActiveDeviceIndex won't call it + Base_OnActiveDeviceChanged(); + } + Audio::SetActiveDeviceIndex(activeDeviceIndex); #ifdef AL_SOFT_source_spatialize if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize")) ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel); @@ -856,13 +863,6 @@ bool AudioBackendOAL::Base_Init() ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::HRTF); #endif Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor); - alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model - if (Audio::GetActiveDeviceIndex() == Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1)) - { - // Manually create context if SetActiveDeviceIndex won't call it - Base_OnActiveDeviceChanged(); - } - Audio::SetActiveDeviceIndex(activeDeviceIndex); ALC::Inited = true; // Log service info diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index da9e3b39b..285b9ec72 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -246,7 +246,7 @@ VariantType::VariantType(VariantType&& other) noexcept VariantType& VariantType::operator=(const Types& type) { Type = type; - if (StaticName) + if (!StaticName) Allocator::Free(TypeName); TypeName = nullptr; StaticName = 0; @@ -265,7 +265,7 @@ VariantType& VariantType::operator=(const VariantType& other) { ASSERT(this != &other); Type = other.Type; - if (StaticName) + if (!StaticName) Allocator::Free(TypeName); StaticName = other.StaticName; if (StaticName) @@ -315,7 +315,7 @@ void VariantType::SetTypeName(const StringView& typeName) { if (StringUtils::Length(TypeName) != typeName.Length()) { - if (StaticName) + if (!StaticName) Allocator::Free(TypeName); StaticName = 0; TypeName = static_cast(Allocator::Allocate(typeName.Length() + 1)); @@ -328,7 +328,7 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName) { if (StringUtils::Length(TypeName) != typeName.Length() || StaticName != staticName) { - if (StaticName) + if (!StaticName) Allocator::Free(TypeName); StaticName = staticName; if (staticName) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 116866848..efe89bf5c 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -6,14 +6,13 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Random.h" #include "Engine/Engine/Engine.h" +#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Content/Deprecated.h" #if !FOLIAGE_USE_SINGLE_QUAD_TREE #include "Engine/Threading/JobSystem.h" -#if FOLIAGE_USE_DRAW_CALLS_BATCHING -#include "Engine/Graphics/RenderTools.h" -#endif #endif #include "Engine/Level/SceneQuery.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -125,7 +124,7 @@ void Foliage::AddToCluster(ChunkedArrayLODs.Get()[lod].Meshes; for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) @@ -141,7 +140,7 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan auto* e = result.TryGet(key); if (!e) { - e = &result.Add(key, BatchedDrawCall(renderContext.List))->Value; + e = &result.Add(key, BatchedDrawCall(context.RenderContext.List))->Value; ASSERT_LOW_LAYER(key.Mat); e->DrawCall.Material = key.Mat; e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr; @@ -152,21 +151,18 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan auto& instanceData = e->Instances.AddOne(); Matrix world; const Transform transform = _transform.LocalToWorld(instance.Transform); - const Float3 translation = transform.Translation - renderContext.View.Origin; + const Float3 translation = transform.Translation - context.ViewOrigin; Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); constexpr float worldDeterminantSign = 1.0f; instanceData.Store(world, world, instance.Lightmap.UVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor); } } -void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const +void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const { // Skip clusters that around too far from view - const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); - if (Float3::Distance(lodView->Position, cluster->TotalBoundsSphere.Center - lodView->Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) + if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) return; - const Vector3 viewOrigin = renderContext.View.Origin; - //DebugDraw::DrawBox(cluster->Bounds, Color::Red); // Draw visible children @@ -178,10 +174,10 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, BoundingBox box; #define DRAW_CLUSTER(idx) \ box = cluster->Children[idx]->TotalBounds; \ - box.Minimum -= viewOrigin; \ - box.Maximum -= viewOrigin; \ - if (renderContext.View.CullingFrustum.Intersects(box)) \ - DrawCluster(renderContext, cluster->Children[idx], type, drawCallsLists, result) + box.Minimum -= context.ViewOrigin; \ + box.Maximum -= context.ViewOrigin; \ + if (context.RenderContext.View.CullingFrustum.Intersects(box)) \ + DrawCluster(context, cluster->Children[idx], drawCallsLists, result) DRAW_CLUSTER(0); DRAW_CLUSTER(1); DRAW_CLUSTER(2); @@ -192,21 +188,22 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, { // Draw visible instances const auto frame = Engine::FrameCount; - const auto model = type.Model.Get(); - const auto transitionLOD = renderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions + const auto model = context.FoliageType.Model.Get(); + const auto transitionLOD = context.RenderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions // TODO: move DrawState to be stored per-view (so shadows can fade objects on their own) for (int32 i = 0; i < cluster->Instances.Count(); i++) { auto& instance = *cluster->Instances.Get()[i]; BoundingSphere sphere = instance.Bounds; - sphere.Center -= viewOrigin; - if (Float3::Distance(lodView->Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && - renderContext.View.CullingFrustum.Intersects(sphere)) + sphere.Center -= context.ViewOrigin; + if (Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && + context.RenderContext.View.CullingFrustum.Intersects(sphere) && + RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq) { const auto modelFrame = instance.DrawState.PrevFrame + 1; // Select a proper LOD index (model may be culled) - int32 lodIndex = RenderTools::ComputeModelLOD(model, sphere.Center, (float)sphere.Radius, renderContext); + int32 lodIndex = RenderTools::ComputeModelLOD(model, sphere.Center, (float)sphere.Radius, context.RenderContext); if (lodIndex == -1) { // Handling model fade-out transition @@ -231,20 +228,20 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, { const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD); const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result); + DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result); } } else if (instance.DrawState.LODTransition < 255) { const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD); const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result); + DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result); } } instance.DrawState.PrevFrame = frame; continue; } - lodIndex += renderContext.View.ModelLODBias; + lodIndex += context.RenderContext.View.ModelLODBias; lodIndex = model->ClampLODIndex(lodIndex); if (transitionLOD) @@ -278,19 +275,19 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, // Draw if (instance.DrawState.PrevLOD == lodIndex) { - DrawInstance(renderContext, instance, type, model, lodIndex, 0.0f, drawCallsLists, result); + DrawInstance(context, instance, model, lodIndex, 0.0f, drawCallsLists, result); } else if (instance.DrawState.PrevLOD == -1) { const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result); + DrawInstance(context, instance, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result); } else { const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD); const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result); - DrawInstance(renderContext, instance, type, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result); + DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result); + DrawInstance(context, instance, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result); } //DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen); @@ -304,14 +301,11 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, #else -void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw) +void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::DrawInfo& draw) { // Skip clusters that around too far from view - const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); - if (Float3::Distance(lodView->Position, cluster->TotalBoundsSphere.Center - lodView->Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) + if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) return; - const Vector3 viewOrigin = renderContext.View.Origin; - //DebugDraw::DrawBox(cluster->Bounds, Color::Red); // Draw visible children @@ -323,10 +317,10 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, BoundingBox box; #define DRAW_CLUSTER(idx) \ box = cluster->Children[idx]->TotalBounds; \ - box.Minimum -= viewOrigin; \ - box.Maximum -= viewOrigin; \ - if (renderContext.View.CullingFrustum.Intersects(box)) \ - DrawCluster(renderContext, cluster->Children[idx], draw) + box.Minimum -= context.ViewOrigin; \ + box.Maximum -= context.ViewOrigin; \ + if (context.RenderContext.View.CullingFrustum.Intersects(box)) \ + DrawCluster(context, cluster->Children[idx], draw) DRAW_CLUSTER(0); DRAW_CLUSTER(1); DRAW_CLUSTER(2); @@ -342,16 +336,17 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, auto& instance = *cluster->Instances[i]; auto& type = FoliageTypes[instance.Type]; BoundingSphere sphere = instance.Bounds; - sphere.Center -= viewOrigin; + sphere.Center -= context.ViewOrigin; // Check if can draw this instance if (type._canDraw && - Float3::Distance(lodView->Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && - renderContext.View.CullingFrustum.Intersects(sphere)) + Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && + context.RenderContext.View.CullingFrustum.Intersects(sphere) && + RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq) { Matrix world; const Transform transform = _transform.LocalToWorld(instance.Transform); - const Float3 translation = transform.Translation - renderContext.View.Origin; + const Float3 translation = transform.Translation - context.ViewOrigin; Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); // Disable motion blur @@ -367,7 +362,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, draw.PerInstanceRandom = instance.Random; draw.DrawModes = type.DrawModes; draw.SetStencilValue(_layer); - type.Model->Draw(renderContext, draw); + type.Model->Draw(context.RenderContext, draw); //DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen); @@ -446,22 +441,50 @@ void Foliage::DrawFoliageJob(int32 i) PROFILE_CPU(); PROFILE_MEM(Graphics); const FoliageType& type = FoliageTypes[i]; - if (type.IsReady() && type.Model->CanBeRendered()) + if (type._canDraw) { DrawCallsList drawCallsLists[MODEL_MAX_LODS]; for (RenderContext& renderContext : _renderContextBatch->Contexts) + { +#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING DrawType(renderContext, type, drawCallsLists); +#else + Mesh::DrawInfo draw; + draw.Flags = GetStaticFlags(); + draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass); + draw.LODBias = 0; + draw.ForcedLOD = -1; + draw.VertexColors = nullptr; + draw.Deformation = nullptr; + DrawType(renderContext, type, draw); +#endif + } } } #endif +#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists) +#else +void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Mesh::DrawInfo& draw) +#endif { if (!type.Root || !FOLIAGE_CAN_DRAW(renderContext, type)) return; const DrawPass typeDrawModes = FOLIAGE_GET_DRAW_MODES(renderContext, type); PROFILE_CPU_ASSET(type.Model); + DrawContext context + { + renderContext, + renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View, + type, + renderContext.View.Origin, + Math::Square(Graphics::Shadows::MinObjectPixelSize), + renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y, + }; + if (context.RenderContext.View.Pass != DrawPass::Depth) + context.MinObjectPixelSizeSq = 0.0f; // Don't use it in main view #if FOLIAGE_USE_DRAW_CALLS_BATCHING // Initialize draw calls for foliage type all LODs meshes for (int32 lod = 0; lod < type.Model->LODs.Count(); lod++) @@ -506,7 +529,7 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr // Draw instances of the foliage type BatchedDrawCalls result(&renderContext.List->Memory); - DrawCluster(renderContext, type.Root, type, drawCallsLists, result); + DrawCluster(context, type.Root, drawCallsLists, result); // Submit draw calls with valid instances added for (auto& e : result) @@ -568,10 +591,22 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr } } #else - DrawCluster(renderContext, type.Root, draw); + DrawCluster(context, type.Root, draw); #endif } +void Foliage::InitType(const RenderView& view, FoliageType& type) +{ + const DrawPass drawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); + type._canDraw = type.IsReady() && drawModes != DrawPass::None && type.Model && type.Model->CanBeRendered(); + for (int32 j = 0; j < type.Entries.Count(); j++) + { + auto& e = type.Entries[j]; + e.ReceiveDecals = type.ReceiveDecals != 0; + e.ShadowsMode = type.ShadowsMode; + } +} + int32 Foliage::GetInstancesCount() const { return Instances.Count(); @@ -1131,12 +1166,7 @@ void Foliage::Draw(RenderContext& renderContext) // Cache data per foliage instance type for (auto& type : FoliageTypes) { - for (int32 j = 0; j < type.Entries.Count(); j++) - { - auto& e = type.Entries[j]; - e.ReceiveDecals = type.ReceiveDecals != 0; - e.ShadowsMode = type.ShadowsMode; - } + InitType(renderContext.View, type); } if (renderContext.View.Pass == DrawPass::GlobalSDF) @@ -1202,12 +1232,7 @@ void Foliage::Draw(RenderContext& renderContext) // Draw single foliage instance projection into Global Surface Atlas auto& instance = *(FoliageInstance*)GlobalSurfaceAtlasPass::Instance()->GetCurrentActorObject(); auto& type = FoliageTypes[instance.Type]; - for (int32 i = 0; i < type.Entries.Count(); i++) - { - auto& e = type.Entries[i]; - e.ReceiveDecals = type.ReceiveDecals != 0; - e.ShadowsMode = type.ShadowsMode; - } + InitType(renderContext.View, type); Matrix world; const Transform transform = _transform.LocalToWorld(instance.Transform); renderContext.View.GetWorldMatrix(transform, world); @@ -1239,8 +1264,9 @@ void Foliage::Draw(RenderContext& renderContext) draw.LODBias = 0; draw.ForcedLOD = -1; draw.VertexColors = nullptr; + draw.Deformation = nullptr; #else - DrawCallsList drawCallsLists[MODEL_MAX_LODS]; + DrawCallsList draw[MODEL_MAX_LODS]; #endif #if FOLIAGE_USE_SINGLE_QUAD_TREE if (Root) @@ -1248,7 +1274,7 @@ void Foliage::Draw(RenderContext& renderContext) #else for (auto& type : FoliageTypes) { - DrawType(renderContext, type, drawCallsLists); + DrawType(renderContext, type, draw); } #endif } @@ -1265,14 +1291,7 @@ void Foliage::Draw(RenderContextBatch& renderContextBatch) { // Cache data per foliage instance type for (FoliageType& type : FoliageTypes) - { - for (int32 j = 0; j < type.Entries.Count(); j++) - { - auto& e = type.Entries[j]; - e.ReceiveDecals = type.ReceiveDecals != 0; - e.ShadowsMode = type.ShadowsMode; - } - } + InitType(view, type); // Run async job for each foliage type _renderContextBatch = &renderContextBatch; diff --git a/Source/Engine/Foliage/Foliage.h b/Source/Engine/Foliage/Foliage.h index 6f8b36cf4..83ab6206c 100644 --- a/Source/Engine/Foliage/Foliage.h +++ b/Source/Engine/Foliage/Foliage.h @@ -158,6 +158,15 @@ public: private: void AddToCluster(ChunkedArray& clusters, FoliageCluster* cluster, FoliageInstance& instance); + struct DrawContext + { + RenderContext& RenderContext; + const RenderView& LodView; + const FoliageType& FoliageType; + Vector3 ViewOrigin; + float MinObjectPixelSizeSq; + float ViewScreenSizeSq; + }; #if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING struct DrawKey { @@ -181,10 +190,12 @@ private: typedef Array> DrawCallsList; typedef Dictionary BatchedDrawCalls; - void DrawInstance(RenderContext& renderContext, FoliageInstance& instance, const FoliageType& type, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; - void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; + void DrawInstance(DrawContext& context, FoliageInstance& instance, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; + void DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; + void DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists); #else - void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw); + void DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::DrawInfo& draw); + void DrawType(RenderContext& renderContext, const FoliageType& type, Mesh::DrawInfo& draw); #endif #if !FOLIAGE_USE_SINGLE_QUAD_TREE void DrawClusterGlobalSDF(class GlobalSignDistanceFieldPass* globalSDF, const BoundingBox& globalSDFBounds, FoliageCluster* cluster, const FoliageType& type); @@ -192,7 +203,8 @@ private: void DrawFoliageJob(int32 i); RenderContextBatch* _renderContextBatch; #endif - void DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists); + + void InitType(const RenderView& view, FoliageType& type); public: /// diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h index bbe7e7739..224ed0bd8 100644 --- a/Source/Engine/Foliage/FoliageType.h +++ b/Source/Engine/Foliage/FoliageType.h @@ -47,6 +47,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb friend Foliage; private: uint8 _isReady : 1; + uint8 _canDraw : 1; public: /// diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 4d5dcecd7..f463df2fe 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -32,6 +32,7 @@ bool Graphics::SpreadWorkload = true; #if !BUILD_RELEASE || USE_EDITOR float Graphics::TestValue = 0.0f; #endif +float Graphics::Shadows::MinObjectPixelSize = 2.0f; bool Graphics::PostProcessing::ColorGradingVolumeLUT = true; #if GRAPHICS_API_NULL diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 97ba6a724..aed1232f6 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -101,8 +101,17 @@ public: #endif public: + // Shadows rendering configuration. + API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Shadows + { + DECLARE_SCRIPTING_TYPE_MINIMAL(Shadows); + + // The minimum size in pixels of objects to cast shadows. Improves performance by skipping too small objects (eg. sub-pixel) from rendering into shadow maps. + API_FIELD() static float MinObjectPixelSize; + }; + // Post Processing effects rendering configuration. - API_CLASS(Static, Attributes = "DebugCommand") class FLAXENGINE_API PostProcessing + API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API PostProcessing { DECLARE_SCRIPTING_TYPE_MINIMAL(PostProcessing); diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp index 379f57eb7..48daecc83 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp @@ -20,15 +20,6 @@ void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, Semaphore _waitSemaphores.Add(waitSemaphore); } -void CmdBufferVulkan::Wait(float timeInSecondsToWait) -{ - if (!IsSubmitted()) - return; - const bool failed = _device->FenceManager.WaitForFence(_fence, (uint64)(timeInSecondsToWait * 1e9)); - ASSERT(!failed); - RefreshFenceStatus(); -} - void CmdBufferVulkan::Begin() { PROFILE_CPU(); @@ -145,6 +136,16 @@ void CmdBufferVulkan::EndEvent() #endif +void CmdBufferVulkan::Wait(float timeoutSeconds) +{ + if (!IsSubmitted()) + return; + PROFILE_CPU(); + const bool failed = _device->FenceManager.WaitForFence(GetFence(), timeoutSeconds); + ASSERT(!failed); + RefreshFenceStatus(); +} + void CmdBufferVulkan::RefreshFenceStatus() { if (_state == State::Submitted) @@ -306,14 +307,6 @@ void CmdBufferManagerVulkan::SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaph _activeCmdBuffer = nullptr; } -void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float timeInSecondsToWait) -{ - ASSERT(cmdBuffer->IsSubmitted()); - const bool failed = _device->FenceManager.WaitForFence(cmdBuffer->GetFence(), (uint64)(timeInSecondsToWait * 1e9)); - ASSERT(!failed); - cmdBuffer->RefreshFenceStatus(); -} - void CmdBufferManagerVulkan::GetNewActiveCommandBuffer() { PROFILE_CPU(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h index fef86b0bb..925f7a40f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h @@ -119,7 +119,6 @@ public: public: void AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore); - void Wait(float timeInSecondsToWait = 1.0f); void Begin(); void End(); @@ -137,6 +136,7 @@ public: void EndEvent(); #endif + void Wait(float timeoutSeconds = VULKAN_WAIT_TIMEOUT); void RefreshFenceStatus(); }; @@ -210,8 +210,7 @@ public: public: void SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaphore = nullptr); - void WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float timeInSecondsToWait = 1.0f); - void RefreshFenceStatus(const CmdBufferVulkan* skipCmdBuffer = nullptr) + void RefreshFenceStatus(CmdBufferVulkan* skipCmdBuffer = nullptr) { _pool.RefreshFenceStatus(skipCmdBuffer); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/Config.h b/Source/Engine/GraphicsDevice/Vulkan/Config.h index 7a4129a5c..fd5880400 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Config.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Config.h @@ -49,6 +49,11 @@ #define VULKAN_USE_TIMER_QUERIES 1 #endif +// Fence wait operation timeout in seconds +#ifndef VULKAN_WAIT_TIMEOUT +#define VULKAN_WAIT_TIMEOUT 5.0f +#endif + // Toggles GPUTimerQueryVulkan to use BeginQuery/EndQuery via GPuContext rather than old custom implementation #define GPU_VULKAN_QUERY_NEW 1 diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 5266a704b..5699350b2 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -2253,16 +2253,15 @@ FenceVulkan* FenceManagerVulkan::AllocateFence(bool createSignaled) return fence; } -bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds) const +bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, float timeoutSeconds) const { if (fence->IsSignaled) return false; PROFILE_CPU(); ZoneColor(TracyWaitZoneColor); ASSERT(_usedFences.Contains(fence)); - if (timeInNanoseconds) - timeInNanoseconds = 1000ll * 1000ll * 1000LL; // 1s - const VkResult result = vkWaitForFences(_device->Device, 1, &fence->Handle, true, timeInNanoseconds); + uint64 timeNanoseconds = (uint64)((double)timeoutSeconds * 1000000000.0); + const VkResult result = vkWaitForFences(_device->Device, 1, &fence->Handle, true, timeNanoseconds); LOG_VULKAN_RESULT(result); if (result == VK_SUCCESS) { @@ -2290,11 +2289,11 @@ void FenceManagerVulkan::ReleaseFence(FenceVulkan*& fence) fence = nullptr; } -void FenceManagerVulkan::WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds) +void FenceManagerVulkan::WaitAndReleaseFence(FenceVulkan*& fence, float timeoutSeconds) { ScopeLock lock(_device->_fenceLock); if (!fence->IsSignaled) - WaitForFence(fence, timeInNanoseconds); + WaitForFence(fence, timeoutSeconds); ResetFence(fence); _usedFences.Remove(fence); _freeFences.Add(fence); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 85ad4a647..b5bfe9075 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -106,7 +106,7 @@ public: } // Returns true if waiting timed out or failed, false otherwise. - bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds = 0) const; + bool WaitForFence(FenceVulkan* fence, float timeoutSeconds = VULKAN_WAIT_TIMEOUT) const; void ResetFence(FenceVulkan* fence) const; @@ -114,7 +114,7 @@ public: void ReleaseFence(FenceVulkan*& fence); // Sets the fence handle to null - void WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds = 0); + void WaitAndReleaseFence(FenceVulkan*& fence, float timeoutSeconds = VULKAN_WAIT_TIMEOUT); private: // Returns true if fence was signaled, otherwise false. diff --git a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp index 4d4e06967..39b0d2691 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp @@ -62,10 +62,7 @@ void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 signalSemaphoresCoun const bool WaitForIdleOnSubmit = false; if (WaitForIdleOnSubmit) { - bool success = _device->FenceManager.WaitForFence(fence); - ASSERT(success); - ASSERT(_device->FenceManager.IsFenceSignaled(fence)); - cmdBuffer->GetOwner()->RefreshFenceStatus(); + cmdBuffer->Wait(); } #endif diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index ed667ae05..379c54930 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2485,7 +2485,7 @@ void PhysicsBackend::SetRigidActorPose(void* actor, const Vector3& position, con if (kinematic) { auto actorPhysX = (PxRigidDynamic*)actor; - if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION) + if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION || !(actorPhysX->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC)) { // Ensures the disabled kinematic actor ends up in the correct pose after enabling simulation actorPhysX->setGlobalPose(trans, wakeUp); diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index b14fc8b6d..4a2867d3b 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -731,6 +731,7 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP DrawCallsLists[(int32)DrawCallsListType::MotionVectors].Indices.Add(index); } } + float minObjectPixelSizeSq = Math::Square(Graphics::Shadows::MinObjectPixelSize); for (int32 i = 1; i < renderContextBatch.Contexts.Count(); i++) { const RenderContext& renderContext = renderContextBatch.Contexts.Get()[i]; @@ -738,7 +739,8 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP drawModes = modes & renderContext.View.Pass; if (drawModes != DrawPass::None && (staticFlags & renderContext.View.StaticFlagsMask) == renderContext.View.StaticFlagsCompare && - renderContext.View.CullingFrustum.Intersects(bounds)) + renderContext.View.CullingFrustum.Intersects(bounds) && + RenderTools::ComputeBoundsScreenRadiusSquared(bounds.Center, bounds.Radius, renderContext.View) * (renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y) >= minObjectPixelSizeSq) { renderContext.List->ShadowDepthDrawCallsList.Indices.Add(index); } diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 2fc2ef4b3..7722fc94b 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -437,8 +437,8 @@ namespace FlaxEngine.GUI // Caret if (IsFocused && CaretPosition > -1) { - float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f); - alpha = alpha * alpha * alpha * alpha * alpha * alpha; + float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.8f); + alpha = alpha * alpha; Render2D.FillRectangle(CaretBounds, CaretColor * alpha); } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 1dfd9facc..3d7099db6 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -310,8 +310,8 @@ namespace FlaxEngine.GUI // Caret if (IsFocused && CaretPosition > -1) { - float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f); - alpha = alpha * alpha * alpha * alpha * alpha * alpha; + float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.8f); + alpha = alpha * alpha; Render2D.FillRectangle(CaretBounds, CaretColor * alpha); } diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 243e4786e..489e4c93d 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -191,14 +191,18 @@ namespace FlaxEngine.GUI /// /// Gets or sets the maximum number of characters the user can type into the text box control. /// - [EditorOrder(50), Tooltip("The maximum number of characters the user can type into the text box control.")] + [EditorOrder(50), Limit(-1), Tooltip("The maximum number of characters the user can type into the text box control.")] public int MaxLength { get => _maxLength; set { - if (_maxLength <= 0) - throw new ArgumentOutOfRangeException(nameof(MaxLength)); + // Cap at min of -1 for no max length + if (value <= 0) + { + _maxLength = -1; + return; + } if (_maxLength != value) { @@ -275,7 +279,7 @@ namespace FlaxEngine.GUI /// Gets or sets the speed of the caret flashing animation. /// [EditorDisplay("Caret Style"), EditorOrder(2021), Tooltip("The speed of the caret flashing animation.")] - public float CaretFlashSpeed { get; set; } = 6.0f; + public float CaretFlashSpeed { get; set; } = 6.5f; /// /// Gets or sets the speed of the selection background flashing animation. @@ -381,7 +385,7 @@ namespace FlaxEngine.GUI value = value.Replace(DelChar.ToString(), ""); // Clamp length - if (value.Length > MaxLength) + if (value.Length > MaxLength && MaxLength != -1) value = value.Substring(0, MaxLength); // Ensure to use only single line @@ -514,7 +518,7 @@ namespace FlaxEngine.GUI AutoFocus = true; _isMultiline = isMultiline; - _maxLength = 2147483646; + _maxLength = -1; _selectionStart = _selectionEnd = -1; var style = Style.Current; @@ -710,7 +714,7 @@ namespace FlaxEngine.GUI str = str.Replace("\n", ""); int selectionLength = SelectionLength; - int charactersLeft = MaxLength - _text.Length + selectionLength; + int charactersLeft = (MaxLength != -1 ? MaxLength : int.MaxValue) - _text.Length + selectionLength; Assert.IsTrue(charactersLeft >= 0); if (charactersLeft == 0) return; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 91e8d5f26..f0cef1b15 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1300,10 +1300,10 @@ namespace Flax.Build.Bindings else if (parameterInfo.Type.IsRef && !parameterInfo.Type.IsConst) { // Non-const lvalue reference parameters needs to be passed via temporary value - if (parameterInfo.IsOut || parameterInfo.IsRef) - contents.Append(indent).AppendFormat("{2}& {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine(); - else + if (parameterInfo.Type.Type is "String" or "StringView" or "StringAnsi" or "StringAnsiView" && parameterInfo.Type.GenericArgs == null) contents.Append(indent).AppendFormat("{2} {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine(); + else + contents.Append(indent).AppendFormat("{2}& {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine(); callParams += parameterInfo.Name; callParams += "Temp"; }