Merge remote-tracking branch 'origin/master' into 1.12

# Conflicts:
#	Source/Editor/Options/InterfaceOptions.cs
#	Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
#	Source/Engine/Graphics/Graphics.cpp
#	Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
#	Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h
#	Source/Engine/GraphicsDevice/Vulkan/Config.h
#	Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
#	Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h
#	Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp
This commit is contained in:
Wojtek Figat
2026-03-10 15:08:43 +01:00
28 changed files with 381 additions and 201 deletions

View File

@@ -739,6 +739,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
var style = FlaxEngine.GUI.Style.Current;
// Area for drag&drop scripts
var dragArea = layout.CustomContainer<DragAreaControl>();
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)
{

View File

@@ -37,25 +37,35 @@ namespace FlaxEditor.CustomEditors.Elements
public override ContainerControl ContainerControl => Panel;
/// <summary>
/// Adds utility settings button to the group header.
/// Add utility settings button to the group header.
/// </summary>
/// <returns>The created control.</returns>
public Image AddSettingsButton()
{
return AddHeaderButton("Settings", 0, Style.Current.Settings);
}
/// <summary>
/// Adds a button to the group header.
/// </summary>
/// <returns>The created control.</returns>
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),
};
}
}

View File

@@ -161,7 +161,7 @@ namespace FlaxEditor.Options
}
/// <summary>
/// Options focus Game Window behaviour when play mode is entered.
/// Options for focus Game Window behaviour when play mode is entered.
/// </summary>
public enum PlayModeFocus
{
@@ -209,6 +209,22 @@ namespace FlaxEditor.Options
ClientSide,
}
/// <summary>
/// Generic options for a disabled or hidden state. Used for example in create content button.
/// </summary>
public enum DisabledHidden
{
/// <summary>
/// Disabled state.
/// </summary>
Disabled,
/// <summary>
/// Hidden state.
/// </summary>
Hidden,
}
/// <summary>
/// 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.
/// </summary>
@@ -562,6 +578,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)]
public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating what should happen to unavaliable options in the content create menu.
/// </summary>
[DefaultValue(DisabledHidden.Hidden)]
[EditorDisplay("Content"), EditorOrder(600)]
public DisabledHidden UnavaliableContentCreateOptions { get; set; } = DisabledHidden.Hidden;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);

View File

@@ -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),

View File

@@ -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),

View File

@@ -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];

View File

@@ -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)

View File

@@ -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();

View File

@@ -207,16 +207,16 @@ public:
API_PROPERTY() void SetAttenuation(float value);
/// <summary>
/// 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).
/// </summary>
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;
}
/// <summary>
/// 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).
/// </summary>
API_PROPERTY() void SetDopplerFactor(float value);

View File

@@ -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

View File

@@ -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<char*>(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)

View File

@@ -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(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instance, const FoliageType& type, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
void Foliage::DrawInstance(DrawContext& context, FoliageInstance& instance, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
{
const auto& meshes = model->LODs.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<float>(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<float>(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<float>(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<float>(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;

View File

@@ -158,6 +158,15 @@ public:
private:
void AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE>& 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<struct DrawCall, InlinedAllocation<8>> DrawCallsList;
typedef Dictionary<DrawKey, struct BatchedDrawCall, ConcurrentArenaAllocation> 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:
/// <summary>

View File

@@ -47,6 +47,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb
friend Foliage;
private:
uint8 _isReady : 1;
uint8 _canDraw : 1;
public:
/// <summary>

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -191,14 +191,18 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the maximum number of characters the user can type into the text box control.
/// </summary>
[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.
/// </summary>
[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;
/// <summary>
/// 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;

View File

@@ -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";
}