Merge branch 'master' into Visject-ConvertConstantToParameter

This commit is contained in:
Nils Hausfeld
2023-10-16 19:34:22 +02:00
107 changed files with 1157 additions and 614 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -182,6 +182,7 @@ namespace FlaxEditor.GUI.Input
}
SlidingEnd?.Invoke();
Defocus();
Parent?.Focus();
}
/// <inheritdoc />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.Archetypes
Size = new Float2(200, 100),
DefaultValues = new object[]
{
0.0f,
0.5f,
},
Elements = new[]
{

View File

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

View File

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

View File

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

View File

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

View 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;
}

View File

@@ -41,6 +41,8 @@ namespace FlaxEditor.Viewport
Gizmos[i].Update(deltaTime);
}
}
/// <inheritdoc />
public EditorViewport Viewport => this;
/// <inheritdoc />
public GizmosCollection Gizmos { get; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ class ScreenService : public EngineService
{
public:
ScreenService()
: EngineService(TEXT("Screen"), 120)
: EngineService(TEXT("Screen"), 500)
{
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -132,7 +132,7 @@ class LevelService : public EngineService
{
public:
LevelService()
: EngineService(TEXT("Scene Manager"), 30)
: EngineService(TEXT("Scene Manager"), 200)
{
}

View File

@@ -29,7 +29,7 @@ class PrefabManagerService : public EngineService
{
public:
PrefabManagerService()
: EngineService(TEXT("Prefab Manager"), 110)
: EngineService(TEXT("Prefab Manager"))
{
}
};

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ class OnlineService : public EngineService
{
public:
OnlineService()
: EngineService(TEXT("Online"), 100)
: EngineService(TEXT("Online"), 500)
{
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ private:
Windows::HANDLE _monitor = nullptr;
Windows::LONG _clipCursorRect[4];
int32 _regionWidth = 0, _regionHeight = 0;
Float2 _minimizedScreenPosition = Float2::Zero;
public:

View File

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

View File

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

View File

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

View File

@@ -105,7 +105,7 @@ class ProbesRendererService : public EngineService
{
public:
ProbesRendererService()
: EngineService(TEXT("Probes Renderer"), 70)
: EngineService(TEXT("Probes Renderer"), 500)
{
}

View File

@@ -90,7 +90,7 @@ class PluginManagerService : public EngineService
{
public:
PluginManagerService()
: EngineService(TEXT("Plugin Manager"), 130)
: EngineService(TEXT("Plugin Manager"), 100)
{
}

View File

@@ -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, &parameterTypeHandles);
_parameterTypes.Set(parameterTypeHandles, _paramsCount);
MCore::GC::FreeMemory(parameterTypeHandles);
}
if (_paramsCount == 0)
return;
void** parameterTypeHandles;
CallStaticMethod<void, void*, void***>(GetMethodParameterTypesPtr, _handle, &parameterTypeHandles);
_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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1399,6 +1399,12 @@ namespace FlaxEngine.GUI
}
case KeyboardKeys.Escape:
{
if (IsReadOnly)
{
SetSelection(_selectionEnd);
return true;
}
RestoreTextFromStart();
if (!IsNavFocused)

View File

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