diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index 18f653f86..0e94c5c06 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2c8aa181a814d69b15ffec6493a71a6f42ae816ce04f7803cff2d5073b4b3c4f
-size 11790
+oid sha256:e075583620e62407503c73f52487c204ddcad421e80fa621aebd55af1cfb08d5
+size 11798
diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs
index e1a3acdf6..259be104b 100644
--- a/Source/Editor/Content/GUI/ContentView.cs
+++ b/Source/Editor/Content/GUI/ContentView.cs
@@ -220,8 +220,9 @@ namespace FlaxEditor.Content.GUI
// Remove references and unlink items
for (int i = 0; i < _items.Count; i++)
{
- _items[i].Parent = null;
- _items[i].RemoveReference(this);
+ var item = _items[i];
+ item.Parent = null;
+ item.RemoveReference(this);
}
_items.Clear();
@@ -263,11 +264,12 @@ namespace FlaxEditor.Content.GUI
// Add references and link items
for (int i = 0; i < items.Count; i++)
{
- if (items[i].Visible)
+ var item = items[i];
+ if (item.Visible && !_items.Contains(item))
{
- items[i].Parent = this;
- items[i].AddReference(this);
- _items.Add(items[i]);
+ item.Parent = this;
+ item.AddReference(this);
+ _items.Add(item);
}
}
if (selection != null)
@@ -279,6 +281,8 @@ namespace FlaxEditor.Content.GUI
// Sort items depending on sortMethod parameter
_children.Sort(((control, control1) =>
{
+ if (control == null || control1 == null)
+ return 0;
if (sortType == SortType.AlphabeticReverse)
{
if (control.CompareTo(control1) > 0)
diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs
index 6bbdc0a51..604caa704 100644
--- a/Source/Editor/Content/Items/ContentItem.cs
+++ b/Source/Editor/Content/Items/ContentItem.cs
@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
/// The new path.
internal virtual void UpdatePath(string value)
{
- Assert.AreNotEqual(Path, value);
-
// Set path
Path = StringUtils.NormalizePath(value);
FileName = System.IO.Path.GetFileName(value);
diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs
index 0477ad18e..2dccc9e47 100644
--- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs
+++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs
@@ -54,7 +54,7 @@ namespace FlaxEditor.Content
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Texture)request.Asset);
+ return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs
index 78d88b440..004c2aed7 100644
--- a/Source/Editor/Content/Proxy/SceneProxy.cs
+++ b/Source/Editor/Content/Proxy/SceneProxy.cs
@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
return item is SceneItem;
}
+ ///
+ public override bool AcceptsAsset(string typeName, string path)
+ {
+ return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
+ }
+
///
public override bool CanCreate(ContentFolder targetLocation)
{
diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
index 160c783ed..1282e4daa 100644
--- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
+++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
@@ -406,18 +406,16 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < maxChecks; i++)
{
var request = _requests[i];
-
try
{
if (request.IsReady)
- {
return request;
- }
}
catch (Exception ex)
{
- Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
+ Editor.LogWarning(ex);
+ _requests.RemoveAt(i--);
}
}
@@ -515,7 +513,6 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < checks; i++)
{
var request = _requests[i];
-
try
{
if (request.IsReady)
@@ -529,8 +526,9 @@ namespace FlaxEditor.Content.Thumbnails
}
catch (Exception ex)
{
- Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
+ Editor.LogWarning(ex);
+ _requests.RemoveAt(i--);
}
}
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index c9bbc016e..8306475d1 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -131,7 +131,8 @@ bool DeployDataStep::Perform(CookingData& data)
if (FileSystem::DirectoryExists(dstDotnet))
{
String cachedData;
- File::ReadAllText(dotnetCacheFilePath, cachedData);
+ if (FileSystem::FileExists(dotnetCacheFilePath))
+ File::ReadAllText(dotnetCacheFilePath, cachedData);
if (cachedData != dotnetCachedValue)
{
FileSystem::DeleteDirectory(dstDotnet);
@@ -360,7 +361,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX);
- if (data.Configuration != BuildConfiguration::Release)
+ if (!buildSettings.SkipDefaultFonts)
data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register custom assets (eg. plugins)
diff --git a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
index c6043e8b6..4053c6e16 100644
--- a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
@@ -36,7 +36,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
var gizmos = gizmoOwner.Gizmos;
_gizmoMode = new ClothPaintingGizmoMode();
-
+
var projectCache = Editor.Instance.ProjectCache;
if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue))
_gizmoMode.PaintValue = JsonSerializer.Deserialize(cachedPaintValue);
@@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
_gizmoMode.BrushSize = JsonSerializer.Deserialize(cachedBrushSize);
if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
_gizmoMode.BrushStrength = JsonSerializer.Deserialize(cachedBrushStrength);
-
+
gizmos.AddMode(_gizmoMode);
_prevMode = gizmos.ActiveMode;
gizmos.ActiveMode = _gizmoMode;
diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
index a6c4e6623..8fb742b5e 100644
--- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
@@ -1,6 +1,13 @@
-using FlaxEditor.CustomEditors.Editors;
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Actions;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using System.Collections.Generic;
namespace FlaxEditor.CustomEditors.Dedicated;
@@ -10,6 +17,10 @@ namespace FlaxEditor.CustomEditors.Dedicated;
[CustomEditor(typeof(MissingScript)), DefaultEditor]
public class MissingScriptEditor : GenericEditor
{
+ private DropPanel _dropPanel;
+ private Button _replaceScriptButton;
+ private CheckBox _shouldReplaceAllCheckbox;
+
///
public override void Initialize(LayoutElementsContainer layout)
{
@@ -18,9 +29,137 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout);
return;
}
+ _dropPanel = dropPanel;
+ _dropPanel.HeaderTextColor = Color.OrangeRed;
- dropPanel.HeaderTextColor = Color.OrangeRed;
+ var replaceScriptPanel = new Panel
+ {
+ Parent = _dropPanel,
+ Height = 64,
+ };
+
+ _replaceScriptButton = new Button
+ {
+ Text = "Replace Script",
+ TooltipText = "Replaces the missing script with a given script type",
+ AnchorPreset = AnchorPresets.TopCenter,
+ Width = 240,
+ Height = 24,
+ X = -120,
+ Y = 0,
+ Parent = replaceScriptPanel,
+ };
+ _replaceScriptButton.Clicked += OnReplaceScriptButtonClicked;
+
+ var replaceAllLabel = new Label
+ {
+ Text = "Replace all matching missing scripts",
+ TooltipText = "Whether or not to apply this script change to all scripts missing the same type.",
+ AnchorPreset = AnchorPresets.BottomCenter,
+ Y = -34,
+ Parent = replaceScriptPanel,
+ };
+ replaceAllLabel.X -= FlaxEngine.GUI.Style.Current.FontSmall.MeasureText(replaceAllLabel.Text).X;
+
+ _shouldReplaceAllCheckbox = new CheckBox
+ {
+ TooltipText = replaceAllLabel.TooltipText,
+ AnchorPreset = AnchorPresets.BottomCenter,
+ Y = -34,
+ Parent = replaceScriptPanel,
+ };
+
+ float centerDifference = (_shouldReplaceAllCheckbox.Right - replaceAllLabel.Left) / 2;
+ replaceAllLabel.X += centerDifference;
+ _shouldReplaceAllCheckbox.X += centerDifference;
base.Initialize(layout);
}
+
+ private void FindActorsWithMatchingMissingScript(List missingScripts)
+ {
+ foreach (Actor actor in Level.GetActors(typeof(Actor)))
+ {
+ for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
+ {
+ Script actorScript = actor.Scripts[scriptIndex];
+ if (actorScript is not MissingScript missingActorScript)
+ continue;
+
+ MissingScript currentMissing = Values[0] as MissingScript;
+ if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
+ continue;
+
+ missingScripts.Add(missingActorScript);
+ }
+ }
+ }
+
+ private void RunReplacementMultiCast(List actions)
+ {
+ if (actions.Count == 0)
+ {
+ Editor.LogWarning("Failed to replace scripts!");
+ return;
+ }
+
+ var multiAction = new MultiUndoAction(actions);
+ multiAction.Do();
+ var presenter = ParentEditor.Presenter;
+ if (presenter != null)
+ {
+ presenter.Undo.AddAction(multiAction);
+ presenter.Control.Focus();
+ }
+ }
+
+ private void ReplaceScript(ScriptType script, bool replaceAllInScene)
+ {
+ var actions = new List(4);
+
+ var missingScripts = new List();
+ if (!replaceAllInScene)
+ missingScripts.Add((MissingScript)Values[0]);
+ else
+ FindActorsWithMatchingMissingScript(missingScripts);
+
+ foreach (var missingScript in missingScripts)
+ actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
+ RunReplacementMultiCast(actions);
+
+ for (int actionIdx = 0; actionIdx < actions.Count; actionIdx++)
+ {
+ AddRemoveScript addRemoveScriptAction = (AddRemoveScript)actions[actionIdx];
+ int orderInParent = addRemoveScriptAction.GetOrderInParent();
+
+ Script newScript = missingScripts[actionIdx].Actor.Scripts[orderInParent];
+ missingScripts[actionIdx].ReferenceScript = newScript;
+ }
+ actions.Clear();
+
+ foreach (var missingScript in missingScripts)
+ actions.Add(AddRemoveScript.Remove(missingScript));
+ RunReplacementMultiCast(actions);
+ }
+
+ private void OnReplaceScriptButtonClicked()
+ {
+ var scripts = Editor.Instance.CodeEditing.Scripts.Get();
+ if (scripts.Count == 0)
+ {
+ // No scripts
+ var cm1 = new ContextMenu();
+ cm1.AddButton("No scripts in project");
+ cm1.Show(_dropPanel, _replaceScriptButton.BottomLeft);
+ return;
+ }
+
+ // Show context menu with list of scripts to add
+ var cm = new ItemsListContextMenu(180);
+ for (int i = 0; i < scripts.Count; i++)
+ cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
+ cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
+ cm.SortItems();
+ cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
+ }
}
diff --git a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
index e46040a42..07af5e991 100644
--- a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
+++ b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
///
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
///
- [System.Obsolete("Deprecated in 1.4")]
+ [System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public DoubleValueBox DoubleValue => ValueBox;
///
diff --git a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs
index 552e9d125..789d8966e 100644
--- a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs
+++ b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
///
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
///
- [System.Obsolete("Deprecated in 1.4, ValueBox instead")]
+ [System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public FloatValueBox FloatValue => ValueBox;
///
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 1b46222ea..b384b6515 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -364,7 +364,7 @@ namespace FlaxEditor
{
foreach (var preview in activePreviews)
{
- if (preview == loadingPreview ||
+ if (preview == loadingPreview ||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
{
// Link it to the prefab preview to see it in the editor
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index 3e5d22eb0..8d6b0f9e2 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -482,8 +482,8 @@ namespace FlaxEditor.GUI
Focus();
});
if (_selected != null)
- {
- var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
+ {
+ var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs
index 7af36fae0..49a60a04e 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.ContextMenu
// Hide parent CM popups and set itself as child
parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0)));
}
-
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
index 161b3f4ae..27878a763 100644
--- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
+++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
@@ -319,7 +319,9 @@ namespace FlaxEditor.GUI.Dialogs
protected override void OnShow()
{
// Auto cancel on lost focus
+#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnCancel;
+#endif
base.OnShow();
}
diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs
index 07fc3ff0d..5054aee98 100644
--- a/Source/Editor/GUI/Dialogs/Dialog.cs
+++ b/Source/Editor/GUI/Dialogs/Dialog.cs
@@ -293,7 +293,7 @@ namespace FlaxEditor.GUI.Dialogs
if (Root != null)
{
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
- Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
+ Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
}
return true;
}
diff --git a/Source/Editor/GUI/Input/SearchBox.cs b/Source/Editor/GUI/Input/SearchBox.cs
index 994ef14b1..73cfe55bf 100644
--- a/Source/Editor/GUI/Input/SearchBox.cs
+++ b/Source/Editor/GUI/Input/SearchBox.cs
@@ -20,7 +20,7 @@ namespace FlaxEditor.GUI.Input
: this(false, 0, 0)
{
}
-
+
///
/// Init search box
///
@@ -28,7 +28,7 @@ namespace FlaxEditor.GUI.Input
: base(isMultiline, x, y, width)
{
WatermarkText = "Search...";
-
+
ClearSearchButton = new Button
{
Parent = this,
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index 492887611..321782a8e 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -182,6 +182,7 @@ namespace FlaxEditor.GUI.Input
}
SlidingEnd?.Invoke();
Defocus();
+ Parent?.Focus();
}
///
diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs
index b07d693e5..cb9cb09b2 100644
--- a/Source/Editor/GUI/Row.cs
+++ b/Source/Editor/GUI/Row.cs
@@ -241,7 +241,7 @@ namespace FlaxEditor.GUI
{
DoubleClick?.Invoke();
RowDoubleClick?.Invoke(this);
-
+
return base.OnMouseDoubleClick(location, button);
}
diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs
index f9bfd3f6c..d830b9e0f 100644
--- a/Source/Editor/GUI/Tabs/Tabs.cs
+++ b/Source/Editor/GUI/Tabs/Tabs.cs
@@ -191,6 +191,8 @@ namespace FlaxEditor.GUI.Tabs
get => _autoTabsSizeAuto;
set
{
+ if (_autoTabsSizeAuto == value)
+ return;
_autoTabsSizeAuto = value;
PerformLayout();
}
@@ -204,11 +206,11 @@ namespace FlaxEditor.GUI.Tabs
get => _orientation;
set
{
+ if (_orientation == value)
+ return;
_orientation = value;
-
if (UseScroll)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
-
PerformLayout();
}
}
@@ -402,6 +404,14 @@ namespace FlaxEditor.GUI.Tabs
tabHeader.Size = tabsSize;
}
}
+ else if (UseScroll)
+ {
+ // If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability
+ if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible)
+ tabsSize.Y += TabsPanel.HScrollBar.Height;
+ else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible)
+ tabsSize.X += TabsPanel.VScrollBar.Width;
+ }
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal
diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs
index a28810179..ff653196e 100644
--- a/Source/Editor/Gizmo/IGizmoOwner.cs
+++ b/Source/Editor/Gizmo/IGizmoOwner.cs
@@ -11,6 +11,11 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
public interface IGizmoOwner
{
+ ///
+ /// Gets the gizmos collection.
+ ///
+ FlaxEditor.Viewport.EditorViewport Viewport { get; }
+
///
/// Gets the gizmos collection.
///
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index dd5cc6c76..6123d348a 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -162,10 +162,23 @@ namespace FlaxEditor.Gizmo
// Scale gizmo to fit on-screen
Vector3 position = Position;
- Vector3 vLength = Owner.ViewPosition - position;
- float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
- _screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
-
+ if (Owner.Viewport.UseOrthographicProjection)
+ {
+ //[hack] this is far form ideal the View Position is in wrong location, any think using the View Position will have problem
+ //the camera system needs rewrite the to be a camera on springarm, similar how the ArcBallCamera is handled
+ //the ortho projection cannot exist with fps camera because there is no
+ // - focus point to calculate correct View Position with Orthographic Scale as a reference and Orthographic Scale from View Position
+ // with make the camera jump
+ // - and deaph so w and s movment in orto mode moves the cliping plane now
+ float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
+ _screenScale = gizmoSize * (50 * Owner.Viewport.OrthographicScale);
+ }
+ else
+ {
+ Vector3 vLength = Owner.ViewPosition - position;
+ float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
+ _screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
+ }
// Setup world
Quaternion orientation = GetSelectedObject(0).Orientation;
_gizmoWorld = new Transform(position, orientation, new Float3(_screenScale));
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index eec55c788..80f419a6f 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -649,8 +649,6 @@ namespace FlaxEditor.Modules
// Special case for folders
if (item is ContentFolder folder)
{
- // TODO: maybe don't remove folders recursive but at once?
-
// Delete all children
if (folder.Children.Count > 0)
{
@@ -664,6 +662,9 @@ namespace FlaxEditor.Modules
// Remove directory
if (deletedByUser && Directory.Exists(path))
{
+ // Flush files removal before removing folder (loaded assets remove file during object destruction in Asset::OnDeleteObject)
+ FlaxEngine.Scripting.FlushRemovedObjects();
+
try
{
Directory.Delete(path, true);
@@ -810,10 +811,9 @@ namespace FlaxEditor.Modules
{
if (node == null)
return;
-
- // Temporary data
var folder = node.Folder;
var path = folder.Path;
+ var canHaveAssets = node.CanHaveAssets;
if (_isDuringFastSetup)
{
@@ -832,20 +832,38 @@ namespace FlaxEditor.Modules
var child = folder.Children[i];
if (!child.Exists)
{
- // Send info
+ // Item doesn't exist anymore
Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed"));
-
- // Destroy it
Delete(child, false);
-
i--;
}
+ else if (canHaveAssets && child is AssetItem childAsset)
+ {
+ // Check if asset type doesn't match the item proxy (eg. item reimported as Material Instance instead of Material)
+ if (FlaxEngine.Content.GetAssetInfo(child.Path, out var assetInfo))
+ {
+ bool changed = assetInfo.ID != childAsset.ID;
+ if (!changed && assetInfo.TypeName != childAsset.TypeName)
+ {
+ // Use proxy check (eg. scene asset might accept different typename than AssetInfo reports)
+ var proxy = GetAssetProxy(childAsset.TypeName, child.Path);
+ if (proxy == null)
+ proxy = GetAssetProxy(assetInfo.TypeName, child.Path);
+ changed = !proxy.AcceptsAsset(assetInfo.TypeName, child.Path);
+ }
+ if (changed)
+ {
+ OnAssetTypeInfoChanged(childAsset, ref assetInfo);
+ i--;
+ }
+ }
+ }
}
}
// Find files
var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly);
- if (node.CanHaveAssets)
+ if (canHaveAssets)
{
LoadAssets(node, files);
}
@@ -1134,17 +1152,19 @@ namespace FlaxEditor.Modules
RebuildInternal();
- Editor.ContentImporting.ImportFileEnd += ContentImporting_ImportFileDone;
+ Editor.ContentImporting.ImportFileEnd += (obj, failed) =>
+ {
+ var path = obj.ResultUrl;
+ if (!failed)
+ FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path));
+ };
_enableEvents = true;
}
- private void ContentImporting_ImportFileDone(IFileEntryAction obj, bool failed)
+ private void OnImportFileDone(string path)
{
- if (failed)
- return;
-
// Check if already has that element
- var item = Find(obj.ResultUrl);
+ var item = Find(path);
if (item is BinaryAssetItem binaryAssetItem)
{
// Get asset info from the registry (content layer will update cache it just after import)
@@ -1154,19 +1174,8 @@ namespace FlaxEditor.Modules
// For eg. change texture to sprite atlas on reimport
if (binaryAssetItem.TypeName != assetInfo.TypeName)
{
- // Asset type has been changed!
- Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", item.Path, binaryAssetItem.TypeName, assetInfo.TypeName));
- Editor.Windows.CloseAllEditors(item);
-
- // Remove this item from the database and some related data
var toRefresh = binaryAssetItem.ParentFolder;
- binaryAssetItem.Dispose();
- toRefresh.Children.Remove(binaryAssetItem);
- if (!binaryAssetItem.HasDefaultThumbnail)
- {
- // Delete old thumbnail and remove it from the cache
- Editor.Instance.Thumbnails.DeletePreview(binaryAssetItem);
- }
+ OnAssetTypeInfoChanged(binaryAssetItem, ref assetInfo);
// Refresh the parent folder to find the new asset (it should have different type or some other format)
RefreshFolder(toRefresh, false);
@@ -1183,6 +1192,23 @@ namespace FlaxEditor.Modules
}
}
+ private void OnAssetTypeInfoChanged(AssetItem assetItem, ref AssetInfo assetInfo)
+ {
+ // Asset type has been changed!
+ Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", assetItem.Path, assetItem.TypeName, assetInfo.TypeName));
+ Editor.Windows.CloseAllEditors(assetItem);
+
+ // Remove this item from the database and some related data
+ assetItem.Dispose();
+ assetItem.ParentFolder.Children.Remove(assetItem);
+
+ // Delete old thumbnail and remove it from the cache
+ if (!assetItem.HasDefaultThumbnail)
+ {
+ Editor.Instance.Thumbnails.DeletePreview(assetItem);
+ }
+ }
+
internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
{
// Ensure to be ready for external events
diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs
index 1a8920916..f262864dd 100644
--- a/Source/Editor/Modules/ContentEditingModule.cs
+++ b/Source/Editor/Modules/ContentEditingModule.cs
@@ -104,7 +104,7 @@ namespace FlaxEditor.Modules
hint = "Too long name.";
return false;
}
-
+
if (item.IsFolder && shortName.EndsWith("."))
{
hint = "Name cannot end with '.'";
diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs
index c94ba650e..85dd50d35 100644
--- a/Source/Editor/Modules/ContentImportingModule.cs
+++ b/Source/Editor/Modules/ContentImportingModule.cs
@@ -55,7 +55,7 @@ namespace FlaxEditor.Modules
public event Action ImportingQueueBegin;
///
- /// Occurs when file is being imported.
+ /// Occurs when file is being imported. Can be called on non-main thread.
///
public event Action ImportFileBegin;
@@ -67,12 +67,12 @@ namespace FlaxEditor.Modules
public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed);
///
- /// Occurs when file importing end.
+ /// Occurs when file importing end. Can be called on non-main thread.
///
public event ImportFileEndDelegate ImportFileEnd;
///
- /// Occurs when assets importing ends.
+ /// Occurs when assets importing ends. Can be called on non-main thread.
///
public event Action ImportingQueueEnd;
diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs
index bb76e125b..adb5f7685 100644
--- a/Source/Editor/Modules/PrefabsModule.cs
+++ b/Source/Editor/Modules/PrefabsModule.cs
@@ -133,7 +133,7 @@ namespace FlaxEditor.Modules
return;
var actorsList = new List();
Utilities.Utils.GetActorsTree(actorsList, actor);
-
+
var actions = new IUndoAction[actorsList.Count];
for (int i = 0; i < actorsList.Count; i++)
actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]);
diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs
index 2b8bf718e..970ca8e99 100644
--- a/Source/Editor/Modules/SceneEditingModule.cs
+++ b/Source/Editor/Modules/SceneEditingModule.cs
@@ -453,7 +453,7 @@ namespace FlaxEditor.Modules
{
Editor.Windows.SceneWin.Focus();
}
-
+
// fix scene window layout
Editor.Windows.SceneWin.PerformLayout();
Editor.Windows.SceneWin.PerformLayout();
@@ -520,7 +520,7 @@ namespace FlaxEditor.Modules
Undo.AddAction(new MultiUndoAction(pasteAction, selectAction));
OnSelectionChanged();
}
-
+
// Scroll to new selected node while pasting
Editor.Windows.SceneWin.ScrollToSelectedNode();
}
@@ -620,7 +620,7 @@ namespace FlaxEditor.Modules
Undo.AddAction(new MultiUndoAction(undoActions));
OnSelectionChanged();
}
-
+
// Scroll to new selected node while duplicating
Editor.Windows.SceneWin.ScrollToSelectedNode();
}
diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs
index 257262585..7789eb7c4 100644
--- a/Source/Editor/Modules/SceneModule.cs
+++ b/Source/Editor/Modules/SceneModule.cs
@@ -332,7 +332,7 @@ namespace FlaxEditor.Modules
continue;
scenes.Add(s);
}
-
+
// In play-mode Editor mocks the level streaming script
if (Editor.IsPlayMode)
{
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
index 4089f6d79..96cf44e09 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
@@ -29,10 +29,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool CheckFunc(ScriptType scriptType)
{
- if (scriptType.IsStatic ||
- scriptType.IsGenericType ||
- !scriptType.IsPublic ||
- scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
+ if (scriptType.IsStatic ||
+ scriptType.IsGenericType ||
+ !scriptType.IsPublic ||
+ scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
var managedType = TypeUtils.GetType(scriptType);
@@ -410,9 +410,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
base.OnUpdate();
// Automatic project files generation after workspace modifications
- if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty)
+ if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling)
{
- Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
+ // Try to delay generation when a lot of files are added at once
+ if (ScriptsBuilder.IsSourceDirtyFor(TimeSpan.FromMilliseconds(150)))
+ Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
}
}
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index c882efb87..1369ecd1e 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -2,7 +2,6 @@
using System;
using System.IO;
-using System.Linq;
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI;
@@ -11,7 +10,6 @@ using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Input;
using FlaxEditor.Progress.Handlers;
using FlaxEditor.SceneGraph;
-using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Windows;
@@ -208,6 +206,7 @@ namespace FlaxEditor.Modules
_toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale;
//
_toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive;
+ _toolStripBuildScenes.Visible = Editor.Options.Options.General.BuildActions?.Length != 0;
_toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning;
//
var play = _toolStripPlay;
@@ -299,7 +298,7 @@ namespace FlaxEditor.Modules
else
text = "Ready";
- if(ProgressVisible)
+ if (ProgressVisible)
{
color = Style.Current.Statusbar.Loading;
}
@@ -402,7 +401,7 @@ namespace FlaxEditor.Modules
{
UpdateStatusBar();
}
- else if(ProgressVisible)
+ else if (ProgressVisible)
{
UpdateStatusBar();
}
@@ -557,7 +556,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Game Settings", () =>
{
var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath);
- if(item != null)
+ if (item != null)
Editor.ContentEditing.Open(item);
});
@@ -653,7 +652,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Information about Flax", () => new AboutDialog().Show());
}
- private void OnOptionsChanged(FlaxEditor.Options.EditorOptions options)
+ private void OnOptionsChanged(EditorOptions options)
{
var inputOptions = options.Input;
@@ -688,6 +687,8 @@ namespace FlaxEditor.Modules
_menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
+
+ UpdateToolstrip();
}
private void InitToolstrip(RootControl mainWindow)
@@ -709,11 +710,11 @@ namespace FlaxEditor.Modules
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator();
- // Cook scenes
+ // Build scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run
- _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})");
+ _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index d0c8b9747..1a4f5ce9e 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -276,9 +276,6 @@ namespace FlaxEditor.Modules
// Get metadata
int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture);
- var virtualDesktopBounds = Platform.VirtualDesktopBounds;
- var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
- var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
switch (version)
{
@@ -288,31 +285,9 @@ namespace FlaxEditor.Modules
if (MainWindow)
{
var mainWindowNode = root["MainWindow"];
- Rectangle bounds = LoadBounds(mainWindowNode["Bounds"]);
- bool isMaximized = bool.Parse(mainWindowNode.GetAttribute("IsMaximized"));
-
- // Clamp position to match current desktop dimensions (if window was on desktop that is now inactive)
- if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y)
- bounds.Location = virtualDesktopSafeLeftCorner;
-
- if (isMaximized)
- {
- if (MainWindow.IsMaximized)
- MainWindow.Restore();
- MainWindow.ClientPosition = bounds.Location;
- MainWindow.Maximize();
- }
- else
- {
- if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1)
- {
- MainWindow.ClientBounds = bounds;
- }
- else
- {
- MainWindow.ClientPosition = bounds.Location;
- }
- }
+ bool isMaximized = true, isMinimized = false;
+ Rectangle bounds = LoadBounds(mainWindowNode, ref isMaximized, ref isMinimized);
+ LoadWindow(MainWindow, ref bounds, isMaximized, false);
}
// Load master panel structure
@@ -332,11 +307,13 @@ namespace FlaxEditor.Modules
continue;
// Get window properties
- Rectangle bounds = LoadBounds(child["Bounds"]);
+ bool isMaximized = false, isMinimized = false;
+ Rectangle bounds = LoadBounds(child, ref isMaximized, ref isMinimized);
// Create window and floating dock panel
var window = FloatWindowDockPanel.CreateFloatWindow(MainWindow.GUI, bounds.Location, bounds.Size, WindowStartPosition.Manual, string.Empty);
var panel = new FloatWindowDockPanel(masterPanel, window.GUI);
+ LoadWindow(panel.Window.Window, ref bounds, isMaximized, isMinimized);
// Load structure
LoadPanel(child, panel);
@@ -493,23 +470,67 @@ namespace FlaxEditor.Modules
private static void SaveBounds(XmlWriter writer, Window win)
{
- var bounds = win.ClientBounds;
-
- writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
- writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
- writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture));
- writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture));
+ writer.WriteStartElement("Bounds");
+ {
+ var bounds = win.ClientBounds;
+ writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("IsMaximized", win.IsMaximized.ToString());
+ writer.WriteAttributeString("IsMinimized", win.IsMinimized.ToString());
+ }
+ writer.WriteEndElement();
}
- private static Rectangle LoadBounds(XmlElement node)
+ private static Rectangle LoadBounds(XmlElement node, ref bool isMaximized, ref bool isMinimized)
{
- float x = float.Parse(node.GetAttribute("X"), CultureInfo.InvariantCulture);
- float y = float.Parse(node.GetAttribute("Y"), CultureInfo.InvariantCulture);
- float width = float.Parse(node.GetAttribute("Width"), CultureInfo.InvariantCulture);
- float height = float.Parse(node.GetAttribute("Height"), CultureInfo.InvariantCulture);
+ var bounds = node["Bounds"];
+ var isMaximizedText = bounds.GetAttribute("IsMaximized");
+ if (!string.IsNullOrEmpty(isMaximizedText))
+ isMaximized = bool.Parse(isMaximizedText);
+ var isMinimizedText = bounds.GetAttribute("IsMinimized");
+ if (!string.IsNullOrEmpty(isMinimizedText))
+ isMinimized = bool.Parse(isMinimizedText);
+ float x = float.Parse(bounds.GetAttribute("X"), CultureInfo.InvariantCulture);
+ float y = float.Parse(bounds.GetAttribute("Y"), CultureInfo.InvariantCulture);
+ float width = float.Parse(bounds.GetAttribute("Width"), CultureInfo.InvariantCulture);
+ float height = float.Parse(bounds.GetAttribute("Height"), CultureInfo.InvariantCulture);
return new Rectangle(x, y, width, height);
}
+ private static void LoadWindow(Window win, ref Rectangle bounds, bool isMaximized, bool isMinimized)
+ {
+ var virtualDesktopBounds = Platform.VirtualDesktopBounds;
+ var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
+ var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
+
+ // Clamp position to match current desktop dimensions (if window was on desktop that is now inactive)
+ if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y)
+ bounds.Location = virtualDesktopSafeLeftCorner;
+
+ if (isMaximized)
+ {
+ if (win.IsMaximized)
+ win.Restore();
+ win.ClientPosition = bounds.Location;
+ win.Maximize();
+ }
+ else
+ {
+ if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1)
+ {
+ win.ClientBounds = bounds;
+ }
+ else
+ {
+ win.ClientPosition = bounds.Location;
+ }
+ if (isMinimized)
+ win.Minimize();
+ }
+ }
+
private class LayoutNameDialog : Dialog
{
private TextBox _textbox;
@@ -609,13 +630,8 @@ namespace FlaxEditor.Modules
if (MainWindow)
{
writer.WriteStartElement("MainWindow");
- writer.WriteAttributeString("IsMaximized", MainWindow.IsMaximized.ToString());
-
- writer.WriteStartElement("Bounds");
SaveBounds(writer, MainWindow);
writer.WriteEndElement();
-
- writer.WriteEndElement();
}
// Master panel structure
@@ -628,22 +644,13 @@ namespace FlaxEditor.Modules
{
var panel = masterPanel.FloatingPanels[i];
var window = panel.Window;
-
if (window == null)
- {
- Editor.LogWarning("Floating panel has missing window");
continue;
- }
writer.WriteStartElement("Float");
-
SavePanel(writer, panel);
-
- writer.WriteStartElement("Bounds");
SaveBounds(writer, window.Window);
writer.WriteEndElement();
-
- writer.WriteEndElement();
}
writer.WriteEndElement();
diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs
index 33124bab0..9f29f4bdb 100644
--- a/Source/Editor/Options/GeneralOptions.cs
+++ b/Source/Editor/Options/GeneralOptions.cs
@@ -117,7 +117,7 @@ namespace FlaxEditor.Options
///
/// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).
///
- [EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
+ [EditorDisplay("General"), EditorOrder(200), ExpandGroups, Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
public BuildAction[] BuildActions { get; set; } =
{
BuildAction.CSG,
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index c2d744239..07e899c5e 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -244,11 +244,11 @@ namespace FlaxEditor.Options
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328),
- Statusbar = new Style.StatusbarStyle()
+ Statusbar = new Style.StatusbarStyle
{
PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424),
- Loading = Color.FromBgra(0xFF2D2D30)
+ Loading = Color.FromBgra(0xFF2D2D30),
},
// Fonts
@@ -271,7 +271,7 @@ namespace FlaxEditor.Options
Scale = Editor.Icons.Scale32,
Scalar = Editor.Icons.Scalar32,
- SharedTooltip = new Tooltip()
+ SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;
diff --git a/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs b/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs
index 7bbefbc2d..fa1e943f0 100644
--- a/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs
+++ b/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs
@@ -19,25 +19,25 @@ namespace FlaxEditor.Progress.Handlers
public ImportAssetsProgress()
{
var importing = Editor.Instance.ContentImporting;
- importing.ImportingQueueBegin += OnStart;
- importing.ImportingQueueEnd += OnEnd;
+ importing.ImportingQueueBegin += () => FlaxEngine.Scripting.InvokeOnUpdate(OnStart);
+ importing.ImportingQueueEnd += () => FlaxEngine.Scripting.InvokeOnUpdate(OnEnd);
importing.ImportFileBegin += OnImportFileBegin;
}
private void OnImportFileBegin(IFileEntryAction importFileEntry)
{
+ string info;
if (importFileEntry is ImportFileEntry)
- _currentInfo = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl));
+ info = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl));
else
- _currentInfo = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
- UpdateProgress();
- }
-
- private void UpdateProgress()
- {
- var importing = Editor.Instance.ContentImporting;
- var info = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
- OnUpdate(importing.ImportingProgress, info);
+ info = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
+ FlaxEngine.Scripting.InvokeOnUpdate(() =>
+ {
+ _currentInfo = info;
+ var importing = Editor.Instance.ContentImporting;
+ var text = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
+ OnUpdate(importing.ImportingProgress, text);
+ });
}
}
}
diff --git a/Source/Editor/Progress/ProgressHandler.cs b/Source/Editor/Progress/ProgressHandler.cs
index 2738b7c57..9581a0de6 100644
--- a/Source/Editor/Progress/ProgressHandler.cs
+++ b/Source/Editor/Progress/ProgressHandler.cs
@@ -16,7 +16,7 @@ namespace FlaxEditor.Progress
///
/// The calling handler.
public delegate void ProgressDelegate(ProgressHandler handler);
-
+
///
/// Progress failed handler event delegate
///
@@ -127,7 +127,7 @@ namespace FlaxEditor.Progress
{
if (!_isActive)
throw new InvalidOperationException("Already ended.");
-
+
_isActive = false;
_progress = 0;
_infoText = string.Empty;
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp
index 2357fd241..a65b3ec9e 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp
@@ -286,81 +286,17 @@ namespace VisualStudio
return "Visual Studio open timout";
}
- static ComPtr FindItem(const ComPtr& projectItems, BSTR filePath)
- {
- long count;
- projectItems->get_Count(&count);
- if (count == 0)
- return nullptr;
-
- for (long i = 1; i <= count; i++) // They are counting from [1..Count]
- {
- ComPtr projectItem;
- projectItems->Item(_variant_t(i), &projectItem);
- if (!projectItem)
- continue;
-
- short fileCount = 0;
- projectItem->get_FileCount(&fileCount);
- for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++)
- {
- _bstr_t filename;
- projectItem->get_FileNames(fileIndex, filename.GetAddress());
-
- if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename))
- {
- return projectItem;
- }
- }
-
- ComPtr childProjectItems;
- projectItem->get_ProjectItems(&childProjectItems);
- if (childProjectItems)
- {
- ComPtr result = FindItem(childProjectItems, filePath);
- if (result)
- return result;
- }
- }
-
- return nullptr;
- }
-
- static ComPtr FindItem(const ComPtr& solution, BSTR filePath)
- {
+ static ComPtr FindItem(const ComPtr& solution, BSTR filePath)
+ {
HRESULT result;
- ComPtr projects;
- result = solution->get_Projects(&projects);
+ ComPtr projectItem;
+ result = solution->FindProjectItem(filePath, &projectItem);
if (FAILED(result))
return nullptr;
- long projectsCount = 0;
- result = projects->get_Count(&projectsCount);
- if (FAILED(result))
- return nullptr;
-
- for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
- {
- ComPtr project;
- result = projects->Item(_variant_t(projectIndex), &project);
- if (FAILED(result) || !project)
- continue;
-
- ComPtr projectItems;
- result = project->get_ProjectItems(&projectItems);
- if (FAILED(result) || !projectItems)
- continue;
-
- auto projectItem = FindItem(projectItems, filePath);
- if (projectItem)
- {
- return projectItem;
- }
- }
-
- return nullptr;
- }
+ return projectItem;
+ }
// Opens a file on a specific line in a running Visual Studio instance.
//
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index 8807379cf..0b3cf6452 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -170,7 +170,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty()
bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout)
{
ScopeLock scopeLock(_locker);
- return _lastSourceCodeEdited > (_lastCompileAction + timeout);
+ return _lastSourceCodeEdited > _lastCompileAction && DateTime::Now() > _lastSourceCodeEdited + timeout;
}
bool ScriptsBuilder::IsCompiling()
@@ -266,7 +266,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
{
- String args(TEXT("-log -genproject "));
+ String args(TEXT("-log -mutex -genproject "));
args += customArgs;
_wasProjectStructureChanged = false;
return RunBuildTool(args);
@@ -669,7 +669,7 @@ void ScriptsBuilderService::Update()
}
// Check if compile code (if has been edited)
- const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50);
+ const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(150);
auto mainWindow = Engine::MainWindow;
if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused())
{
diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h
index f954b0fd0..0a11bd9b7 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.h
+++ b/Source/Editor/Scripting/ScriptsBuilder.h
@@ -68,7 +68,7 @@ public:
///
/// Time to use for checking.
/// True if source code is dirty, otherwise false.
- static bool IsSourceDirtyFor(const TimeSpan& timeout);
+ API_FUNCTION() static bool IsSourceDirtyFor(const TimeSpan& timeout);
///
/// Returns true if scripts are being now compiled/reloaded.
diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs
index 16b7993de..40a3d2a63 100644
--- a/Source/Editor/Surface/Archetypes/Animation.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.cs
@@ -34,6 +34,9 @@ namespace FlaxEditor.Surface.Archetypes
///
public class Sample : SurfaceNode
{
+ private AssetSelect _assetSelect;
+ private Box _assetBox;
+
///
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
@@ -54,16 +57,42 @@ namespace FlaxEditor.Surface.Archetypes
base.OnSurfaceLoaded(action);
if (Surface != null)
+ {
+ _assetSelect = GetChild();
+ if (TryGetBox(8, out var box))
+ {
+ _assetBox = box;
+ _assetSelect.Visible = !_assetBox.HasAnyConnection;
+ }
UpdateTitle();
+ }
}
private void UpdateTitle()
{
var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]);
- Title = asset?.ShortName ?? "Animation";
+ if (_assetBox != null)
+ Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
+ else
+ Title = asset?.ShortName ?? "Animation";
+
var style = Style.Current;
Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160);
}
+
+ ///
+ public override void ConnectionTick(Box box)
+ {
+ base.ConnectionTick(box);
+
+ if (_assetBox == null)
+ return;
+ if (box.ID != _assetBox.ID)
+ return;
+
+ _assetSelect.Visible = !box.HasAnyConnection;
+ UpdateTitle();
+ }
}
///
@@ -305,7 +334,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "Speed", true, typeof(float), 5, 1),
NodeElementArchetype.Factory.Input(1, "Loop", true, typeof(bool), 6, 2),
NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 7, 3),
- NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 3, 0, typeof(FlaxEngine.Animation)),
+ NodeElementArchetype.Factory.Input(3, "Animation Asset", true, typeof(FlaxEngine.Animation), 8),
+ NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 0, typeof(FlaxEngine.Animation)),
}
},
new NodeArchetype
diff --git a/Source/Editor/Surface/Archetypes/Layers.cs b/Source/Editor/Surface/Archetypes/Layers.cs
index 4a46276c3..0308cab15 100644
--- a/Source/Editor/Surface/Archetypes/Layers.cs
+++ b/Source/Editor/Surface/Archetypes/Layers.cs
@@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(200, 100),
DefaultValues = new object[]
{
- 0.0f,
+ 0.5f,
},
Elements = new[]
{
diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs
index 3fe727840..6ed5a61e6 100644
--- a/Source/Editor/Surface/Archetypes/Parameters.cs
+++ b/Source/Editor/Surface/Archetypes/Parameters.cs
@@ -510,7 +510,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++)
{
- if(elements[i].Type != NodeElementType.Output)
+ if (elements[i].Type != NodeElementType.Output)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
@@ -533,7 +533,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++)
{
- if(elements[i].Type != NodeElementType.Input)
+ if (elements[i].Type != NodeElementType.Input)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
return true;
@@ -725,7 +725,7 @@ namespace FlaxEditor.Surface.Archetypes
///
protected override bool UseNormalMaps => false;
-
+
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (inputType == ScriptType.Object)
@@ -743,7 +743,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++)
{
- if(elements[i].Type != NodeElementType.Output)
+ if (elements[i].Type != NodeElementType.Output)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
return true;
@@ -765,7 +765,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++)
{
- if(elements[i].Type != NodeElementType.Input)
+ if (elements[i].Type != NodeElementType.Input)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
return true;
@@ -789,7 +789,7 @@ namespace FlaxEditor.Surface.Archetypes
///
protected override bool UseNormalMaps => false;
-
+
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (inputType == ScriptType.Object)
@@ -987,7 +987,7 @@ namespace FlaxEditor.Surface.Archetypes
_combobox.Width = Width - 50;
}
}
-
+
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return inputType == ScriptType.Void;
@@ -997,7 +997,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (outputType == ScriptType.Void)
return true;
-
+
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs
index f60f6ff3a..e7b76a538 100644
--- a/Source/Editor/Surface/VisjectSurface.Draw.cs
+++ b/Source/Editor/Surface/VisjectSurface.Draw.cs
@@ -141,7 +141,7 @@ namespace FlaxEditor.Surface
if (_connectionInstigator is Archetypes.Tools.RerouteNode)
{
- if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true})
+ if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
{
actualStartPos = endPos;
actualEndPos = startPos;
diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs
index c16bdd7e6..c9a521734 100644
--- a/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs
+++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmo.cs
@@ -150,6 +150,12 @@ namespace FlaxEditor.Tools.Terrain
return;
}
+ // Increase or decrease brush size with scroll
+ if (Input.GetKey(KeyboardKeys.Shift))
+ {
+ Mode.CurrentBrush.Size += dt * Mode.CurrentBrush.Size * Input.Mouse.ScrollDelta * 5f;
+ }
+
// Check if no terrain is selected
var terrain = SelectedTerrain;
if (!terrain)
diff --git a/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs b/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs
index 96270a740..fef8bbf09 100644
--- a/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs
+++ b/Source/Editor/Tools/Terrain/SculptTerrainGizmo.cs
@@ -158,6 +158,12 @@ namespace FlaxEditor.Tools.Terrain
return;
}
+ // Increase or decrease brush size with scroll
+ if (Input.GetKey(KeyboardKeys.Shift))
+ {
+ Mode.CurrentBrush.Size += dt * Mode.CurrentBrush.Size * Input.Mouse.ScrollDelta * 5f;
+ }
+
// Check if selected terrain was changed during painting
if (terrain != _paintTerrain && IsPainting)
{
diff --git a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
index 69a790b39..dac2900f4 100644
--- a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
+++ b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
@@ -49,7 +49,16 @@ namespace FlaxEditor.Actions
_scriptTypeName = script.TypeName;
_prefabId = script.PrefabID;
_prefabObjectId = script.PrefabObjectID;
- _scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script);
+ try
+ {
+ _scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script);
+ }
+ catch (Exception ex)
+ {
+ _scriptData = null;
+ Debug.LogError("Failed to serialize script data for Undo due to exception");
+ Debug.LogException(ex);
+ }
_parentId = script.Actor.ID;
_orderInParent = script.OrderInParent;
_enabled = script.Enabled;
@@ -66,6 +75,11 @@ namespace FlaxEditor.Actions
_enabled = true;
}
+ public int GetOrderInParent()
+ {
+ return _orderInParent;
+ }
+
///
/// Creates a new added script undo action.
///
@@ -175,6 +189,7 @@ namespace FlaxEditor.Actions
script.Parent = parentActor;
if (_orderInParent != -1)
script.OrderInParent = _orderInParent;
+ _orderInParent = script.OrderInParent; // Ensure order is correct for script that want to use it later
if (_prefabObjectId != Guid.Empty)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId);
Editor.Instance.Scene.MarkSceneEdited(parentActor.Scene);
diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp
index 8e3fc0e4a..a2418ceb1 100644
--- a/Source/Editor/Utilities/EditorUtilities.cpp
+++ b/Source/Editor/Utilities/EditorUtilities.cpp
@@ -394,8 +394,12 @@ bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon)
// - icon/cursor/etc data
std::fstream stream;
+#if PLATFORM_WINDOWS
+ stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
+#else
StringAsANSI<> pathAnsi(path.Get());
stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
+#endif
if (!stream.is_open())
{
LOG(Warning, "Cannot open file");
diff --git a/Source/Editor/Utilities/ScreenUtilities.cpp b/Source/Editor/Utilities/ScreenUtilities.cpp
index 44f52350e..730a69aa3 100644
--- a/Source/Editor/Utilities/ScreenUtilities.cpp
+++ b/Source/Editor/Utilities/ScreenUtilities.cpp
@@ -73,6 +73,7 @@ Color32 ScreenUtilities::GetColorAt(const Float2& pos)
outputColor.R = color.red / 256;
outputColor.G = color.green / 256;
outputColor.B = color.blue / 256;
+ outputColor.A = 255;
return outputColor;
}
diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs
index 9a4fd34a2..a24dd2f38 100644
--- a/Source/Editor/Viewport/EditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/EditorGizmoViewport.cs
@@ -41,6 +41,8 @@ namespace FlaxEditor.Viewport
Gizmos[i].Update(deltaTime);
}
}
+ ///
+ public EditorViewport Viewport => this;
///
public GizmosCollection Gizmos { get; }
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 13c06157a..9a444f2da 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -593,12 +593,14 @@ namespace FlaxEditor.Viewport
{
ref var vv = ref v.Options[j];
var button = childMenu.AddButton(vv.Name);
+ button.CloseMenuOnClick = false;
button.Tag = vv.Mode;
}
}
else
{
var button = debugView.AddButton(v.Name);
+ button.CloseMenuOnClick = false;
button.Tag = v.Mode;
}
}
@@ -1119,7 +1121,12 @@ namespace FlaxEditor.Viewport
var win = (WindowRootControl)Root;
// Get current mouse position in the view
- _viewMousePos = PointFromWindow(win.MousePosition);
+ {
+ // When the window is not focused, the position in window does not return sane values
+ Float2 pos = PointFromWindow(win.MousePosition);
+ if (!float.IsInfinity(pos.LengthSquared))
+ _viewMousePos = pos;
+ }
// Update input
var window = win.Window;
@@ -1582,7 +1589,14 @@ namespace FlaxEditor.Viewport
private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{
if (button.Tag is ViewMode v)
+ {
Task.ViewMode = v;
+ var cm = button.ParentContextMenu;
+ WidgetViewModeShowHide(cm);
+ var mainCM = ViewWidgetButtonMenu.GetChildMenu("Debug View").ContextMenu;
+ if (mainCM != null && cm != mainCM)
+ WidgetViewModeShowHide(mainCM);
+ }
}
private void WidgetViewModeShowHide(Control cm)
@@ -1594,7 +1608,7 @@ namespace FlaxEditor.Viewport
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b && b.Tag is ViewMode v)
- b.Icon = Task.View.Mode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
+ b.Icon = Task.ViewMode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 1dc459135..eb462deb1 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -306,6 +306,8 @@ namespace FlaxEditor.Viewport
var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
}
+ ///
+ public EditorViewport Viewport => this;
///
public GizmosCollection Gizmos { get; }
diff --git a/Source/Editor/Viewport/Previews/AudioClipPreview.cs b/Source/Editor/Viewport/Previews/AudioClipPreview.cs
index 446cef657..3235cd194 100644
--- a/Source/Editor/Viewport/Previews/AudioClipPreview.cs
+++ b/Source/Editor/Viewport/Previews/AudioClipPreview.cs
@@ -171,7 +171,7 @@ namespace FlaxEditor.Viewport.Previews
case DrawModes.Fill:
clipsInView = 1.0f;
clipWidth = width;
- samplesPerIndex = (uint)(samplesPerChannel / width);
+ samplesPerIndex = (uint)(samplesPerChannel / width) * info.NumChannels;
break;
case DrawModes.Single:
clipsInView = Mathf.Min(clipsInView, 1.0f);
diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs
index 48b551675..7fccd697f 100644
--- a/Source/Editor/ViewportDebugDrawData.cs
+++ b/Source/Editor/ViewportDebugDrawData.cs
@@ -97,7 +97,7 @@ namespace FlaxEditor
if (_highlightMaterial == null
|| (_highlights.Count == 0 && _highlightTriangles.Count == 0)
|| renderContext.View.Pass == DrawPass.Depth
- )
+ )
return;
Profiler.BeginEvent("ViewportDebugDrawData.OnDraw");
diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs
index 0d244479c..2e048b924 100644
--- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs
+++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs
@@ -388,14 +388,16 @@ namespace FlaxEditor.Windows.Assets
protected override void OnShow()
{
// Check if has no asset (but has item linked)
- if (_asset == null && _item != null)
+ var item = _item;
+ if (_asset == null && item != null)
{
// Load asset
_asset = LoadAsset();
if (_asset == null)
{
- Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", _item.Path, typeof(T)));
+ Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", item.Path, typeof(T)));
Close();
+ Editor.ContentDatabase.RefreshFolder(item, false);
return;
}
diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs
index 8cf31f416..b1d9796dc 100644
--- a/Source/Editor/Windows/Assets/AudioClipWindow.cs
+++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs
@@ -335,6 +335,22 @@ namespace FlaxEditor.Windows.Assets
}
}
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (base.OnKeyDown(key))
+ return true;
+
+ if (key == KeyboardKeys.Spacebar)
+ {
+ if (_previewSource?.State == AudioSource.States.Playing)
+ OnPause();
+ else
+ OnPlay();
+ }
+ return false;
+ }
+
///
public override bool UseLayoutData => true;
diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
index 5f1273999..3e0525fb7 100644
--- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
@@ -521,8 +521,11 @@ namespace FlaxEditor.Windows.Assets
///
protected override void OnClose()
{
- // Discard unsaved changes
- _properties.DiscardChanges();
+ if (Asset)
+ {
+ // Discard unsaved changes
+ _properties.DiscardChanges();
+ }
// Cleanup
_undo.Clear();
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs
index 6de553a30..05b59b800 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs
@@ -153,7 +153,7 @@ namespace FlaxEditor.Windows.Assets
{
OnPasteAction(pasteAction);
}
-
+
// Scroll to new selected node
ScrollToSelectedNode();
}
@@ -183,7 +183,7 @@ namespace FlaxEditor.Windows.Assets
{
OnPasteAction(pasteAction);
}
-
+
// Scroll to new selected node
ScrollToSelectedNode();
}
@@ -334,7 +334,7 @@ namespace FlaxEditor.Windows.Assets
}, action2.ActionString);
action.Do();
Undo.AddAction(action);
-
+
_treePanel.PerformLayout();
_treePanel.PerformLayout();
}
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 4deaf5d7d..4face0dc0 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -207,7 +207,7 @@ namespace FlaxEditor.Windows.Assets
InputActions.Add(options => options.Rename, Rename);
InputActions.Add(options => options.FocusSelection, _viewport.FocusSelection);
}
-
+
///
/// Enables or disables vertical and horizontal scrolling on the tree panel.
///
@@ -257,7 +257,7 @@ namespace FlaxEditor.Windows.Assets
{
if (base.OnMouseUp(location, button))
return true;
-
+
if (button == MouseButton.Right && _treePanel.ContainsPoint(ref location))
{
_tree.Deselect();
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 03873df57..56136cfbf 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -2,6 +2,7 @@
using System;
using System.IO;
+using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
@@ -249,6 +250,10 @@ namespace FlaxEditor.Windows
});
}
+ // Remove any leftover separator
+ if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
+ cm.ItemsContainer.Children.Last().Dispose();
+
// Show it
cm.Show(this, location);
}
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index a06ec839d..d43174cf3 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -629,8 +629,9 @@ namespace FlaxEditor.Windows
if (items.Count == 0)
return;
- // TODO: remove items that depend on different items in the list: use wants to remove `folderA` and `folderA/asset.x`, we should just remove `folderA`
+ // Sort items to remove files first, then folders
var toDelete = new List(items);
+ toDelete.Sort((a, b) => a.IsFolder ? 1 : b.IsFolder ? -1 : a.Compare(b));
string msg = toDelete.Count == 1
? string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path)
diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs
index dbee0e8e7..d29000d27 100644
--- a/Source/Editor/Windows/Profiler/Network.cs
+++ b/Source/Editor/Windows/Profiler/Network.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Networking;
namespace FlaxEngine
{
@@ -37,11 +38,14 @@ namespace FlaxEditor.Windows.Profiler
{
private readonly SingleChart _dataSentChart;
private readonly SingleChart _dataReceivedChart;
+ private readonly SingleChart _dataSentRateChart;
+ private readonly SingleChart _dataReceivedRateChart;
private readonly Table _tableRpc;
private readonly Table _tableRep;
- private SamplesBuffer _events;
private List _tableRowsCache;
- private FlaxEngine.Networking.NetworkDriverStats _prevStats;
+ private SamplesBuffer _events;
+ private NetworkDriverStats _prevStats;
+ private List _stats;
public Network()
: base("Network")
@@ -76,6 +80,20 @@ namespace FlaxEditor.Windows.Profiler
Parent = layout,
};
_dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _dataSentRateChart = new SingleChart
+ {
+ Title = "Data Sent Rate",
+ FormatSample = FormatSampleBytesRate,
+ Parent = layout,
+ };
+ _dataSentRateChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _dataReceivedRateChart = new SingleChart
+ {
+ Title = "Data Received Rate",
+ FormatSample = FormatSampleBytesRate,
+ Parent = layout,
+ };
+ _dataReceivedRateChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Tables
_tableRpc = InitTable(layout, "RPC Name");
@@ -87,24 +105,52 @@ namespace FlaxEditor.Windows.Profiler
{
_dataSentChart.Clear();
_dataReceivedChart.Clear();
+ _dataSentRateChart.Clear();
+ _dataReceivedRateChart.Clear();
_events?.Clear();
+ _stats?.Clear();
+ _prevStats = new NetworkDriverStats();
}
///
public override void Update(ref SharedUpdateData sharedData)
{
// Gather peer stats
- var peers = FlaxEngine.Networking.NetworkPeer.Peers;
- var stats = new FlaxEngine.Networking.NetworkDriverStats();
+ var peers = NetworkPeer.Peers;
+ var thisStats = new NetworkDriverStats();
+ thisStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT
foreach (var peer in peers)
{
var peerStats = peer.NetworkDriver.GetStats();
- stats.TotalDataSent += peerStats.TotalDataSent;
- stats.TotalDataReceived += peerStats.TotalDataReceived;
+ thisStats.TotalDataSent += peerStats.TotalDataSent;
+ thisStats.TotalDataReceived += peerStats.TotalDataReceived;
}
- _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0));
- _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0));
- _prevStats = stats;
+ var stats = thisStats;
+ stats.TotalDataSent = (uint)Mathf.Max((long)thisStats.TotalDataSent - (long)_prevStats.TotalDataSent, 0);
+ stats.TotalDataReceived = (uint)Mathf.Max((long)thisStats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0);
+ _dataSentChart.AddSample(stats.TotalDataSent);
+ _dataReceivedChart.AddSample(stats.TotalDataReceived);
+ _prevStats = thisStats;
+ if (_stats == null)
+ _stats = new List();
+ _stats.Add(stats);
+
+ // Remove all stats older than 1 second
+ while (_stats.Count > 0 && thisStats.RTT - _stats[0].RTT >= 1.0f)
+ _stats.RemoveAt(0);
+
+ // Calculate average data rates (from last second)
+ var avgStats = new NetworkDriverStats();
+ foreach (var e in _stats)
+ {
+ avgStats.TotalDataSent += e.TotalDataSent;
+ avgStats.TotalDataReceived += e.TotalDataReceived;
+ }
+ avgStats.TotalDataSent /= (uint)_stats.Count;
+ avgStats.TotalDataReceived /= (uint)_stats.Count;
+ _dataSentRateChart.AddSample(avgStats.TotalDataSent);
+ _dataReceivedRateChart.AddSample(avgStats.TotalDataReceived);
+
// Gather network events
var events = ProfilingTools.EventsNetwork;
@@ -118,6 +164,8 @@ namespace FlaxEditor.Windows.Profiler
{
_dataSentChart.SelectedSampleIndex = selectedFrame;
_dataReceivedChart.SelectedSampleIndex = selectedFrame;
+ _dataSentRateChart.SelectedSampleIndex = selectedFrame;
+ _dataReceivedRateChart.SelectedSampleIndex = selectedFrame;
// Update events tables
if (_events != null)
@@ -128,7 +176,7 @@ namespace FlaxEditor.Windows.Profiler
_tableRep.IsLayoutLocked = true;
RecycleTableRows(_tableRpc, _tableRowsCache);
RecycleTableRows(_tableRep, _tableRowsCache);
-
+
var events = _events.Get(selectedFrame);
var rowCount = Int2.Zero;
if (events != null && events.Length != 0)
@@ -186,7 +234,7 @@ namespace FlaxEditor.Windows.Profiler
_tableRep.Visible = rowCount.Y != 0;
_tableRpc.Children.Sort(SortRows);
_tableRep.Children.Sort(SortRows);
-
+
_tableRpc.UnlockChildrenRecursive();
_tableRpc.PerformLayout();
_tableRep.UnlockChildrenRecursive();
@@ -257,6 +305,11 @@ namespace FlaxEditor.Windows.Profiler
return Utilities.Utils.FormatBytesCount((ulong)v);
}
+ private static string FormatSampleBytesRate(float v)
+ {
+ return Utilities.Utils.FormatBytesCount((ulong)v) + "/s";
+ }
+
private static string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);
diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs
index 999156dca..2e0169d47 100644
--- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs
+++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs
@@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler
/// The sample value
public T Get(int index)
{
- if (index >= _data.Length || _data.Length == 0)
+ if (_count == 0 || index >= _data.Length || _data.Length == 0)
return default;
return index == -1 ? _data[_count - 1] : _data[index];
}
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 73ddbc41c..0518fe248 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "AnimGraph.h"
+#include "Engine/Core/Types/VariantValueCast.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Assets/SkeletonMask.h"
#include "Engine/Content/Assets/AnimationGraphFunction.h"
@@ -512,18 +513,6 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
transitionIndex++;
continue;
}
- const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
- if (transition.RuleGraph && !useDefaultRule)
- {
- // Execute transition rule
- auto rootNode = transition.RuleGraph->GetRootNode();
- ASSERT(rootNode);
- if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
- {
- transitionIndex++;
- continue;
- }
- }
// Evaluate source state transition data (position, length, etc.)
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
@@ -543,6 +532,19 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
transitionData.Length = ZeroTolerance;
}
+ const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
+ if (transition.RuleGraph && !useDefaultRule)
+ {
+ // Execute transition rule
+ auto rootNode = transition.RuleGraph->GetRootNode();
+ ASSERT(rootNode);
+ if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
+ {
+ transitionIndex++;
+ continue;
+ }
+ }
+
// Check if can trigger the transition
bool canEnter = false;
if (useDefaultRule)
@@ -749,9 +751,16 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Animation
case 2:
{
- const auto anim = node->Assets[0].As();
+ auto anim = node->Assets[0].As();
auto& bucket = context.Data->State[node->BucketIndex].Animation;
+ // Override animation when animation reference box is connected
+ auto animationAssetBox = node->TryGetBox(8);
+ if (animationAssetBox && animationAssetBox->HasConnection())
+ {
+ anim = TVariantValueCast::Cast(tryGetValue(animationAssetBox, Value::Null));
+ }
+
switch (box->ID)
{
// Animation
diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp
index afe5c4b49..cb32e0967 100644
--- a/Source/Engine/Audio/AudioSource.cpp
+++ b/Source/Engine/Audio/AudioSource.cpp
@@ -187,7 +187,6 @@ float AudioSource::GetTime() const
return 0.0f;
float time = AudioBackend::Source::GetCurrentBufferTime(this);
- ASSERT(time >= 0.0f && time <= Clip->GetLength());
if (UseStreaming())
{
diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
index 2f65a4b6c..ace8b6591 100644
--- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
+++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
@@ -33,7 +33,8 @@
int alError = alGetError(); \
if (alError != 0) \
{ \
- LOG(Error, "OpenAL method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), alError, __LINE__ - 1); \
+ const Char* errorStr = GetOpenALErrorString(alError); \
+ LOG(Error, "OpenAL method {0} failed with error 0x{1:X}:{2} (at line {3})", TEXT(#method), alError, errorStr, __LINE__ - 1); \
} \
}
#endif
@@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return 0;
}
+const Char* GetOpenALErrorString(int error)
+{
+ switch (error)
+ {
+ case AL_NO_ERROR:
+ return TEXT("AL_NO_ERROR");
+ case AL_INVALID_NAME:
+ return TEXT("AL_INVALID_NAME");
+ case AL_INVALID_ENUM:
+ return TEXT("AL_INVALID_ENUM");
+ case AL_INVALID_VALUE:
+ return TEXT("AL_INVALID_VALUE");
+ case AL_INVALID_OPERATION:
+ return TEXT("AL_INVALID_OPERATION");
+ case AL_OUT_OF_MEMORY:
+ return TEXT("AL_OUT_OF_MEMORY");
+ default:
+ break;
+ }
+ return TEXT("???");
+}
+
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
{
#if ALC_MULTIPLE_LISTENERS
@@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init()
// Init
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
- ALC::RebuildContexts(true);
+ int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
+ if (clampedIndex == Audio::GetActiveDeviceIndex())
+ {
+ ALC::RebuildContexts(true);
+ }
Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp
index f90c58875..3092a2229 100644
--- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp
+++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp
@@ -27,7 +27,15 @@
#define MAX_INPUT_CHANNELS 2
#define MAX_OUTPUT_CHANNELS 8
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
-
+#if ENABLE_ASSERTION
+#define XAUDIO2_CHECK_ERROR(method) \
+ if (hr != 0) \
+ { \
+ LOG(Error, "XAudio2 method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), (uint32)hr, __LINE__ - 1); \
+ }
+#else
+#define XAUDIO2_CHECK_ERROR(method)
+#endif
#define FLAX_COORD_SCALE 0.01f // units are meters
#define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE
#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * FLAX_COORD_SCALE, vec.Y * FLAX_COORD_SCALE, vec.Z * FLAX_COORD_SCALE)
@@ -104,7 +112,9 @@ namespace XAudio2
COM_DECLSPEC_NOTHROW void STDMETHODCALLTYPE OnVoiceError(THIS_ void* pBufferContext, HRESULT Error) override
{
+#if ENABLE_ASSERTION
LOG(Warning, "IXAudio2VoiceCallback::OnVoiceError! Error: 0x{0:x}", Error);
+#endif
}
public:
@@ -121,7 +131,8 @@ namespace XAudio2
XAUDIO2_SEND_DESCRIPTOR Destination;
float Pitch;
float Pan;
- float StartTime;
+ float StartTimeForQueueBuffer;
+ float LastBufferStartTime;
float DopplerFactor;
uint64 LastBufferStartSamplesPlayed;
int32 BuffersProcessed;
@@ -145,7 +156,8 @@ namespace XAudio2
Destination.pOutputVoice = nullptr;
Pitch = 1.0f;
Pan = 0.0f;
- StartTime = 0.0f;
+ StartTimeForQueueBuffer = 0.0f;
+ LastBufferStartTime = 0.0f;
IsDirty = false;
Is3D = false;
IsPlaying = false;
@@ -255,18 +267,18 @@ namespace XAudio2
buffer.pAudioData = aBuffer->Data.Get();
buffer.AudioBytes = aBuffer->Data.Count();
- if (aSource->StartTime > ZeroTolerance)
+ if (aSource->StartTimeForQueueBuffer > ZeroTolerance)
{
- buffer.PlayBegin = (UINT32)(aSource->StartTime * (aBuffer->Info.SampleRate * aBuffer->Info.NumChannels));
- buffer.PlayLength = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels - buffer.PlayBegin;
- aSource->StartTime = 0;
+ // Offset start position when playing buffer with a custom time offset
+ const uint32 bytesPerSample = aBuffer->Info.BitDepth / 8 * aBuffer->Info.NumChannels;
+ buffer.PlayBegin = (UINT32)(aSource->StartTimeForQueueBuffer * aBuffer->Info.SampleRate);
+ buffer.PlayLength = (buffer.AudioBytes / bytesPerSample) - buffer.PlayBegin;
+ aSource->LastBufferStartTime = aSource->StartTimeForQueueBuffer;
+ aSource->StartTimeForQueueBuffer = 0;
}
const HRESULT hr = aSource->Voice->SubmitSourceBuffer(&buffer);
- if (FAILED(hr))
- {
- LOG(Warning, "XAudio2: Failed to submit source buffer (error: 0x{0:x})", hr);
- }
+ XAUDIO2_CHECK_ERROR(SubmitSourceBuffer);
}
void VoiceCallback::OnBufferEnd(void* pBufferContext)
@@ -375,7 +387,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
const auto& header = clip->AudioHeader;
auto& format = aSource->Format;
format.wFormatTag = WAVE_FORMAT_PCM;
- format.nChannels = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
+ format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
format.nSamplesPerSec = header.Info.SampleRate;
format.wBitsPerSample = header.Info.BitDepth;
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8));
@@ -391,12 +403,10 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
1,
&aSource->Destination
};
- const HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList);
+ HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList);
+ XAUDIO2_CHECK_ERROR(CreateSourceVoice);
if (FAILED(hr))
- {
- LOG(Error, "Failed to create XAudio2 voice. Error: 0x{0:x}", hr);
return;
- }
// Prepare source state
aSource->Callback.Source = source;
@@ -410,7 +420,8 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
aSource->DopplerFactor = source->GetDopplerFactor();
aSource->UpdateTransform(source);
aSource->UpdateVelocity(source);
- aSource->Voice->SetVolume(source->GetVolume());
+ hr = aSource->Voice->SetVolume(source->GetVolume());
+ XAUDIO2_CHECK_ERROR(SetVolume);
// 0 is invalid ID so shift them
sourceID++;
@@ -451,7 +462,8 @@ void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source)
auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice)
{
- aSource->Voice->SetVolume(source->GetVolume());
+ const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume());
+ XAUDIO2_CHECK_ERROR(SetVolume);
}
}
@@ -494,12 +506,18 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
+ HRESULT hr;
const bool isPlaying = source->IsActuallyPlayingSth();
if (isPlaying)
- aSource->Voice->Stop();
+ {
+ hr = aSource->Voice->Stop();
+ XAUDIO2_CHECK_ERROR(Stop);
+ }
- aSource->Voice->FlushSourceBuffers();
+ hr = aSource->Voice->FlushSourceBuffers();
+ XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
aSource->LastBufferStartSamplesPlayed = 0;
+ aSource->LastBufferStartTime = 0;
aSource->BuffersProcessed = 0;
XAUDIO2_BUFFER buffer = { 0 };
@@ -512,12 +530,15 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels;
buffer.PlayBegin = state.SamplesPlayed % totalSamples;
buffer.PlayLength = totalSamples - buffer.PlayBegin;
- aSource->StartTime = 0;
+ aSource->StartTimeForQueueBuffer = 0;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
if (isPlaying)
- aSource->Voice->Start();
+ {
+ hr = aSource->Voice->Start();
+ XAUDIO2_CHECK_ERROR(Start);
+ }
}
void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source)
@@ -572,7 +593,8 @@ void AudioBackendXAudio2::Source_Play(AudioSource* source)
if (aSource && aSource->Voice && !aSource->IsPlaying)
{
// Play
- aSource->Voice->Start();
+ const HRESULT hr = aSource->Voice->Start();
+ XAUDIO2_CHECK_ERROR(Start);
aSource->IsPlaying = true;
}
}
@@ -583,7 +605,8 @@ void AudioBackendXAudio2::Source_Pause(AudioSource* source)
if (aSource && aSource->Voice && aSource->IsPlaying)
{
// Pause
- aSource->Voice->Stop();
+ const HRESULT hr = aSource->Voice->Stop();
+ XAUDIO2_CHECK_ERROR(Stop);
aSource->IsPlaying = false;
}
}
@@ -593,14 +616,18 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice)
{
- aSource->StartTime = 0.0f;
+ aSource->StartTimeForQueueBuffer = 0.0f;
+ aSource->LastBufferStartTime = 0.0f;
// Pause
- aSource->Voice->Stop();
+ HRESULT hr = aSource->Voice->Stop();
+ XAUDIO2_CHECK_ERROR(Stop);
aSource->IsPlaying = false;
// Unset streaming buffers to rewind
- aSource->Voice->FlushSourceBuffers();
+ hr = aSource->Voice->FlushSourceBuffers();
+ XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
+ Platform::Sleep(10); // TODO: find a better way to handle case when VoiceCallback::OnBufferEnd is called after source was stopped thus BuffersProcessed != 0, probably via buffers contexts ptrs
aSource->BuffersProcessed = 0;
aSource->Callback.PeekSamples();
}
@@ -612,7 +639,7 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float
if (aSource)
{
// Store start time so next buffer submitted will start from here (assumes audio is stopped)
- aSource->StartTime = value;
+ aSource->StartTimeForQueueBuffer = value;
}
}
@@ -628,8 +655,9 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source
aSource->Voice->GetState(&state);
const uint32 numChannels = clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / numChannels;
+ const uint32 sampleRate = clipInfo.SampleRate;// / clipInfo.NumChannels;
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
- time = aSource->StartTime + (state.SamplesPlayed % totalSamples) / static_cast(Math::Max(1U, clipInfo.SampleRate));
+ time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast(Math::Max(1U, sampleRate));
}
return time;
}
@@ -697,10 +725,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
if (aSource && aSource->Voice)
{
const HRESULT hr = aSource->Voice->FlushSourceBuffers();
- if (FAILED(hr))
- {
- LOG(Warning, "XAudio2: FlushSourceBuffers failed. Error: 0x{0:x}", hr);
- }
+ XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
aSource->BuffersProcessed = 0;
}
}
@@ -749,8 +774,7 @@ void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const Aud
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
- const uint32 bytesPerSample = info.BitDepth / 8;
- const int32 samplesLength = info.NumSamples * bytesPerSample;
+ const uint32 samplesLength = info.NumSamples * info.BitDepth / 8;
aBuffer->Info = info;
aBuffer->Data.Set(samples, samplesLength);
@@ -779,7 +803,8 @@ void AudioBackendXAudio2::Base_SetVolume(float value)
{
if (XAudio2::MasteringVoice)
{
- XAudio2::MasteringVoice->SetVolume(value);
+ const HRESULT hr = XAudio2::MasteringVoice->SetVolume(value);
+ XAUDIO2_CHECK_ERROR(SetVolume);
}
}
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index 309a82e04..613c7c2c2 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -538,11 +538,7 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
- // Check if is already loaded
- if (IsLoaded())
- return;
-
- // Start loading (using async tasks)
+ ASSERT(!IsLoaded());
ASSERT(_loadingTask == nullptr);
_loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr);
diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp
index 76ed34b80..276b6b1f7 100644
--- a/Source/Engine/Content/Assets/Material.cpp
+++ b/Source/Engine/Content/Assets/Material.cpp
@@ -20,6 +20,7 @@
#include "Engine/ShadersCompilation/Config.h"
#if BUILD_DEBUG
#include "Engine/Engine/Globals.h"
+#include "Engine/Scripting/BinaryModule.h"
#endif
#endif
@@ -256,7 +257,9 @@ Asset::LoadResult Material::load()
#if BUILD_DEBUG && USE_EDITOR
// Dump generated material source to the temporary file
+ BinaryModule::Locker.Lock();
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt"));
+ BinaryModule::Locker.Unlock();
#endif
// Encrypt source code
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index ea627f2c7..bd27abc5a 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -154,7 +154,7 @@ void ContentService::LateUpdate()
// Unload marked assets
for (int32 i = 0; i < ToUnload.Count(); i++)
{
- Asset* asset = ToUnload[i];
+ Asset* asset = ToUnload[i];
// Check if has no references
if (asset->GetReferencesCount() <= 0)
@@ -521,37 +521,33 @@ Asset* Content::GetAsset(const Guid& id)
void Content::DeleteAsset(Asset* asset)
{
- ScopeLock locker(AssetsLocker);
-
- // Validate
if (asset == nullptr || asset->_deleteFileOnUnload)
- {
- // Back
return;
- }
LOG(Info, "Deleting asset {0}...", asset->ToString());
+ // Ensure that asset is loaded (easier than cancel in-flight loading)
+ asset->WaitForLoaded();
+
// Mark asset for delete queue (delete it after auto unload)
asset->_deleteFileOnUnload = true;
// Unload
- UnloadAsset(asset);
+ asset->DeleteObject();
}
void Content::DeleteAsset(const StringView& path)
{
- ScopeLock locker(AssetsLocker);
-
- // Check if is loaded
+ // Try to delete already loaded asset
Asset* asset = GetAsset(path);
if (asset != nullptr)
{
- // Delete asset
DeleteAsset(asset);
return;
}
+ ScopeLock locker(AssetsLocker);
+
// Remove from registry
AssetInfo info;
if (Cache.DeleteAsset(path, &info))
@@ -573,7 +569,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
// Check if given id is invalid
if (!id.IsValid())
{
- // Cancel operation
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
return;
}
@@ -585,7 +580,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
storage->CloseFileHandles(); // Close file handle to allow removing it
if (!storage->HasAsset(id))
{
- // Skip removing
LOG(Warning, "Cannot remove file \'{0}\'. It doesn\'t contain asset {1}.", path, id);
return;
}
@@ -703,13 +697,11 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
LOG(Warning, "Cannot copy file to destination.");
return true;
}
-
if (JsonStorageProxy::ChangeId(dstPath, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
-
return false;
}
@@ -774,12 +766,9 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
+ if (auto storage = ContentStorageManager::GetStorage(dstPath))
{
- auto storage = ContentStorageManager::GetStorage(dstPath);
- if (storage)
- {
- storage->Reload();
- }
+ storage->Reload();
}
}
@@ -790,10 +779,8 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
void Content::UnloadAsset(Asset* asset)
{
- // Check input
if (asset == nullptr)
return;
-
asset->DeleteObject();
}
@@ -919,12 +906,8 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script
Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
- // Early out
if (!id.IsValid())
- {
- // Back
return nullptr;
- }
// Check if asset has been already loaded
Asset* result = GetAsset(id);
@@ -936,7 +919,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString());
return nullptr;
}
-
return result;
}
@@ -954,12 +936,8 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LoadCallAssetsLocker.Lock();
const bool contains = LoadCallAssets.Contains(id);
LoadCallAssetsLocker.Unlock();
-
if (!contains)
- {
return GetAsset(id);
- }
-
Platform::Sleep(1);
}
}
@@ -967,7 +945,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
// Mark asset as loading
LoadCallAssets.Add(id);
-
LoadCallAssetsLocker.Unlock();
}
@@ -988,7 +965,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
// Get cached asset info (from registry)
if (!GetAssetInfo(id, assetInfo))
{
- LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString());
+ LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
return nullptr;
}
@@ -1032,11 +1009,13 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
ASSERT(!Assets.ContainsKey(id));
#endif
Assets.Add(id, result);
- AssetsLocker.Unlock();
// Start asset loading
+ // TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization
result->startLoading();
+ AssetsLocker.Unlock();
+
return result;
}
diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
index 5ee384769..19d6fdd31 100644
--- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
+++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
@@ -31,10 +31,13 @@ public:
if (Asset)
{
Asset->Locker.Lock();
- Asset->_loadFailed = true;
- Asset->_isLoaded = false;
- LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
- Asset->_loadingTask = nullptr;
+ if (Asset->_loadingTask == this)
+ {
+ Asset->_loadFailed = true;
+ Asset->_isLoaded = false;
+ LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
+ Asset->_loadingTask = nullptr;
+ }
Asset->Locker.Unlock();
}
}
@@ -73,7 +76,10 @@ protected:
{
if (Asset)
{
- Asset->_loadingTask = nullptr;
+ Asset->Locker.Lock();
+ if (Asset->_loadingTask == this)
+ Asset->_loadingTask = nullptr;
+ Asset->Locker.Unlock();
Asset = nullptr;
}
@@ -84,7 +90,10 @@ protected:
{
if (Asset)
{
- Asset->_loadingTask = nullptr;
+ Asset->Locker.Lock();
+ if (Asset->_loadingTask == this)
+ Asset->_loadingTask = nullptr;
+ Asset->Locker.Unlock();
Asset = nullptr;
}
diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp
index b9a4f5675..b34bbfaaf 100644
--- a/Source/Engine/ContentImporters/ImportModelFile.cpp
+++ b/Source/Engine/ContentImporters/ImportModelFile.cpp
@@ -124,7 +124,8 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
// Import model file
ModelData modelData;
String errorMsg;
- String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath);
+ String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
+ autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
{
LOG(Error, "Cannot import model file. {0}", errorMsg);
diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h
index 38bf92fb8..b3765a9fe 100644
--- a/Source/Engine/Core/Collections/ChunkedArray.h
+++ b/Source/Engine/Core/Collections/ChunkedArray.h
@@ -100,7 +100,7 @@ public:
int32 _chunkIndex;
int32 _index;
- Iterator(ChunkedArray const* collection, const int32 index)
+ Iterator(const ChunkedArray* collection, const int32 index)
: _collection(const_cast(collection))
, _chunkIndex(index / ChunkSize)
, _index(index % ChunkSize)
@@ -122,29 +122,29 @@ public:
{
}
- public:
- FORCE_INLINE ChunkedArray* GetChunkedArray() const
+ Iterator(Iterator&& i)
+ : _collection(i._collection)
+ , _chunkIndex(i._chunkIndex)
+ , _index(i._index)
{
- return _collection;
}
+ public:
FORCE_INLINE int32 Index() const
{
return _chunkIndex * ChunkSize + _index;
}
- public:
- bool IsEnd() const
+ FORCE_INLINE bool IsEnd() const
{
- return Index() == _collection->Count();
+ return (_chunkIndex * ChunkSize + _index) == _collection->_count;
}
- bool IsNotEnd() const
+ FORCE_INLINE bool IsNotEnd() const
{
- return Index() != _collection->Count();
+ return (_chunkIndex * ChunkSize + _index) != _collection->_count;
}
- public:
FORCE_INLINE T& operator*() const
{
return _collection->_chunks[_chunkIndex]->At(_index);
@@ -155,7 +155,6 @@ public:
return &_collection->_chunks[_chunkIndex]->At(_index);
}
- public:
FORCE_INLINE bool operator==(const Iterator& v) const
{
return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index;
@@ -166,17 +165,22 @@ public:
return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index;
}
- public:
+ Iterator& operator=(const Iterator& v)
+ {
+ _collection = v._collection;
+ _chunkIndex = v._chunkIndex;
+ _index = v._index;
+ return *this;
+ }
+
Iterator& operator++()
{
// Check if it is not at end
- const int32 end = _collection->Count();
- if (Index() != end)
+ if ((_chunkIndex * ChunkSize + _index) != _collection->_count)
{
// Move forward within chunk
_index++;
- // Check if need to change chunk
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
{
// Move to next chunk
@@ -189,9 +193,9 @@ public:
Iterator operator++(int)
{
- Iterator temp = *this;
- ++temp;
- return temp;
+ Iterator i = *this;
+ ++i;
+ return i;
}
Iterator& operator--()
@@ -199,7 +203,6 @@ public:
// Check if it's not at beginning
if (_index != 0 || _chunkIndex != 0)
{
- // Check if need to change chunk
if (_index == 0)
{
// Move to previous chunk
@@ -217,9 +220,9 @@ public:
Iterator operator--(int)
{
- Iterator temp = *this;
- --temp;
- return temp;
+ Iterator i = *this;
+ --i;
+ return i;
}
};
@@ -294,7 +297,7 @@ public:
{
if (IsEmpty())
return;
- ASSERT(i.GetChunkedArray() == this);
+ ASSERT(i._collection == this);
ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize);
ASSERT(i.Index() < Count());
@@ -432,11 +435,31 @@ public:
Iterator End() const
{
- return Iterator(this, Count());
+ return Iterator(this, _count);
}
Iterator IteratorAt(int32 index) const
{
return Iterator(this, index);
}
+
+ FORCE_INLINE Iterator begin()
+ {
+ return Iterator(this, 0);
+ }
+
+ FORCE_INLINE Iterator end()
+ {
+ return Iterator(this, _count);
+ }
+
+ FORCE_INLINE const Iterator begin() const
+ {
+ return Iterator(this, 0);
+ }
+
+ FORCE_INLINE const Iterator end() const
+ {
+ return Iterator(this, _count);
+ }
};
diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h
index 318c304e0..3232c6b02 100644
--- a/Source/Engine/Core/Config/BuildSettings.h
+++ b/Source/Engine/Core/Config/BuildSettings.h
@@ -4,6 +4,7 @@
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/SceneReference.h"
@@ -76,6 +77,12 @@ public:
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
bool ShadersGenerateDebugData = false;
+ ///
+ /// If checked, skips bundling default engine fonts for UI. Use if to reduce build size if you don't use default engine fonts but custom ones only.
+ ///
+ API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
+ bool SkipDefaultFonts = false;
+
///
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
///
@@ -106,6 +113,7 @@ public:
DESERIALIZE(AdditionalAssetFolders);
DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData);
+ DESERIALIZE(SkipDefaultFonts);
DESERIALIZE(SkipDotnetPackaging);
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
}
diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp
index e3d97e149..bb8d42aba 100644
--- a/Source/Engine/Core/Log.cpp
+++ b/Source/Engine/Core/Log.cpp
@@ -3,16 +3,15 @@
#include "Log.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Types/DateTime.h"
-#include "Engine/Core/Collections/Sorting.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Serialization/FileWriteStream.h"
-#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Debug/Exceptions/Exceptions.h"
#if USE_EDITOR
-#include "Engine/Core/Collections/Array.h"
+#include "Engine/Core/Collections/Sorting.h"
#endif
#include
@@ -199,35 +198,36 @@ void Log::Logger::WriteFloor()
void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w)
{
const TimeSpan time = DateTime::Now() - LogStartTime;
+ const int32 msgLength = msg.Length();
fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type));
// On Windows convert all '\n' into '\r\n'
#if PLATFORM_WINDOWS
- const int32 msgLength = msg.Length();
- if (msgLength > 1)
+ bool hasWindowsNewLine = false;
+ for (int32 i = 1; i < msgLength && !hasWindowsNewLine; i++)
+ hasWindowsNewLine |= msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n';
+ if (hasWindowsNewLine)
{
- MemoryWriteStream msgStream(msgLength * sizeof(Char));
- msgStream.WriteChar(msg[0]);
+ Array msgStream;
+ msgStream.EnsureCapacity(msgLength);
+ msgStream.Add(msg.Get()[0]);
for (int32 i = 1; i < msgLength; i++)
{
- if (msg[i - 1] != '\r' && msg[i] == '\n')
- msgStream.WriteChar(TEXT('\r'));
- msgStream.WriteChar(msg[i]);
+ if (msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n')
+ msgStream.Add(TEXT('\r'));
+ msgStream.Add(msg.Get()[i]);
}
- msgStream.WriteChar(msg[msgLength]);
- msgStream.WriteChar(TEXT('\0'));
- fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.GetHandle());
- //w.append(msgStream.GetHandle(), msgStream.GetHandle() + msgStream.GetPosition());
+ msgStream.Add(TEXT('\0'));
+ w.append(msgStream.Get(), (const Char*)(msgStream.Get() + msgStream.Count()));
+ //fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.Get());
+ return;
}
- else
- {
- //w.append(msg.Get(), msg.Get() + msg.Length());
- fmt_flax::format(w, TEXT("{}"), msg);
- }
-#else
- fmt_flax::format(w, TEXT("{}"), msg);
#endif
+
+ // Output raw message to the log
+ w.append(msg.Get(), msg.Get() + msg.Length());
+ //fmt_flax::format(w, TEXT("{}"), msg);
}
void Log::Logger::Write(LogType type, const StringView& msg)
diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h
index af93fc0f2..836ead090 100644
--- a/Source/Engine/Core/Math/Color.h
+++ b/Source/Engine/Core/Math/Color.h
@@ -71,7 +71,7 @@ public:
/// The green channel value.
/// The blue channel value.
/// The alpha channel value.
- Color(float r, float g, float b, float a = 1)
+ FORCE_INLINE Color(float r, float g, float b, float a = 1)
: R(r)
, G(g)
, B(b)
@@ -203,7 +203,7 @@ public:
return Color(R - b.R, G - b.G, B - b.B, A - b.A);
}
- Color operator*(const Color& b) const
+ FORCE_INLINE Color operator*(const Color& b) const
{
return Color(R * b.R, G * b.G, B * b.B, A * b.A);
}
diff --git a/Source/Engine/Core/Math/Half.cpp b/Source/Engine/Core/Math/Half.cpp
index 4e4b6eb50..b3c1696a5 100644
--- a/Source/Engine/Core/Math/Half.cpp
+++ b/Source/Engine/Core/Math/Half.cpp
@@ -1,10 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Half.h"
-#include "Rectangle.h"
-#include "Vector2.h"
-#include "Vector3.h"
#include "Vector4.h"
+#include "Rectangle.h"
#include "Color.h"
static_assert(sizeof(Half) == 2, "Invalid Half type size.");
@@ -16,12 +14,47 @@ Half2 Half2::Zero(0.0f, 0.0f);
Half3 Half3::Zero(0.0f, 0.0f, 0.0f);
Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f);
-Half2::Half2(const Float2& v)
+#if !USE_SSE_HALF_CONVERSION
+
+Half Float16Compressor::Compress(float value)
{
- X = Float16Compressor::Compress(v.X);
- Y = Float16Compressor::Compress(v.Y);
+ Bits v, s;
+ v.f = value;
+ uint32 sign = v.si & signN;
+ v.si ^= sign;
+ sign >>= shiftSign; // logical shift
+ s.si = mulN;
+ s.si = static_cast(s.f * v.f); // correct subnormals
+ v.si ^= (s.si ^ v.si) & -(minN > v.si);
+ v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
+ v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
+ v.ui >>= shift; // logical shift
+ v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
+ v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
+ return v.ui | sign;
}
+float Float16Compressor::Decompress(Half value)
+{
+ Bits v;
+ v.ui = value;
+ int32 sign = v.si & signC;
+ v.si ^= sign;
+ sign <<= shiftSign;
+ v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
+ v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
+ Bits s;
+ s.si = mulC;
+ s.f *= v.si;
+ const int32 mask = -(norC > v.si);
+ v.si <<= shift;
+ v.si ^= (s.si ^ v.si) & mask;
+ v.si |= sign;
+ return v.f;
+}
+
+#endif
+
Float2 Half2::ToFloat2() const
{
return Float2(
@@ -30,13 +63,6 @@ Float2 Half2::ToFloat2() const
);
}
-Half3::Half3(const Float3& v)
-{
- X = Float16Compressor::Compress(v.X);
- Y = Float16Compressor::Compress(v.Y);
- Z = Float16Compressor::Compress(v.Z);
-}
-
Float3 Half3::ToFloat3() const
{
return Float3(
diff --git a/Source/Engine/Core/Math/Half.h b/Source/Engine/Core/Math/Half.h
index 346b5d6b3..44b90df66 100644
--- a/Source/Engine/Core/Math/Half.h
+++ b/Source/Engine/Core/Math/Half.h
@@ -3,6 +3,8 @@
#pragma once
#include "Math.h"
+#include "Vector2.h"
+#include "Vector3.h"
///
/// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa
@@ -45,54 +47,23 @@ class FLAXENGINE_API Float16Compressor
static const int32 minD = minC - subC - 1;
public:
- static Half Compress(const float value)
- {
#if USE_SSE_HALF_CONVERSION
+ FORCE_INLINE static Half Compress(float value)
+ {
__m128 value1 = _mm_set_ss(value);
__m128i value2 = _mm_cvtps_ph(value1, 0);
return static_cast(_mm_cvtsi128_si32(value2));
-#else
- Bits v, s;
- v.f = value;
- uint32 sign = v.si & signN;
- v.si ^= sign;
- sign >>= shiftSign; // logical shift
- s.si = mulN;
- s.si = static_cast(s.f * v.f); // correct subnormals
- v.si ^= (s.si ^ v.si) & -(minN > v.si);
- v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
- v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
- v.ui >>= shift; // logical shift
- v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
- v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
- return v.ui | sign;
-#endif
}
-
- static float Decompress(const Half value)
+ FORCE_INLINE static float Decompress(Half value)
{
-#if USE_SSE_HALF_CONVERSION
__m128i value1 = _mm_cvtsi32_si128(static_cast(value));
__m128 value2 = _mm_cvtph_ps(value1);
return _mm_cvtss_f32(value2);
-#else
- Bits v;
- v.ui = value;
- int32 sign = v.si & signC;
- v.si ^= sign;
- sign <<= shiftSign;
- v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
- v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
- Bits s;
- s.si = mulC;
- s.f *= v.si;
- const int32 mask = -(norC > v.si);
- v.si <<= shift;
- v.si ^= (s.si ^ v.si) & mask;
- v.si |= sign;
- return v.f;
-#endif
}
+#else
+ static Half Compress(float value);
+ static float Decompress(Half value);
+#endif
};
///
@@ -128,7 +99,7 @@ public:
///
/// X component
/// Y component
- Half2(Half x, Half y)
+ FORCE_INLINE Half2(Half x, Half y)
: X(x)
, Y(y)
{
@@ -139,7 +110,7 @@ public:
///
/// X component
/// Y component
- Half2(float x, float y)
+ FORCE_INLINE Half2(float x, float y)
{
X = Float16Compressor::Compress(x);
Y = Float16Compressor::Compress(y);
@@ -149,7 +120,11 @@ public:
/// Init
///
/// X and Y components
- Half2(const Float2& v);
+ FORCE_INLINE Half2(const Float2& v)
+ {
+ X = Float16Compressor::Compress(v.X);
+ Y = Float16Compressor::Compress(v.Y);
+ }
public:
Float2 ToFloat2() const;
@@ -185,21 +160,26 @@ public:
public:
Half3() = default;
- Half3(Half x, Half y, Half z)
+ FORCE_INLINE Half3(Half x, Half y, Half z)
: X(x)
, Y(y)
, Z(z)
{
}
- Half3(const float x, const float y, const float z)
+ FORCE_INLINE Half3(float x, float y, float z)
{
X = Float16Compressor::Compress(x);
Y = Float16Compressor::Compress(y);
Z = Float16Compressor::Compress(z);
}
- Half3(const Float3& v);
+ FORCE_INLINE Half3(const Float3& v)
+ {
+ X = Float16Compressor::Compress(v.X);
+ Y = Float16Compressor::Compress(v.Y);
+ Z = Float16Compressor::Compress(v.Z);
+ }
public:
Float3 ToFloat3() const;
diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp
index bb3fd8bf1..a020f7844 100644
--- a/Source/Engine/Core/ObjectsRemovalService.cpp
+++ b/Source/Engine/Core/ObjectsRemovalService.cpp
@@ -5,7 +5,6 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
-#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
@@ -14,16 +13,15 @@ const Char* HertzSizesData[] = { TEXT("Hz"), TEXT("KHz"), TEXT("MHz"), TEXT("GHz
Span Utilities::Private::BytesSizes(BytesSizesData, ARRAY_COUNT(BytesSizesData));
Span Utilities::Private::HertzSizes(HertzSizesData, ARRAY_COUNT(HertzSizesData));
-namespace ObjectsRemovalServiceImpl
+namespace
{
CriticalSection PoolLocker;
DateTime LastUpdate;
float LastUpdateGameTime;
Dictionary
internal static int GetFieldOffset(FieldInfo field, Type type)
{
+ if (field.IsLiteral)
+ return 0;
+
// Get the address of the field, source: https://stackoverflow.com/a/56512720
int fieldOffset = Unsafe.Read((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF;
if (!type.IsValueType)
@@ -436,6 +441,23 @@ namespace FlaxEngine.Interop
return fieldOffset;
}
+#if USE_AOT
+ ///
+ /// Helper utility to set field of the referenced value via reflection.
+ ///
+ internal static void SetReferenceTypeField(FieldInfo field, ref T fieldOwner, object fieldValue)
+ {
+ if (typeof(T).IsValueType)
+ {
+ // Value types need setting via boxed object to properly propagate value
+ object fieldOwnerBoxed = fieldOwner;
+ field.SetValue(fieldOwnerBoxed, fieldValue);
+ fieldOwner = (T)fieldOwnerBoxed;
+ }
+ else
+ field.SetValue(fieldOwner, fieldValue);
+ }
+#else
///
/// Returns a reference to the value of the field.
///
@@ -462,6 +484,7 @@ namespace FlaxEngine.Interop
byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset;
return ref Unsafe.AsRef(fieldPtr);
}
+#endif
}
///
@@ -735,29 +758,49 @@ namespace FlaxEngine.Interop
private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
{
+#if USE_AOT
+ IntPtr fieldValue = Unsafe.Read(nativeFieldPtr.ToPointer());
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#else
ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer());
+#endif
fieldSize = IntPtr.Size;
}
private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
{
+#if USE_AOT
+ IntPtr fieldValue = Unsafe.Read(nativeFieldPtr.ToPointer());
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#else
ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer());
+#endif
fieldSize = IntPtr.Size;
}
private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
{
- ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
- Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef);
+#if USE_AOT
+ object boxed = field.GetValue(fieldOwner);
+ IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed));
+#else
+ IntPtr fieldValue = FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValue);
fieldSize = IntPtr.Size;
}
private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
{
- ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
- Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef);
+#if USE_AOT
+ object boxed = field.GetValue(fieldOwner);
+ IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed));
+#else
+ IntPtr fieldValue = FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValue);
fieldSize = IntPtr.Size;
}
@@ -799,8 +842,15 @@ namespace FlaxEngine.Interop
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
}
- ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false);
+#if USE_AOT
+ TField fieldValue = default;
+#else
+ ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, nativeFieldPtr, false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -813,8 +863,15 @@ namespace FlaxEngine.Interop
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
}
- ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false);
+#if USE_AOT
+ TField fieldValue = default;
+#else
+ ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, nativeFieldPtr, false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -825,8 +882,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ TField[] fieldValue = (TField[])field.GetValue(fieldOwner);
+#else
+ ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -837,8 +901,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ TField[] fieldValue = null;
+#else
+ ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -852,11 +923,11 @@ namespace FlaxEngine.Interop
}
#if USE_AOT
- TField fieldValueRef = (TField)field.GetValue(fieldOwner);
+ TField fieldValue = (TField)field.GetValue(fieldOwner);
#else
- ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+ ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
#endif
- MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr);
+ MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr);
}
internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -870,11 +941,11 @@ namespace FlaxEngine.Interop
}
#if USE_AOT
- TField fieldValueRef = (TField)field.GetValue(fieldOwner);
+ TField fieldValue = (TField)field.GetValue(fieldOwner);
#else
- ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+ ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
#endif
- MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr);
+ MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr);
}
}
@@ -904,8 +975,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ TField fieldValue = null;
+#else
+ ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -915,8 +993,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ TField fieldValue = default;
+#else
+ ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -926,8 +1011,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ TField[] fieldValue = null;
+#else
+ ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -937,8 +1029,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ TField[] fieldValue = null;
+#else
+ ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false);
+#if USE_AOT
+ FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
+#endif
}
internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -948,8 +1047,12 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr);
+#if USE_AOT
+ TField fieldValue = (TField)field.GetValue(fieldOwner);
+#else
+ ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr);
}
internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -959,8 +1062,12 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
- ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
- MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr);
+#if USE_AOT
+ TField fieldValue = (TField)field.GetValue(fieldOwner);
+#else
+ ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner);
+#endif
+ MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr);
}
}
}
@@ -1044,7 +1151,7 @@ namespace FlaxEngine.Interop
var fields = MarshalHelper.marshallableFields;
var offsets = MarshalHelper.marshallableFieldOffsets;
var marshallers = MarshalHelper.toNativeFieldMarshallers;
- for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++)
+ for (int i = 0; i < fields.Length; i++)
{
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
nativePtr += fieldSize;
@@ -1306,11 +1413,13 @@ namespace FlaxEngine.Interop
return RuntimeHelpers.GetUninitializedObject(wrappedType);
}
+#if !USE_AOT
internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr)
{
- object obj = CreateObject();
+ object obj = RuntimeHelpers.GetUninitializedObject(wrappedType);
if (obj is Object)
{
+ // TODO: use UnsafeAccessorAttribute on .NET 8 and use this path on all platforms (including non-Desktop, see MCore::ScriptingObject::CreateScriptingObject)
{
ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj);
fieldRef = unmanagedPtr;
@@ -1331,8 +1440,9 @@ namespace FlaxEngine.Interop
return obj;
}
+#endif
- public static implicit operator Type(TypeHolder holder) => holder?.type ?? null;
+ public static implicit operator Type(TypeHolder holder) => holder?.type;
public bool Equals(TypeHolder other) => type == other.type;
public bool Equals(Type other) => type == other;
public override int GetHashCode() => type.GetHashCode();
diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp
index fc7e8a022..ede49cf92 100644
--- a/Source/Engine/Engine/Screen.cpp
+++ b/Source/Engine/Engine/Screen.cpp
@@ -22,7 +22,7 @@ class ScreenService : public EngineService
{
public:
ScreenService()
- : EngineService(TEXT("Screen"), 120)
+ : EngineService(TEXT("Screen"), 500)
{
}
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index 82765f151..898139ce7 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -634,15 +634,12 @@ void Foliage::RemoveFoliageType(int32 index)
int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const
{
PROFILE_CPU();
-
int32 result = 0;
-
- for (auto i = Instances.Begin(); i.IsNotEnd(); i++)
+ for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
if (i->Type == index)
result++;
}
-
return result;
}
diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp
index 019cfad5a..34d8e7661 100644
--- a/Source/Engine/Graphics/GPUDevice.cpp
+++ b/Source/Engine/Graphics/GPUDevice.cpp
@@ -503,6 +503,9 @@ void GPUDevice::DrawEnd()
// Call present on all used tasks
int32 presentCount = 0;
bool anyVSync = false;
+#if COMPILE_WITH_PROFILER
+ const double presentStart = Platform::GetTimeSeconds();
+#endif
for (int32 i = 0; i < RenderTask::Tasks.Count(); i++)
{
const auto task = RenderTask::Tasks[i];
@@ -537,6 +540,10 @@ void GPUDevice::DrawEnd()
#endif
GetMainContext()->Flush();
}
+#if COMPILE_WITH_PROFILER
+ const double presentEnd = Platform::GetTimeSeconds();
+ ProfilerGPU::OnPresentTime((float)((presentEnd - presentStart) * 1000.0));
+#endif
_wasVSyncUsed = anyVSync;
_isRendering = false;
diff --git a/Source/Engine/Graphics/GPULimits.h b/Source/Engine/Graphics/GPULimits.h
index 11da5aca3..0cae4fdfd 100644
--- a/Source/Engine/Graphics/GPULimits.h
+++ b/Source/Engine/Graphics/GPULimits.h
@@ -259,6 +259,11 @@ API_STRUCT() struct GPULimits
///
API_FIELD() bool HasDepthAsSRV;
+ ///
+ /// True if device supports depth buffer clipping (see GPUPipelineState::Description::DepthClipEnable).
+ ///
+ API_FIELD() bool HasDepthClip;
+
///
/// True if device supports depth buffer texture as a readonly depth buffer (can be sampled in the shader while performing depth-test).
///
diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp
index 9ba2975b2..36dfc4615 100644
--- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp
@@ -92,6 +92,8 @@ bool DecalMaterialShader::Load()
{
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
psDesc0.VS = _shader->GetVS("VS_Decal");
+ if (psDesc0.VS == nullptr)
+ return true;
psDesc0.PS = _shader->GetPS("PS_Decal");
psDesc0.CullMode = CullMode::Normal;
diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
index 52a0bd551..a899dbc12 100644
--- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
@@ -136,6 +136,7 @@ void DeferredMaterialShader::Unload()
bool DeferredMaterialShader::Load()
{
+ bool failed = false;
auto psDesc = GPUPipelineState::Description::Default;
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;
if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest))
@@ -155,16 +156,20 @@ bool DeferredMaterialShader::Load()
// GBuffer Pass
psDesc.VS = _shader->GetVS("VS");
+ failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer");
_cache.Default.Init(psDesc);
psDesc.VS = _shader->GetVS("VS", 1);
+ failed |= psDesc.VS == nullptr;
_cacheInstanced.Default.Init(psDesc);
// GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1)
psDesc.VS = _shader->GetVS("VS");
+ failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
_cache.DefaultLightmap.Init(psDesc);
psDesc.VS = _shader->GetVS("VS", 1);
+ failed |= psDesc.VS == nullptr;
_cacheInstanced.DefaultLightmap.Init(psDesc);
// GBuffer Pass with skinning
@@ -233,5 +238,5 @@ bool DeferredMaterialShader::Load()
psDesc.VS = _shader->GetVS("VS_Skinned");
_cache.DepthSkinned.Init(psDesc);
- return false;
+ return failed;
}
diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp
index d391f3295..9fb532b4e 100644
--- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp
@@ -174,6 +174,8 @@ bool ForwardMaterialShader::Load()
// Forward Pass
psDesc.VS = _shader->GetVS("VS");
+ if (psDesc.VS == nullptr)
+ return true;
psDesc.PS = _shader->GetPS("PS_Forward");
psDesc.DepthWriteEnable = false;
psDesc.BlendMode = BlendingMode::AlphaBlend;
diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs
index 0a986d44d..68ff7e07f 100644
--- a/Source/Engine/Graphics/Mesh.cs
+++ b/Source/Engine/Graphics/Mesh.cs
@@ -339,7 +339,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
/// The vertex colors (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, int[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -357,7 +357,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
/// The vertex colors (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -375,7 +375,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
/// The vertex colors (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, uint[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -393,7 +393,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
/// The vertex colors (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -411,7 +411,7 @@ namespace FlaxEngine
/// The tangent vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
/// The vertex colors (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -429,7 +429,7 @@ namespace FlaxEngine
/// The tangent vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
/// The vertex colors (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index f03c013c6..1e531d0e0 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -609,7 +609,7 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c
ScopeLock lock(model->Locker);
if (model->IsVirtual())
{
- LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download");
+ LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
return true;
}
diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp
index d061d0745..727fe7754 100644
--- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp
+++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp
@@ -318,7 +318,9 @@ void MeshData::BuildIndexBuffer()
}
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Generated index buffer for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), Positions.Count(), Indices.Count());
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, Indices.Count(), TEXT("indices"));
}
void MeshData::FindPositions(const Float3& position, float epsilon, Array& result)
@@ -449,7 +451,9 @@ bool MeshData::GenerateNormals(float smoothingAngle)
}
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("normals"));
return false;
}
@@ -685,7 +689,10 @@ bool MeshData::GenerateTangents(float smoothingAngle)
#endif
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("tangents"));
+
return false;
}
@@ -872,7 +879,9 @@ void MeshData::ImproveCacheLocality()
Allocator::Free(piCandidates);
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Cache relevant optimize for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime));
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("optimized indices"));
}
float MeshData::CalculateTrianglesArea() const
diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/SkinnedMesh.cs
index 8fb7a83dc..a7b7594bb 100644
--- a/Source/Engine/Graphics/SkinnedMesh.cs
+++ b/Source/Engine/Graphics/SkinnedMesh.cs
@@ -216,7 +216,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex).
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, int[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
@@ -235,7 +235,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex).
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
@@ -254,7 +254,7 @@ namespace FlaxEngine
/// The normal vectors (per vertex).
/// The tangent vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp
index c29c6254d..026422799 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp
@@ -359,6 +359,7 @@ bool GPUDeviceDX11::Init()
limits.HasAppendConsumeBuffers = true;
limits.HasSeparateRenderTargetBlendState = true;
limits.HasDepthAsSRV = true;
+ limits.HasDepthClip = true;
limits.HasReadOnlyDepth = true;
limits.HasMultisampleDepthAsSRV = true;
limits.HasTypedUAVLoad = featureDataD3D11Options2.TypedUAVLoadAdditionalFormats != 0;
@@ -382,6 +383,7 @@ bool GPUDeviceDX11::Init()
limits.HasAppendConsumeBuffers = false;
limits.HasSeparateRenderTargetBlendState = false;
limits.HasDepthAsSRV = false;
+ limits.HasDepthClip = true;
limits.HasReadOnlyDepth = createdFeatureLevel == D3D_FEATURE_LEVEL_10_1;
limits.HasMultisampleDepthAsSRV = false;
limits.HasTypedUAVLoad = false;
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp
index e2266f551..d9dc54f97 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp
@@ -381,6 +381,7 @@ bool GPUDeviceDX12::Init()
limits.HasAppendConsumeBuffers = true;
limits.HasSeparateRenderTargetBlendState = true;
limits.HasDepthAsSRV = true;
+ limits.HasDepthClip = true;
limits.HasReadOnlyDepth = true;
limits.HasMultisampleDepthAsSRV = true;
limits.HasTypedUAVLoad = options.TypedUAVLoadAdditionalFormats != 0;
diff --git a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp
index 7869ef312..fa390ccab 100644
--- a/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp
+++ b/Source/Engine/GraphicsDevice/Null/GPUDeviceNull.cpp
@@ -50,18 +50,7 @@ bool GPUDeviceNull::Init()
// Init device limits
{
auto& limits = Limits;
- limits.HasCompute = false;
- limits.HasTessellation = false;
- limits.HasGeometryShaders = false;
- limits.HasInstancing = false;
- limits.HasVolumeTextureRendering = false;
- limits.HasDrawIndirect = false;
- limits.HasAppendConsumeBuffers = false;
- limits.HasSeparateRenderTargetBlendState = false;
- limits.HasDepthAsSRV = false;
- limits.HasReadOnlyDepth = false;
- limits.HasMultisampleDepthAsSRV = false;
- limits.HasTypedUAVLoad = false;
+ Platform::MemoryClear(&limits, sizeof(limits));
limits.MaximumMipLevelsCount = 14;
limits.MaximumTexture1DSize = 8192;
limits.MaximumTexture1DArraySize = 512;
@@ -70,11 +59,8 @@ bool GPUDeviceNull::Init()
limits.MaximumTexture3DSize = 2048;
limits.MaximumTextureCubeSize = 16384;
limits.MaximumSamplerAnisotropy = 1;
-
for (int32 i = 0; i < static_cast(PixelFormat::MAX); i++)
- {
FeaturesPerFormat[i] = FormatFeatures(static_cast(i), MSAALevel::None, FormatSupport::None);
- }
}
// Create main context
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp
index d758ec999..79e882f6d 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp
@@ -1210,16 +1210,16 @@ void GPUContextVulkan::ResolveMultisample(GPUTexture* sourceMultisampleTexture,
void GPUContextVulkan::DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex)
{
- const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
+ const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
vkCmdDraw(cmdBuffer->GetHandle(), verticesCount, instanceCount, startVertex, startInstance);
RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3);
}
void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex)
{
- const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
+ const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
vkCmdDrawIndexed(cmdBuffer->GetHandle(), indicesCount, instanceCount, startIndex, startVertex, startInstance);
RENDER_STAT_DRAW_CALL(0, indicesCount / 3 * instanceCount);
}
@@ -1227,10 +1227,9 @@ void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instance
void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
{
ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
-
+ OnDrawCall();
auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
- OnDrawCall();
vkCmdDrawIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndirectCommand));
RENDER_STAT_DRAW_CALL(0, 0);
}
@@ -1238,10 +1237,9 @@ void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 of
void GPUContextVulkan::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
{
ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
-
+ OnDrawCall();
auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
- OnDrawCall();
vkCmdDrawIndexedIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndexedIndirectCommand));
RENDER_STAT_DRAW_CALL(0, 0);
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
index b4a6112a9..9c553060f 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
@@ -1704,6 +1704,7 @@ bool GPUDeviceVulkan::Init()
limits.HasDrawIndirect = PhysicalDeviceLimits.maxDrawIndirectCount >= 1;
limits.HasAppendConsumeBuffers = false; // TODO: add Append Consume buffers support for Vulkan
limits.HasSeparateRenderTargetBlendState = true;
+ limits.HasDepthClip = PhysicalDeviceFeatures.depthClamp;
limits.HasDepthAsSRV = true;
limits.HasReadOnlyDepth = true;
limits.HasMultisampleDepthAsSRV = !!PhysicalDeviceFeatures.sampleRateShading;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp
index 136a7c6e5..c5c1970e0 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp
@@ -340,7 +340,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc)
break;
}
_descRasterization.frontFace = VK_FRONT_FACE_CLOCKWISE;
- _descRasterization.depthClampEnable = !desc.DepthClipEnable;
+ _descRasterization.depthClampEnable = !desc.DepthClipEnable && _device->Limits.HasDepthClip;
_descRasterization.lineWidth = 1.0f;
_desc.pRasterizationState = &_descRasterization;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp
index 9c58b77b0..cce86799a 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.cpp
@@ -16,7 +16,7 @@ void GPUTimerQueryVulkan::Interrupt(CmdBufferVulkan* cmdBuffer)
if (!_interrupted)
{
_interrupted = true;
- WriteTimestamp(cmdBuffer, _queries[_queryIndex].End);
+ WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
}
@@ -28,7 +28,7 @@ void GPUTimerQueryVulkan::Resume(CmdBufferVulkan* cmdBuffer)
e.End.Pool = nullptr;
_interrupted = false;
- WriteTimestamp(cmdBuffer, e.Begin);
+ WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
_queries.Add(e);
_queryIndex++;
@@ -56,13 +56,13 @@ bool GPUTimerQueryVulkan::GetResult(Query& query)
return false;
}
-void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query) const
+void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const
{
auto pool = _device->FindAvailableTimestampQueryPool();
uint32 index;
pool->AcquireQuery(index);
- vkCmdWriteTimestamp(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, pool->GetHandle(), index);
+ vkCmdWriteTimestamp(cmdBuffer->GetHandle(), stage, pool->GetHandle(), index);
pool->MarkQueryAsStarted(index);
query.Pool = pool;
@@ -168,7 +168,7 @@ void GPUTimerQueryVulkan::Begin()
_queryIndex = 0;
_interrupted = false;
- WriteTimestamp(cmdBuffer, e.Begin);
+ WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
context->GetCmdBufferManager()->OnQueryBegin(this);
ASSERT(_queries.IsEmpty());
@@ -193,7 +193,7 @@ void GPUTimerQueryVulkan::End()
if (!_interrupted)
{
- WriteTimestamp(cmdBuffer, _queries[_queryIndex].End);
+ WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
context->GetCmdBufferManager()->OnQueryEnd(this);
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h
index b081b946d..ddf461b13 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h
@@ -59,7 +59,7 @@ public:
private:
bool GetResult(Query& query);
- void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query) const;
+ void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const;
bool TryGetResult();
bool UseQueries();
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 459e1fb6b..88f7e6876 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -706,13 +706,12 @@ void AnimatedModel::UpdateBounds()
const int32 bonesCount = skeleton.Bones.Count();
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation()));
- _box = box;
}
// Apply margin based on model dimensions
const Vector3 modelBoxSize = modelBox.GetSize();
- const Vector3 center = _box.GetCenter();
- const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
+ const Vector3 center = box.GetCenter();
+ const Vector3 sizeHalf = Vector3::Max(box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
}
else
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index d070d99a2..0c5c4d73b 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -12,7 +12,7 @@
///
/// Performs an animation and renders a skinned model.
///
-API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Other\")")
+API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Visuals\")")
class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(AnimatedModel);
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index 2e932096e..2f064eb8a 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -10,7 +10,8 @@
///
/// Renders model on the screen.
///
-API_CLASS(Attributes="ActorContextMenu(\"New/Model\")") class FLAXENGINE_API StaticModel : public ModelInstanceActor
+API_CLASS(Attributes="ActorContextMenu(\"New/Model\"), ActorToolbox(\"Visuals\")")
+class FLAXENGINE_API StaticModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(StaticModel);
private:
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index 83b24c841..dfd5786c6 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -132,7 +132,7 @@ class LevelService : public EngineService
{
public:
LevelService()
- : EngineService(TEXT("Scene Manager"), 30)
+ : EngineService(TEXT("Scene Manager"), 200)
{
}
@@ -1218,10 +1218,9 @@ bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer,
// Json resource data
writer.JKEY("Data");
writer.StartArray();
+ SceneObject** objects = allObjects.Get();
for (int32 i = 0; i < allObjects.Count(); i++)
- {
- writer.SceneObject(allObjects[i]);
- }
+ writer.SceneObject(objects[i]);
writer.EndArray();
}
writer.EndObject();
diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp
index 123013064..cd9d893ec 100644
--- a/Source/Engine/Level/Prefabs/PrefabManager.cpp
+++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp
@@ -29,7 +29,7 @@ class PrefabManagerService : public EngineService
{
public:
PrefabManagerService()
- : EngineService(TEXT("Prefab Manager"), 110)
+ : EngineService(TEXT("Prefab Manager"))
{
}
};
diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp
index 7f62d943b..5632d8bc7 100644
--- a/Source/Engine/Level/SceneObjectsFactory.cpp
+++ b/Source/Engine/Level/SceneObjectsFactory.cpp
@@ -14,6 +14,9 @@
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/ThreadLocal.h"
+#if !BUILD_RELEASE || USE_EDITOR
+#include "Engine/Level/Level.h"
+#endif
SceneObjectsFactory::Context::Context(ISerializeModifier* modifier)
: Modifier(modifier)
@@ -254,6 +257,10 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value)
{
+#if !BUILD_RELEASE || USE_EDITOR
+ // Prevent race-conditions when logging missing objects (especially when adding dummy MissingScript)
+ ScopeLock lock(Level::ScenesLock);
+
// Print invalid object data contents
rapidjson_flax::StringBuffer buffer;
PrettyJsonWriter writer(buffer);
@@ -280,6 +287,7 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
}
}
+#endif
}
Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id)
@@ -361,14 +369,14 @@ SceneObjectsFactory::PrefabSyncData::PrefabSyncData(Array& sceneOb
{
}
-void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData& data)
+void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyncData& data)
{
PROFILE_CPU_NAMED("SetupPrefabInstances");
const int32 count = data.Data.Size();
ASSERT(count <= data.SceneObjects.Count());
for (int32 i = 0; i < count; i++)
{
- SceneObject* obj = data.SceneObjects[i];
+ const SceneObject* obj = data.SceneObjects[i];
if (!obj)
continue;
const auto& stream = data.Data[i];
@@ -409,6 +417,21 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData&
// Add to the prefab instance IDs mapping
auto& prefabInstance = context.Instances[index];
prefabInstance.IdsMapping[prefabObjectId] = id;
+
+ // Walk over nested prefabs to link any subobjects into this object (eg. if nested prefab uses cross-object references to link them correctly)
+ NESTED_PREFAB_WALK:
+ const ISerializable::DeserializeStream* prefabData;
+ if (prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData) && JsonTools::GetGuidIfValid(prefabObjectId, *prefabData, "PrefabObjectID"))
+ {
+ prefabId = JsonTools::GetGuid(stream, "PrefabID");
+ prefab = Content::LoadAsync(prefabId);
+ if (prefab && !prefab->WaitForLoaded())
+ {
+ // Map prefab object ID to the deserialized instance ID
+ prefabInstance.IdsMapping[prefabObjectId] = id;
+ goto NESTED_PREFAB_WALK;
+ }
+ }
}
}
diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h
index f7f495449..97cf6b9ce 100644
--- a/Source/Engine/Level/SceneObjectsFactory.h
+++ b/Source/Engine/Level/SceneObjectsFactory.h
@@ -100,7 +100,7 @@ public:
///
/// The serialization context.
/// The sync data.
- static void SetupPrefabInstances(Context& context, PrefabSyncData& data);
+ static void SetupPrefabInstances(Context& context, const PrefabSyncData& data);
///
/// Synchronizes the new prefab instances by spawning missing objects that were added to prefab but were not saved with scene objects collection.
diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp
index 80055c3a4..b05f0f7d3 100644
--- a/Source/Engine/Navigation/NavCrowd.cpp
+++ b/Source/Engine/Navigation/NavCrowd.cpp
@@ -3,6 +3,9 @@
#include "NavCrowd.h"
#include "NavMesh.h"
#include "NavMeshRuntime.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Level/Level.h"
+#include "Engine/Level/Scene/Scene.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include
@@ -26,6 +29,15 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMesh* navMesh)
bool NavCrowd::Init(const NavAgentProperties& agentProperties, int32 maxAgents)
{
NavMeshRuntime* navMeshRuntime = NavMeshRuntime::Get(agentProperties);
+#if !BUILD_RELEASE
+ if (!navMeshRuntime)
+ {
+ if (NavMeshRuntime::Get())
+ LOG(Error, "Cannot create crowd. Failed to find a navmesh that matches a given agent properties.");
+ else
+ LOG(Error, "Cannot create crowd. No navmesh is loaded.");
+ }
+#endif
return Init(agentProperties.Radius * 3.0f, maxAgents, navMeshRuntime);
}
@@ -33,6 +45,41 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe
{
if (!_crowd || !navMesh)
return true;
+
+ // This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh
+ if (navMesh->GetNavMesh() == nullptr)
+ {
+ PROFILE_CPU_NAMED("WaitForNavMesh");
+ if (IsInMainThread())
+ {
+ // Wait for any navmesh data load
+ for (const Scene* scene : Level::Scenes)
+ {
+ for (const NavMesh* actor : scene->Navigation.Meshes)
+ {
+ if (actor->DataAsset)
+ {
+ actor->DataAsset->WaitForLoaded();
+ if (navMesh->GetNavMesh())
+ break;
+ }
+ }
+ if (navMesh->GetNavMesh())
+ break;
+ }
+ }
+ else
+ {
+ while (navMesh->GetNavMesh() == nullptr)
+ Platform::Sleep(1);
+ }
+ if (navMesh->GetNavMesh() == nullptr)
+ {
+ LOG(Error, "Cannot create crowd. Navmesh is not yet laoded.");
+ return true;
+ }
+ }
+
return !_crowd->init(maxAgents, maxAgentRadius, navMesh->GetNavMesh());
}
@@ -48,7 +95,7 @@ Vector3 NavCrowd::GetAgentPosition(int32 id) const
{
Vector3 result = Vector3::Zero;
const dtCrowdAgent* agent = _crowd->getAgent(id);
- if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID)
+ if (agent)
{
result = Float3(agent->npos);
}
@@ -59,7 +106,7 @@ Vector3 NavCrowd::GetAgentVelocity(int32 id) const
{
Vector3 result = Vector3::Zero;
const dtCrowdAgent* agent = _crowd->getAgent(id);
- if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID)
+ if (agent)
{
result = Float3(agent->vel);
}
diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp
index 4d85238e3..1e9ea5b79 100644
--- a/Source/Engine/Networking/NetworkReplicator.cpp
+++ b/Source/Engine/Networking/NetworkReplicator.cpp
@@ -258,18 +258,32 @@ void NetworkReplicationService::Dispose()
NetworkReplicationService NetworkReplicationServiceInstance;
-void INetworkSerializable_Serialize(void* instance, NetworkStream* stream, void* tag)
+void INetworkSerializable_Native_Serialize(void* instance, NetworkStream* stream, void* tag)
{
const int16 vtableOffset = (int16)(intptr)tag;
((INetworkSerializable*)((byte*)instance + vtableOffset))->Serialize(stream);
}
-void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream, void* tag)
+void INetworkSerializable_Native_Deserialize(void* instance, NetworkStream* stream, void* tag)
{
const int16 vtableOffset = (int16)(intptr)tag;
((INetworkSerializable*)((byte*)instance + vtableOffset))->Deserialize(stream);
}
+void INetworkSerializable_Script_Serialize(void* instance, NetworkStream* stream, void* tag)
+{
+ auto obj = (ScriptingObject*)instance;
+ auto interface = ScriptingObject::ToInterface(obj);
+ interface->Serialize(stream);
+}
+
+void INetworkSerializable_Script_Deserialize(void* instance, NetworkStream* stream, void* tag)
+{
+ auto obj = (ScriptingObject*)instance;
+ auto interface = ScriptingObject::ToInterface(obj);
+ interface->Deserialize(stream);
+}
+
NetworkReplicatedObject* ResolveObject(Guid objectId)
{
auto it = Objects.Find(objectId);
@@ -1064,9 +1078,21 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle,
const ScriptingType::InterfaceImplementation* interface = type.GetInterface(INetworkSerializable::TypeInitializer);
if (interface)
{
- serializer.Methods[0] = INetworkSerializable_Serialize;
- serializer.Methods[1] = INetworkSerializable_Deserialize;
- serializer.Tags[0] = serializer.Tags[1] = (void*)(intptr)interface->VTableOffset; // Pass VTableOffset to the callback
+ if (interface->IsNative)
+ {
+ // Native interface (implemented in C++)
+ serializer.Methods[0] = INetworkSerializable_Native_Serialize;
+ serializer.Methods[1] = INetworkSerializable_Native_Deserialize;
+ serializer.Tags[0] = serializer.Tags[1] = (void*)(intptr)interface->VTableOffset; // Pass VTableOffset to the callback
+ }
+ else
+ {
+ // Generic interface (implemented in C# or elsewhere)
+ ASSERT(type.Type == ScriptingTypes::Script);
+ serializer.Methods[0] = INetworkSerializable_Script_Serialize;
+ serializer.Methods[1] = INetworkSerializable_Script_Deserialize;
+ serializer.Tags[0] = serializer.Tags[1] = nullptr;
+ }
SerializersTable.Add(typeHandle, serializer);
}
else if (const ScriptingTypeHandle baseTypeHandle = typeHandle.GetType().GetBaseType())
diff --git a/Source/Engine/Online/Online.cpp b/Source/Engine/Online/Online.cpp
index 313497f98..bb79c6d60 100644
--- a/Source/Engine/Online/Online.cpp
+++ b/Source/Engine/Online/Online.cpp
@@ -10,7 +10,7 @@ class OnlineService : public EngineService
{
public:
OnlineService()
- : EngineService(TEXT("Online"), 100)
+ : EngineService(TEXT("Online"), 500)
{
}
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
index 9d91d0013..cb2d7004e 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
@@ -7,7 +7,7 @@
#include "Engine/Graphics/RenderTask.h"
#define GET_VIEW() auto mainViewTask = MainRenderTask::Instance && MainRenderTask::Instance->LastUsedFrame != 0 ? MainRenderTask::Instance : nullptr
-#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[node->Attributes[index]].Offset)
+#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[context.AttributesRemappingTable[node->Attributes[index]]].Offset)
#define GET_PARTICLE_ATTRIBUTE(index, type) *(type*)ACCESS_PARTICLE_ATTRIBUTE(index)
void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
@@ -436,9 +436,19 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node
auto* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]];
Box* functionOutputBox = functionOutputNode->TryGetBox(0);
+ // Setup particle attributes remapping (so particle data access nodes inside the function will read data at proper offset, see macro ACCESS_PARTICLE_ATTRIBUTE)
+ byte attributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT];
+ Platform::MemoryCopy(attributesRemappingTable, context.AttributesRemappingTable, sizeof(attributesRemappingTable));
+ for (int32 i = 0; i < graph->Layout.Attributes.Count(); i++)
+ {
+ const ParticleAttribute& e = graph->Layout.Attributes[i];
+ context.AttributesRemappingTable[i] = context.Data->Buffer->Layout->FindAttribute(e.Name, e.ValueType);
+ }
+
// Evaluate the function output
context.GraphStack.Push(graph);
value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero;
+ Platform::MemoryCopy(context.AttributesRemappingTable, attributesRemappingTable, sizeof(attributesRemappingTable));
context.GraphStack.Pop();
break;
}
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
index d86bc6d54..a8f7898e1 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
@@ -133,6 +133,8 @@ void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEff
context.ViewTask = effect->GetRenderTask();
context.CallStackSize = 0;
context.Functions.Clear();
+ for (int32 i = 0; i < PARTICLE_ATTRIBUTES_MAX_COUNT; i++)
+ context.AttributesRemappingTable[i] = i;
}
bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, BoundingBox& result)
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
index 9d1d48c81..4f55da6e2 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
@@ -119,6 +119,7 @@ struct ParticleEmitterGraphCPUContext
class SceneRenderTask* ViewTask;
Array> GraphStack;
Dictionary Functions;
+ byte AttributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT]; // Maps node attribute indices to the current particle layout (used to support accessing particle data from function graph which has different layout).
int32 CallStackSize = 0;
VisjectExecutor::Node* CallStack[PARTICLE_EMITTER_MAX_CALL_STACK];
};
diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
index 32f6b1e23..57ffa251a 100644
--- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
+++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
@@ -6,6 +6,7 @@
#include "Engine/Particles/ParticleEmitter.h"
#include "Engine/Particles/ParticleEffect.h"
#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/GPUContext.h"
@@ -168,7 +169,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic
if (viewTask)
{
bindMeta.Buffers = viewTask->Buffers;
- bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = true;
+ bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = bindMeta.Buffers && bindMeta.Buffers->GetWidth() != 0;
}
else
{
@@ -179,7 +180,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic
for (int32 i = 0; i < data.Parameters.Count(); i++)
{
// Copy instance parameters values
- _params[i].SetValue(data.Parameters[i]);
+ _params[i].SetValue(data.Parameters.Get()[i]);
}
MaterialParamsLink link;
link.This = &_params;
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp
index de142827c..22512481b 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp
@@ -40,7 +40,7 @@ namespace
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttribute::ValueTypes valueType, AccessMode mode)
{
// Find this attribute
- return AccessParticleAttribute(caller, ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute(name, valueType), mode);
+ return AccessParticleAttribute(caller, GetRootGraph()->Layout.FindAttribute(name, valueType), mode);
}
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, int32 index, AccessMode mode)
@@ -55,7 +55,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt
if (value.Variable.Type != VariantType::Null)
return value.Variable;
- auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[index];
+ auto& attribute = GetRootGraph()->Layout.Attributes[index];
const VariantType::Types type = GetValueType(attribute.ValueType);
// Generate local variable name that matches the attribute name for easier shader source debugging
@@ -77,7 +77,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt
else if (_contextType == ParticleContextType::Initialize)
{
// Initialize with default value
- const Value defaultValue(((ParticleEmitterGraphGPU*)_graphStack.Peek())->AttributesDefaults[index]);
+ const Value defaultValue(GetRootGraph()->AttributesDefaults[index]);
value.Variable = writeLocal(type, defaultValue.Value, caller, localName);
}
else
@@ -251,10 +251,10 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
{
const Char* format;
auto valueType = static_cast(node->Values[1].AsInt);
- const int32 attributeIndex = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute((StringView)node->Values[0], valueType);
+ const int32 attributeIndex = GetRootGraph()->Layout.FindAttribute((StringView)node->Values[0], valueType);
if (attributeIndex == -1)
return;
- auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[attributeIndex];
+ auto& attribute = GetRootGraph()->Layout.Attributes[attributeIndex];
const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("context.ParticleIndex"))), VariantType::Uint);
switch (valueType)
{
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
index 513252e80..024823293 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
@@ -30,10 +30,10 @@ void ParticleEmitterGraphGPU::ClearCache()
{
for (int32 i = 0; i < Nodes.Count(); i++)
{
- auto& node = Nodes[i];
+ ParticleEmitterGraphGPUNode& node = Nodes.Get()[i];
for (int32 j = 0; j < node.Boxes.Count(); j++)
{
- node.Boxes[j].Cache.Clear();
+ node.Boxes.Get()[j].Cache.Clear();
}
}
}
@@ -86,9 +86,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer&
auto& layout = baseGraph->Layout;
_attributeValues.Resize(layout.Attributes.Count());
for (int32 i = 0; i < _attributeValues.Count(); i++)
- {
_attributeValues[i] = AttributeCache();
- }
_contextUsesKill = false;
// Cache attributes
@@ -112,7 +110,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer&
for (int32 i = 0; i < baseGraph->InitModules.Count(); i++)
{
- ProcessModule(baseGraph->InitModules[i]);
+ ProcessModule(baseGraph->InitModules.Get()[i]);
}
WriteParticleAttributesWrites();
@@ -135,7 +133,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer&
for (int32 i = 0; i < baseGraph->UpdateModules.Count(); i++)
{
- ProcessModule(baseGraph->UpdateModules[i]);
+ ProcessModule(baseGraph->UpdateModules.Get()[i]);
}
// Dead particles removal
@@ -321,22 +319,22 @@ void ParticleEmitterGPUGenerator::clearCache()
// Reset cached boxes values
for (int32 i = 0; i < _graphs.Count(); i++)
{
- _graphs[i]->ClearCache();
+ _graphs.Get()[i]->ClearCache();
}
- for (auto& e : _functions)
+ for (const auto& e : _functions)
{
- for (auto& node : e.Value->Nodes)
+ auto& nodes = ((ParticleEmitterGraphGPU*)e.Value)->Nodes;
+ for (int32 i = 0; i < nodes.Count(); i++)
{
+ ParticleEmitterGraphGPUNode& node = nodes.Get()[i];
for (int32 j = 0; j < node.Boxes.Count(); j++)
- node.Boxes[j].Cache.Clear();
+ node.Boxes.Get()[j].Cache.Clear();
}
}
// Reset cached attributes
for (int32 i = 0; i < _attributeValues.Count(); i++)
- {
- _attributeValues[i] = AttributeCache();
- }
+ _attributeValues.Get()[i] = AttributeCache();
_contextUsesKill = false;
}
@@ -344,10 +342,11 @@ void ParticleEmitterGPUGenerator::clearCache()
void ParticleEmitterGPUGenerator::WriteParticleAttributesWrites()
{
bool hadAnyWrite = false;
+ ParticleEmitterGraphGPU* graph = GetRootGraph();
for (int32 i = 0; i < _attributeValues.Count(); i++)
{
auto& value = _attributeValues[i];
- auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[i];
+ auto& attribute = graph->Layout.Attributes[i];
// Skip not used attributes or read-only attributes
if (value.Variable.Type == VariantType::Null || ((int)value.Access & (int)AccessMode::Write) == 0)
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
index 82f9c2c93..84834a6e5 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
@@ -94,7 +94,6 @@ public:
///
/// Gets the root graph.
///
- /// The base graph.
FORCE_INLINE ParticleEmitterGraphGPU* GetRootGraph() const
{
return _graphs.First();
@@ -154,12 +153,12 @@ private:
bool IsLocalSimulationSpace() const
{
- return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local;
+ return GetRootGraph()->SimulationSpace == ParticlesSimulationSpace::Local;
}
bool IsWorldSimulationSpace() const
{
- return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::World;
+ return GetRootGraph()->SimulationSpace == ParticlesSimulationSpace::World;
}
};
diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
index f5946c226..91dae0ed2 100644
--- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
+++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
@@ -37,12 +37,12 @@ public:
///
/// Flag valid for used particle nodes that need per-particle data to evaluate its value (including dependant nodes linked to input boxes). Used to skip per-particle graph evaluation if graph uses the same value for all particles (eg. is not using per-particle seed or position node).
///
- bool UsesParticleData;
+ bool UsesParticleData = false;
///
/// Flag valid for used particle nodes that result in constant data (nothing random nor particle data).
///
- bool IsConstant;
+ bool IsConstant = true;
///
/// The cached particle attribute indices used by the simulation graph to access particle properties.
@@ -50,6 +50,8 @@ public:
int32 Attributes[PARTICLE_EMITTER_MAX_ATTRIBUTES_REFS_PER_NODE];
};
+void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference& asset, bool& usesParticleData, ParticleLayout& layout);
+
///
/// The Particle Emitter Graph used to simulate particles.
///
@@ -157,8 +159,6 @@ public:
if (node->Used)
return;
node->Used = true;
- node->UsesParticleData = false;
- node->IsConstant = true;
#define USE_ATTRIBUTE(name, valueType, slot) \
{ \
@@ -292,14 +292,11 @@ public:
case GRAPH_NODE_MAKE_TYPE(14, 214):
case GRAPH_NODE_MAKE_TYPE(14, 215):
case GRAPH_NODE_MAKE_TYPE(14, 216):
- {
node->IsConstant = false;
break;
- }
// Particle Emitter Function
case GRAPH_NODE_MAKE_TYPE(14, 300):
- node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]);
- node->UsesParticleData = true; // TODO: analyze emitter function graph to detect if it's actually using any particle data at all (even from inputs after inline)
+ InitParticleEmitterFunctionCall((Guid)node->Values[0], node->Assets[0], node->UsesParticleData, Layout);
break;
// Particle Index
case GRAPH_NODE_MAKE_TYPE(14, 301):
@@ -564,7 +561,7 @@ public:
return true;
// Compute particle data layout and initialize used nodes (for only used nodes, start depth searching rom the modules)
- Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Float3);
+ //Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Float3);
#define PROCESS_MODULES(modules) for (int32 i = 0; i < modules.Count(); i++) { modules[i]->Used = false; InitializeNode(modules[i]); }
PROCESS_MODULES(SpawnModules);
PROCESS_MODULES(InitModules);
diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp
index 20e4c0490..ea1161c6b 100644
--- a/Source/Engine/Particles/ParticleEmitter.cpp
+++ b/Source/Engine/Particles/ParticleEmitter.cpp
@@ -19,6 +19,7 @@
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
#if BUILD_DEBUG
#include "Engine/Engine/Globals.h"
+#include "Engine/Scripting/BinaryModule.h"
#endif
#endif
#if COMPILE_WITH_GPU_PARTICLES
@@ -185,7 +186,9 @@ Asset::LoadResult ParticleEmitter::load()
#if BUILD_DEBUG && USE_EDITOR
// Dump generated shader source to the temporary file
+ BinaryModule::Locker.Lock();
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("particle_emitter.txt"));
+ BinaryModule::Locker.Unlock();
#endif
// Encrypt source code
diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp
index c91bc0d82..5abb4a434 100644
--- a/Source/Engine/Particles/ParticleEmitterFunction.cpp
+++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp
@@ -9,6 +9,27 @@
#endif
#include "Engine/Content/Factories/BinaryAssetFactory.h"
+void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference& asset, bool& usesParticleData, ParticleLayout& layout)
+{
+ const auto function = Content::Load(assetId);
+ asset = function;
+ if (function)
+ {
+ // Insert any used particle data into the calling graph
+ for (const ParticleAttribute& e : function->Graph.Layout.Attributes)
+ {
+ if (layout.FindAttribute(e.Name, e.ValueType) == -1)
+ layout.AddAttribute(e.Name, e.ValueType);
+ }
+
+ // Detect if function needs to be evaluated per-particle
+ for (int32 i = 0; i < function->Outputs.Count() && !usesParticleData; i++)
+ {
+ usesParticleData = function->Graph.Nodes[function->Outputs.Get()[i]].UsesParticleData;
+ }
+ }
+}
+
REGISTER_BINARY_ASSET(ParticleEmitterFunction, "FlaxEngine.ParticleEmitterFunction", false);
ParticleEmitterFunction::ParticleEmitterFunction(const SpawnParams& params, const AssetInfo* info)
diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp
index dc2c42584..4c8cffdcc 100644
--- a/Source/Engine/Physics/CollisionCooking.cpp
+++ b/Source/Engine/Physics/CollisionCooking.cpp
@@ -127,6 +127,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali
const auto& mesh = *meshes[i];
if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0)
continue;
+ if (mesh.GetVertexCount() == 0)
+ continue;
int32 count;
if (mesh.DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffers[i], count))
@@ -159,6 +161,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali
const auto& mesh = *meshes[i];
if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0)
continue;
+ if (mesh.GetVertexCount() == 0)
+ continue;
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, vertexBuffers[i]);
if (task == nullptr)
@@ -208,6 +212,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali
const int32 firstVertexIndex = vertexCounter;
const int32 vertexCount = vertexCounts[i];
+ if (vertexCount == 0)
+ continue;
Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, vData.Get(), vertexCount * sizeof(Float3));
vertexCounter += vertexCount;
diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp
index 36f28c111..54a49671d 100644
--- a/Source/Engine/Physics/CollisionData.cpp
+++ b/Source/Engine/Physics/CollisionData.cpp
@@ -23,12 +23,16 @@ CollisionData::CollisionData(const SpawnParams& params, const AssetInfo* info)
bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
- // Validate state
if (!IsVirtual())
{
LOG(Warning, "Only virtual assets can be modified at runtime.");
return true;
}
+ if (IsInMainThread() && modelObj && modelObj->IsVirtual())
+ {
+ LOG(Error, "Cannot cook collision data for virtual models on a main thread (virtual models data is stored on GPU only). Use thread pool or async task.");
+ return true;
+ }
// Prepare
CollisionCooking::Argument arg;
@@ -43,18 +47,12 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i
SerializedOptions options;
BytesContainer outputData;
if (CollisionCooking::CookCollision(arg, options, outputData))
- {
return true;
- }
-
- // Clear state
- unload(true);
// Load data
+ unload(true);
if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok)
- {
return true;
- }
// Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation)
onLoaded();
diff --git a/Source/Engine/Physics/CollisionData.cs b/Source/Engine/Physics/CollisionData.cs
index 8b54c6218..e7f76a90f 100644
--- a/Source/Engine/Physics/CollisionData.cs
+++ b/Source/Engine/Physics/CollisionData.cs
@@ -19,7 +19,7 @@ namespace FlaxEngine
/// The convex mesh generation flags.
/// The convex mesh vertex limit. Use values in range [8;255]
/// True if failed, otherwise false.
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public bool CookCollision(CollisionDataType type, Vector3[] vertices, uint[] triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
{
if (vertices == null)
@@ -43,7 +43,7 @@ namespace FlaxEngine
/// The convex mesh generation flags.
/// The convex mesh vertex limit. Use values in range [8;255]
/// True if failed, otherwise false.
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public bool CookCollision(CollisionDataType type, Vector3[] vertices, int[] triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
{
if (vertices == null)
@@ -60,7 +60,7 @@ namespace FlaxEngine
///
/// The output vertex buffer.
/// The output index buffer.
- [Obsolete("Deprecated in 1.4")]
+ [Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void ExtractGeometry(out Vector3[] vertexBuffer, out int[] indexBuffer)
{
ExtractGeometry(out Float3[] tmp, out indexBuffer);
diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
index 7c9355099..684aeca19 100644
--- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
@@ -132,7 +132,8 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
const PxReal* impulses = pair.contactImpulses;
//const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED);
- const PxU32 hasImpulses = (pair.flags & PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
+ const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
+ const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH);
PxU32 nbContacts = 0;
PxVec3 totalImpulse(0.0f);
@@ -166,7 +167,8 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
}
// Extract velocities
- if (j.nextItemSet())
+ c.ThisVelocity = c.OtherVelocity = Vector3::Zero;
+ if (hasPostVelocities && j.nextItemSet())
{
ASSERT(j.contactPairIndex == pairIndex);
if (j.postSolverVelocity)
@@ -177,10 +179,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
c.ThisVelocity = P2C(linearVelocityActor0);
c.OtherVelocity = P2C(linearVelocityActor1);
}
- else
- {
- c.ThisVelocity = c.OtherVelocity = Vector3::Zero;
- }
}
c.ContactsCount = nbContacts;
@@ -195,6 +193,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
RemovedCollisions.Add(c);
}
}
+ ASSERT(!j.nextItemSet());
}
void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count)
diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp
index 78fa8953d..a04fc0229 100644
--- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp
+++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp
@@ -7,6 +7,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp
index cc18fc3ed..9e306019a 100644
--- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp
+++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp
@@ -8,6 +8,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Log.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Engine/Platform/Base/FileBase.h b/Source/Engine/Platform/Base/FileBase.h
index c6f76c610..5587e20a9 100644
--- a/Source/Engine/Platform/Base/FileBase.h
+++ b/Source/Engine/Platform/Base/FileBase.h
@@ -7,7 +7,6 @@
#include "Engine/Core/NonCopyable.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Types/DateTime.h"
-#include "Engine/Core/Collections/Array.h"
class StringBuilder;
template
@@ -16,17 +15,51 @@ class DataContainer;
///
/// Specifies how the operating system should open a file.
///
-DECLARE_ENUM_FLAGS_5(FileMode, uint32, CreateAlways, 2, CreateNew, 1, OpenAlways, 4, OpenExisting, 3, TruncateExisting, 5);
+enum class FileMode : uint32
+{
+ // Creates a new file, only if it does not already exist.
+ CreateNew = 1,
+ // Creates a new file, always.
+ CreateAlways = 2,
+ // Opens a file, only if it exists. Fails if file doesn't exist.
+ OpenExisting = 3,
+ // Opens a file, always.
+ OpenAlways = 4,
+ // Opens a file and truncates it so that its size is zero bytes, only if it exists. Fails if file doesn't exist.
+ TruncateExisting = 5,
+};
///
/// Defines constants for read, write, or read/write access to a file.
///
-DECLARE_ENUM_FLAGS_3(FileAccess, uint32, Read, 0x80000000, Write, 0x40000000, ReadWrite, (uint32)FileAccess::Read | (uint32)FileAccess::Write);
+enum class FileAccess : uint32
+{
+ // Enables reading data from the file.
+ Read = 0x80000000,
+ // Enables writing data to the file.
+ Write = 0x40000000,
+ // Enables both data read and write operations on the file.
+ ReadWrite = Read | Write,
+};
///
/// Contains constants for controlling the kind of access other objects can have to the same file.
///
-DECLARE_ENUM_FLAGS_6(FileShare, uint32, Delete, 0x00000004, None, 0x00000000, Read, 0x00000001, Write, 0x00000002, ReadWrite, (uint32)FileShare::Read | (uint32)FileShare::Write, All, (uint32)FileShare::ReadWrite | (uint32)FileShare::Delete);
+enum class FileShare : uint32
+{
+ // Prevents any operations on the file file it's opened.
+ None = 0x00000000,
+ // Allows read operations on a file.
+ Read = 0x00000001,
+ // Allows write operations on a file.
+ Write = 0x00000002,
+ // Allows delete operations on a file.
+ Delete = 0x00000004,
+ // Allows read and write operations on a file.
+ ReadWrite = Read | Write,
+ // Allows any operations on a file.
+ All = ReadWrite | Delete,
+};
///
/// The base class for file objects.
@@ -34,7 +67,6 @@ DECLARE_ENUM_FLAGS_6(FileShare, uint32, Delete, 0x00000004, None, 0x00000000, Re
class FLAXENGINE_API FileBase : public NonCopyable
{
public:
-
///
/// Finalizes an instance of the class.
///
@@ -43,7 +75,6 @@ public:
}
public:
-
///
/// Reads data from a file.
///
@@ -68,7 +99,6 @@ public:
virtual void Close() = 0;
public:
-
///
/// Gets size of the file (in bytes).
///
@@ -100,14 +130,13 @@ public:
virtual bool IsOpened() const = 0;
public:
-
static bool ReadAllBytes(const StringView& path, byte* data, int32 length);
- static bool ReadAllBytes(const StringView& path, Array& data);
+ static bool ReadAllBytes(const StringView& path, Array& data);
static bool ReadAllBytes(const StringView& path, DataContainer& data);
static bool ReadAllText(const StringView& path, String& data);
static bool ReadAllText(const StringView& path, StringAnsi& data);
static bool WriteAllBytes(const StringView& path, const byte* data, int32 length);
- static bool WriteAllBytes(const StringView& path, const Array& data);
+ static bool WriteAllBytes(const StringView& path, const Array& data);
static bool WriteAllText(const StringView& path, const String& data, Encoding encoding);
static bool WriteAllText(const StringView& path, const StringBuilder& data, Encoding encoding);
static bool WriteAllText(const StringView& path, const Char* data, int32 length, Encoding encoding);
diff --git a/Source/Engine/Platform/Base/FileSystemBase.cpp b/Source/Engine/Platform/Base/FileSystemBase.cpp
index f2f45a6d4..3d0c053d5 100644
--- a/Source/Engine/Platform/Base/FileSystemBase.cpp
+++ b/Source/Engine/Platform/Base/FileSystemBase.cpp
@@ -5,6 +5,7 @@
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Engine/Globals.h"
diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
index 279813470..8da3a378b 100644
--- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
+++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Log.h"
#include "Engine/Utilities/StringConverter.h"
@@ -69,7 +70,30 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView&
}
FILE* f = popen(cmd, "r");
char buf[2048];
- fgets(buf, ARRAY_COUNT(buf), f);
+ char* writePointer = buf;
+ int remainingCapacity = ARRAY_COUNT(buf);
+ // make sure we read all output from kdialog
+ while (remainingCapacity > 0 && fgets(writePointer, remainingCapacity, f))
+ {
+ int r = strlen(writePointer);
+ writePointer += r;
+ remainingCapacity -= r;
+ }
+ if (remainingCapacity <= 0)
+ {
+ LOG(Error, "You selected more files than an internal buffer can hold. Try selecting fewer files at a time.");
+ // in case of an overflow we miss the closing null byte, add it after the rightmost linefeed
+ while (*writePointer != '\n')
+ {
+ writePointer--;
+ if (writePointer == buf)
+ {
+ *buf = 0;
+ break;
+ }
+ }
+ *(++writePointer) = 0;
+ }
int result = pclose(f);
if (result != 0)
{
diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp
index c765feef4..e2ed24f13 100644
--- a/Source/Engine/Platform/Mac/MacFileSystem.cpp
+++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Log.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
index 38384f05f..d07c333e0 100644
--- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
+++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
@@ -8,6 +8,7 @@
#include "Engine/Platform/Windows/ComPtr.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Core/Types/StringView.h"
+#include "Engine/Core/Collections/Array.h"
#include "../Win32/IncludeWindowsHeaders.h"
// Hack this stuff (the problem is that GDI has function named Rectangle -> like one of the Flax core types)
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp
index 1bc50f207..ff65e9864 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.cpp
+++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp
@@ -108,8 +108,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP;
#elif WINDOWS_USE_NEWER_BORDER_LESS
if (settings.IsRegularWindow)
- style |= WS_THICKFRAME | WS_SYSMENU;
- style |= WS_CAPTION;
+ style |= WS_THICKFRAME | WS_SYSMENU | WS_CAPTION;
#endif
exStyle |= WS_EX_WINDOWEDGE;
}
@@ -220,12 +219,6 @@ void WindowsWindow::Show()
if (!_settings.HasBorder)
{
SetWindowPos(_handle, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER);
- if (!_settings.IsRegularWindow && _settings.ShowAfterFirstPaint && _settings.StartPosition == WindowStartPosition::Manual)
- {
- int32 x = Math::TruncToInt(_settings.Position.X);
- int32 y = Math::TruncToInt(_settings.Position.Y);
- SetWindowPos(_handle, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
- }
}
#endif
@@ -539,6 +532,12 @@ Float2 WindowsWindow::ClientToScreen(const Float2& clientPos) const
{
ASSERT(HasHWND());
+ if (_minimized)
+ {
+ // Return cached position when window is not on screen
+ return _minimizedScreenPosition + clientPos;
+ }
+
POINT p;
p.x = static_cast(clientPos.X);
p.y = static_cast(clientPos.Y);
@@ -1116,6 +1115,28 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
if (SIZE_MINIMIZED == wParam)
{
+ // Get the minimized window position in workspace coordinates
+ WINDOWPLACEMENT placement;
+ placement.length = sizeof(WINDOWPLACEMENT);
+ GetWindowPlacement(_handle, &placement);
+
+ // Calculate client offsets from window borders and title bar
+ RECT winRect = { 0, 0, static_cast(_clientSize.X), static_cast(_clientSize.Y) };
+ LONG style = GetWindowLong(_handle, GWL_STYLE);
+ LONG exStyle = GetWindowLong(_handle, GWL_EXSTYLE);
+ AdjustWindowRectEx(&winRect, style, FALSE, exStyle);
+
+ // Calculate monitor offsets from taskbar position
+ const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO monitorInfo;
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoW(monitor, &monitorInfo);
+
+ // Convert the workspace coordinates to screen space and store it
+ _minimizedScreenPosition = Float2(
+ static_cast(placement.rcNormalPosition.left + monitorInfo.rcWork.left - monitorInfo.rcMonitor.left - winRect.left),
+ static_cast(placement.rcNormalPosition.top + monitorInfo.rcWork.top - monitorInfo.rcMonitor.top - winRect.top));
+
_minimized = true;
_maximized = false;
}
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h
index e15f86a45..186793b1e 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.h
+++ b/Source/Engine/Platform/Windows/WindowsWindow.h
@@ -32,6 +32,7 @@ private:
Windows::HANDLE _monitor = nullptr;
Windows::LONG _clipCursorRect[4];
int32 _regionWidth = 0, _regionHeight = 0;
+ Float2 _minimizedScreenPosition = Float2::Zero;
public:
diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp
index 3054abe67..6944441be 100644
--- a/Source/Engine/Profiler/ProfilerGPU.cpp
+++ b/Source/Engine/Profiler/ProfilerGPU.cpp
@@ -74,6 +74,7 @@ void ProfilerGPU::EventBuffer::Clear()
_data.Clear();
_isResolved = false;
FrameIndex = 0;
+ PresentTime = 0.0f;
}
GPUTimerQuery* ProfilerGPU::GetTimerQuery()
@@ -133,7 +134,9 @@ void ProfilerGPU::BeginFrame()
// Clear stats
RenderStatsData::Counter = RenderStatsData();
_depth = 0;
- Buffers[CurrentBuffer].FrameIndex = Engine::FrameCount;
+ auto& buffer = Buffers[CurrentBuffer];
+ buffer.FrameIndex = Engine::FrameCount;
+ buffer.PresentTime = 0.0f;
// Try to resolve previous frames
for (int32 i = 0; i < PROFILER_GPU_EVENTS_FRAMES; i++)
@@ -149,6 +152,12 @@ void ProfilerGPU::OnPresent()
buffer.EndAll();
}
+void ProfilerGPU::OnPresentTime(float time)
+{
+ auto& buffer = Buffers[CurrentBuffer];
+ buffer.PresentTime += time;
+}
+
void ProfilerGPU::EndFrame()
{
if (_depth)
@@ -164,7 +173,7 @@ void ProfilerGPU::EndFrame()
buffer.Clear();
}
-bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData)
+bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData)
{
uint64 maxFrame = 0;
int32 maxFrameIndex = -1;
@@ -177,17 +186,19 @@ bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData
maxFrameIndex = i;
}
}
-
if (maxFrameIndex != -1)
{
auto& frame = frames[maxFrameIndex];
const auto root = frame.Get(0);
drawTimeMs = root->Time;
+ presentTimeMs = frame.PresentTime;
statsData = root->Stats;
return true;
}
+ // No data
drawTimeMs = 0.0f;
+ presentTimeMs = 0.0f;
Platform::MemoryClear(&statsData, sizeof(statsData));
return false;
}
diff --git a/Source/Engine/Profiler/ProfilerGPU.h b/Source/Engine/Profiler/ProfilerGPU.h
index 29392b7a5..94ed345b9 100644
--- a/Source/Engine/Profiler/ProfilerGPU.h
+++ b/Source/Engine/Profiler/ProfilerGPU.h
@@ -20,6 +20,8 @@ class GPUTimerQuery;
API_CLASS(Static) class FLAXENGINE_API ProfilerGPU
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ProfilerGPU);
+ friend class Engine;
+ friend class GPUDevice;
public:
///
/// Represents single CPU profiling event data.
@@ -69,6 +71,11 @@ public:
///
uint64 FrameIndex;
+ ///
+ /// Sum of all present events duration on CPU during this frame (in milliseconds).
+ ///
+ float PresentTime;
+
///
/// Determines whether this buffer has ready data (resolved and not empty).
///
@@ -125,7 +132,7 @@ public:
///
/// True if GPU profiling is enabled, otherwise false to disable events collecting and GPU timer queries usage. Can be changed during rendering.
///
- static bool Enabled;
+ API_FIELD() static bool Enabled;
///
/// The current frame buffer to collect events.
@@ -151,32 +158,20 @@ public:
/// The event token index returned by the BeginEvent method.
static void EndEvent(int32 index);
- ///
- /// Begins the new frame rendering. Called by the engine to sync profiling data.
- ///
- static void BeginFrame();
-
- ///
- /// Called when just before flushing current frame GPU commands (via Present or Flush). Call active timer queries should be ended now.
- ///
- static void OnPresent();
-
- ///
- /// Ends the frame rendering. Called by the engine to sync profiling data.
- ///
- static void EndFrame();
-
///
/// Tries to get the rendering stats from the last frame drawing (that has been resolved and has valid data).
///
/// The draw execution time on a GPU (in milliseconds).
+ /// The final frame present time on a CPU (in milliseconds). Time game waited for vsync or GPU to finish previous frame rendering.
/// The rendering stats data.
/// True if got the data, otherwise false.
- static bool GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData);
+ API_FUNCTION() static bool GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData);
- ///
- /// Releases resources. Calls to the profiling API after Dispose are not valid
- ///
+private:
+ static void BeginFrame();
+ static void OnPresent();
+ static void OnPresentTime(float time);
+ static void EndFrame();
static void Dispose();
};
diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp
index 61c0e2c33..113a04ec8 100644
--- a/Source/Engine/Profiler/ProfilingTools.cpp
+++ b/Source/Engine/Profiler/ProfilingTools.cpp
@@ -48,7 +48,9 @@ void ProfilingToolsService::Update()
stats.PhysicsTimeMs = static_cast(Time::Physics.LastLength * 1000.0);
stats.DrawCPUTimeMs = static_cast(Time::Draw.LastLength * 1000.0);
- ProfilerGPU::GetLastFrameData(stats.DrawGPUTimeMs, stats.DrawStats);
+ float presentTime;
+ ProfilerGPU::GetLastFrameData(stats.DrawGPUTimeMs, presentTime, stats.DrawStats);
+ stats.DrawCPUTimeMs = Math::Max(stats.DrawCPUTimeMs - presentTime, 0.0f); // Remove swapchain present wait time to exclude from drawing on CPU
}
// Extract CPU profiler events
diff --git a/Source/Engine/Render2D/SpriteAtlas.cpp b/Source/Engine/Render2D/SpriteAtlas.cpp
index 0e0aa071f..ac711b8b8 100644
--- a/Source/Engine/Render2D/SpriteAtlas.cpp
+++ b/Source/Engine/Render2D/SpriteAtlas.cpp
@@ -47,10 +47,16 @@ int32 SpriteAtlas::GetSpritesCount() const
Sprite SpriteAtlas::GetSprite(int32 index) const
{
- CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite())
+ CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite());
return Sprites.Get()[index];
}
+void SpriteAtlas::GetSpriteArea(int32 index, Rectangle& result) const
+{
+ CHECK(index >= 0 && index < Sprites.Count());
+ result = Sprites.Get()[index].Area;
+}
+
void SpriteAtlas::SetSprite(int32 index, const Sprite& value)
{
CHECK(index >= 0 && index < Sprites.Count());
diff --git a/Source/Engine/Render2D/SpriteAtlas.cs b/Source/Engine/Render2D/SpriteAtlas.cs
index b3796ffd3..93cdf68b2 100644
--- a/Source/Engine/Render2D/SpriteAtlas.cs
+++ b/Source/Engine/Render2D/SpriteAtlas.cs
@@ -70,7 +70,13 @@ namespace FlaxEngine
[NoSerialize]
public Float2 Size
{
- get => Area.Size * Atlas.Size;
+ get
+ {
+ if (Atlas == null)
+ throw new InvalidOperationException("Cannot use invalid sprite.");
+ Atlas.GetSpriteArea(Index, out var area);
+ return area.Size * Atlas.Size;
+ }
set
{
var area = Area;
@@ -89,7 +95,8 @@ namespace FlaxEngine
{
if (Atlas == null)
throw new InvalidOperationException("Cannot use invalid sprite.");
- return Atlas.GetSprite(Index).Area;
+ Atlas.GetSpriteArea(Index, out var area);
+ return area;
}
set
{
diff --git a/Source/Engine/Render2D/SpriteAtlas.h b/Source/Engine/Render2D/SpriteAtlas.h
index 6fcda5162..533440c68 100644
--- a/Source/Engine/Render2D/SpriteAtlas.h
+++ b/Source/Engine/Render2D/SpriteAtlas.h
@@ -120,6 +120,14 @@ public:
/// The sprite data.
API_FUNCTION() Sprite GetSprite(int32 index) const;
+ ///
+ /// Gets the sprite area.
+ ///
+ /// The index.
+ /// The output sprite area.
+ /// The sprite data.
+ API_FUNCTION() void GetSpriteArea(int32 index, API_PARAM(Out) Rectangle& result) const;
+
///
/// Sets the sprite data.
///
diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp
index e24022b07..a965dee09 100644
--- a/Source/Engine/Renderer/ProbesRenderer.cpp
+++ b/Source/Engine/Renderer/ProbesRenderer.cpp
@@ -105,7 +105,7 @@ class ProbesRendererService : public EngineService
{
public:
ProbesRendererService()
- : EngineService(TEXT("Probes Renderer"), 70)
+ : EngineService(TEXT("Probes Renderer"), 500)
{
}
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index b22effdc9..7e0cd1053 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -9,6 +9,7 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h"
+#include "Engine/Scripting/Enums.h"
#if USE_EDITOR
#include "Engine/Renderer/Lightmaps.h"
#endif
@@ -86,11 +87,12 @@ bool ShadowsPass::Init()
const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture);
_supportsShadows = EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D)
&& EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison);
+ // TODO: fallback to 32-bit shadow map format if 16-bit is not supported
if (!_supportsShadows)
{
LOG(Warning, "GPU doesn't support shadows rendering");
- LOG(Warning, "Format: {0}, features support: {1}", (int32)SHADOW_MAPS_FORMAT, (uint32)formatFeaturesDepth.Support);
- LOG(Warning, "Format: {0}, features support: {1}", (int32)formatTexture, (uint32)formatFeaturesTexture.Support);
+ LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(SHADOW_MAPS_FORMAT), (uint32)formatFeaturesDepth.Support);
+ LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(formatTexture), (uint32)formatFeaturesTexture.Support);
}
return false;
diff --git a/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp b/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp
index 27bb8aa47..e3e5b4342 100644
--- a/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp
+++ b/Source/Engine/Scripting/Internal/EngineInternalCalls.cpp
@@ -190,7 +190,6 @@ DEFINE_INTERNAL_CALL(bool) ScriptingInternal_IsTypeFromGameScripts(MTypeObject*
DEFINE_INTERNAL_CALL(void) ScriptingInternal_FlushRemovedObjects()
{
- ASSERT(IsInMainThread());
ObjectsRemovalService::Flush();
}
diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp
index 7cb4d1cfb..6d3508b3d 100644
--- a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp
+++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp
@@ -1,5 +1,8 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
#include "ManagedDictionary.h"
+#if USE_CSHARP
Dictionary ManagedDictionary::CachedDictionaryTypes;
#if !USE_MONO_AOT
ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType;
@@ -12,3 +15,4 @@ MMethod* ManagedDictionary::CreateInstance;
MMethod* ManagedDictionary::AddDictionaryItem;
MMethod* ManagedDictionary::GetDictionaryKeys;
#endif
+#endif
diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h
index 3862627d0..ff1645e5e 100644
--- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h
+++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h
@@ -25,8 +25,8 @@ private:
MonoAssembly* _monoAssembly = nullptr;
MonoImage* _monoImage = nullptr;
#elif USE_NETCORE
- StringAnsi _fullname;
void* _handle = nullptr;
+ StringAnsi _fullname;
#endif
MDomain* _domain;
@@ -50,6 +50,7 @@ public:
/// The assembly name.
MAssembly(MDomain* domain, const StringAnsiView& name);
+#if USE_NETCORE
///
/// Initializes a new instance of the class.
///
@@ -58,6 +59,7 @@ public:
/// The assembly full name.
/// The managed handle of the assembly.
MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle);
+#endif
///
/// Finalizes an instance of the class.
diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp
index d9cc6f863..02a834a9f 100644
--- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp
+++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp
@@ -48,18 +48,22 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name)
{
}
+#if USE_NETCORE
+
MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const StringAnsiView& fullname, void* handle)
- : _domain(domain)
+ : _handle(handle)
+ , _fullname(fullname)
+ , _domain(domain)
, _isLoaded(false)
, _isLoading(false)
, _hasCachedClasses(false)
, _reloadCount(0)
, _name(name)
- , _fullname(fullname)
- , _handle(handle)
{
}
+#endif
+
MAssembly::~MAssembly()
{
Unload();
diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp
index 328afa69c..a8198474c 100644
--- a/Source/Engine/Scripting/Plugins/PluginManager.cpp
+++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp
@@ -90,7 +90,7 @@ class PluginManagerService : public EngineService
{
public:
PluginManagerService()
- : EngineService(TEXT("Plugin Manager"), 130)
+ : EngineService(TEXT("Plugin Manager"), 100)
{
}
diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp
index 7f82f8dfc..b3e5a0072 100644
--- a/Source/Engine/Scripting/Runtime/DotNet.cpp
+++ b/Source/Engine/Scripting/Runtime/DotNet.cpp
@@ -41,12 +41,18 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
+#ifdef USE_MONO_AOT_MODULE
+void* MonoAotModuleHandle = nullptr;
+#endif
+MonoDomain* MonoDomainHandle = nullptr;
#else
#error "Unknown .NET runtime host."
#endif
@@ -180,7 +186,7 @@ void* GetStaticMethodPointer(const String& methodName);
/// Calls the managed static method in NativeInterop class with given parameters.
///
template
-inline RetType CallStaticMethodByName(const String& methodName, Args... args)
+FORCE_INLINE RetType CallStaticMethodByName(const String& methodName, Args... args)
{
typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
return ((fun)GetStaticMethodPointer(methodName))(args...);
@@ -190,16 +196,16 @@ inline RetType CallStaticMethodByName(const String& methodName, Args... args)
/// Calls the managed static method with given parameters.
///
template
-inline RetType CallStaticMethod(void* methodPtr, Args... args)
+FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args)
{
typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
return ((fun)methodPtr)(args...);
}
-void RegisterNativeLibrary(const char* moduleName, const char* modulePath)
+void RegisterNativeLibrary(const char* moduleName, const Char* modulePath)
{
static void* RegisterNativeLibraryPtr = GetStaticMethodPointer(TEXT("RegisterNativeLibrary"));
- CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath);
+ CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath);
}
bool InitHostfxr();
@@ -281,7 +287,7 @@ bool MCore::LoadEngine()
flaxLibraryPath = ::String(StringUtils::GetDirectoryName(Platform::GetExecutableFilePath())) / StringUtils::GetFileName(flaxLibraryPath);
}
#endif
- RegisterNativeLibrary("FlaxEngine", StringAnsi(flaxLibraryPath).Get());
+ RegisterNativeLibrary("FlaxEngine", flaxLibraryPath.Get());
MRootDomain = New("Root");
MDomains.Add(MRootDomain);
@@ -516,6 +522,12 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem)
void MCore::Thread::Attach()
{
+#if DOTNET_HOST_MONO
+ if (!IsInMainThread() && !mono_domain_get())
+ {
+ mono_thread_attach(MonoDomainHandle);
+ }
+#endif
}
void MCore::Thread::Exit()
@@ -617,14 +629,33 @@ bool MCore::Type::IsReference(MType* type)
void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id)
{
+#if PLATFORM_DESKTOP && !USE_MONO_AOT
static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues"));
CallStaticMethod(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id);
+#else
+ const MField* monoUnmanagedPtrField = klass->GetField("__unmanagedPtr");
+ if (monoUnmanagedPtrField)
+ monoUnmanagedPtrField->SetValue(object, &unmanagedPtr);
+ const MField* monoIdField = klass->GetField("__internalId");
+ if (id != nullptr && monoIdField)
+ monoIdField->SetValue(object, (void*)id);
+#endif
}
MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id)
{
+#if PLATFORM_DESKTOP && !USE_MONO_AOT
static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate"));
return CallStaticMethod(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id);
+#else
+ MObject* object = MCore::Object::New(klass);
+ if (object)
+ {
+ MCore::ScriptingObject::SetInternalValues(klass, object, unmanagedPtr, id);
+ MCore::Object::Init(object);
+ }
+ return object;
+#endif
}
const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
@@ -699,7 +730,6 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
StringAnsi assemblyName;
StringAnsi assemblyFullName;
GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName);
-
assembly = New(nullptr, assemblyName, assemblyFullName, assemblyHandle);
CachedAssemblyHandles.Add(assemblyHandle, assembly);
}
@@ -766,13 +796,13 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa
if (nativePath.HasChars())
{
StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name);
- RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get());
+ RegisterNativeLibrary(nativeName.Get(), nativePath.Get());
}
#if USE_EDITOR
// Register the editor module location for Assembly resolver
else
{
- RegisterNativeLibrary(_name.Get(), StringAnsi(assemblyPath).Get());
+ RegisterNativeLibrary(_name.Get(), assemblyPath.Get());
}
#endif
@@ -1192,9 +1222,9 @@ MException::~MException()
MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes)
: _handle(handle)
, _type(type)
+ , _fieldOffset(fieldOffset)
, _parentClass(parentClass)
, _name(name)
- , _fieldOffset(fieldOffset)
, _hasCachedAttributes(false)
{
switch (attributes & MFieldAttributes::FieldAccessMask)
@@ -1241,8 +1271,8 @@ void MField::GetValue(MObject* instance, void* result) const
void MField::GetValueReference(MObject* instance, void* result) const
{
- static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReferenceWithOffset"));
- CallStaticMethod(FieldGetValueReferencePtr, instance, _fieldOffset, result);
+ static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReference"));
+ CallStaticMethod(FieldGetValueReferencePtr, instance, _handle, _fieldOffset, result);
}
MObject* MField::GetValueBoxed(MObject* instance) const
@@ -1335,19 +1365,22 @@ MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 par
void MMethod::CacheSignature() const
{
- _hasCachedSignature = true;
+ ScopeLock lock(BinaryModule::Locker);
+ if (_hasCachedSignature)
+ return;
static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType"));
static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes"));
-
_returnType = CallStaticMethod(GetMethodReturnTypePtr, _handle);
+ if (_paramsCount != 0)
+ {
+ void** parameterTypeHandles;
+ CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
+ _parameterTypes.Set(parameterTypeHandles, _paramsCount);
+ MCore::GC::FreeMemory(parameterTypeHandles);
+ }
- if (_paramsCount == 0)
- return;
- void** parameterTypeHandles;
- CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
- _parameterTypes.Set(parameterTypeHandles, _paramsCount);
- MCore::GC::FreeMemory(parameterTypeHandles);
+ _hasCachedSignature = true;
}
MObject* MMethod::Invoke(void* instance, void** params, MObject** exception) const
@@ -1403,7 +1436,7 @@ MType* MMethod::GetParameterType(int32 paramIdx) const
if (!_hasCachedSignature)
CacheSignature();
ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
- return (MType*)_parameterTypes[paramIdx];
+ return (MType*)_parameterTypes.Get()[paramIdx];
}
bool MMethod::GetParameterIsOut(int32 paramIdx) const
@@ -1767,11 +1800,6 @@ void* GetStaticMethodPointer(const String& methodName)
#elif DOTNET_HOST_MONO
-#ifdef USE_MONO_AOT_MODULE
-void* MonoAotModuleHandle = nullptr;
-#endif
-MonoDomain* MonoDomainHandle = nullptr;
-
void OnLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData)
{
String currentDomain(logDomain);
diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp
index ef0c42818..00f16fd44 100644
--- a/Source/Engine/Scripting/Runtime/Mono.cpp
+++ b/Source/Engine/Scripting/Runtime/Mono.cpp
@@ -2157,7 +2157,7 @@ MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unma
if (managedInstance)
{
// Set unmanaged object handle and id
- MCore::ScriptingObject::SetInternalValues(klass, managedInstance, unmanagedPtr, _id);
+ MCore::ScriptingObject::SetInternalValues(klass, managedInstance, unmanagedPtr, id);
// Initialize managed instance (calls constructor)
MCore::Object::Init(managedInstance);
diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp
index a25d59c59..414a3e666 100644
--- a/Source/Engine/Scripting/Runtime/None.cpp
+++ b/Source/Engine/Scripting/Runtime/None.cpp
@@ -1,9 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Engine/Scripting/Types.h"
-
#if !USE_CSHARP
-
+#include "Engine/Core/Types/Span.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp
index cce66cd98..a57833314 100644
--- a/Source/Engine/Scripting/Scripting.cpp
+++ b/Source/Engine/Scripting/Scripting.cpp
@@ -474,28 +474,32 @@ bool Scripting::Load()
// Load FlaxEngine
const String flaxEnginePath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll");
auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine();
- if (flaxEngineModule->Assembly->Load(flaxEnginePath))
+ if (!flaxEngineModule->Assembly->IsLoaded())
{
- LOG(Error, "Failed to load FlaxEngine C# assembly.");
- return true;
- }
+ if (flaxEngineModule->Assembly->Load(flaxEnginePath))
+ {
+ LOG(Error, "Failed to load FlaxEngine C# assembly.");
+ return true;
+ }
+ onEngineLoaded(flaxEngineModule->Assembly);
- onEngineLoaded(flaxEngineModule->Assembly);
-
- // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
- // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename
+ // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
+ // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename
#if USE_LARGE_WORLDS
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"];
#else
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"];
#endif
- flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"];
- flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"];
- flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"];
+#if USE_CSHARP
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"];
+#endif
+ }
#if USE_EDITOR
// Skip loading game modules in Editor on startup - Editor loads them later during splash screen (eg. after first compilation)
diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs
index 8347fe7f8..67ba854e0 100644
--- a/Source/Engine/Scripting/Scripting.cs
+++ b/Source/Engine/Scripting/Scripting.cs
@@ -278,19 +278,30 @@ namespace FlaxEngine
BackgroundNormal = Color.FromBgra(0xFF3F3F46),
BorderNormal = Color.FromBgra(0xFF54545C),
TextBoxBackground = Color.FromBgra(0xFF333337),
- ProgressNormal = Color.FromBgra(0xFF0ad328),
TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46),
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
- SharedTooltip = new Tooltip(),
- Statusbar = new Style.StatusbarStyle()
+ ProgressNormal = Color.FromBgra(0xFF0ad328),
+ Statusbar = new Style.StatusbarStyle
{
PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424),
- Loading = Color.FromBgra(0xFF2D2D30)
- }
+ Loading = Color.FromBgra(0xFF2D2D30),
+ },
+
+ SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;
+ // Use optionally bundled default font (matches Editor)
+ var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular");
+ if (defaultFont)
+ {
+ style.FontTitle = defaultFont.CreateFont(18);
+ style.FontLarge = defaultFont.CreateFont(14);
+ style.FontMedium = defaultFont.CreateFont(9);
+ style.FontSmall = defaultFont.CreateFont(9);
+ }
+
Style.Current = style;
}
diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp
index c32f37066..59ac26484 100644
--- a/Source/Engine/Scripting/ScriptingObject.cpp
+++ b/Source/Engine/Scripting/ScriptingObject.cpp
@@ -6,6 +6,7 @@
#include "BinaryModule.h"
#include "Engine/Level/Actor.h"
#include "Engine/Core/Log.h"
+#include "Engine/Core/Types/Pair.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/Content.h"
@@ -25,7 +26,8 @@
#define ScriptingObject_id "__internalId"
// TODO: don't leak memory (use some kind of late manual GC for those wrapper objects)
-Dictionary ScriptingObjectsInterfaceWrappers;
+typedef Pair ScriptingObjectsInterfaceKey;
+Dictionary ScriptingObjectsInterfaceWrappers;
SerializableScriptingObject::SerializableScriptingObject(const SpawnParams& params)
: ScriptingObject(params)
@@ -202,10 +204,10 @@ ScriptingObject* ScriptingObject::FromInterface(void* interfaceObj, const Script
}
// Special case for interface wrapper object
- for (auto& e : ScriptingObjectsInterfaceWrappers)
+ for (const auto& e : ScriptingObjectsInterfaceWrappers)
{
if (e.Value == interfaceObj)
- return e.Key;
+ return e.Key.First;
}
return nullptr;
@@ -226,10 +228,11 @@ void* ScriptingObject::ToInterface(ScriptingObject* obj, const ScriptingTypeHand
else if (interface)
{
// Interface implemented in scripting (eg. C# class inherits C++ interface)
- if (!ScriptingObjectsInterfaceWrappers.TryGet(obj, result))
+ const ScriptingObjectsInterfaceKey key(obj, interfaceType);
+ if (!ScriptingObjectsInterfaceWrappers.TryGet(key, result))
{
result = interfaceType.GetType().Interface.GetInterfaceWrapper(obj);
- ScriptingObjectsInterfaceWrappers.Add(obj, result);
+ ScriptingObjectsInterfaceWrappers.Add(key, result);
}
}
return result;
@@ -241,7 +244,7 @@ ScriptingObject* ScriptingObject::ToNative(MObject* obj)
#if USE_CSHARP
if (obj)
{
-#if USE_MONO
+#if USE_MONO || USE_MONO_AOT
const auto ptrField = MCore::Object::GetClass(obj)->GetField(ScriptingObject_unmanagedPtr);
CHECK_RETURN(ptrField, nullptr);
ptrField->GetValue(obj, &ptr);
diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs
index 7a27a74fd..c36e00b6a 100644
--- a/Source/Engine/Serialization/JsonSerializer.cs
+++ b/Source/Engine/Serialization/JsonSerializer.cs
@@ -73,6 +73,7 @@ namespace FlaxEngine.Json
if (IsWriting)
{
// Reset writing state (eg if previous serialization got exception)
+ SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
JsonWriter = new JsonTextWriter(StringWriter)
{
IndentChar = '\t',
diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h
index 94acd1d79..f9dc0870a 100644
--- a/Source/Engine/Serialization/Serialization.h
+++ b/Source/Engine/Serialization/Serialization.h
@@ -405,14 +405,14 @@ namespace Serialization
// ISerializable
- inline bool ShouldSerialize(ISerializable& v, const void* otherObj)
+ inline bool ShouldSerialize(const ISerializable& v, const void* otherObj)
{
return true;
}
- inline void Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj)
+ inline void Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
{
stream.StartObject();
- v.Serialize(stream, otherObj);
+ const_cast(&v)->Serialize(stream, otherObj);
stream.EndObject();
}
inline void Deserialize(ISerializable::DeserializeStream& stream, ISerializable& v, ISerializeModifier* modifier)
@@ -421,15 +421,15 @@ namespace Serialization
}
template
- inline typename TEnableIf::Value, bool>::Type ShouldSerialize(ISerializable& v, const void* otherObj)
+ inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const ISerializable& v, const void* otherObj)
{
return true;
}
template
- inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj)
+ inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
{
stream.StartObject();
- v.Serialize(stream, otherObj);
+ const_cast(&v)->Serialize(stream, otherObj);
stream.EndObject();
}
template
@@ -441,12 +441,12 @@ namespace Serialization
// Scripting Object
template
- inline typename TEnableIf::Value, bool>::Type ShouldSerialize(T*& v, const void* otherObj)
+ inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj)
{
return !otherObj || v != *(T**)otherObj;
}
template
- inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, T*& v, const void* otherObj)
+ inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T*& v, const void* otherObj)
{
stream.Guid(v ? v->GetID() : Guid::Empty);
}
@@ -568,12 +568,13 @@ namespace Serialization
{
if (!otherObj)
return true;
- const auto other = (Array*)otherObj;
+ const auto other = (const Array*)otherObj;
if (v.Count() != other->Count())
return true;
+ const T* vPtr = v.Get();
for (int32 i = 0; i < v.Count(); i++)
{
- if (ShouldSerialize((T&)v[i], (const void*)&other->At(i)))
+ if (ShouldSerialize(vPtr[i], (const void*)&other->At(i)))
return true;
}
return false;
@@ -582,8 +583,9 @@ namespace Serialization
inline void Serialize(ISerializable::SerializeStream& stream, const Array& v, const void* otherObj)
{
stream.StartArray();
+ const T* vPtr = v.Get();
for (int32 i = 0; i < v.Count(); i++)
- Serialize(stream, (T&)v[i], nullptr);
+ Serialize(stream, vPtr[i], nullptr);
stream.EndArray();
}
template
@@ -593,8 +595,9 @@ namespace Serialization
{
const auto& streamArray = stream.GetArray();
v.Resize(streamArray.Size());
+ T* vPtr = v.Get();
for (int32 i = 0; i < v.Count(); i++)
- Deserialize(streamArray[i], v[i], modifier);
+ Deserialize(streamArray[i], vPtr[i], modifier);
}
else if (TIsPODType::Value && stream.IsString())
{
diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
index ce4116134..de2ba6e54 100644
--- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
+++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
@@ -28,6 +28,8 @@
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Platform/FileSystemWatcher.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Engine/Globals.h"
#include "Editor/Editor.h"
#include "Editor/ProjectInfo.h"
#endif
@@ -143,9 +145,22 @@ bool ShadersCompilation::Compile(ShaderCompilationOptions& options)
#endif
}
- // Print info if succeed
- if (result == false)
+ if (result)
{
+#if USE_EDITOR
+ // Output shader source to easily investigate errors (eg. for generated shaders like materials or particles)
+ const String outputSourceFolder = Globals::ProjectCacheFolder / TEXT("/Shaders/Source");
+ const String outputSourcePath = outputSourceFolder / options.TargetName + TEXT(".hlsl");
+ if (!FileSystem::DirectoryExists(outputSourceFolder))
+ FileSystem::CreateDirectory(outputSourceFolder);
+ File::WriteAllBytes(outputSourcePath, (const byte*)options.Source, options.SourceLength);
+ LOG(Error, "Shader compilation '{0}' failed (profile: {1})", options.TargetName, ::ToString(options.Profile));
+ LOG(Error, "Source: {0}", outputSourcePath);
+#endif
+ }
+ else
+ {
+ // Success
const DateTime endTime = DateTime::NowUTC();
LOG(Info, "Shader compilation '{0}' succeed in {1} ms (profile: {2})", options.TargetName, Math::CeilToInt(static_cast((endTime - startTime).GetTotalMilliseconds())), ::ToString(options.Profile));
}
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
index 8db8a0d0d..fe45b8b96 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
@@ -147,7 +147,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value)
case 8:
{
const Value defaultValue = MaterialValue::InitForZero(VariantType::Void);
- const Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero);
+ Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero).AsFloat();
if (alpha.IsZero())
{
// Bottom-only
@@ -178,6 +178,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value)
auto topHeightScaled = writeLocal(VariantType::Float, String::Format(TEXT("{0} * {1}"), topHeight.Value, alpha.Value), node);
auto heightStart = writeLocal(VariantType::Float, String::Format(TEXT("max({0}, {1}) - 0.05"), bottomHeightScaled.Value, topHeightScaled.Value), node);
auto bottomLevel = writeLocal(VariantType::Float, String::Format(TEXT("max({0} - {1}, 0.0001)"), topHeightScaled.Value, heightStart.Value), node);
+ alpha = writeLocal(VariantType::Float, alpha.Value, node);
_writer.Write(TEXT("\t{0} = {1} / (max({2} - {3}, 0) + {4});\n"), alpha.Value, bottomLevel.Value, bottomHeightScaled.Value, heightStart.Value, bottomLevel.Value);
}
#define EAT_BOX(type) writeBlending(MaterialGraphBoxes::type, value, bottom, top, alpha)
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index b703b47d1..116f63407 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -22,6 +22,7 @@
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Pair.h"
+#include "Engine/Core/Types/Variant.h"
#include "Engine/Graphics/Models/SkeletonUpdater.h"
#include "Engine/Graphics/Models/SkeletonMapping.h"
#include "Engine/Core/Utilities.h"
@@ -396,6 +397,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(SDFResolution);
SERIALIZE(SplitObjects);
SERIALIZE(ObjectIndex);
+ SERIALIZE(SubAssetFolder);
}
void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -441,6 +443,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(SDFResolution);
DESERIALIZE(SplitObjects);
DESERIALIZE(ObjectIndex);
+ DESERIALIZE(SubAssetFolder);
// [Deprecated on 23.11.2021, expires on 21.11.2023]
int32 AnimationIndex = -1;
@@ -744,6 +747,32 @@ void MeshOptDeallocate(void* ptr)
Allocator::Free(ptr);
}
+void TrySetupMaterialParameter(MaterialInstance* instance, Span paramNames, const Variant& value, MaterialParameterType type)
+{
+ for (const Char* name : paramNames)
+ {
+ for (MaterialParameter& param : instance->Params)
+ {
+ const MaterialParameterType paramType = param.GetParameterType();
+ if (type != paramType)
+ {
+ if (type == MaterialParameterType::Color)
+ {
+ if (paramType != MaterialParameterType::Vector3 ||
+ paramType != MaterialParameterType::Vector4)
+ continue;
+ }
+ else
+ continue;
+ }
+ if (StringUtils::CompareIgnoreCase(name, param.GetName().Get()) != 0)
+ continue;
+ param.SetValue(value);
+ return;
+ }
+ }
+}
+
bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput)
{
LOG(Info, "Importing model from \'{0}\'", path);
@@ -1014,9 +1043,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
{
// Create material instance
AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID);
- if (MaterialInstance* materialInstance = Content::Load(assetPath))
+ if (auto* materialInstance = Content::Load(assetPath))
{
materialInstance->SetBaseMaterial(options.InstanceToImportAs);
+
+ // Customize base material based on imported material (blind guess based on the common names used in materials)
+ const Char* diffuseColorNames[] = { TEXT("color"), TEXT("col"), TEXT("diffuse"), TEXT("basecolor"), TEXT("base color") };
+ TrySetupMaterialParameter(materialInstance, ToSpan(diffuseColorNames, ARRAY_COUNT(diffuseColorNames)), material.Diffuse.Color, MaterialParameterType::Color);
+ const Char* emissiveColorNames[] = { TEXT("emissive"), TEXT("emission"), TEXT("light") };
+ TrySetupMaterialParameter(materialInstance, ToSpan(emissiveColorNames, ARRAY_COUNT(emissiveColorNames)), material.Emissive.Color, MaterialParameterType::Color);
+ const Char* opacityValueNames[] = { TEXT("opacity"), TEXT("alpha") };
+ TrySetupMaterialParameter(materialInstance, ToSpan(opacityValueNames, ARRAY_COUNT(opacityValueNames)), material.Opacity.Value, MaterialParameterType::Float);
+
materialInstance->Save();
}
else
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h
index 36bb9b12c..bad10b86e 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.h
+++ b/Source/Engine/Tools/ModelTool/ModelTool.h
@@ -370,6 +370,12 @@ public:
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Splitting\")")
int32 ObjectIndex = -1;
+ public: // Other
+
+ // If specified, will be used as sub-directory name for automatically imported sub assets such as textures and materials. Set to whitespace (single space) to import to the same directory.
+ API_FIELD(Attributes="EditorOrder(3030), EditorDisplay(\"Other\")")
+ String SubAssetFolder = TEXT("");
+
// Runtime data for objects splitting during import (used internally)
void* SplitContext = nullptr;
Function OnSplitImport;
diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
index c08b59f48..55b58634d 100644
--- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
@@ -1399,6 +1399,12 @@ namespace FlaxEngine.GUI
}
case KeyboardKeys.Escape:
{
+ if (IsReadOnly)
+ {
+ SetSelection(_selectionEnd);
+ return true;
+ }
+
RestoreTextFromStart();
if (!IsNavFocused)
diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp
index fb4f54e7f..25446c9c5 100644
--- a/Source/Engine/UI/SpriteRender.cpp
+++ b/Source/Engine/UI/SpriteRender.cpp
@@ -113,7 +113,7 @@ void SpriteRender::Draw(RenderContext& renderContext)
auto model = _quadModel.As();
if (model->GetLoadedLODs() == 0)
return;
- const auto& view = renderContext.View;
+ const auto& view = (renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View);
Matrix m1, m2, m3, world;
Matrix::Scaling(_size.X, _size.Y, 1.0f, m2);
Matrix::RotationY(PI, m3);
diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp
index b3318c75c..a83d61f7a 100644
--- a/Source/Engine/UI/UICanvas.cpp
+++ b/Source/Engine/UI/UICanvas.cpp
@@ -56,6 +56,7 @@ UICanvas::UICanvas(const SpawnParams& params)
UICanvas_EndPlay = mclass->GetMethod("EndPlay");
UICanvas_ParentChanged = mclass->GetMethod("ParentChanged");
UICanvas_Serialize = mclass->GetMethod("Serialize");
+ Platform::MemoryBarrier();
}
#endif
}
diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp
index 56d7c9d06..6564cde2b 100644
--- a/Source/Engine/Visject/ShaderGraphValue.cpp
+++ b/Source/Engine/Visject/ShaderGraphValue.cpp
@@ -40,11 +40,15 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v)
break;
case VariantType::Float:
Type = VariantType::Types::Float;
- Value = String::Format(TEXT("{:.8f}"), v.AsFloat);
+ Value = String::Format(TEXT("{}"), v.AsFloat);
+ if (Value.Find('.') == -1)
+ Value = String::Format(TEXT("{:.1f}"), v.AsFloat);
break;
case VariantType::Double:
Type = VariantType::Types::Float;
- Value = String::Format(TEXT("{:.8f}"), (float)v.AsDouble);
+ Value = String::Format(TEXT("{}"), (float)v.AsDouble);
+ if (Value.Find('.') == -1)
+ Value = String::Format(TEXT("{:.1f}"), (float)v.AsDouble);
break;
case VariantType::Float2:
{
@@ -134,6 +138,29 @@ bool ShaderGraphValue::IsOne() const
}
}
+bool ShaderGraphValue::IsLiteral() const
+{
+ switch (Type)
+ {
+ case VariantType::Types::Bool:
+ case VariantType::Types::Int:
+ case VariantType::Types::Uint:
+ case VariantType::Types::Float:
+ if (Value.HasChars())
+ {
+ for (int32 i = 0; i < Value.Length(); i++)
+ {
+ const Char c = Value[i];
+ if (!StringUtils::IsDigit(c) && c != '.')
+ return false;
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
ShaderGraphValue ShaderGraphValue::InitForZero(VariantType::Types type)
{
const Char* v;
diff --git a/Source/Engine/Visject/ShaderGraphValue.h b/Source/Engine/Visject/ShaderGraphValue.h
index 7c7e25154..439b59077 100644
--- a/Source/Engine/Visject/ShaderGraphValue.h
+++ b/Source/Engine/Visject/ShaderGraphValue.h
@@ -143,8 +143,7 @@ public:
///
/// Returns true if value is valid.
///
- /// True if is valid, otherwise false.
- bool IsValid() const
+ FORCE_INLINE bool IsValid() const
{
return Type != VariantType::Types::Null;
}
@@ -152,8 +151,7 @@ public:
///
/// Returns true if value is invalid.
///
- /// True if is invalid, otherwise false.
- bool IsInvalid() const
+ FORCE_INLINE bool IsInvalid() const
{
return Type == VariantType::Types::Null;
}
@@ -161,15 +159,18 @@ public:
///
/// Checks if value contains static part with zero.
///
- /// True if contains zero number.
bool IsZero() const;
///
/// Checks if value contains static part with one.
///
- /// True if contains one number.
bool IsOne() const;
+ ///
+ /// Checks if value is a compile-time constant literal (eg. int, bool or float).
+ ///
+ bool IsLiteral() const;
+
///
/// Clears this instance.
///
diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h
index 5836afb2f..085c05de0 100644
--- a/Source/Engine/Visject/VisjectGraph.h
+++ b/Source/Engine/Visject/VisjectGraph.h
@@ -262,11 +262,11 @@ protected:
FORCE_INLINE Value tryGetValue(Box* box)
{
- return box && box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : Value::Zero;
+ return box && box->Connections.HasItems() ? eatBox(box->GetParent(), (VisjectGraphBox*)box->Connections.Get()[0]) : Value::Zero;
}
FORCE_INLINE Value tryGetValue(Box* box, const Value& defaultValue)
{
- return box && box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : defaultValue;
+ return box && box->Connections.HasItems() ? eatBox(box->GetParent(), (VisjectGraphBox*)box->Connections.Get()[0]) : defaultValue;
}
};
diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader
index 3d4f7dd46..bc4f272fc 100644
--- a/Source/Shaders/GlobalSignDistanceField.shader
+++ b/Source/Shaders/GlobalSignDistanceField.shader
@@ -211,7 +211,7 @@ float SampleSDF(uint3 voxelCoordMip, int3 offset)
float result = GlobalSDFTex[voxelCoordMip].r;
// Extend by distance to the sampled texel location
- float distanceInWorldUnits = length(offset) * (MaxDistance / (float)GenerateMipTexResolution);
+ float distanceInWorldUnits = length((float3)offset) * (MaxDistance / (float)GenerateMipTexResolution);
float distanceToVoxel = distanceInWorldUnits / MaxDistance;
result = CombineDistanceToSDF(result, distanceToVoxel);
diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
index c64ee38b3..6e9fbea83 100644
--- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
+++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
@@ -128,7 +128,7 @@ namespace Flax.Build
sourceFiles.Sort();
// Build assembly
- BuildDotNet(graph, buildData, targetBuildOptions, target.OutputName, sourceFiles);
+ BuildDotNet(graph, buildData, targetBuildOptions, target.OutputName, sourceFiles, optimizeAssembly: buildData.TargetOptions.ScriptingAPI.Optimization);
}
// Deploy files
@@ -152,7 +152,7 @@ namespace Flax.Build
}
}
- private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null)
+ private static void BuildDotNet(TaskGraph graph, BuildData buildData, BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null, bool? optimizeAssembly = null)
{
// Setup build options
var buildPlatform = Platform.BuildTargetPlatform;
@@ -257,10 +257,8 @@ namespace Flax.Build
if (buildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings)
args.Add("-nowarn:1591");
- // Optimizations prevent debugging, only enable in release builds
- var optimize = buildData.Configuration == TargetConfiguration.Release;
- if (buildData.TargetOptions.ScriptingAPI.Optimization.HasValue)
- optimize = buildData.TargetOptions.ScriptingAPI.Optimization.Value;
+ // Optimizations prevent debugging, only enable in release builds by default
+ var optimize = optimizeAssembly.HasValue ? optimizeAssembly.Value : buildData.Configuration == TargetConfiguration.Release;
args.Add(optimize ? "/optimize+" : "/optimize-");
#if !USE_NETCORE
args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies));
@@ -374,11 +372,15 @@ namespace Flax.Build
// Get references
fileReferences.Clear();
+ bool? optimizeAssembly = null;
foreach (var module in binaryModule)
{
if (!buildData.Modules.TryGetValue(module, out var moduleBuildOptions))
continue;
+ if (moduleBuildOptions.ScriptingAPI.Optimization.HasValue)
+ optimizeAssembly |= moduleBuildOptions.ScriptingAPI.Optimization;
+
// Find references based on the modules dependencies
foreach (var dependencyName in moduleBuildOptions.PublicDependencies.Concat(moduleBuildOptions.PrivateDependencies))
{
@@ -408,7 +410,7 @@ namespace Flax.Build
}
// Build assembly
- BuildDotNet(graph, buildData, buildOptions, binaryModuleName + ".CSharp", sourceFiles, fileReferences, binaryModule);
+ BuildDotNet(graph, buildData, buildOptions, binaryModuleName + ".CSharp", sourceFiles, fileReferences, binaryModule, optimizeAssembly);
}
}
}
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
index 72cefbfca..e161024b4 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
@@ -225,7 +225,7 @@ namespace Flax.Build.NativeCpp
public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable;
///
- /// Enable code optimization.
+ /// Enable code optimizations for the managed module assembly.
///
public bool? Optimization;
@@ -237,19 +237,13 @@ namespace Flax.Build.NativeCpp
/// Adds the other options into this.
///
/// The other.
- public void Add(ScriptingAPIOptions other, bool addBuildOptions = true)
+ public void Add(ScriptingAPIOptions other)
{
Defines.AddRange(other.Defines);
SystemReferences.AddRange(other.SystemReferences);
FileReferences.AddRange(other.FileReferences);
Analyzers.AddRange(other.Analyzers);
IgnoreMissingDocumentationWarnings |= other.IgnoreMissingDocumentationWarnings;
-
- if (addBuildOptions)
- {
- if (other.Optimization.HasValue)
- Optimization |= other.Optimization;
- }
}
}
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs
index 1e8550c4e..225e46ba7 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs
@@ -403,7 +403,7 @@ namespace Flax.Build
moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
- moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false);
+ moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
}
}
@@ -418,7 +418,7 @@ namespace Flax.Build
moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
- moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false);
+ moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI);
moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules);
}
}
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
index 887197258..a044adc0a 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
@@ -166,7 +166,7 @@ namespace Flax.Build.Projects.VisualStudioCode
foreach (var configuration in project.Configurations)
{
var target = configuration.Target;
- var name = project.Name + '|' + configuration.Name;
+ var name = solution.Name + '|' + configuration.Name;
json.BeginObject();
@@ -294,132 +294,121 @@ namespace Flax.Build.Projects.VisualStudioCode
json.BeginArray("configurations");
{
- foreach (var project in solution.Projects)
+ var cppProject = solution.Projects.FirstOrDefault(x => x.BaseName == solution.Name || x.Name == solution.Name);
+ var csharpProject = solution.Projects.FirstOrDefault(x => x.BaseName == solution.MainProject.Targets[0].Modules[0] || x.Name == solution.MainProject.Targets[0].Modules[0]);
+
+ if (cppProject != null)
{
- if (project.Name == "BuildScripts")
- continue;
// C++ project
- if (project.Type == TargetType.NativeCpp)
+ foreach (var configuration in cppProject.Configurations)
{
- // Skip generating launch profiles for plugins and dependencies
- if (solution.MainProject.Name != "Flax" && project.Name != "Flax.Build" && solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath)
- continue;
+ var name = solution.Name + '|' + configuration.Name + " (C++)";
+ var target = configuration.Target;
+ var outputType = cppProject.OutputType ?? target.OutputType;
+ var outputTargetFilePath = target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable);
- foreach (var configuration in project.Configurations)
+ json.BeginObject();
{
- var name = project.Name + '|' + configuration.Name;
- var target = configuration.Target;
+ if (configuration.Platform == TargetPlatform.Windows)
+ json.AddField("type", "cppvsdbg");
+ else
+ json.AddField("type", "cppdbg");
+ json.AddField("name", name);
+ json.AddField("request", "launch");
+ json.AddField("preLaunchTask", solution.Name + '|' + configuration.Name);
+ json.AddField("cwd", buildToolWorkspace);
- var outputType = project.OutputType ?? target.OutputType;
- var outputTargetFilePath = target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable);
+ WriteNativePlatformLaunchSettings(json, configuration.Platform);
- json.BeginObject();
+ if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor."))
{
if (configuration.Platform == TargetPlatform.Windows)
- json.AddField("type", "cppvsdbg");
- else
- json.AddField("type", "cppdbg");
- json.AddField("name", name);
- json.AddField("request", "launch");
- json.AddField("preLaunchTask", name);
- json.AddField("cwd", buildToolWorkspace);
-
- WriteNativePlatformLaunchSettings(json, configuration.Platform);
-
- if (outputType != TargetOutputType.Executable && configuration.Name.StartsWith("Editor."))
{
- if (configuration.Platform == TargetPlatform.Windows)
- {
- var editorFolder = configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32";
- json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor.exe"));
- }
- else if (configuration.Platform == TargetPlatform.Linux)
- json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor"));
- else if (configuration.Platform == TargetPlatform.Mac)
- json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor"));
-
- json.BeginArray("args");
- {
- json.AddUnnamedField("-project");
- json.AddUnnamedField(buildToolWorkspace);
- json.AddUnnamedField("-skipCompile");
- if (hasMonoProjects)
- {
- json.AddUnnamedField("-debug");
- json.AddUnnamedField("127.0.0.1:55555");
- }
- }
- json.EndArray();
+ var editorFolder = configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32";
+ json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor.exe"));
}
- else
+ else if (configuration.Platform == TargetPlatform.Linux)
+ json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Linux", configuration.ConfigurationName, "FlaxEditor"));
+ else if (configuration.Platform == TargetPlatform.Mac)
+ json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", "Mac", configuration.ConfigurationName, "FlaxEditor"));
+
+ json.BeginArray("args");
{
- json.AddField("program", outputTargetFilePath);
- json.BeginArray("args");
+ json.AddUnnamedField("-project");
+ json.AddUnnamedField(buildToolWorkspace);
+ json.AddUnnamedField("-skipCompile");
+ if (hasMonoProjects)
{
- if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac)
- json.AddUnnamedField("--std");
- if (hasMonoProjects)
- {
- json.AddUnnamedField("-debug");
- json.AddUnnamedField("127.0.0.1:55555");
- }
+ json.AddUnnamedField("-debug");
+ json.AddUnnamedField("127.0.0.1:55555");
}
- json.EndArray();
}
-
- json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis"));
+ json.EndArray();
}
- json.EndObject();
- }
- }
- // C# project
- else if (project.Type == TargetType.DotNetCore)
- {
- // Skip generating launch profiles for plugins and dependencies
- if (solution.MainProject.WorkspaceRootPath != project.WorkspaceRootPath)
- continue;
-
- foreach (var configuration in project.Configurations)
- {
- var name = project.Name + '|' + configuration.Name + " (C#)";
- var outputTargetFilePath = configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable);
-
- json.BeginObject();
+ else
{
- json.AddField("type", "coreclr");
- json.AddField("name", name);
- json.AddField("request", "launch");
- json.AddField("preLaunchTask", solution.Name + '|' + configuration.Name);
- json.AddField("cwd", buildToolWorkspace);
- if (configuration.Platform == Platform.BuildPlatform.Target)
+ json.AddField("program", outputTargetFilePath);
+ json.BeginArray("args");
{
- var editorFolder = configuration.Platform == TargetPlatform.Windows ? (configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32") : configuration.Platform.ToString();
- json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor"));
- json.BeginArray("args");
+ if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac)
+ json.AddUnnamedField("--std");
+ if (hasMonoProjects)
{
- json.AddUnnamedField("-project");
- json.AddUnnamedField(buildToolWorkspace);
- json.AddUnnamedField("-skipCompile");
+ json.AddUnnamedField("-debug");
+ json.AddUnnamedField("127.0.0.1:55555");
}
- json.EndArray();
- }
- else
- {
- json.AddField("program", configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable));
- }
-
- switch (configuration.Platform)
- {
- case TargetPlatform.Windows:
- json.AddField("stopAtEntry", false);
- json.AddField("externalConsole", true);
- break;
- case TargetPlatform.Linux:
- break;
}
+ json.EndArray();
}
- json.EndObject();
+
+ json.AddField("visualizerFile", Path.Combine(Globals.EngineRoot, "Source", "flax.natvis"));
}
+ json.EndObject();
+ }
+ }
+ if (csharpProject != null)
+ {
+ // C# project
+ foreach (var configuration in csharpProject.Configurations)
+ {
+ var name = solution.Name + '|' + configuration.Name + " (C#)";
+ var outputTargetFilePath = configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable);
+
+ json.BeginObject();
+ {
+ json.AddField("type", "coreclr");
+ json.AddField("name", name);
+ json.AddField("request", "launch");
+ json.AddField("preLaunchTask", solution.Name + '|' + configuration.Name);
+ json.AddField("cwd", buildToolWorkspace);
+ if (configuration.Platform == Platform.BuildPlatform.Target)
+ {
+ var editorFolder = configuration.Platform == TargetPlatform.Windows ? (configuration.Architecture == TargetArchitecture.x64 ? "Win64" : "Win32") : configuration.Platform.ToString();
+ json.AddField("program", Path.Combine(Globals.EngineRoot, "Binaries", "Editor", editorFolder, configuration.ConfigurationName, "FlaxEditor"));
+ json.BeginArray("args");
+ {
+ json.AddUnnamedField("-project");
+ json.AddUnnamedField(buildToolWorkspace);
+ json.AddUnnamedField("-skipCompile");
+ }
+ json.EndArray();
+ }
+ else
+ {
+ json.AddField("program", configuration.Target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable));
+ }
+
+ switch (configuration.Platform)
+ {
+ case TargetPlatform.Windows:
+ json.AddField("stopAtEntry", false);
+ json.AddField("externalConsole", true);
+ break;
+ case TargetPlatform.Linux:
+ break;
+ }
+ }
+ json.EndObject();
}
}
}
@@ -428,6 +417,9 @@ namespace Flax.Build.Projects.VisualStudioCode
{
foreach (var platform in solution.Projects.SelectMany(x => x.Configurations).Select(x => x.Platform).Distinct())
{
+ if (platform != TargetPlatform.Windows && platform != TargetPlatform.Linux && platform != TargetPlatform.Mac)
+ continue;
+
json.BeginObject();
{
if (platform == TargetPlatform.Windows)