Merge branch 'master' into Visject-ConvertConstantToParameter
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
|
||||
/// <param name="value">The new path.</param>
|
||||
internal virtual void UpdatePath(string value)
|
||||
{
|
||||
Assert.AreNotEqual(Path, value);
|
||||
|
||||
// Set path
|
||||
Path = StringUtils.NormalizePath(value);
|
||||
FileName = System.IO.Path.GetFileName(value);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc />
|
||||
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<MissingScript> 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<IUndoAction> 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<IUndoAction>(4);
|
||||
|
||||
var missingScripts = new List<MissingScript>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
/// <summary>
|
||||
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
|
||||
/// </summary>
|
||||
[System.Obsolete("Deprecated in 1.4")]
|
||||
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
|
||||
public DoubleValueBox DoubleValue => ValueBox;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
|
||||
/// <summary>
|
||||
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
|
||||
/// </summary>
|
||||
[System.Obsolete("Deprecated in 1.4, ValueBox instead")]
|
||||
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
|
||||
public FloatValueBox FloatValue => ValueBox;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ namespace FlaxEditor.GUI.Input
|
||||
}
|
||||
SlidingEnd?.Invoke();
|
||||
Defocus();
|
||||
Parent?.Focus();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,6 +11,11 @@ namespace FlaxEditor.Gizmo
|
||||
[HideInEditor]
|
||||
public interface IGizmoOwner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the gizmos collection.
|
||||
/// </summary>
|
||||
FlaxEditor.Viewport.EditorViewport Viewport { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gizmos collection.
|
||||
/// </summary>
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
@@ -1134,17 +1135,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)
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace FlaxEditor.Modules
|
||||
public event Action ImportingQueueBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when file is being imported.
|
||||
/// Occurs when file is being imported. Can be called on non-main thread.
|
||||
/// </summary>
|
||||
public event Action<IFileEntryAction> ImportFileBegin;
|
||||
|
||||
@@ -67,12 +67,12 @@ namespace FlaxEditor.Modules
|
||||
public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when file importing end.
|
||||
/// Occurs when file importing end. Can be called on non-main thread.
|
||||
/// </summary>
|
||||
public event ImportFileEndDelegate ImportFileEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when assets importing ends.
|
||||
/// Occurs when assets importing ends. Can be called on non-main thread.
|
||||
/// </summary>
|
||||
public event Action ImportingQueueEnd;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,81 +286,17 @@ namespace VisualStudio
|
||||
return "Visual Studio open timout";
|
||||
}
|
||||
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::ProjectItems>& 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<EnvDTE::ProjectItem> 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<EnvDTE::ProjectItems> childProjectItems;
|
||||
projectItem->get_ProjectItems(&childProjectItems);
|
||||
if (childProjectItems)
|
||||
{
|
||||
ComPtr<EnvDTE::ProjectItem> result = FindItem(childProjectItems, filePath);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
|
||||
{
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<EnvDTE::Projects> projects;
|
||||
result = solution->get_Projects(&projects);
|
||||
ComPtr<EnvDTE::ProjectItem> 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<EnvDTE::Project> project;
|
||||
result = projects->Item(_variant_t(projectIndex), &project);
|
||||
if (FAILED(result) || !project)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::ProjectItems> 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.
|
||||
//
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="timeout">Time to use for checking.</param>
|
||||
/// <returns>True if source code is dirty, otherwise false.</returns>
|
||||
static bool IsSourceDirtyFor(const TimeSpan& timeout);
|
||||
API_FUNCTION() static bool IsSourceDirtyFor(const TimeSpan& timeout);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if scripts are being now compiled/reloaded.
|
||||
|
||||
@@ -59,8 +59,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
if (Surface != null)
|
||||
{
|
||||
_assetSelect = GetChild<AssetSelect>();
|
||||
_assetBox = GetBox(8);
|
||||
_assetSelect.Visible = !_assetBox.HasAnyConnection;
|
||||
if (TryGetBox(8, out var box))
|
||||
{
|
||||
_assetBox = box;
|
||||
_assetSelect.Visible = !_assetBox.HasAnyConnection;
|
||||
}
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
@@ -68,7 +71,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
private void UpdateTitle()
|
||||
{
|
||||
var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]);
|
||||
Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
|
||||
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);
|
||||
}
|
||||
@@ -78,6 +85,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
base.ConnectionTick(box);
|
||||
|
||||
if (_assetBox == null)
|
||||
return;
|
||||
if (box.ID != _assetBox.ID)
|
||||
return;
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Size = new Float2(200, 100),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
0.0f,
|
||||
0.5f,
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -75,6 +75,11 @@ namespace FlaxEditor.Actions
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
public int GetOrderInParent()
|
||||
{
|
||||
return _orderInParent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new added script undo action.
|
||||
/// </summary>
|
||||
@@ -184,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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace FlaxEditor.Viewport
|
||||
Gizmos[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public EditorViewport Viewport => this;
|
||||
|
||||
/// <inheritdoc />
|
||||
public GizmosCollection Gizmos { get; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +306,8 @@ namespace FlaxEditor.Viewport
|
||||
var orient = ViewOrientation;
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public EditorViewport Viewport => this;
|
||||
|
||||
/// <inheritdoc />
|
||||
public GizmosCollection Gizmos { get; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -335,6 +335,22 @@ namespace FlaxEditor.Windows.Assets
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool UseLayoutData => true;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<ContentItem>(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)
|
||||
|
||||
@@ -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<ProfilingTools.NetworkEventStat[]> _events;
|
||||
private List<Row> _tableRowsCache;
|
||||
private FlaxEngine.Networking.NetworkDriverStats _prevStats;
|
||||
private SamplesBuffer<ProfilingTools.NetworkEventStat[]> _events;
|
||||
private NetworkDriverStats _prevStats;
|
||||
private List<NetworkDriverStats> _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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<NetworkDriverStats>();
|
||||
_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)
|
||||
@@ -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);
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler
|
||||
/// <returns>The sample value</returns>
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -753,6 +754,13 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
auto anim = node->Assets[0].As<Animation>();
|
||||
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<Animation*>::Cast(tryGetValue(animationAssetBox, Value::Null));
|
||||
}
|
||||
|
||||
switch (box->ID)
|
||||
{
|
||||
// Animation
|
||||
@@ -762,18 +770,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
|
||||
const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
|
||||
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
|
||||
|
||||
// Override animation when animation reference box is connected
|
||||
auto animationAssetBox = node->GetBox(8);
|
||||
if (animationAssetBox->HasConnection())
|
||||
{
|
||||
const Value assetBoxValue = tryGetValue(animationAssetBox, Value::Null);
|
||||
if (assetBoxValue != Value::Null)
|
||||
anim = (Animation*)assetBoxValue.AsAsset;
|
||||
else
|
||||
anim = nullptr;
|
||||
}
|
||||
|
||||
const float length = anim ? anim->GetLength() : 0.0f;
|
||||
|
||||
// Calculate new time position
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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<float>(Math::Max(1U, clipInfo.SampleRate));
|
||||
time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
|
||||
bool SkipDefaultFonts = false;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
@@ -106,6 +113,7 @@ public:
|
||||
DESERIALIZE(AdditionalAssetFolders);
|
||||
DESERIALIZE(ShadersNoOptimize);
|
||||
DESERIALIZE(ShadersGenerateDebugData);
|
||||
DESERIALIZE(SkipDefaultFonts);
|
||||
DESERIALIZE(SkipDotnetPackaging);
|
||||
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
|
||||
}
|
||||
|
||||
@@ -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 <iostream>
|
||||
|
||||
@@ -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<Char> 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)
|
||||
|
||||
@@ -596,11 +596,13 @@ void EngineImpl::InitPaths()
|
||||
Globals::ProjectCacheFolder = Globals::ProjectFolder / TEXT("Cache");
|
||||
#endif
|
||||
|
||||
#if USE_MONO
|
||||
// We must ensure that engine is located in folder which path contains only ANSI characters
|
||||
// Why? Mono lib must have etc and lib folders at ANSI path
|
||||
// But project can be located on Unicode path
|
||||
if (!Globals::StartupFolder.IsANSI())
|
||||
Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters."));
|
||||
#endif
|
||||
|
||||
#if !PLATFORM_SWITCH && !FLAX_TESTS
|
||||
// Setup directories
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace FlaxEngine.Interop
|
||||
internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr)
|
||||
{
|
||||
string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr);
|
||||
string modulePath = Marshal.PtrToStringAnsi(modulePathPtr);
|
||||
string modulePath = Marshal.PtrToStringUni(modulePathPtr);
|
||||
libraryPaths[moduleName] = modulePath;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class ScreenService : public EngineService
|
||||
{
|
||||
public:
|
||||
ScreenService()
|
||||
: EngineService(TEXT("Screen"), 120)
|
||||
: EngineService(TEXT("Screen"), 500)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -339,7 +339,7 @@ namespace FlaxEngine
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
[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
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
[Obsolete("Deprecated in 1.4")]
|
||||
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
|
||||
public void UpdateMesh(List<Vector3> vertices, List<int> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
|
||||
{
|
||||
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
|
||||
@@ -375,7 +375,7 @@ namespace FlaxEngine
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
[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
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
[Obsolete("Deprecated in 1.4")]
|
||||
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
|
||||
public void UpdateMesh(List<Vector3> vertices, List<uint> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
|
||||
{
|
||||
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
|
||||
@@ -411,7 +411,7 @@ namespace FlaxEngine
|
||||
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
[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
|
||||
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
/// <param name="colors">The vertex colors (per vertex).</param>
|
||||
[Obsolete("Deprecated in 1.4")]
|
||||
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
|
||||
public void UpdateMesh(List<Vector3> vertices, List<ushort> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
|
||||
{
|
||||
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<int32>& 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
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace FlaxEngine
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
[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
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
[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
|
||||
/// <param name="normals">The normal vectors (per vertex).</param>
|
||||
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
|
||||
/// <param name="uv">The texture coordinates (per vertex).</param>
|
||||
[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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// <summary>
|
||||
/// Performs an animation and renders a skinned model.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
/// <summary>
|
||||
/// Renders model on the screen.
|
||||
/// </summary>
|
||||
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:
|
||||
|
||||
@@ -132,7 +132,7 @@ class LevelService : public EngineService
|
||||
{
|
||||
public:
|
||||
LevelService()
|
||||
: EngineService(TEXT("Scene Manager"), 30)
|
||||
: EngineService(TEXT("Scene Manager"), 200)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class PrefabManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
PrefabManagerService()
|
||||
: EngineService(TEXT("Prefab Manager"), 110)
|
||||
: EngineService(TEXT("Prefab Manager"))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<SceneObject*>& 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<Prefab>(prefabId);
|
||||
if (prefab && !prefab->WaitForLoaded())
|
||||
{
|
||||
// Map prefab object ID to the deserialized instance ID
|
||||
prefabInstance.IdsMapping[prefabObjectId] = id;
|
||||
goto NESTED_PREFAB_WALK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
/// </remarks>
|
||||
/// <param name="context">The serialization context.</param>
|
||||
/// <param name="data">The sync data.</param>
|
||||
static void SetupPrefabInstances(Context& context, PrefabSyncData& data);
|
||||
static void SetupPrefabInstances(Context& context, const PrefabSyncData& data);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes the new prefab instances by spawning missing objects that were added to prefab but were not saved with scene objects collection.
|
||||
|
||||
@@ -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 <ThirdParty/recastnavigation/DetourCrowd.h>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class OnlineService : public EngineService
|
||||
{
|
||||
public:
|
||||
OnlineService()
|
||||
: EngineService(TEXT("Online"), 100)
|
||||
: EngineService(TEXT("Online"), 500)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -119,6 +119,7 @@ struct ParticleEmitterGraphCPUContext
|
||||
class SceneRenderTask* ViewTask;
|
||||
Array<ParticleEmitterGraphCPU*, FixedAllocation<32>> GraphStack;
|
||||
Dictionary<VisjectExecutor::Node*, ParticleEmitterGraphCPU*> 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];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ParticleAttribute::ValueTypes>(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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -94,7 +94,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the root graph.
|
||||
/// </summary>
|
||||
/// <returns>The base graph.</returns>
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ public:
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
bool UsesParticleData;
|
||||
bool UsesParticleData = false;
|
||||
|
||||
/// <summary>
|
||||
/// Flag valid for used particle nodes that result in constant data (nothing random nor particle data).
|
||||
/// </summary>
|
||||
bool IsConstant;
|
||||
bool IsConstant = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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>& asset, bool& usesParticleData, ParticleLayout& layout);
|
||||
|
||||
/// <summary>
|
||||
/// The Particle Emitter Graph used to simulate particles.
|
||||
/// </summary>
|
||||
@@ -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<Asset>((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);
|
||||
|
||||
@@ -9,6 +9,27 @@
|
||||
#endif
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
|
||||
void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference<Asset>& asset, bool& usesParticleData, ParticleLayout& layout)
|
||||
{
|
||||
const auto function = Content::Load<ParticleEmitterFunction>(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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace FlaxEngine
|
||||
/// <param name="convexFlags">The convex mesh generation flags.</param>
|
||||
/// <param name="convexVertexLimit">The convex mesh vertex limit. Use values in range [8;255]</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
[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
|
||||
/// <param name="convexFlags">The convex mesh generation flags.</param>
|
||||
/// <param name="convexVertexLimit">The convex mesh vertex limit. Use values in range [8;255]</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
[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
|
||||
/// </summary>
|
||||
/// <param name="vertexBuffer">The output vertex buffer.</param>
|
||||
/// <param name="indexBuffer">The output index buffer.</param>
|
||||
[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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<typename T>
|
||||
@@ -16,17 +15,51 @@ class DataContainer;
|
||||
/// <summary>
|
||||
/// Specifies how the operating system should open a file.
|
||||
/// </summary>
|
||||
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,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Defines constants for read, write, or read/write access to a file.
|
||||
/// </summary>
|
||||
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,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Contains constants for controlling the kind of access other objects can have to the same file.
|
||||
/// </summary>
|
||||
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,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 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:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="FileBase"/> class.
|
||||
/// </summary>
|
||||
@@ -43,7 +75,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a file.
|
||||
/// </summary>
|
||||
@@ -68,7 +99,6 @@ public:
|
||||
virtual void Close() = 0;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets size of the file (in bytes).
|
||||
/// </summary>
|
||||
@@ -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<byte>& data);
|
||||
static bool ReadAllBytes(const StringView& path, Array<byte, HeapAllocation>& data);
|
||||
static bool ReadAllBytes(const StringView& path, DataContainer<byte>& 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<byte>& data);
|
||||
static bool WriteAllBytes(const StringView& path, const Array<byte, HeapAllocation>& 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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -532,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<LONG>(clientPos.X);
|
||||
p.y = static_cast<LONG>(clientPos.Y);
|
||||
@@ -1109,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<LONG>(_clientSize.X), static_cast<LONG>(_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<float>(placement.rcNormalPosition.left + monitorInfo.rcWork.left - monitorInfo.rcMonitor.left - winRect.left),
|
||||
static_cast<float>(placement.rcNormalPosition.top + monitorInfo.rcWork.top - monitorInfo.rcMonitor.top - winRect.top));
|
||||
|
||||
_minimized = true;
|
||||
_maximized = false;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
Windows::HANDLE _monitor = nullptr;
|
||||
Windows::LONG _clipCursorRect[4];
|
||||
int32 _regionWidth = 0, _regionHeight = 0;
|
||||
Float2 _minimizedScreenPosition = Float2::Zero;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
/// <summary>
|
||||
/// Represents single CPU profiling event data.
|
||||
@@ -69,6 +71,11 @@ public:
|
||||
/// </summary>
|
||||
uint64 FrameIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Sum of all present events duration on CPU during this frame (in milliseconds).
|
||||
/// </summary>
|
||||
float PresentTime;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this buffer has ready data (resolved and not empty).
|
||||
/// </summary>
|
||||
@@ -125,7 +132,7 @@ public:
|
||||
/// <summary>
|
||||
/// True if GPU profiling is enabled, otherwise false to disable events collecting and GPU timer queries usage. Can be changed during rendering.
|
||||
/// </summary>
|
||||
static bool Enabled;
|
||||
API_FIELD() static bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// The current frame buffer to collect events.
|
||||
@@ -151,32 +158,20 @@ public:
|
||||
/// <param name="index">The event token index returned by the BeginEvent method.</param>
|
||||
static void EndEvent(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Begins the new frame rendering. Called by the engine to sync profiling data.
|
||||
/// </summary>
|
||||
static void BeginFrame();
|
||||
|
||||
/// <summary>
|
||||
/// Called when just before flushing current frame GPU commands (via Present or Flush). Call active timer queries should be ended now.
|
||||
/// </summary>
|
||||
static void OnPresent();
|
||||
|
||||
/// <summary>
|
||||
/// Ends the frame rendering. Called by the engine to sync profiling data.
|
||||
/// </summary>
|
||||
static void EndFrame();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the rendering stats from the last frame drawing (that has been resolved and has valid data).
|
||||
/// </summary>
|
||||
/// <param name="drawTimeMs">The draw execution time on a GPU (in milliseconds).</param>
|
||||
/// <param name="presentTimeMs">The final frame present time on a CPU (in milliseconds). Time game waited for vsync or GPU to finish previous frame rendering.</param>
|
||||
/// <param name="statsData">The rendering stats data.</param>
|
||||
/// <returns>True if got the data, otherwise false.</returns>
|
||||
static bool GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData);
|
||||
API_FUNCTION() static bool GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData);
|
||||
|
||||
/// <summary>
|
||||
/// Releases resources. Calls to the profiling API after Dispose are not valid
|
||||
/// </summary>
|
||||
private:
|
||||
static void BeginFrame();
|
||||
static void OnPresent();
|
||||
static void OnPresentTime(float time);
|
||||
static void EndFrame();
|
||||
static void Dispose();
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ void ProfilingToolsService::Update()
|
||||
stats.PhysicsTimeMs = static_cast<float>(Time::Physics.LastLength * 1000.0);
|
||||
stats.DrawCPUTimeMs = static_cast<float>(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
|
||||
|
||||
@@ -105,7 +105,7 @@ class ProbesRendererService : public EngineService
|
||||
{
|
||||
public:
|
||||
ProbesRendererService()
|
||||
: EngineService(TEXT("Probes Renderer"), 70)
|
||||
: EngineService(TEXT("Probes Renderer"), 500)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class PluginManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
PluginManagerService()
|
||||
: EngineService(TEXT("Plugin Manager"), 130)
|
||||
: EngineService(TEXT("Plugin Manager"), 100)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -202,10 +202,10 @@ FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... 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<void, const char*, const char*>(RegisterNativeLibraryPtr, moduleName, modulePath);
|
||||
CallStaticMethod<void, const char*, const Char*>(RegisterNativeLibraryPtr, moduleName, modulePath);
|
||||
}
|
||||
|
||||
bool InitHostfxr();
|
||||
@@ -287,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<MDomain>("Root");
|
||||
MDomains.Add(MRootDomain);
|
||||
@@ -730,7 +730,6 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
|
||||
StringAnsi assemblyName;
|
||||
StringAnsi assemblyFullName;
|
||||
GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName);
|
||||
|
||||
assembly = New<MAssembly>(nullptr, assemblyName, assemblyFullName, assemblyHandle);
|
||||
CachedAssemblyHandles.Add(assemblyHandle, assembly);
|
||||
}
|
||||
@@ -797,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
|
||||
|
||||
@@ -1223,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)
|
||||
@@ -1366,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<void*, void*>(GetMethodReturnTypePtr, _handle);
|
||||
if (_paramsCount != 0)
|
||||
{
|
||||
void** parameterTypeHandles;
|
||||
CallStaticMethod<void, void*, void***>(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
|
||||
_parameterTypes.Set(parameterTypeHandles, _paramsCount);
|
||||
MCore::GC::FreeMemory(parameterTypeHandles);
|
||||
}
|
||||
|
||||
if (_paramsCount == 0)
|
||||
return;
|
||||
void** parameterTypeHandles;
|
||||
CallStaticMethod<void, void*, void***>(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
|
||||
_parameterTypes.Set(parameterTypeHandles, _paramsCount);
|
||||
MCore::GC::FreeMemory(parameterTypeHandles);
|
||||
_hasCachedSignature = true;
|
||||
}
|
||||
|
||||
MObject* MMethod::Invoke(void* instance, void** params, MObject** exception) const
|
||||
@@ -1434,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
|
||||
|
||||
@@ -474,30 +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
|
||||
#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"];
|
||||
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)
|
||||
|
||||
@@ -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<FontAsset>("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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ISerializable*>(&v)->Serialize(stream, otherObj);
|
||||
stream.EndObject();
|
||||
}
|
||||
inline void Deserialize(ISerializable::DeserializeStream& stream, ISerializable& v, ISerializeModifier* modifier)
|
||||
@@ -421,15 +421,15 @@ namespace Serialization
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value, bool>::Type ShouldSerialize(ISerializable& v, const void* otherObj)
|
||||
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value, bool>::Type ShouldSerialize(const ISerializable& v, const void* otherObj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
template<typename T>
|
||||
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj)
|
||||
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
|
||||
{
|
||||
stream.StartObject();
|
||||
v.Serialize(stream, otherObj);
|
||||
const_cast<ISerializable*>(&v)->Serialize(stream, otherObj);
|
||||
stream.EndObject();
|
||||
}
|
||||
template<typename T>
|
||||
@@ -441,12 +441,12 @@ namespace Serialization
|
||||
// Scripting Object
|
||||
|
||||
template<typename T>
|
||||
inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::Value, bool>::Type ShouldSerialize(T*& v, const void* otherObj)
|
||||
inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj)
|
||||
{
|
||||
return !otherObj || v != *(T**)otherObj;
|
||||
}
|
||||
template<typename T>
|
||||
inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, T*& v, const void* otherObj)
|
||||
inline typename TEnableIf<TIsBaseOf<ScriptingObject, T>::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<T, AllocationType>*)otherObj;
|
||||
const auto other = (const Array<T, AllocationType>*)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<T, AllocationType>& 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<typename T, typename AllocationType = HeapAllocation>
|
||||
@@ -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<T>::Value && stream.IsString())
|
||||
{
|
||||
|
||||
@@ -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<float>((endTime - startTime).GetTotalMilliseconds())), ::ToString(options.Profile));
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1399,6 +1399,12 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
case KeyboardKeys.Escape:
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
SetSelection(_selectionEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
RestoreTextFromStart();
|
||||
|
||||
if (!IsNavFocused)
|
||||
|
||||
@@ -113,7 +113,7 @@ void SpriteRender::Draw(RenderContext& renderContext)
|
||||
auto model = _quadModel.As<Model>();
|
||||
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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user