Merge branch 'master' into 1.8

This commit is contained in:
Wojtek Figat
2023-10-18 12:57:28 +02:00
169 changed files with 1850 additions and 918 deletions

Binary file not shown.

View File

@@ -220,8 +220,9 @@ namespace FlaxEditor.Content.GUI
// Remove references and unlink items // Remove references and unlink items
for (int i = 0; i < _items.Count; i++) for (int i = 0; i < _items.Count; i++)
{ {
_items[i].Parent = null; var item = _items[i];
_items[i].RemoveReference(this); item.Parent = null;
item.RemoveReference(this);
} }
_items.Clear(); _items.Clear();
@@ -263,11 +264,12 @@ namespace FlaxEditor.Content.GUI
// Add references and link items // Add references and link items
for (int i = 0; i < items.Count; i++) 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; item.Parent = this;
items[i].AddReference(this); item.AddReference(this);
_items.Add(items[i]); _items.Add(item);
} }
} }
if (selection != null) if (selection != null)
@@ -279,6 +281,8 @@ namespace FlaxEditor.Content.GUI
// Sort items depending on sortMethod parameter // Sort items depending on sortMethod parameter
_children.Sort(((control, control1) => _children.Sort(((control, control1) =>
{ {
if (control == null || control1 == null)
return 0;
if (sortType == SortType.AlphabeticReverse) if (sortType == SortType.AlphabeticReverse)
{ {
if (control.CompareTo(control1) > 0) if (control.CompareTo(control1) > 0)

View File

@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
/// <param name="value">The new path.</param> /// <param name="value">The new path.</param>
internal virtual void UpdatePath(string value) internal virtual void UpdatePath(string value)
{ {
Assert.AreNotEqual(Path, value);
// Set path // Set path
Path = StringUtils.NormalizePath(value); Path = StringUtils.NormalizePath(value);
FileName = System.IO.Path.GetFileName(value); FileName = System.IO.Path.GetFileName(value);

View File

@@ -54,7 +54,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Texture)request.Asset); return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((CubeTexture)request.Asset);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
return item is SceneItem; return item is SceneItem;
} }
/// <inheritdoc />
public override bool AcceptsAsset(string typeName, string path)
{
return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation) public override bool CanCreate(ContentFolder targetLocation)
{ {

View File

@@ -406,18 +406,16 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < maxChecks; i++) for (int i = 0; i < maxChecks; i++)
{ {
var request = _requests[i]; var request = _requests[i];
try try
{ {
if (request.IsReady) if (request.IsReady)
{
return request; return request;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}."); Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
Editor.LogWarning(ex);
_requests.RemoveAt(i--);
} }
} }
@@ -515,7 +513,6 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < checks; i++) for (int i = 0; i < checks; i++)
{ {
var request = _requests[i]; var request = _requests[i];
try try
{ {
if (request.IsReady) if (request.IsReady)
@@ -529,8 +526,9 @@ namespace FlaxEditor.Content.Thumbnails
} }
catch (Exception ex) catch (Exception ex)
{ {
Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}."); Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
Editor.LogWarning(ex);
_requests.RemoveAt(i--);
} }
} }

View File

@@ -131,7 +131,8 @@ bool DeployDataStep::Perform(CookingData& data)
if (FileSystem::DirectoryExists(dstDotnet)) if (FileSystem::DirectoryExists(dstDotnet))
{ {
String cachedData; String cachedData;
File::ReadAllText(dotnetCacheFilePath, cachedData); if (FileSystem::FileExists(dotnetCacheFilePath))
File::ReadAllText(dotnetCacheFilePath, cachedData);
if (cachedData != dotnetCachedValue) if (cachedData != dotnetCachedValue)
{ {
FileSystem::DeleteDirectory(dstDotnet); FileSystem::DeleteDirectory(dstDotnet);
@@ -360,7 +361,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME); data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX); data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX); data.AddRootEngineAsset(SMAA_SEARCH_TEX);
if (data.Configuration != BuildConfiguration::Release) if (!buildSettings.SkipDefaultFonts)
data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular")); data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register custom assets (eg. plugins) // Register custom assets (eg. plugins)

View File

@@ -36,7 +36,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
return; return;
var gizmos = gizmoOwner.Gizmos; var gizmos = gizmoOwner.Gizmos;
_gizmoMode = new ClothPaintingGizmoMode(); _gizmoMode = new ClothPaintingGizmoMode();
var projectCache = Editor.Instance.ProjectCache; var projectCache = Editor.Instance.ProjectCache;
if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue)) if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue))
_gizmoMode.PaintValue = JsonSerializer.Deserialize<float>(cachedPaintValue); _gizmoMode.PaintValue = JsonSerializer.Deserialize<float>(cachedPaintValue);
@@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
_gizmoMode.BrushSize = JsonSerializer.Deserialize<float>(cachedBrushSize); _gizmoMode.BrushSize = JsonSerializer.Deserialize<float>(cachedBrushSize);
if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength)) if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
_gizmoMode.BrushStrength = JsonSerializer.Deserialize<float>(cachedBrushStrength); _gizmoMode.BrushStrength = JsonSerializer.Deserialize<float>(cachedBrushStrength);
gizmos.AddMode(_gizmoMode); gizmos.AddMode(_gizmoMode);
_prevMode = gizmos.ActiveMode; _prevMode = gizmos.ActiveMode;
gizmos.ActiveMode = _gizmoMode; gizmos.ActiveMode = _gizmoMode;

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;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using System.Collections.Generic;
namespace FlaxEditor.CustomEditors.Dedicated; namespace FlaxEditor.CustomEditors.Dedicated;
@@ -10,6 +17,10 @@ namespace FlaxEditor.CustomEditors.Dedicated;
[CustomEditor(typeof(MissingScript)), DefaultEditor] [CustomEditor(typeof(MissingScript)), DefaultEditor]
public class MissingScriptEditor : GenericEditor public class MissingScriptEditor : GenericEditor
{ {
private DropPanel _dropPanel;
private Button _replaceScriptButton;
private CheckBox _shouldReplaceAllCheckbox;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
@@ -18,9 +29,137 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout); base.Initialize(layout);
return; 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); 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> /// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024] /// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary> /// </summary>
[System.Obsolete("Deprecated in 1.4")] [System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public DoubleValueBox DoubleValue => ValueBox; public DoubleValueBox DoubleValue => ValueBox;
/// <summary> /// <summary>

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary> /// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024] /// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary> /// </summary>
[System.Obsolete("Deprecated in 1.4, ValueBox instead")] [System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
public FloatValueBox FloatValue => ValueBox; public FloatValueBox FloatValue => ValueBox;
/// <summary> /// <summary>

View File

@@ -364,7 +364,7 @@ namespace FlaxEditor
{ {
foreach (var preview in activePreviews) foreach (var preview in activePreviews)
{ {
if (preview == loadingPreview || if (preview == loadingPreview ||
(preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control)))) (preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control))))
{ {
// Link it to the prefab preview to see it in the editor // Link it to the prefab preview to see it in the editor

View File

@@ -482,8 +482,8 @@ namespace FlaxEditor.GUI
Focus(); Focus();
}); });
if (_selected != null) if (_selected != null)
{ {
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path); var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName); popup.ScrollToAndHighlightItemByName(selectedAssetName);
} }
} }

View File

@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.ContextMenu
// Hide parent CM popups and set itself as child // Hide parent CM popups and set itself as child
parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0))); parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0)));
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {

View File

@@ -319,7 +319,9 @@ namespace FlaxEditor.GUI.Dialogs
protected override void OnShow() protected override void OnShow()
{ {
// Auto cancel on lost focus // Auto cancel on lost focus
#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnCancel; ((WindowRootControl)Root).Window.LostFocus += OnCancel;
#endif
base.OnShow(); base.OnShow();
} }

View File

@@ -293,7 +293,7 @@ namespace FlaxEditor.GUI.Dialogs
if (Root != null) if (Root != null)
{ {
bool shiftDown = Root.GetKey(KeyboardKeys.Shift); bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
} }
return true; return true;
} }

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor.GUI.Input
: this(false, 0, 0) : this(false, 0, 0)
{ {
} }
/// <summary> /// <summary>
/// Init search box /// Init search box
/// </summary> /// </summary>
@@ -28,7 +28,7 @@ namespace FlaxEditor.GUI.Input
: base(isMultiline, x, y, width) : base(isMultiline, x, y, width)
{ {
WatermarkText = "Search..."; WatermarkText = "Search...";
ClearSearchButton = new Button ClearSearchButton = new Button
{ {
Parent = this, Parent = this,

View File

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

View File

@@ -241,7 +241,7 @@ namespace FlaxEditor.GUI
{ {
DoubleClick?.Invoke(); DoubleClick?.Invoke();
RowDoubleClick?.Invoke(this); RowDoubleClick?.Invoke(this);
return base.OnMouseDoubleClick(location, button); return base.OnMouseDoubleClick(location, button);
} }

View File

@@ -191,6 +191,8 @@ namespace FlaxEditor.GUI.Tabs
get => _autoTabsSizeAuto; get => _autoTabsSizeAuto;
set set
{ {
if (_autoTabsSizeAuto == value)
return;
_autoTabsSizeAuto = value; _autoTabsSizeAuto = value;
PerformLayout(); PerformLayout();
} }
@@ -204,11 +206,11 @@ namespace FlaxEditor.GUI.Tabs
get => _orientation; get => _orientation;
set set
{ {
if (_orientation == value)
return;
_orientation = value; _orientation = value;
if (UseScroll) if (UseScroll)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical; TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
PerformLayout(); PerformLayout();
} }
} }
@@ -402,6 +404,14 @@ namespace FlaxEditor.GUI.Tabs
tabHeader.Size = tabsSize; 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 // Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal TabsPanel.Size = _orientation == Orientation.Horizontal

View File

@@ -11,6 +11,11 @@ namespace FlaxEditor.Gizmo
[HideInEditor] [HideInEditor]
public interface IGizmoOwner public interface IGizmoOwner
{ {
/// <summary>
/// Gets the gizmos collection.
/// </summary>
FlaxEditor.Viewport.EditorViewport Viewport { get; }
/// <summary> /// <summary>
/// Gets the gizmos collection. /// Gets the gizmos collection.
/// </summary> /// </summary>

View File

@@ -162,10 +162,23 @@ namespace FlaxEditor.Gizmo
// Scale gizmo to fit on-screen // Scale gizmo to fit on-screen
Vector3 position = Position; Vector3 position = Position;
Vector3 vLength = Owner.ViewPosition - position; if (Owner.Viewport.UseOrthographicProjection)
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize; {
_screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize); //[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 // Setup world
Quaternion orientation = GetSelectedObject(0).Orientation; Quaternion orientation = GetSelectedObject(0).Orientation;
_gizmoWorld = new Transform(position, orientation, new Float3(_screenScale)); _gizmoWorld = new Transform(position, orientation, new Float3(_screenScale));

View File

@@ -649,8 +649,6 @@ namespace FlaxEditor.Modules
// Special case for folders // Special case for folders
if (item is ContentFolder folder) if (item is ContentFolder folder)
{ {
// TODO: maybe don't remove folders recursive but at once?
// Delete all children // Delete all children
if (folder.Children.Count > 0) if (folder.Children.Count > 0)
{ {
@@ -664,6 +662,9 @@ namespace FlaxEditor.Modules
// Remove directory // Remove directory
if (deletedByUser && Directory.Exists(path)) 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 try
{ {
Directory.Delete(path, true); Directory.Delete(path, true);
@@ -810,10 +811,9 @@ namespace FlaxEditor.Modules
{ {
if (node == null) if (node == null)
return; return;
// Temporary data
var folder = node.Folder; var folder = node.Folder;
var path = folder.Path; var path = folder.Path;
var canHaveAssets = node.CanHaveAssets;
if (_isDuringFastSetup) if (_isDuringFastSetup)
{ {
@@ -832,20 +832,38 @@ namespace FlaxEditor.Modules
var child = folder.Children[i]; var child = folder.Children[i];
if (!child.Exists) if (!child.Exists)
{ {
// Send info // Item doesn't exist anymore
Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed")); Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed"));
// Destroy it
Delete(child, false); Delete(child, false);
i--; i--;
} }
else if (canHaveAssets && child is AssetItem childAsset)
{
// Check if asset type doesn't match the item proxy (eg. item reimported as Material Instance instead of Material)
if (FlaxEngine.Content.GetAssetInfo(child.Path, out var assetInfo))
{
bool changed = assetInfo.ID != childAsset.ID;
if (!changed && assetInfo.TypeName != childAsset.TypeName)
{
// Use proxy check (eg. scene asset might accept different typename than AssetInfo reports)
var proxy = GetAssetProxy(childAsset.TypeName, child.Path);
if (proxy == null)
proxy = GetAssetProxy(assetInfo.TypeName, child.Path);
changed = !proxy.AcceptsAsset(assetInfo.TypeName, child.Path);
}
if (changed)
{
OnAssetTypeInfoChanged(childAsset, ref assetInfo);
i--;
}
}
}
} }
} }
// Find files // Find files
var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly); var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly);
if (node.CanHaveAssets) if (canHaveAssets)
{ {
LoadAssets(node, files); LoadAssets(node, files);
} }
@@ -1134,17 +1152,19 @@ namespace FlaxEditor.Modules
RebuildInternal(); RebuildInternal();
Editor.ContentImporting.ImportFileEnd += ContentImporting_ImportFileDone; Editor.ContentImporting.ImportFileEnd += (obj, failed) =>
{
var path = obj.ResultUrl;
if (!failed)
FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path));
};
_enableEvents = true; _enableEvents = true;
} }
private void ContentImporting_ImportFileDone(IFileEntryAction obj, bool failed) private void OnImportFileDone(string path)
{ {
if (failed)
return;
// Check if already has that element // Check if already has that element
var item = Find(obj.ResultUrl); var item = Find(path);
if (item is BinaryAssetItem binaryAssetItem) if (item is BinaryAssetItem binaryAssetItem)
{ {
// Get asset info from the registry (content layer will update cache it just after import) // Get asset info from the registry (content layer will update cache it just after import)
@@ -1154,19 +1174,8 @@ namespace FlaxEditor.Modules
// For eg. change texture to sprite atlas on reimport // For eg. change texture to sprite atlas on reimport
if (binaryAssetItem.TypeName != assetInfo.TypeName) if (binaryAssetItem.TypeName != assetInfo.TypeName)
{ {
// Asset type has been changed!
Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", item.Path, binaryAssetItem.TypeName, assetInfo.TypeName));
Editor.Windows.CloseAllEditors(item);
// Remove this item from the database and some related data
var toRefresh = binaryAssetItem.ParentFolder; var toRefresh = binaryAssetItem.ParentFolder;
binaryAssetItem.Dispose(); OnAssetTypeInfoChanged(binaryAssetItem, ref assetInfo);
toRefresh.Children.Remove(binaryAssetItem);
if (!binaryAssetItem.HasDefaultThumbnail)
{
// Delete old thumbnail and remove it from the cache
Editor.Instance.Thumbnails.DeletePreview(binaryAssetItem);
}
// Refresh the parent folder to find the new asset (it should have different type or some other format) // Refresh the parent folder to find the new asset (it should have different type or some other format)
RefreshFolder(toRefresh, false); RefreshFolder(toRefresh, false);
@@ -1183,6 +1192,23 @@ namespace FlaxEditor.Modules
} }
} }
private void OnAssetTypeInfoChanged(AssetItem assetItem, ref AssetInfo assetInfo)
{
// Asset type has been changed!
Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", assetItem.Path, assetItem.TypeName, assetInfo.TypeName));
Editor.Windows.CloseAllEditors(assetItem);
// Remove this item from the database and some related data
assetItem.Dispose();
assetItem.ParentFolder.Children.Remove(assetItem);
// Delete old thumbnail and remove it from the cache
if (!assetItem.HasDefaultThumbnail)
{
Editor.Instance.Thumbnails.DeletePreview(assetItem);
}
}
internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e) internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
{ {
// Ensure to be ready for external events // Ensure to be ready for external events

View File

@@ -104,7 +104,7 @@ namespace FlaxEditor.Modules
hint = "Too long name."; hint = "Too long name.";
return false; return false;
} }
if (item.IsFolder && shortName.EndsWith(".")) if (item.IsFolder && shortName.EndsWith("."))
{ {
hint = "Name cannot end with '.'"; hint = "Name cannot end with '.'";

View File

@@ -55,7 +55,7 @@ namespace FlaxEditor.Modules
public event Action ImportingQueueBegin; public event Action ImportingQueueBegin;
/// <summary> /// <summary>
/// Occurs when file is being imported. /// Occurs when file is being imported. Can be called on non-main thread.
/// </summary> /// </summary>
public event Action<IFileEntryAction> ImportFileBegin; public event Action<IFileEntryAction> ImportFileBegin;
@@ -67,12 +67,12 @@ namespace FlaxEditor.Modules
public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed); public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed);
/// <summary> /// <summary>
/// Occurs when file importing end. /// Occurs when file importing end. Can be called on non-main thread.
/// </summary> /// </summary>
public event ImportFileEndDelegate ImportFileEnd; public event ImportFileEndDelegate ImportFileEnd;
/// <summary> /// <summary>
/// Occurs when assets importing ends. /// Occurs when assets importing ends. Can be called on non-main thread.
/// </summary> /// </summary>
public event Action ImportingQueueEnd; public event Action ImportingQueueEnd;

View File

@@ -133,7 +133,7 @@ namespace FlaxEditor.Modules
return; return;
var actorsList = new List<Actor>(); var actorsList = new List<Actor>();
Utilities.Utils.GetActorsTree(actorsList, actor); Utilities.Utils.GetActorsTree(actorsList, actor);
var actions = new IUndoAction[actorsList.Count]; var actions = new IUndoAction[actorsList.Count];
for (int i = 0; i < actorsList.Count; i++) for (int i = 0; i < actorsList.Count; i++)
actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]); actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]);

View File

@@ -453,7 +453,7 @@ namespace FlaxEditor.Modules
{ {
Editor.Windows.SceneWin.Focus(); Editor.Windows.SceneWin.Focus();
} }
// fix scene window layout // fix scene window layout
Editor.Windows.SceneWin.PerformLayout(); Editor.Windows.SceneWin.PerformLayout();
Editor.Windows.SceneWin.PerformLayout(); Editor.Windows.SceneWin.PerformLayout();
@@ -520,7 +520,7 @@ namespace FlaxEditor.Modules
Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); Undo.AddAction(new MultiUndoAction(pasteAction, selectAction));
OnSelectionChanged(); OnSelectionChanged();
} }
// Scroll to new selected node while pasting // Scroll to new selected node while pasting
Editor.Windows.SceneWin.ScrollToSelectedNode(); Editor.Windows.SceneWin.ScrollToSelectedNode();
} }
@@ -620,7 +620,7 @@ namespace FlaxEditor.Modules
Undo.AddAction(new MultiUndoAction(undoActions)); Undo.AddAction(new MultiUndoAction(undoActions));
OnSelectionChanged(); OnSelectionChanged();
} }
// Scroll to new selected node while duplicating // Scroll to new selected node while duplicating
Editor.Windows.SceneWin.ScrollToSelectedNode(); Editor.Windows.SceneWin.ScrollToSelectedNode();
} }

View File

@@ -332,7 +332,7 @@ namespace FlaxEditor.Modules
continue; continue;
scenes.Add(s); scenes.Add(s);
} }
// In play-mode Editor mocks the level streaming script // In play-mode Editor mocks the level streaming script
if (Editor.IsPlayMode) if (Editor.IsPlayMode)
{ {

View File

@@ -29,10 +29,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool CheckFunc(ScriptType scriptType) private static bool CheckFunc(ScriptType scriptType)
{ {
if (scriptType.IsStatic || if (scriptType.IsStatic ||
scriptType.IsGenericType || scriptType.IsGenericType ||
!scriptType.IsPublic || !scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) || scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)) scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false; return false;
var managedType = TypeUtils.GetType(scriptType); var managedType = TypeUtils.GetType(scriptType);
@@ -410,9 +410,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
base.OnUpdate(); base.OnUpdate();
// Automatic project files generation after workspace modifications // 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

@@ -2,7 +2,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI; using FlaxEditor.GUI;
@@ -11,7 +10,6 @@ using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.Progress.Handlers; using FlaxEditor.Progress.Handlers;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Utilities; using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Windows; using FlaxEditor.Windows;
@@ -208,6 +206,7 @@ namespace FlaxEditor.Modules
_toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale; _toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale;
// //
_toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive; _toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive;
_toolStripBuildScenes.Visible = Editor.Options.Options.General.BuildActions?.Length != 0;
_toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning; _toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning;
// //
var play = _toolStripPlay; var play = _toolStripPlay;
@@ -299,7 +298,7 @@ namespace FlaxEditor.Modules
else else
text = "Ready"; text = "Ready";
if(ProgressVisible) if (ProgressVisible)
{ {
color = Style.Current.Statusbar.Loading; color = Style.Current.Statusbar.Loading;
} }
@@ -402,7 +401,7 @@ namespace FlaxEditor.Modules
{ {
UpdateStatusBar(); UpdateStatusBar();
} }
else if(ProgressVisible) else if (ProgressVisible)
{ {
UpdateStatusBar(); UpdateStatusBar();
} }
@@ -557,7 +556,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Game Settings", () => cm.AddButton("Game Settings", () =>
{ {
var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath); var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath);
if(item != null) if (item != null)
Editor.ContentEditing.Open(item); Editor.ContentEditing.Open(item);
}); });
@@ -653,7 +652,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Information about Flax", () => new AboutDialog().Show()); cm.AddButton("Information about Flax", () => new AboutDialog().Show());
} }
private void OnOptionsChanged(FlaxEditor.Options.EditorOptions options) private void OnOptionsChanged(EditorOptions options)
{ {
var inputOptions = options.Input; var inputOptions = options.Input;
@@ -688,6 +687,8 @@ namespace FlaxEditor.Modules
_menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString(); _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
MainMenuShortcutKeysUpdated?.Invoke(); MainMenuShortcutKeysUpdated?.Invoke();
UpdateToolstrip();
} }
private void InitToolstrip(RootControl mainWindow) private void InitToolstrip(RootControl mainWindow)
@@ -709,11 +710,11 @@ namespace FlaxEditor.Modules
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})"); _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator(); ToolStrip.AddSeparator();
// Cook scenes // Build scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})"); _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run // Cook and run
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})"); _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
_toolStripCook.ContextMenu = new ContextMenu(); _toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked); _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator(); _toolStripCook.ContextMenu.AddSeparator();

View File

@@ -276,9 +276,6 @@ namespace FlaxEditor.Modules
// Get metadata // Get metadata
int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture); int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture);
var virtualDesktopBounds = Platform.VirtualDesktopBounds;
var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
switch (version) switch (version)
{ {
@@ -288,31 +285,9 @@ namespace FlaxEditor.Modules
if (MainWindow) if (MainWindow)
{ {
var mainWindowNode = root["MainWindow"]; var mainWindowNode = root["MainWindow"];
Rectangle bounds = LoadBounds(mainWindowNode["Bounds"]); bool isMaximized = true, isMinimized = false;
bool isMaximized = bool.Parse(mainWindowNode.GetAttribute("IsMaximized")); Rectangle bounds = LoadBounds(mainWindowNode, ref isMaximized, ref isMinimized);
LoadWindow(MainWindow, ref bounds, isMaximized, false);
// 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;
}
}
} }
// Load master panel structure // Load master panel structure
@@ -332,11 +307,13 @@ namespace FlaxEditor.Modules
continue; continue;
// Get window properties // 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 // Create window and floating dock panel
var window = FloatWindowDockPanel.CreateFloatWindow(MainWindow.GUI, bounds.Location, bounds.Size, WindowStartPosition.Manual, string.Empty); var window = FloatWindowDockPanel.CreateFloatWindow(MainWindow.GUI, bounds.Location, bounds.Size, WindowStartPosition.Manual, string.Empty);
var panel = new FloatWindowDockPanel(masterPanel, window.GUI); var panel = new FloatWindowDockPanel(masterPanel, window.GUI);
LoadWindow(panel.Window.Window, ref bounds, isMaximized, isMinimized);
// Load structure // Load structure
LoadPanel(child, panel); LoadPanel(child, panel);
@@ -493,23 +470,67 @@ namespace FlaxEditor.Modules
private static void SaveBounds(XmlWriter writer, Window win) private static void SaveBounds(XmlWriter writer, Window win)
{ {
var bounds = win.ClientBounds; writer.WriteStartElement("Bounds");
{
writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture)); var bounds = win.ClientBounds;
writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture)); writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture)); writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("Height", bounds.Height.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); var bounds = node["Bounds"];
float y = float.Parse(node.GetAttribute("Y"), CultureInfo.InvariantCulture); var isMaximizedText = bounds.GetAttribute("IsMaximized");
float width = float.Parse(node.GetAttribute("Width"), CultureInfo.InvariantCulture); if (!string.IsNullOrEmpty(isMaximizedText))
float height = float.Parse(node.GetAttribute("Height"), CultureInfo.InvariantCulture); 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); 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 class LayoutNameDialog : Dialog
{ {
private TextBox _textbox; private TextBox _textbox;
@@ -609,13 +630,8 @@ namespace FlaxEditor.Modules
if (MainWindow) if (MainWindow)
{ {
writer.WriteStartElement("MainWindow"); writer.WriteStartElement("MainWindow");
writer.WriteAttributeString("IsMaximized", MainWindow.IsMaximized.ToString());
writer.WriteStartElement("Bounds");
SaveBounds(writer, MainWindow); SaveBounds(writer, MainWindow);
writer.WriteEndElement(); writer.WriteEndElement();
writer.WriteEndElement();
} }
// Master panel structure // Master panel structure
@@ -628,22 +644,13 @@ namespace FlaxEditor.Modules
{ {
var panel = masterPanel.FloatingPanels[i]; var panel = masterPanel.FloatingPanels[i];
var window = panel.Window; var window = panel.Window;
if (window == null) if (window == null)
{
Editor.LogWarning("Floating panel has missing window");
continue; continue;
}
writer.WriteStartElement("Float"); writer.WriteStartElement("Float");
SavePanel(writer, panel); SavePanel(writer, panel);
writer.WriteStartElement("Bounds");
SaveBounds(writer, window.Window); SaveBounds(writer, window.Window);
writer.WriteEndElement(); writer.WriteEndElement();
writer.WriteEndElement();
} }
writer.WriteEndElement(); writer.WriteEndElement();

View File

@@ -117,7 +117,7 @@ namespace FlaxEditor.Options
/// <summary> /// <summary>
/// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh). /// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).
/// </summary> /// </summary>
[EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")] [EditorDisplay("General"), EditorOrder(200), ExpandGroups, Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
public BuildAction[] BuildActions { get; set; } = public BuildAction[] BuildActions { get; set; } =
{ {
BuildAction.CSG, BuildAction.CSG,

View File

@@ -244,11 +244,11 @@ namespace FlaxEditor.Options
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328), ProgressNormal = Color.FromBgra(0xFF0ad328),
Statusbar = new Style.StatusbarStyle() Statusbar = new Style.StatusbarStyle
{ {
PlayMode = Color.FromBgra(0xFF2F9135), PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424), Failed = Color.FromBgra(0xFF9C2424),
Loading = Color.FromBgra(0xFF2D2D30) Loading = Color.FromBgra(0xFF2D2D30),
}, },
// Fonts // Fonts
@@ -271,7 +271,7 @@ namespace FlaxEditor.Options
Scale = Editor.Icons.Scale32, Scale = Editor.Icons.Scale32,
Scalar = Editor.Icons.Scalar32, Scalar = Editor.Icons.Scalar32,
SharedTooltip = new Tooltip() SharedTooltip = new Tooltip(),
}; };
style.DragWindow = style.BackgroundSelected * 0.7f; style.DragWindow = style.BackgroundSelected * 0.7f;

View File

@@ -19,25 +19,25 @@ namespace FlaxEditor.Progress.Handlers
public ImportAssetsProgress() public ImportAssetsProgress()
{ {
var importing = Editor.Instance.ContentImporting; var importing = Editor.Instance.ContentImporting;
importing.ImportingQueueBegin += OnStart; importing.ImportingQueueBegin += () => FlaxEngine.Scripting.InvokeOnUpdate(OnStart);
importing.ImportingQueueEnd += OnEnd; importing.ImportingQueueEnd += () => FlaxEngine.Scripting.InvokeOnUpdate(OnEnd);
importing.ImportFileBegin += OnImportFileBegin; importing.ImportFileBegin += OnImportFileBegin;
} }
private void OnImportFileBegin(IFileEntryAction importFileEntry) private void OnImportFileBegin(IFileEntryAction importFileEntry)
{ {
string info;
if (importFileEntry is ImportFileEntry) 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 else
_currentInfo = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl); info = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
UpdateProgress(); FlaxEngine.Scripting.InvokeOnUpdate(() =>
} {
_currentInfo = info;
private void UpdateProgress() var importing = Editor.Instance.ContentImporting;
{ var text = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
var importing = Editor.Instance.ContentImporting; OnUpdate(importing.ImportingProgress, text);
var info = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize); });
OnUpdate(importing.ImportingProgress, info);
} }
} }
} }

View File

@@ -16,7 +16,7 @@ namespace FlaxEditor.Progress
/// </summary> /// </summary>
/// <param name="handler">The calling handler.</param> /// <param name="handler">The calling handler.</param>
public delegate void ProgressDelegate(ProgressHandler handler); public delegate void ProgressDelegate(ProgressHandler handler);
/// <summary> /// <summary>
/// Progress failed handler event delegate /// Progress failed handler event delegate
/// </summary> /// </summary>
@@ -127,7 +127,7 @@ namespace FlaxEditor.Progress
{ {
if (!_isActive) if (!_isActive)
throw new InvalidOperationException("Already ended."); throw new InvalidOperationException("Already ended.");
_isActive = false; _isActive = false;
_progress = 0; _progress = 0;
_infoText = string.Empty; _infoText = string.Empty;

View File

@@ -286,81 +286,17 @@ namespace VisualStudio
return "Visual Studio open timout"; return "Visual Studio open timout";
} }
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::ProjectItems>& projectItems, BSTR filePath) static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, 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)
{
HRESULT result; HRESULT result;
ComPtr<EnvDTE::Projects> projects; ComPtr<EnvDTE::ProjectItem> projectItem;
result = solution->get_Projects(&projects); result = solution->FindProjectItem(filePath, &projectItem);
if (FAILED(result)) if (FAILED(result))
return nullptr; return nullptr;
long projectsCount = 0; return projectItem;
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;
}
// Opens a file on a specific line in a running Visual Studio instance. // 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) bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout)
{ {
ScopeLock scopeLock(_locker); ScopeLock scopeLock(_locker);
return _lastSourceCodeEdited > (_lastCompileAction + timeout); return _lastSourceCodeEdited > _lastCompileAction && DateTime::Now() > _lastSourceCodeEdited + timeout;
} }
bool ScriptsBuilder::IsCompiling() bool ScriptsBuilder::IsCompiling()
@@ -266,7 +266,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
bool ScriptsBuilder::GenerateProject(const StringView& customArgs) bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
{ {
String args(TEXT("-log -genproject ")); String args(TEXT("-log -mutex -genproject "));
args += customArgs; args += customArgs;
_wasProjectStructureChanged = false; _wasProjectStructureChanged = false;
return RunBuildTool(args); return RunBuildTool(args);
@@ -669,7 +669,7 @@ void ScriptsBuilderService::Update()
} }
// Check if compile code (if has been edited) // Check if compile code (if has been edited)
const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50); const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(150);
auto mainWindow = Engine::MainWindow; auto mainWindow = Engine::MainWindow;
if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused()) if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused())
{ {

View File

@@ -68,7 +68,7 @@ public:
/// </summary> /// </summary>
/// <param name="timeout">Time to use for checking.</param> /// <param name="timeout">Time to use for checking.</param>
/// <returns>True if source code is dirty, otherwise false.</returns> /// <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> /// <summary>
/// Returns true if scripts are being now compiled/reloaded. /// Returns true if scripts are being now compiled/reloaded.

View File

@@ -34,6 +34,9 @@ namespace FlaxEditor.Surface.Archetypes
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" /> /// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class Sample : SurfaceNode public class Sample : SurfaceNode
{ {
private AssetSelect _assetSelect;
private Box _assetBox;
/// <inheritdoc /> /// <inheritdoc />
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
@@ -54,16 +57,42 @@ namespace FlaxEditor.Surface.Archetypes
base.OnSurfaceLoaded(action); base.OnSurfaceLoaded(action);
if (Surface != null) if (Surface != null)
{
_assetSelect = GetChild<AssetSelect>();
if (TryGetBox(8, out var box))
{
_assetBox = box;
_assetSelect.Visible = !_assetBox.HasAnyConnection;
}
UpdateTitle(); UpdateTitle();
}
} }
private void UpdateTitle() private void UpdateTitle()
{ {
var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]); var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]);
Title = asset?.ShortName ?? "Animation"; if (_assetBox != null)
Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
else
Title = asset?.ShortName ?? "Animation";
var style = Style.Current; var style = Style.Current;
Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160); Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160);
} }
/// <inheritdoc />
public override void ConnectionTick(Box box)
{
base.ConnectionTick(box);
if (_assetBox == null)
return;
if (box.ID != _assetBox.ID)
return;
_assetSelect.Visible = !box.HasAnyConnection;
UpdateTitle();
}
} }
/// <summary> /// <summary>
@@ -305,7 +334,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "Speed", true, typeof(float), 5, 1), NodeElementArchetype.Factory.Input(0, "Speed", true, typeof(float), 5, 1),
NodeElementArchetype.Factory.Input(1, "Loop", true, typeof(bool), 6, 2), NodeElementArchetype.Factory.Input(1, "Loop", true, typeof(bool), 6, 2),
NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 7, 3), NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 7, 3),
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 3, 0, typeof(FlaxEngine.Animation)), NodeElementArchetype.Factory.Input(3, "Animation Asset", true, typeof(FlaxEngine.Animation), 8),
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 0, typeof(FlaxEngine.Animation)),
} }
}, },
new NodeArchetype new NodeArchetype

View File

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

View File

@@ -510,7 +510,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++) for (var i = 0; i < elements.Length; i++)
{ {
if(elements[i].Type != NodeElementType.Output) if (elements[i].Type != NodeElementType.Output)
continue; continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint)) if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
@@ -533,7 +533,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++) for (var i = 0; i < elements.Length; i++)
{ {
if(elements[i].Type != NodeElementType.Input) if (elements[i].Type != NodeElementType.Input)
continue; continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint)) if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
return true; return true;
@@ -725,7 +725,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc /> /// <inheritdoc />
protected override bool UseNormalMaps => false; protected override bool UseNormalMaps => false;
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context) internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
if (inputType == ScriptType.Object) if (inputType == ScriptType.Object)
@@ -743,7 +743,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++) for (var i = 0; i < elements.Length; i++)
{ {
if(elements[i].Type != NodeElementType.Output) if (elements[i].Type != NodeElementType.Output)
continue; continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint)) if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
return true; return true;
@@ -765,7 +765,7 @@ namespace FlaxEditor.Surface.Archetypes
for (var i = 0; i < elements.Length; i++) for (var i = 0; i < elements.Length; i++)
{ {
if(elements[i].Type != NodeElementType.Input) if (elements[i].Type != NodeElementType.Input)
continue; continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint)) if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
return true; return true;
@@ -789,7 +789,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc /> /// <inheritdoc />
protected override bool UseNormalMaps => false; protected override bool UseNormalMaps => false;
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context) internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
if (inputType == ScriptType.Object) if (inputType == ScriptType.Object)
@@ -987,7 +987,7 @@ namespace FlaxEditor.Surface.Archetypes
_combobox.Width = Width - 50; _combobox.Width = Width - 50;
} }
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
return inputType == ScriptType.Void; return inputType == ScriptType.Void;
@@ -997,7 +997,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
if (outputType == ScriptType.Void) if (outputType == ScriptType.Void)
return true; return true;
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]); SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null; ScriptType type = parameter?.Type ?? ScriptType.Null;

View File

@@ -141,7 +141,7 @@ namespace FlaxEditor.Surface
if (_connectionInstigator is Archetypes.Tools.RerouteNode) if (_connectionInstigator is Archetypes.Tools.RerouteNode)
{ {
if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true}) if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
{ {
actualStartPos = endPos; actualStartPos = endPos;
actualEndPos = startPos; actualEndPos = startPos;

View File

@@ -150,6 +150,12 @@ namespace FlaxEditor.Tools.Terrain
return; 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 // Check if no terrain is selected
var terrain = SelectedTerrain; var terrain = SelectedTerrain;
if (!terrain) if (!terrain)

View File

@@ -158,6 +158,12 @@ namespace FlaxEditor.Tools.Terrain
return; 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 // Check if selected terrain was changed during painting
if (terrain != _paintTerrain && IsPainting) if (terrain != _paintTerrain && IsPainting)
{ {

View File

@@ -49,7 +49,16 @@ namespace FlaxEditor.Actions
_scriptTypeName = script.TypeName; _scriptTypeName = script.TypeName;
_prefabId = script.PrefabID; _prefabId = script.PrefabID;
_prefabObjectId = script.PrefabObjectID; _prefabObjectId = script.PrefabObjectID;
_scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script); try
{
_scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script);
}
catch (Exception ex)
{
_scriptData = null;
Debug.LogError("Failed to serialize script data for Undo due to exception");
Debug.LogException(ex);
}
_parentId = script.Actor.ID; _parentId = script.Actor.ID;
_orderInParent = script.OrderInParent; _orderInParent = script.OrderInParent;
_enabled = script.Enabled; _enabled = script.Enabled;
@@ -66,6 +75,11 @@ namespace FlaxEditor.Actions
_enabled = true; _enabled = true;
} }
public int GetOrderInParent()
{
return _orderInParent;
}
/// <summary> /// <summary>
/// Creates a new added script undo action. /// Creates a new added script undo action.
/// </summary> /// </summary>
@@ -175,6 +189,7 @@ namespace FlaxEditor.Actions
script.Parent = parentActor; script.Parent = parentActor;
if (_orderInParent != -1) if (_orderInParent != -1)
script.OrderInParent = _orderInParent; script.OrderInParent = _orderInParent;
_orderInParent = script.OrderInParent; // Ensure order is correct for script that want to use it later
if (_prefabObjectId != Guid.Empty) if (_prefabObjectId != Guid.Empty)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId); SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId);
Editor.Instance.Scene.MarkSceneEdited(parentActor.Scene); 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 // - icon/cursor/etc data
std::fstream stream; std::fstream stream;
#if PLATFORM_WINDOWS
stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
#else
StringAsANSI<> pathAnsi(path.Get()); StringAsANSI<> pathAnsi(path.Get());
stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
#endif
if (!stream.is_open()) if (!stream.is_open())
{ {
LOG(Warning, "Cannot open file"); LOG(Warning, "Cannot open file");

View File

@@ -73,6 +73,7 @@ Color32 ScreenUtilities::GetColorAt(const Float2& pos)
outputColor.R = color.red / 256; outputColor.R = color.red / 256;
outputColor.G = color.green / 256; outputColor.G = color.green / 256;
outputColor.B = color.blue / 256; outputColor.B = color.blue / 256;
outputColor.A = 255;
return outputColor; return outputColor;
} }

View File

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

View File

@@ -593,12 +593,14 @@ namespace FlaxEditor.Viewport
{ {
ref var vv = ref v.Options[j]; ref var vv = ref v.Options[j];
var button = childMenu.AddButton(vv.Name); var button = childMenu.AddButton(vv.Name);
button.CloseMenuOnClick = false;
button.Tag = vv.Mode; button.Tag = vv.Mode;
} }
} }
else else
{ {
var button = debugView.AddButton(v.Name); var button = debugView.AddButton(v.Name);
button.CloseMenuOnClick = false;
button.Tag = v.Mode; button.Tag = v.Mode;
} }
} }
@@ -1119,7 +1121,12 @@ namespace FlaxEditor.Viewport
var win = (WindowRootControl)Root; var win = (WindowRootControl)Root;
// Get current mouse position in the view // 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 // Update input
var window = win.Window; var window = win.Window;
@@ -1582,7 +1589,14 @@ namespace FlaxEditor.Viewport
private void WidgetViewModeShowHideClicked(ContextMenuButton button) private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{ {
if (button.Tag is ViewMode v) if (button.Tag is ViewMode v)
{
Task.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) private void WidgetViewModeShowHide(Control cm)
@@ -1594,7 +1608,7 @@ namespace FlaxEditor.Viewport
foreach (var e in ccm.Items) foreach (var e in ccm.Items)
{ {
if (e is ContextMenuButton b && b.Tag is ViewMode v) 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; var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
} }
/// <inheritdoc />
public EditorViewport Viewport => this;
/// <inheritdoc /> /// <inheritdoc />
public GizmosCollection Gizmos { get; } public GizmosCollection Gizmos { get; }

View File

@@ -171,7 +171,7 @@ namespace FlaxEditor.Viewport.Previews
case DrawModes.Fill: case DrawModes.Fill:
clipsInView = 1.0f; clipsInView = 1.0f;
clipWidth = width; clipWidth = width;
samplesPerIndex = (uint)(samplesPerChannel / width); samplesPerIndex = (uint)(samplesPerChannel / width) * info.NumChannels;
break; break;
case DrawModes.Single: case DrawModes.Single:
clipsInView = Mathf.Min(clipsInView, 1.0f); clipsInView = Mathf.Min(clipsInView, 1.0f);

View File

@@ -97,7 +97,7 @@ namespace FlaxEditor
if (_highlightMaterial == null if (_highlightMaterial == null
|| (_highlights.Count == 0 && _highlightTriangles.Count == 0) || (_highlights.Count == 0 && _highlightTriangles.Count == 0)
|| renderContext.View.Pass == DrawPass.Depth || renderContext.View.Pass == DrawPass.Depth
) )
return; return;
Profiler.BeginEvent("ViewportDebugDrawData.OnDraw"); Profiler.BeginEvent("ViewportDebugDrawData.OnDraw");

View File

@@ -388,14 +388,16 @@ namespace FlaxEditor.Windows.Assets
protected override void OnShow() protected override void OnShow()
{ {
// Check if has no asset (but has item linked) // Check if has no asset (but has item linked)
if (_asset == null && _item != null) var item = _item;
if (_asset == null && item != null)
{ {
// Load asset // Load asset
_asset = LoadAsset(); _asset = LoadAsset();
if (_asset == null) if (_asset == null)
{ {
Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", _item.Path, typeof(T))); Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", item.Path, typeof(T)));
Close(); Close();
Editor.ContentDatabase.RefreshFolder(item, false);
return; return;
} }

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 /> /// <inheritdoc />
public override bool UseLayoutData => true; public override bool UseLayoutData => true;

View File

@@ -521,8 +521,11 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc /> /// <inheritdoc />
protected override void OnClose() protected override void OnClose()
{ {
// Discard unsaved changes if (Asset)
_properties.DiscardChanges(); {
// Discard unsaved changes
_properties.DiscardChanges();
}
// Cleanup // Cleanup
_undo.Clear(); _undo.Clear();

View File

@@ -153,7 +153,7 @@ namespace FlaxEditor.Windows.Assets
{ {
OnPasteAction(pasteAction); OnPasteAction(pasteAction);
} }
// Scroll to new selected node // Scroll to new selected node
ScrollToSelectedNode(); ScrollToSelectedNode();
} }
@@ -183,7 +183,7 @@ namespace FlaxEditor.Windows.Assets
{ {
OnPasteAction(pasteAction); OnPasteAction(pasteAction);
} }
// Scroll to new selected node // Scroll to new selected node
ScrollToSelectedNode(); ScrollToSelectedNode();
} }
@@ -334,7 +334,7 @@ namespace FlaxEditor.Windows.Assets
}, action2.ActionString); }, action2.ActionString);
action.Do(); action.Do();
Undo.AddAction(action); Undo.AddAction(action);
_treePanel.PerformLayout(); _treePanel.PerformLayout();
_treePanel.PerformLayout(); _treePanel.PerformLayout();
} }

View File

@@ -207,7 +207,7 @@ namespace FlaxEditor.Windows.Assets
InputActions.Add(options => options.Rename, Rename); InputActions.Add(options => options.Rename, Rename);
InputActions.Add(options => options.FocusSelection, _viewport.FocusSelection); InputActions.Add(options => options.FocusSelection, _viewport.FocusSelection);
} }
/// <summary> /// <summary>
/// Enables or disables vertical and horizontal scrolling on the tree panel. /// Enables or disables vertical and horizontal scrolling on the tree panel.
/// </summary> /// </summary>
@@ -257,7 +257,7 @@ namespace FlaxEditor.Windows.Assets
{ {
if (base.OnMouseUp(location, button)) if (base.OnMouseUp(location, button))
return true; return true;
if (button == MouseButton.Right && _treePanel.ContainsPoint(ref location)) if (button == MouseButton.Right && _treePanel.ContainsPoint(ref location))
{ {
_tree.Deselect(); _tree.Deselect();

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting; 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 // Show it
cm.Show(this, location); cm.Show(this, location);
} }

View File

@@ -629,8 +629,9 @@ namespace FlaxEditor.Windows
if (items.Count == 0) if (items.Count == 0)
return; 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); 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 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) ? 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 FlaxEditor.GUI;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Networking;
namespace FlaxEngine namespace FlaxEngine
{ {
@@ -37,11 +38,14 @@ namespace FlaxEditor.Windows.Profiler
{ {
private readonly SingleChart _dataSentChart; private readonly SingleChart _dataSentChart;
private readonly SingleChart _dataReceivedChart; private readonly SingleChart _dataReceivedChart;
private readonly SingleChart _dataSentRateChart;
private readonly SingleChart _dataReceivedRateChart;
private readonly Table _tableRpc; private readonly Table _tableRpc;
private readonly Table _tableRep; private readonly Table _tableRep;
private SamplesBuffer<ProfilingTools.NetworkEventStat[]> _events;
private List<Row> _tableRowsCache; private List<Row> _tableRowsCache;
private FlaxEngine.Networking.NetworkDriverStats _prevStats; private SamplesBuffer<ProfilingTools.NetworkEventStat[]> _events;
private NetworkDriverStats _prevStats;
private List<NetworkDriverStats> _stats;
public Network() public Network()
: base("Network") : base("Network")
@@ -76,6 +80,20 @@ namespace FlaxEditor.Windows.Profiler
Parent = layout, Parent = layout,
}; };
_dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged; _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 // Tables
_tableRpc = InitTable(layout, "RPC Name"); _tableRpc = InitTable(layout, "RPC Name");
@@ -87,24 +105,52 @@ namespace FlaxEditor.Windows.Profiler
{ {
_dataSentChart.Clear(); _dataSentChart.Clear();
_dataReceivedChart.Clear(); _dataReceivedChart.Clear();
_dataSentRateChart.Clear();
_dataReceivedRateChart.Clear();
_events?.Clear(); _events?.Clear();
_stats?.Clear();
_prevStats = new NetworkDriverStats();
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Update(ref SharedUpdateData sharedData) public override void Update(ref SharedUpdateData sharedData)
{ {
// Gather peer stats // Gather peer stats
var peers = FlaxEngine.Networking.NetworkPeer.Peers; var peers = NetworkPeer.Peers;
var stats = new FlaxEngine.Networking.NetworkDriverStats(); var thisStats = new NetworkDriverStats();
thisStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT
foreach (var peer in peers) foreach (var peer in peers)
{ {
var peerStats = peer.NetworkDriver.GetStats(); var peerStats = peer.NetworkDriver.GetStats();
stats.TotalDataSent += peerStats.TotalDataSent; thisStats.TotalDataSent += peerStats.TotalDataSent;
stats.TotalDataReceived += peerStats.TotalDataReceived; thisStats.TotalDataReceived += peerStats.TotalDataReceived;
} }
_dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0)); var stats = thisStats;
_dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0)); stats.TotalDataSent = (uint)Mathf.Max((long)thisStats.TotalDataSent - (long)_prevStats.TotalDataSent, 0);
_prevStats = stats; 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 // Gather network events
var events = ProfilingTools.EventsNetwork; var events = ProfilingTools.EventsNetwork;
@@ -118,6 +164,8 @@ namespace FlaxEditor.Windows.Profiler
{ {
_dataSentChart.SelectedSampleIndex = selectedFrame; _dataSentChart.SelectedSampleIndex = selectedFrame;
_dataReceivedChart.SelectedSampleIndex = selectedFrame; _dataReceivedChart.SelectedSampleIndex = selectedFrame;
_dataSentRateChart.SelectedSampleIndex = selectedFrame;
_dataReceivedRateChart.SelectedSampleIndex = selectedFrame;
// Update events tables // Update events tables
if (_events != null) if (_events != null)
@@ -128,7 +176,7 @@ namespace FlaxEditor.Windows.Profiler
_tableRep.IsLayoutLocked = true; _tableRep.IsLayoutLocked = true;
RecycleTableRows(_tableRpc, _tableRowsCache); RecycleTableRows(_tableRpc, _tableRowsCache);
RecycleTableRows(_tableRep, _tableRowsCache); RecycleTableRows(_tableRep, _tableRowsCache);
var events = _events.Get(selectedFrame); var events = _events.Get(selectedFrame);
var rowCount = Int2.Zero; var rowCount = Int2.Zero;
if (events != null && events.Length != 0) if (events != null && events.Length != 0)
@@ -186,7 +234,7 @@ namespace FlaxEditor.Windows.Profiler
_tableRep.Visible = rowCount.Y != 0; _tableRep.Visible = rowCount.Y != 0;
_tableRpc.Children.Sort(SortRows); _tableRpc.Children.Sort(SortRows);
_tableRep.Children.Sort(SortRows); _tableRep.Children.Sort(SortRows);
_tableRpc.UnlockChildrenRecursive(); _tableRpc.UnlockChildrenRecursive();
_tableRpc.PerformLayout(); _tableRpc.PerformLayout();
_tableRep.UnlockChildrenRecursive(); _tableRep.UnlockChildrenRecursive();
@@ -257,6 +305,11 @@ namespace FlaxEditor.Windows.Profiler
return Utilities.Utils.FormatBytesCount((ulong)v); 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) private static string FormatCellBytes(object x)
{ {
return Utilities.Utils.FormatBytesCount((int)x); return Utilities.Utils.FormatBytesCount((int)x);

View File

@@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler
/// <returns>The sample value</returns> /// <returns>The sample value</returns>
public T Get(int index) public T Get(int index)
{ {
if (index >= _data.Length || _data.Length == 0) if (_count == 0 || index >= _data.Length || _data.Length == 0)
return default; return default;
return index == -1 ? _data[_count - 1] : _data[index]; return index == -1 ? _data[_count - 1] : _data[index];
} }

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "AnimGraph.h" #include "AnimGraph.h"
#include "Engine/Core/Types/VariantValueCast.h"
#include "Engine/Content/Assets/Animation.h" #include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Assets/SkeletonMask.h" #include "Engine/Content/Assets/SkeletonMask.h"
#include "Engine/Content/Assets/AnimationGraphFunction.h" #include "Engine/Content/Assets/AnimationGraphFunction.h"
@@ -512,18 +513,6 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
transitionIndex++; transitionIndex++;
continue; continue;
} }
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (transition.RuleGraph && !useDefaultRule)
{
// Execute transition rule
auto rootNode = transition.RuleGraph->GetRootNode();
ASSERT(rootNode);
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
transitionIndex++;
continue;
}
}
// Evaluate source state transition data (position, length, etc.) // Evaluate source state transition data (position, length, etc.)
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState); const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
@@ -543,6 +532,19 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
transitionData.Length = ZeroTolerance; transitionData.Length = ZeroTolerance;
} }
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (transition.RuleGraph && !useDefaultRule)
{
// Execute transition rule
auto rootNode = transition.RuleGraph->GetRootNode();
ASSERT(rootNode);
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
transitionIndex++;
continue;
}
}
// Check if can trigger the transition // Check if can trigger the transition
bool canEnter = false; bool canEnter = false;
if (useDefaultRule) if (useDefaultRule)
@@ -749,9 +751,16 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Animation // Animation
case 2: case 2:
{ {
const auto anim = node->Assets[0].As<Animation>(); auto anim = node->Assets[0].As<Animation>();
auto& bucket = context.Data->State[node->BucketIndex].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) switch (box->ID)
{ {
// Animation // Animation

View File

@@ -187,7 +187,6 @@ float AudioSource::GetTime() const
return 0.0f; return 0.0f;
float time = AudioBackend::Source::GetCurrentBufferTime(this); float time = AudioBackend::Source::GetCurrentBufferTime(this);
ASSERT(time >= 0.0f && time <= Clip->GetLength());
if (UseStreaming()) if (UseStreaming())
{ {

View File

@@ -33,7 +33,8 @@
int alError = alGetError(); \ int alError = alGetError(); \
if (alError != 0) \ 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 #endif
@@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return 0; 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) void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
{ {
#if ALC_MULTIPLE_LISTENERS #if ALC_MULTIPLE_LISTENERS
@@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init()
// Init // Init
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor); Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model 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); Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize #ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize")) if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))

View File

@@ -27,7 +27,15 @@
#define MAX_INPUT_CHANNELS 2 #define MAX_INPUT_CHANNELS 2
#define MAX_OUTPUT_CHANNELS 8 #define MAX_OUTPUT_CHANNELS 8
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS) #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_COORD_SCALE 0.01f // units are meters
#define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE #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) #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 COM_DECLSPEC_NOTHROW void STDMETHODCALLTYPE OnVoiceError(THIS_ void* pBufferContext, HRESULT Error) override
{ {
#if ENABLE_ASSERTION
LOG(Warning, "IXAudio2VoiceCallback::OnVoiceError! Error: 0x{0:x}", Error); LOG(Warning, "IXAudio2VoiceCallback::OnVoiceError! Error: 0x{0:x}", Error);
#endif
} }
public: public:
@@ -121,7 +131,8 @@ namespace XAudio2
XAUDIO2_SEND_DESCRIPTOR Destination; XAUDIO2_SEND_DESCRIPTOR Destination;
float Pitch; float Pitch;
float Pan; float Pan;
float StartTime; float StartTimeForQueueBuffer;
float LastBufferStartTime;
float DopplerFactor; float DopplerFactor;
uint64 LastBufferStartSamplesPlayed; uint64 LastBufferStartSamplesPlayed;
int32 BuffersProcessed; int32 BuffersProcessed;
@@ -145,7 +156,8 @@ namespace XAudio2
Destination.pOutputVoice = nullptr; Destination.pOutputVoice = nullptr;
Pitch = 1.0f; Pitch = 1.0f;
Pan = 0.0f; Pan = 0.0f;
StartTime = 0.0f; StartTimeForQueueBuffer = 0.0f;
LastBufferStartTime = 0.0f;
IsDirty = false; IsDirty = false;
Is3D = false; Is3D = false;
IsPlaying = false; IsPlaying = false;
@@ -255,18 +267,18 @@ namespace XAudio2
buffer.pAudioData = aBuffer->Data.Get(); buffer.pAudioData = aBuffer->Data.Get();
buffer.AudioBytes = aBuffer->Data.Count(); buffer.AudioBytes = aBuffer->Data.Count();
if (aSource->StartTime > ZeroTolerance) if (aSource->StartTimeForQueueBuffer > ZeroTolerance)
{ {
buffer.PlayBegin = (UINT32)(aSource->StartTime * (aBuffer->Info.SampleRate * aBuffer->Info.NumChannels)); // Offset start position when playing buffer with a custom time offset
buffer.PlayLength = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels - buffer.PlayBegin; const uint32 bytesPerSample = aBuffer->Info.BitDepth / 8 * aBuffer->Info.NumChannels;
aSource->StartTime = 0; 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); const HRESULT hr = aSource->Voice->SubmitSourceBuffer(&buffer);
if (FAILED(hr)) XAUDIO2_CHECK_ERROR(SubmitSourceBuffer);
{
LOG(Warning, "XAudio2: Failed to submit source buffer (error: 0x{0:x})", hr);
}
} }
void VoiceCallback::OnBufferEnd(void* pBufferContext) void VoiceCallback::OnBufferEnd(void* pBufferContext)
@@ -375,7 +387,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
const auto& header = clip->AudioHeader; const auto& header = clip->AudioHeader;
auto& format = aSource->Format; auto& format = aSource->Format;
format.wFormatTag = WAVE_FORMAT_PCM; 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.nSamplesPerSec = header.Info.SampleRate;
format.wBitsPerSample = header.Info.BitDepth; format.wBitsPerSample = header.Info.BitDepth;
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8)); format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8));
@@ -391,12 +403,10 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
1, 1,
&aSource->Destination &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)) if (FAILED(hr))
{
LOG(Error, "Failed to create XAudio2 voice. Error: 0x{0:x}", hr);
return; return;
}
// Prepare source state // Prepare source state
aSource->Callback.Source = source; aSource->Callback.Source = source;
@@ -410,7 +420,8 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
aSource->DopplerFactor = source->GetDopplerFactor(); aSource->DopplerFactor = source->GetDopplerFactor();
aSource->UpdateTransform(source); aSource->UpdateTransform(source);
aSource->UpdateVelocity(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 // 0 is invalid ID so shift them
sourceID++; sourceID++;
@@ -451,7 +462,8 @@ void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source)
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice) 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::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock(); XAudio2::Locker.Unlock();
HRESULT hr;
const bool isPlaying = source->IsActuallyPlayingSth(); const bool isPlaying = source->IsActuallyPlayingSth();
if (isPlaying) 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->LastBufferStartSamplesPlayed = 0;
aSource->LastBufferStartTime = 0;
aSource->BuffersProcessed = 0; aSource->BuffersProcessed = 0;
XAUDIO2_BUFFER buffer = { 0 }; XAUDIO2_BUFFER buffer = { 0 };
@@ -512,12 +530,15 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels; const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels;
buffer.PlayBegin = state.SamplesPlayed % totalSamples; buffer.PlayBegin = state.SamplesPlayed % totalSamples;
buffer.PlayLength = totalSamples - buffer.PlayBegin; buffer.PlayLength = totalSamples - buffer.PlayBegin;
aSource->StartTime = 0; aSource->StartTimeForQueueBuffer = 0;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer); XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
if (isPlaying) if (isPlaying)
aSource->Voice->Start(); {
hr = aSource->Voice->Start();
XAUDIO2_CHECK_ERROR(Start);
}
} }
void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source) void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source)
@@ -572,7 +593,8 @@ void AudioBackendXAudio2::Source_Play(AudioSource* source)
if (aSource && aSource->Voice && !aSource->IsPlaying) if (aSource && aSource->Voice && !aSource->IsPlaying)
{ {
// Play // Play
aSource->Voice->Start(); const HRESULT hr = aSource->Voice->Start();
XAUDIO2_CHECK_ERROR(Start);
aSource->IsPlaying = true; aSource->IsPlaying = true;
} }
} }
@@ -583,7 +605,8 @@ void AudioBackendXAudio2::Source_Pause(AudioSource* source)
if (aSource && aSource->Voice && aSource->IsPlaying) if (aSource && aSource->Voice && aSource->IsPlaying)
{ {
// Pause // Pause
aSource->Voice->Stop(); const HRESULT hr = aSource->Voice->Stop();
XAUDIO2_CHECK_ERROR(Stop);
aSource->IsPlaying = false; aSource->IsPlaying = false;
} }
} }
@@ -593,14 +616,18 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
aSource->StartTime = 0.0f; aSource->StartTimeForQueueBuffer = 0.0f;
aSource->LastBufferStartTime = 0.0f;
// Pause // Pause
aSource->Voice->Stop(); HRESULT hr = aSource->Voice->Stop();
XAUDIO2_CHECK_ERROR(Stop);
aSource->IsPlaying = false; aSource->IsPlaying = false;
// Unset streaming buffers to rewind // 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->BuffersProcessed = 0;
aSource->Callback.PeekSamples(); aSource->Callback.PeekSamples();
} }
@@ -612,7 +639,7 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float
if (aSource) if (aSource)
{ {
// Store start time so next buffer submitted will start from here (assumes audio is stopped) // 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); aSource->Voice->GetState(&state);
const uint32 numChannels = clipInfo.NumChannels; const uint32 numChannels = clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / 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 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; return time;
} }
@@ -697,10 +725,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
const HRESULT hr = aSource->Voice->FlushSourceBuffers(); const HRESULT hr = aSource->Voice->FlushSourceBuffers();
if (FAILED(hr)) XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
{
LOG(Warning, "XAudio2: FlushSourceBuffers failed. Error: 0x{0:x}", hr);
}
aSource->BuffersProcessed = 0; 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::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock(); XAudio2::Locker.Unlock();
const uint32 bytesPerSample = info.BitDepth / 8; const uint32 samplesLength = info.NumSamples * info.BitDepth / 8;
const int32 samplesLength = info.NumSamples * bytesPerSample;
aBuffer->Info = info; aBuffer->Info = info;
aBuffer->Data.Set(samples, samplesLength); aBuffer->Data.Set(samples, samplesLength);
@@ -779,7 +803,8 @@ void AudioBackendXAudio2::Base_SetVolume(float value)
{ {
if (XAudio2::MasteringVoice) 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() void Asset::startLoading()
{ {
// Check if is already loaded ASSERT(!IsLoaded());
if (IsLoaded())
return;
// Start loading (using async tasks)
ASSERT(_loadingTask == nullptr); ASSERT(_loadingTask == nullptr);
_loadingTask = createLoadingTask(); _loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr); ASSERT(_loadingTask != nullptr);

View File

@@ -20,6 +20,7 @@
#include "Engine/ShadersCompilation/Config.h" #include "Engine/ShadersCompilation/Config.h"
#if BUILD_DEBUG #if BUILD_DEBUG
#include "Engine/Engine/Globals.h" #include "Engine/Engine/Globals.h"
#include "Engine/Scripting/BinaryModule.h"
#endif #endif
#endif #endif
@@ -256,7 +257,9 @@ Asset::LoadResult Material::load()
#if BUILD_DEBUG && USE_EDITOR #if BUILD_DEBUG && USE_EDITOR
// Dump generated material source to the temporary file // Dump generated material source to the temporary file
BinaryModule::Locker.Lock();
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt")); source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt"));
BinaryModule::Locker.Unlock();
#endif #endif
// Encrypt source code // Encrypt source code

View File

@@ -154,7 +154,7 @@ void ContentService::LateUpdate()
// Unload marked assets // Unload marked assets
for (int32 i = 0; i < ToUnload.Count(); i++) for (int32 i = 0; i < ToUnload.Count(); i++)
{ {
Asset* asset = ToUnload[i]; Asset* asset = ToUnload[i];
// Check if has no references // Check if has no references
if (asset->GetReferencesCount() <= 0) if (asset->GetReferencesCount() <= 0)
@@ -521,37 +521,33 @@ Asset* Content::GetAsset(const Guid& id)
void Content::DeleteAsset(Asset* asset) void Content::DeleteAsset(Asset* asset)
{ {
ScopeLock locker(AssetsLocker);
// Validate
if (asset == nullptr || asset->_deleteFileOnUnload) if (asset == nullptr || asset->_deleteFileOnUnload)
{
// Back
return; return;
}
LOG(Info, "Deleting asset {0}...", asset->ToString()); 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) // Mark asset for delete queue (delete it after auto unload)
asset->_deleteFileOnUnload = true; asset->_deleteFileOnUnload = true;
// Unload // Unload
UnloadAsset(asset); asset->DeleteObject();
} }
void Content::DeleteAsset(const StringView& path) void Content::DeleteAsset(const StringView& path)
{ {
ScopeLock locker(AssetsLocker); // Try to delete already loaded asset
// Check if is loaded
Asset* asset = GetAsset(path); Asset* asset = GetAsset(path);
if (asset != nullptr) if (asset != nullptr)
{ {
// Delete asset
DeleteAsset(asset); DeleteAsset(asset);
return; return;
} }
ScopeLock locker(AssetsLocker);
// Remove from registry // Remove from registry
AssetInfo info; AssetInfo info;
if (Cache.DeleteAsset(path, &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 // Check if given id is invalid
if (!id.IsValid()) if (!id.IsValid())
{ {
// Cancel operation
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path); LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
return; return;
} }
@@ -585,7 +580,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
storage->CloseFileHandles(); // Close file handle to allow removing it storage->CloseFileHandles(); // Close file handle to allow removing it
if (!storage->HasAsset(id)) if (!storage->HasAsset(id))
{ {
// Skip removing
LOG(Warning, "Cannot remove file \'{0}\'. It doesn\'t contain asset {1}.", path, id); LOG(Warning, "Cannot remove file \'{0}\'. It doesn\'t contain asset {1}.", path, id);
return; return;
} }
@@ -703,13 +697,11 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
LOG(Warning, "Cannot copy file to destination."); LOG(Warning, "Cannot copy file to destination.");
return true; return true;
} }
if (JsonStorageProxy::ChangeId(dstPath, dstId)) if (JsonStorageProxy::ChangeId(dstPath, dstId))
{ {
LOG(Warning, "Cannot change asset ID."); LOG(Warning, "Cannot change asset ID.");
return true; return true;
} }
return false; return false;
} }
@@ -774,12 +766,9 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath); FileSystem::DeleteFile(tmpPath);
// Reload storage // Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath))
{ {
auto storage = ContentStorageManager::GetStorage(dstPath); storage->Reload();
if (storage)
{
storage->Reload();
}
} }
} }
@@ -790,10 +779,8 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
void Content::UnloadAsset(Asset* asset) void Content::UnloadAsset(Asset* asset)
{ {
// Check input
if (asset == nullptr) if (asset == nullptr)
return; return;
asset->DeleteObject(); asset->DeleteObject();
} }
@@ -919,12 +906,8 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script
Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{ {
// Early out
if (!id.IsValid()) if (!id.IsValid())
{
// Back
return nullptr; return nullptr;
}
// Check if asset has been already loaded // Check if asset has been already loaded
Asset* result = GetAsset(id); 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()); LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString());
return nullptr; return nullptr;
} }
return result; return result;
} }
@@ -954,12 +936,8 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LoadCallAssetsLocker.Lock(); LoadCallAssetsLocker.Lock();
const bool contains = LoadCallAssets.Contains(id); const bool contains = LoadCallAssets.Contains(id);
LoadCallAssetsLocker.Unlock(); LoadCallAssetsLocker.Unlock();
if (!contains) if (!contains)
{
return GetAsset(id); return GetAsset(id);
}
Platform::Sleep(1); Platform::Sleep(1);
} }
} }
@@ -967,7 +945,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{ {
// Mark asset as loading // Mark asset as loading
LoadCallAssets.Add(id); LoadCallAssets.Add(id);
LoadCallAssetsLocker.Unlock(); LoadCallAssetsLocker.Unlock();
} }
@@ -988,7 +965,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
// Get cached asset info (from registry) // Get cached asset info (from registry)
if (!GetAssetInfo(id, assetInfo)) 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; return nullptr;
} }
@@ -1032,11 +1009,13 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
ASSERT(!Assets.ContainsKey(id)); ASSERT(!Assets.ContainsKey(id));
#endif #endif
Assets.Add(id, result); Assets.Add(id, result);
AssetsLocker.Unlock();
// Start asset loading // Start asset loading
// TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization
result->startLoading(); result->startLoading();
AssetsLocker.Unlock();
return result; return result;
} }

View File

@@ -31,10 +31,13 @@ public:
if (Asset) if (Asset)
{ {
Asset->Locker.Lock(); Asset->Locker.Lock();
Asset->_loadFailed = true; if (Asset->_loadingTask == this)
Asset->_isLoaded = false; {
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed)); Asset->_loadFailed = true;
Asset->_loadingTask = nullptr; Asset->_isLoaded = false;
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
Asset->_loadingTask = nullptr;
}
Asset->Locker.Unlock(); Asset->Locker.Unlock();
} }
} }
@@ -73,7 +76,10 @@ protected:
{ {
if (Asset) if (Asset)
{ {
Asset->_loadingTask = nullptr; Asset->Locker.Lock();
if (Asset->_loadingTask == this)
Asset->_loadingTask = nullptr;
Asset->Locker.Unlock();
Asset = nullptr; Asset = nullptr;
} }
@@ -84,7 +90,10 @@ protected:
{ {
if (Asset) if (Asset)
{ {
Asset->_loadingTask = nullptr; Asset->Locker.Lock();
if (Asset->_loadingTask == this)
Asset->_loadingTask = nullptr;
Asset->Locker.Unlock();
Asset = nullptr; Asset = nullptr;
} }

View File

@@ -124,7 +124,8 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
// Import model file // Import model file
ModelData modelData; ModelData modelData;
String errorMsg; String errorMsg;
String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath); String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput)) if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
{ {
LOG(Error, "Cannot import model file. {0}", errorMsg); LOG(Error, "Cannot import model file. {0}", errorMsg);

View File

@@ -100,7 +100,7 @@ public:
int32 _chunkIndex; int32 _chunkIndex;
int32 _index; int32 _index;
Iterator(ChunkedArray const* collection, const int32 index) Iterator(const ChunkedArray* collection, const int32 index)
: _collection(const_cast<ChunkedArray*>(collection)) : _collection(const_cast<ChunkedArray*>(collection))
, _chunkIndex(index / ChunkSize) , _chunkIndex(index / ChunkSize)
, _index(index % ChunkSize) , _index(index % ChunkSize)
@@ -122,29 +122,29 @@ public:
{ {
} }
public: Iterator(Iterator&& i)
FORCE_INLINE ChunkedArray* GetChunkedArray() const : _collection(i._collection)
, _chunkIndex(i._chunkIndex)
, _index(i._index)
{ {
return _collection;
} }
public:
FORCE_INLINE int32 Index() const FORCE_INLINE int32 Index() const
{ {
return _chunkIndex * ChunkSize + _index; return _chunkIndex * ChunkSize + _index;
} }
public: FORCE_INLINE bool IsEnd() const
bool IsEnd() const
{ {
return Index() == _collection->Count(); return (_chunkIndex * ChunkSize + _index) == _collection->_count;
} }
bool IsNotEnd() const FORCE_INLINE bool IsNotEnd() const
{ {
return Index() != _collection->Count(); return (_chunkIndex * ChunkSize + _index) != _collection->_count;
} }
public:
FORCE_INLINE T& operator*() const FORCE_INLINE T& operator*() const
{ {
return _collection->_chunks[_chunkIndex]->At(_index); return _collection->_chunks[_chunkIndex]->At(_index);
@@ -155,7 +155,6 @@ public:
return &_collection->_chunks[_chunkIndex]->At(_index); return &_collection->_chunks[_chunkIndex]->At(_index);
} }
public:
FORCE_INLINE bool operator==(const Iterator& v) const FORCE_INLINE bool operator==(const Iterator& v) const
{ {
return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index; return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index;
@@ -166,17 +165,22 @@ public:
return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index; return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index;
} }
public: Iterator& operator=(const Iterator& v)
{
_collection = v._collection;
_chunkIndex = v._chunkIndex;
_index = v._index;
return *this;
}
Iterator& operator++() Iterator& operator++()
{ {
// Check if it is not at end // Check if it is not at end
const int32 end = _collection->Count(); if ((_chunkIndex * ChunkSize + _index) != _collection->_count)
if (Index() != end)
{ {
// Move forward within chunk // Move forward within chunk
_index++; _index++;
// Check if need to change chunk
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1) if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
{ {
// Move to next chunk // Move to next chunk
@@ -189,9 +193,9 @@ public:
Iterator operator++(int) Iterator operator++(int)
{ {
Iterator temp = *this; Iterator i = *this;
++temp; ++i;
return temp; return i;
} }
Iterator& operator--() Iterator& operator--()
@@ -199,7 +203,6 @@ public:
// Check if it's not at beginning // Check if it's not at beginning
if (_index != 0 || _chunkIndex != 0) if (_index != 0 || _chunkIndex != 0)
{ {
// Check if need to change chunk
if (_index == 0) if (_index == 0)
{ {
// Move to previous chunk // Move to previous chunk
@@ -217,9 +220,9 @@ public:
Iterator operator--(int) Iterator operator--(int)
{ {
Iterator temp = *this; Iterator i = *this;
--temp; --i;
return temp; return i;
} }
}; };
@@ -294,7 +297,7 @@ public:
{ {
if (IsEmpty()) if (IsEmpty())
return; return;
ASSERT(i.GetChunkedArray() == this); ASSERT(i._collection == this);
ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize); ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize);
ASSERT(i.Index() < Count()); ASSERT(i.Index() < Count());
@@ -432,11 +435,31 @@ public:
Iterator End() const Iterator End() const
{ {
return Iterator(this, Count()); return Iterator(this, _count);
} }
Iterator IteratorAt(int32 index) const Iterator IteratorAt(int32 index) const
{ {
return Iterator(this, index); return Iterator(this, index);
} }
FORCE_INLINE Iterator begin()
{
return Iterator(this, 0);
}
FORCE_INLINE Iterator end()
{
return Iterator(this, _count);
}
FORCE_INLINE const Iterator begin() const
{
return Iterator(this, 0);
}
FORCE_INLINE const Iterator end() const
{
return Iterator(this, _count);
}
}; };

View File

@@ -4,6 +4,7 @@
#include "Engine/Core/Config/Settings.h" #include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Content/Asset.h" #include "Engine/Content/Asset.h"
#include "Engine/Content/AssetReference.h" #include "Engine/Content/AssetReference.h"
#include "Engine/Content/SceneReference.h" #include "Engine/Content/SceneReference.h"
@@ -76,6 +77,12 @@ public:
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")") API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
bool ShadersGenerateDebugData = false; 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> /// <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. /// 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> /// </summary>
@@ -106,6 +113,7 @@ public:
DESERIALIZE(AdditionalAssetFolders); DESERIALIZE(AdditionalAssetFolders);
DESERIALIZE(ShadersNoOptimize); DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData); DESERIALIZE(ShadersGenerateDebugData);
DESERIALIZE(SkipDefaultFonts);
DESERIALIZE(SkipDotnetPackaging); DESERIALIZE(SkipDotnetPackaging);
DESERIALIZE(SkipUnusedDotnetLibsPackaging); DESERIALIZE(SkipUnusedDotnetLibsPackaging);
} }

View File

@@ -3,16 +3,15 @@
#include "Log.h" #include "Log.h"
#include "Engine/Engine/CommandLine.h" #include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Types/DateTime.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/Time.h"
#include "Engine/Engine/Globals.h" #include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CriticalSection.h" #include "Engine/Platform/CriticalSection.h"
#include "Engine/Serialization/FileWriteStream.h" #include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Debug/Exceptions/Exceptions.h" #include "Engine/Debug/Exceptions/Exceptions.h"
#if USE_EDITOR #if USE_EDITOR
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Sorting.h"
#endif #endif
#include <iostream> #include <iostream>
@@ -199,35 +198,36 @@ void Log::Logger::WriteFloor()
void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w) void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w)
{ {
const TimeSpan time = DateTime::Now() - LogStartTime; const TimeSpan time = DateTime::Now() - LogStartTime;
const int32 msgLength = msg.Length();
fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type)); fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type));
// On Windows convert all '\n' into '\r\n' // On Windows convert all '\n' into '\r\n'
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
const int32 msgLength = msg.Length(); bool hasWindowsNewLine = false;
if (msgLength > 1) 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)); Array<Char> msgStream;
msgStream.WriteChar(msg[0]); msgStream.EnsureCapacity(msgLength);
msgStream.Add(msg.Get()[0]);
for (int32 i = 1; i < msgLength; i++) for (int32 i = 1; i < msgLength; i++)
{ {
if (msg[i - 1] != '\r' && msg[i] == '\n') if (msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n')
msgStream.WriteChar(TEXT('\r')); msgStream.Add(TEXT('\r'));
msgStream.WriteChar(msg[i]); msgStream.Add(msg.Get()[i]);
} }
msgStream.WriteChar(msg[msgLength]); msgStream.Add(TEXT('\0'));
msgStream.WriteChar(TEXT('\0')); w.append(msgStream.Get(), (const Char*)(msgStream.Get() + msgStream.Count()));
fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.GetHandle()); //fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.Get());
//w.append(msgStream.GetHandle(), msgStream.GetHandle() + msgStream.GetPosition()); return;
} }
else
{
//w.append(msg.Get(), msg.Get() + msg.Length());
fmt_flax::format(w, TEXT("{}"), msg);
}
#else
fmt_flax::format(w, TEXT("{}"), msg);
#endif #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) void Log::Logger::Write(LogType type, const StringView& msg)

View File

@@ -71,7 +71,7 @@ public:
/// <param name="g">The green channel value.</param> /// <param name="g">The green channel value.</param>
/// <param name="b">The blue channel value.</param> /// <param name="b">The blue channel value.</param>
/// <param name="a">The alpha channel value.</param> /// <param name="a">The alpha channel value.</param>
Color(float r, float g, float b, float a = 1) FORCE_INLINE Color(float r, float g, float b, float a = 1)
: R(r) : R(r)
, G(g) , G(g)
, B(b) , B(b)
@@ -203,7 +203,7 @@ public:
return Color(R - b.R, G - b.G, B - b.B, A - b.A); return Color(R - b.R, G - b.G, B - b.B, A - b.A);
} }
Color operator*(const Color& b) const FORCE_INLINE Color operator*(const Color& b) const
{ {
return Color(R * b.R, G * b.G, B * b.B, A * b.A); return Color(R * b.R, G * b.G, B * b.B, A * b.A);
} }

View File

@@ -1,10 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Half.h" #include "Half.h"
#include "Rectangle.h"
#include "Vector2.h"
#include "Vector3.h"
#include "Vector4.h" #include "Vector4.h"
#include "Rectangle.h"
#include "Color.h" #include "Color.h"
static_assert(sizeof(Half) == 2, "Invalid Half type size."); static_assert(sizeof(Half) == 2, "Invalid Half type size.");
@@ -16,12 +14,47 @@ Half2 Half2::Zero(0.0f, 0.0f);
Half3 Half3::Zero(0.0f, 0.0f, 0.0f); Half3 Half3::Zero(0.0f, 0.0f, 0.0f);
Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f); Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f);
Half2::Half2(const Float2& v) #if !USE_SSE_HALF_CONVERSION
Half Float16Compressor::Compress(float value)
{ {
X = Float16Compressor::Compress(v.X); Bits v, s;
Y = Float16Compressor::Compress(v.Y); v.f = value;
uint32 sign = v.si & signN;
v.si ^= sign;
sign >>= shiftSign; // logical shift
s.si = mulN;
s.si = static_cast<int32>(s.f * v.f); // correct subnormals
v.si ^= (s.si ^ v.si) & -(minN > v.si);
v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
v.ui >>= shift; // logical shift
v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
return v.ui | sign;
} }
float Float16Compressor::Decompress(Half value)
{
Bits v;
v.ui = value;
int32 sign = v.si & signC;
v.si ^= sign;
sign <<= shiftSign;
v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
Bits s;
s.si = mulC;
s.f *= v.si;
const int32 mask = -(norC > v.si);
v.si <<= shift;
v.si ^= (s.si ^ v.si) & mask;
v.si |= sign;
return v.f;
}
#endif
Float2 Half2::ToFloat2() const Float2 Half2::ToFloat2() const
{ {
return Float2( return Float2(
@@ -30,13 +63,6 @@ Float2 Half2::ToFloat2() const
); );
} }
Half3::Half3(const Float3& v)
{
X = Float16Compressor::Compress(v.X);
Y = Float16Compressor::Compress(v.Y);
Z = Float16Compressor::Compress(v.Z);
}
Float3 Half3::ToFloat3() const Float3 Half3::ToFloat3() const
{ {
return Float3( return Float3(

View File

@@ -3,6 +3,8 @@
#pragma once #pragma once
#include "Math.h" #include "Math.h"
#include "Vector2.h"
#include "Vector3.h"
/// <summary> /// <summary>
/// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa /// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa
@@ -45,54 +47,23 @@ class FLAXENGINE_API Float16Compressor
static const int32 minD = minC - subC - 1; static const int32 minD = minC - subC - 1;
public: public:
static Half Compress(const float value)
{
#if USE_SSE_HALF_CONVERSION #if USE_SSE_HALF_CONVERSION
FORCE_INLINE static Half Compress(float value)
{
__m128 value1 = _mm_set_ss(value); __m128 value1 = _mm_set_ss(value);
__m128i value2 = _mm_cvtps_ph(value1, 0); __m128i value2 = _mm_cvtps_ph(value1, 0);
return static_cast<Half>(_mm_cvtsi128_si32(value2)); return static_cast<Half>(_mm_cvtsi128_si32(value2));
#else
Bits v, s;
v.f = value;
uint32 sign = v.si & signN;
v.si ^= sign;
sign >>= shiftSign; // logical shift
s.si = mulN;
s.si = static_cast<int32>(s.f * v.f); // correct subnormals
v.si ^= (s.si ^ v.si) & -(minN > v.si);
v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
v.ui >>= shift; // logical shift
v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
return v.ui | sign;
#endif
} }
FORCE_INLINE static float Decompress(Half value)
static float Decompress(const Half value)
{ {
#if USE_SSE_HALF_CONVERSION
__m128i value1 = _mm_cvtsi32_si128(static_cast<int>(value)); __m128i value1 = _mm_cvtsi32_si128(static_cast<int>(value));
__m128 value2 = _mm_cvtph_ps(value1); __m128 value2 = _mm_cvtph_ps(value1);
return _mm_cvtss_f32(value2); return _mm_cvtss_f32(value2);
#else
Bits v;
v.ui = value;
int32 sign = v.si & signC;
v.si ^= sign;
sign <<= shiftSign;
v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
Bits s;
s.si = mulC;
s.f *= v.si;
const int32 mask = -(norC > v.si);
v.si <<= shift;
v.si ^= (s.si ^ v.si) & mask;
v.si |= sign;
return v.f;
#endif
} }
#else
static Half Compress(float value);
static float Decompress(Half value);
#endif
}; };
/// <summary> /// <summary>
@@ -128,7 +99,7 @@ public:
/// </summary> /// </summary>
/// <param name="x">X component</param> /// <param name="x">X component</param>
/// <param name="y">Y component</param> /// <param name="y">Y component</param>
Half2(Half x, Half y) FORCE_INLINE Half2(Half x, Half y)
: X(x) : X(x)
, Y(y) , Y(y)
{ {
@@ -139,7 +110,7 @@ public:
/// </summary> /// </summary>
/// <param name="x">X component</param> /// <param name="x">X component</param>
/// <param name="y">Y component</param> /// <param name="y">Y component</param>
Half2(float x, float y) FORCE_INLINE Half2(float x, float y)
{ {
X = Float16Compressor::Compress(x); X = Float16Compressor::Compress(x);
Y = Float16Compressor::Compress(y); Y = Float16Compressor::Compress(y);
@@ -149,7 +120,11 @@ public:
/// Init /// Init
/// </summary> /// </summary>
/// <param name="v">X and Y components</param> /// <param name="v">X and Y components</param>
Half2(const Float2& v); FORCE_INLINE Half2(const Float2& v)
{
X = Float16Compressor::Compress(v.X);
Y = Float16Compressor::Compress(v.Y);
}
public: public:
Float2 ToFloat2() const; Float2 ToFloat2() const;
@@ -185,21 +160,26 @@ public:
public: public:
Half3() = default; Half3() = default;
Half3(Half x, Half y, Half z) FORCE_INLINE Half3(Half x, Half y, Half z)
: X(x) : X(x)
, Y(y) , Y(y)
, Z(z) , Z(z)
{ {
} }
Half3(const float x, const float y, const float z) FORCE_INLINE Half3(float x, float y, float z)
{ {
X = Float16Compressor::Compress(x); X = Float16Compressor::Compress(x);
Y = Float16Compressor::Compress(y); Y = Float16Compressor::Compress(y);
Z = Float16Compressor::Compress(z); Z = Float16Compressor::Compress(z);
} }
Half3(const Float3& v); FORCE_INLINE Half3(const Float3& v)
{
X = Float16Compressor::Compress(v.X);
Y = Float16Compressor::Compress(v.Y);
Z = Float16Compressor::Compress(v.Z);
}
public: public:
Float3 ToFloat3() const; Float3 ToFloat3() const;

View File

@@ -5,7 +5,6 @@
#include "Collections/Dictionary.h" #include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h" #include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h" #include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
@@ -14,16 +13,15 @@ const Char* HertzSizesData[] = { TEXT("Hz"), TEXT("KHz"), TEXT("MHz"), TEXT("GHz
Span<const Char*> Utilities::Private::BytesSizes(BytesSizesData, ARRAY_COUNT(BytesSizesData)); Span<const Char*> Utilities::Private::BytesSizes(BytesSizesData, ARRAY_COUNT(BytesSizesData));
Span<const Char*> Utilities::Private::HertzSizes(HertzSizesData, ARRAY_COUNT(HertzSizesData)); Span<const Char*> Utilities::Private::HertzSizes(HertzSizesData, ARRAY_COUNT(HertzSizesData));
namespace ObjectsRemovalServiceImpl namespace
{ {
CriticalSection PoolLocker; CriticalSection PoolLocker;
DateTime LastUpdate; DateTime LastUpdate;
float LastUpdateGameTime; float LastUpdateGameTime;
Dictionary<Object*, float> Pool(8192); Dictionary<Object*, float> Pool(8192);
uint64 PoolCounter = 0;
} }
using namespace ObjectsRemovalServiceImpl;
class ObjectsRemoval : public EngineService class ObjectsRemoval : public EngineService
{ {
public: public:
@@ -64,6 +62,7 @@ void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime)
PoolLocker.Lock(); PoolLocker.Lock();
Pool[obj] = timeToLive; Pool[obj] = timeToLive;
PoolCounter++;
PoolLocker.Unlock(); PoolLocker.Unlock();
} }
@@ -72,6 +71,7 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
PROFILE_CPU(); PROFILE_CPU();
PoolLocker.Lock(); PoolLocker.Lock();
PoolCounter = 0;
// Update timeouts and delete objects that timed out // Update timeouts and delete objects that timed out
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
@@ -90,6 +90,24 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
} }
} }
// If any object was added to the pool while removing objects (by this thread) then retry removing any nested objects (but without delta time)
if (PoolCounter != 0)
{
RETRY:
PoolCounter = 0;
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value <= 0.0f)
{
Object* obj = i->Key;
Pool.Remove(i);
obj->OnDeleteObject();
}
}
if (PoolCounter != 0)
goto RETRY;
}
PoolLocker.Unlock(); PoolLocker.Unlock();
} }
@@ -121,7 +139,7 @@ void ObjectsRemoval::Dispose()
// Delete all remaining objects // Delete all remaining objects
{ {
ScopeLock lock(PoolLocker); PoolLocker.Lock();
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{ {
Object* obj = i->Key; Object* obj = i->Key;
@@ -129,6 +147,7 @@ void ObjectsRemoval::Dispose()
obj->OnDeleteObject(); obj->OnDeleteObject();
} }
Pool.Clear(); Pool.Clear();
PoolLocker.Unlock();
} }
} }

View File

@@ -107,6 +107,26 @@ public:
ASSERT(index >= 0 && index < _length); ASSERT(index >= 0 && index < _length);
return _data[index]; return _data[index];
} }
FORCE_INLINE T* begin()
{
return _data;
}
FORCE_INLINE T* end()
{
return _data + _length;
}
FORCE_INLINE const T* begin() const
{
return _data;
}
FORCE_INLINE const T* end() const
{
return _data + _length;
}
}; };
template<typename T> template<typename T>

View File

@@ -596,11 +596,13 @@ void EngineImpl::InitPaths()
Globals::ProjectCacheFolder = Globals::ProjectFolder / TEXT("Cache"); Globals::ProjectCacheFolder = Globals::ProjectFolder / TEXT("Cache");
#endif #endif
#if USE_MONO
// We must ensure that engine is located in folder which path contains only ANSI characters // 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 // Why? Mono lib must have etc and lib folders at ANSI path
// But project can be located on Unicode path // But project can be located on Unicode path
if (!Globals::StartupFolder.IsANSI()) if (!Globals::StartupFolder.IsANSI())
Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters.")); Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters."));
#endif
#if !PLATFORM_SWITCH && !FLAX_TESTS #if !PLATFORM_SWITCH && !FLAX_TESTS
// Setup directories // Setup directories

View File

@@ -188,7 +188,7 @@ namespace FlaxEngine.Interop
internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr) internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr)
{ {
string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr); string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr);
string modulePath = Marshal.PtrToStringAnsi(modulePathPtr); string modulePath = Marshal.PtrToStringUni(modulePathPtr);
libraryPaths[moduleName] = modulePath; libraryPaths[moduleName] = modulePath;
} }
@@ -857,36 +857,25 @@ namespace FlaxEngine.Interop
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, int fieldOffset, IntPtr valuePtr)
{ {
object fieldOwner = fieldOwnerHandle.Target; object fieldOwner = fieldOwnerHandle.Target;
IntPtr fieldRef;
#if USE_AOT
FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target); FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
fieldRef = IntPtr.Zero;
Debug.LogError("Not supported FieldGetValueReference");
#else
if (fieldOwner.GetType().IsValueType) if (fieldOwner.GetType().IsValueType)
{ {
ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference<object, IntPtr>(field.fieldOffset, ref fieldOwner); fieldRef = FieldHelper.GetValueTypeFieldReference<object, IntPtr>(fieldOffset, ref fieldOwner);
Unsafe.Write<IntPtr>(valuePtr.ToPointer(), fieldRef);
} }
else else
{ {
ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference<object, IntPtr>(field.fieldOffset, ref fieldOwner); fieldRef = FieldHelper.GetReferenceTypeFieldReference<object, IntPtr>(fieldOffset, ref fieldOwner);
Unsafe.Write<IntPtr>(valuePtr.ToPointer(), fieldRef);
}
}
[UnmanagedCallersOnly]
internal static void FieldGetValueReferenceWithOffset(ManagedHandle fieldOwnerHandle, int fieldOffset, IntPtr valuePtr)
{
object fieldOwner = fieldOwnerHandle.Target;
if (fieldOwner.GetType().IsValueType)
{
ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference<object, IntPtr>(fieldOffset, ref fieldOwner);
Unsafe.Write<IntPtr>(valuePtr.ToPointer(), fieldRef);
}
else
{
ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference<object, IntPtr>(fieldOffset, ref fieldOwner);
Unsafe.Write<IntPtr>(valuePtr.ToPointer(), fieldRef);
} }
#endif
Unsafe.Write<IntPtr>(valuePtr.ToPointer(), fieldRef);
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]

View File

@@ -119,6 +119,7 @@ namespace FlaxEngine.Interop
{ {
} }
#if !USE_AOT
// Cache offsets to frequently accessed fields of FlaxEngine.Object // Cache offsets to frequently accessed fields of FlaxEngine.Object
private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read<int>((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read<int>((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF);
private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read<int>((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read<int>((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF);
@@ -150,6 +151,7 @@ namespace FlaxEngine.Interop
object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr);
return ManagedHandle.Alloc(obj); return ManagedHandle.Alloc(obj);
} }
#endif
internal static void* NativeAlloc(int byteCount) internal static void* NativeAlloc(int byteCount)
{ {
@@ -429,6 +431,9 @@ namespace FlaxEngine.Interop
/// </summary> /// </summary>
internal static int GetFieldOffset(FieldInfo field, Type type) internal static int GetFieldOffset(FieldInfo field, Type type)
{ {
if (field.IsLiteral)
return 0;
// Get the address of the field, source: https://stackoverflow.com/a/56512720 // Get the address of the field, source: https://stackoverflow.com/a/56512720
int fieldOffset = Unsafe.Read<int>((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF; int fieldOffset = Unsafe.Read<int>((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF;
if (!type.IsValueType) if (!type.IsValueType)
@@ -436,6 +441,23 @@ namespace FlaxEngine.Interop
return fieldOffset; return fieldOffset;
} }
#if USE_AOT
/// <summary>
/// Helper utility to set field of the referenced value via reflection.
/// </summary>
internal static void SetReferenceTypeField<T>(FieldInfo field, ref T fieldOwner, object fieldValue)
{
if (typeof(T).IsValueType)
{
// Value types need setting via boxed object to properly propagate value
object fieldOwnerBoxed = fieldOwner;
field.SetValue(fieldOwnerBoxed, fieldValue);
fieldOwner = (T)fieldOwnerBoxed;
}
else
field.SetValue(fieldOwner, fieldValue);
}
#else
/// <summary> /// <summary>
/// Returns a reference to the value of the field. /// Returns a reference to the value of the field.
/// </summary> /// </summary>
@@ -462,6 +484,7 @@ namespace FlaxEngine.Interop
byte* fieldPtr = (byte*)Unsafe.As<T, IntPtr>(ref fieldOwner) + fieldOffset; byte* fieldPtr = (byte*)Unsafe.As<T, IntPtr>(ref fieldOwner) + fieldOffset;
return ref Unsafe.AsRef<TField>(fieldPtr); return ref Unsafe.AsRef<TField>(fieldPtr);
} }
#endif
} }
/// <summary> /// <summary>
@@ -735,29 +758,49 @@ namespace FlaxEngine.Interop
private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
{ {
#if USE_AOT
IntPtr fieldValue = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#else
ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner); ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner);
fieldValueRef = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()); fieldValueRef = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
#endif
fieldSize = IntPtr.Size; fieldSize = IntPtr.Size;
} }
private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
{ {
#if USE_AOT
IntPtr fieldValue = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#else
ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner); ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner);
fieldValueRef = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()); fieldValueRef = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
#endif
fieldSize = IntPtr.Size; fieldSize = IntPtr.Size;
} }
private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
{ {
ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner); #if USE_AOT
Unsafe.Write<IntPtr>(nativeFieldPtr.ToPointer(), fieldValueRef); object boxed = field.GetValue(fieldOwner);
IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed));
#else
IntPtr fieldValue = FieldHelper.GetValueTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner);
#endif
Unsafe.Write<IntPtr>(nativeFieldPtr.ToPointer(), fieldValue);
fieldSize = IntPtr.Size; fieldSize = IntPtr.Size;
} }
private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
{ {
ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner); #if USE_AOT
Unsafe.Write<IntPtr>(nativeFieldPtr.ToPointer(), fieldValueRef); object boxed = field.GetValue(fieldOwner);
IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed));
#else
IntPtr fieldValue = FieldHelper.GetReferenceTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner);
#endif
Unsafe.Write<IntPtr>(nativeFieldPtr.ToPointer(), fieldValue);
fieldSize = IntPtr.Size; fieldSize = IntPtr.Size;
} }
@@ -799,8 +842,15 @@ namespace FlaxEngine.Interop
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
} }
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField>.ToManaged(ref fieldValueRef, nativeFieldPtr, false); TField fieldValue = default;
#else
ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField>.ToManaged(ref fieldValue, nativeFieldPtr, false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -813,8 +863,15 @@ namespace FlaxEngine.Interop
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
} }
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField>.ToManaged(ref fieldValueRef, nativeFieldPtr, false); TField fieldValue = default;
#else
ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField>.ToManaged(ref fieldValue, nativeFieldPtr, false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -825,8 +882,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false); TField[] fieldValue = (TField[])field.GetValue(fieldOwner);
#else
ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField[]>.ToManaged(ref fieldValue, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -837,8 +901,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false); TField[] fieldValue = null;
#else
ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField[]>.ToManaged(ref fieldValue, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -852,11 +923,11 @@ namespace FlaxEngine.Interop
} }
#if USE_AOT #if USE_AOT
TField fieldValueRef = (TField)field.GetValue(fieldOwner); TField fieldValue = (TField)field.GetValue(fieldOwner);
#else #else
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif #endif
MarshalHelper<TField>.ToNative(ref fieldValueRef, nativeFieldPtr); MarshalHelper<TField>.ToNative(ref fieldValue, nativeFieldPtr);
} }
internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -870,11 +941,11 @@ namespace FlaxEngine.Interop
} }
#if USE_AOT #if USE_AOT
TField fieldValueRef = (TField)field.GetValue(fieldOwner); TField fieldValue = (TField)field.GetValue(fieldOwner);
#else #else
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif #endif
MarshalHelper<TField>.ToNative(ref fieldValueRef, nativeFieldPtr); MarshalHelper<TField>.ToNative(ref fieldValue, nativeFieldPtr);
} }
} }
@@ -904,8 +975,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false); TField fieldValue = null;
#else
ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField>.ToManaged(ref fieldValue, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -915,8 +993,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false); TField fieldValue = default;
#else
ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField>.ToManaged(ref fieldValue, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -926,8 +1011,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false); TField[] fieldValue = null;
#else
ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField[]>.ToManaged(ref fieldValue, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -937,8 +1029,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false); TField[] fieldValue = null;
#else
ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField[]>.ToManaged(ref fieldValue, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#endif
} }
internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
@@ -948,8 +1047,12 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField>.ToNative(ref fieldValueRef, nativeFieldPtr); TField fieldValue = (TField)field.GetValue(fieldOwner);
#else
ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField>.ToNative(ref fieldValue, nativeFieldPtr);
} }
internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
@@ -959,8 +1062,12 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner); #if USE_AOT
MarshalHelper<TField>.ToNative(ref fieldValueRef, nativeFieldPtr); TField fieldValue = (TField)field.GetValue(fieldOwner);
#else
ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#endif
MarshalHelper<TField>.ToNative(ref fieldValue, nativeFieldPtr);
} }
} }
} }
@@ -1044,7 +1151,7 @@ namespace FlaxEngine.Interop
var fields = MarshalHelper<T>.marshallableFields; var fields = MarshalHelper<T>.marshallableFields;
var offsets = MarshalHelper<T>.marshallableFieldOffsets; var offsets = MarshalHelper<T>.marshallableFieldOffsets;
var marshallers = MarshalHelper<T>.toNativeFieldMarshallers; var marshallers = MarshalHelper<T>.toNativeFieldMarshallers;
for (int i = 0; i < MarshalHelper<T>.marshallableFields.Length; i++) for (int i = 0; i < fields.Length; i++)
{ {
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize); marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
nativePtr += fieldSize; nativePtr += fieldSize;
@@ -1306,11 +1413,13 @@ namespace FlaxEngine.Interop
return RuntimeHelpers.GetUninitializedObject(wrappedType); return RuntimeHelpers.GetUninitializedObject(wrappedType);
} }
#if !USE_AOT
internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr) internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr)
{ {
object obj = CreateObject(); object obj = RuntimeHelpers.GetUninitializedObject(wrappedType);
if (obj is Object) if (obj is Object)
{ {
// TODO: use UnsafeAccessorAttribute on .NET 8 and use this path on all platforms (including non-Desktop, see MCore::ScriptingObject::CreateScriptingObject)
{ {
ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference<IntPtr>(unmanagedPtrFieldOffset, ref obj); ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference<IntPtr>(unmanagedPtrFieldOffset, ref obj);
fieldRef = unmanagedPtr; fieldRef = unmanagedPtr;
@@ -1331,8 +1440,9 @@ namespace FlaxEngine.Interop
return obj; return obj;
} }
#endif
public static implicit operator Type(TypeHolder holder) => holder?.type ?? null; public static implicit operator Type(TypeHolder holder) => holder?.type;
public bool Equals(TypeHolder other) => type == other.type; public bool Equals(TypeHolder other) => type == other.type;
public bool Equals(Type other) => type == other; public bool Equals(Type other) => type == other;
public override int GetHashCode() => type.GetHashCode(); public override int GetHashCode() => type.GetHashCode();

View File

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

View File

@@ -634,15 +634,12 @@ void Foliage::RemoveFoliageType(int32 index)
int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const
{ {
PROFILE_CPU(); PROFILE_CPU();
int32 result = 0; int32 result = 0;
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
for (auto i = Instances.Begin(); i.IsNotEnd(); i++)
{ {
if (i->Type == index) if (i->Type == index)
result++; result++;
} }
return result; return result;
} }

View File

@@ -503,6 +503,9 @@ void GPUDevice::DrawEnd()
// Call present on all used tasks // Call present on all used tasks
int32 presentCount = 0; int32 presentCount = 0;
bool anyVSync = false; bool anyVSync = false;
#if COMPILE_WITH_PROFILER
const double presentStart = Platform::GetTimeSeconds();
#endif
for (int32 i = 0; i < RenderTask::Tasks.Count(); i++) for (int32 i = 0; i < RenderTask::Tasks.Count(); i++)
{ {
const auto task = RenderTask::Tasks[i]; const auto task = RenderTask::Tasks[i];
@@ -537,6 +540,10 @@ void GPUDevice::DrawEnd()
#endif #endif
GetMainContext()->Flush(); GetMainContext()->Flush();
} }
#if COMPILE_WITH_PROFILER
const double presentEnd = Platform::GetTimeSeconds();
ProfilerGPU::OnPresentTime((float)((presentEnd - presentStart) * 1000.0));
#endif
_wasVSyncUsed = anyVSync; _wasVSyncUsed = anyVSync;
_isRendering = false; _isRendering = false;

View File

@@ -259,6 +259,11 @@ API_STRUCT() struct GPULimits
/// </summary> /// </summary>
API_FIELD() bool HasDepthAsSRV; API_FIELD() bool HasDepthAsSRV;
/// <summary>
/// True if device supports depth buffer clipping (see GPUPipelineState::Description::DepthClipEnable).
/// </summary>
API_FIELD() bool HasDepthClip;
/// <summary> /// <summary>
/// True if device supports depth buffer texture as a readonly depth buffer (can be sampled in the shader while performing depth-test). /// True if device supports depth buffer texture as a readonly depth buffer (can be sampled in the shader while performing depth-test).
/// </summary> /// </summary>

View File

@@ -92,6 +92,8 @@ bool DecalMaterialShader::Load()
{ {
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth; GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
psDesc0.VS = _shader->GetVS("VS_Decal"); psDesc0.VS = _shader->GetVS("VS_Decal");
if (psDesc0.VS == nullptr)
return true;
psDesc0.PS = _shader->GetPS("PS_Decal"); psDesc0.PS = _shader->GetPS("PS_Decal");
psDesc0.CullMode = CullMode::Normal; psDesc0.CullMode = CullMode::Normal;

View File

@@ -136,6 +136,7 @@ void DeferredMaterialShader::Unload()
bool DeferredMaterialShader::Load() bool DeferredMaterialShader::Load()
{ {
bool failed = false;
auto psDesc = GPUPipelineState::Description::Default; auto psDesc = GPUPipelineState::Description::Default;
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None; psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;
if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest)) if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest))
@@ -155,16 +156,20 @@ bool DeferredMaterialShader::Load()
// GBuffer Pass // GBuffer Pass
psDesc.VS = _shader->GetVS("VS"); psDesc.VS = _shader->GetVS("VS");
failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer"); psDesc.PS = _shader->GetPS("PS_GBuffer");
_cache.Default.Init(psDesc); _cache.Default.Init(psDesc);
psDesc.VS = _shader->GetVS("VS", 1); psDesc.VS = _shader->GetVS("VS", 1);
failed |= psDesc.VS == nullptr;
_cacheInstanced.Default.Init(psDesc); _cacheInstanced.Default.Init(psDesc);
// GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1) // GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1)
psDesc.VS = _shader->GetVS("VS"); psDesc.VS = _shader->GetVS("VS");
failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer", 1); psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
_cache.DefaultLightmap.Init(psDesc); _cache.DefaultLightmap.Init(psDesc);
psDesc.VS = _shader->GetVS("VS", 1); psDesc.VS = _shader->GetVS("VS", 1);
failed |= psDesc.VS == nullptr;
_cacheInstanced.DefaultLightmap.Init(psDesc); _cacheInstanced.DefaultLightmap.Init(psDesc);
// GBuffer Pass with skinning // GBuffer Pass with skinning
@@ -233,5 +238,5 @@ bool DeferredMaterialShader::Load()
psDesc.VS = _shader->GetVS("VS_Skinned"); psDesc.VS = _shader->GetVS("VS_Skinned");
_cache.DepthSkinned.Init(psDesc); _cache.DepthSkinned.Init(psDesc);
return false; return failed;
} }

View File

@@ -174,6 +174,8 @@ bool ForwardMaterialShader::Load()
// Forward Pass // Forward Pass
psDesc.VS = _shader->GetVS("VS"); psDesc.VS = _shader->GetVS("VS");
if (psDesc.VS == nullptr)
return true;
psDesc.PS = _shader->GetPS("PS_Forward"); psDesc.PS = _shader->GetPS("PS_Forward");
psDesc.DepthWriteEnable = false; psDesc.DepthWriteEnable = false;
psDesc.BlendMode = BlendingMode::AlphaBlend; 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="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="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (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) 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); 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="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="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (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) 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); 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="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="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (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) 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); 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="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="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (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) 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); 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="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="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (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) 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); 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="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="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (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) 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); 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); ScopeLock lock(model->Locker);
if (model->IsVirtual()) 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; return true;
} }

View File

@@ -318,7 +318,9 @@ void MeshData::BuildIndexBuffer()
} }
const auto endTime = Platform::GetTimeSeconds(); 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) 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(); 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; return false;
} }
@@ -685,7 +689,10 @@ bool MeshData::GenerateTangents(float smoothingAngle)
#endif #endif
const auto endTime = Platform::GetTimeSeconds(); 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; return false;
} }
@@ -872,7 +879,9 @@ void MeshData::ImproveCacheLocality()
Allocator::Free(piCandidates); Allocator::Free(piCandidates);
const auto endTime = Platform::GetTimeSeconds(); 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 float MeshData::CalculateTrianglesArea() const

View File

@@ -216,7 +216,7 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param> /// <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="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="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) 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)); 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="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="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="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) 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)); 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="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="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="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) 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)); UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));

View File

@@ -359,6 +359,7 @@ bool GPUDeviceDX11::Init()
limits.HasAppendConsumeBuffers = true; limits.HasAppendConsumeBuffers = true;
limits.HasSeparateRenderTargetBlendState = true; limits.HasSeparateRenderTargetBlendState = true;
limits.HasDepthAsSRV = true; limits.HasDepthAsSRV = true;
limits.HasDepthClip = true;
limits.HasReadOnlyDepth = true; limits.HasReadOnlyDepth = true;
limits.HasMultisampleDepthAsSRV = true; limits.HasMultisampleDepthAsSRV = true;
limits.HasTypedUAVLoad = featureDataD3D11Options2.TypedUAVLoadAdditionalFormats != 0; limits.HasTypedUAVLoad = featureDataD3D11Options2.TypedUAVLoadAdditionalFormats != 0;
@@ -382,6 +383,7 @@ bool GPUDeviceDX11::Init()
limits.HasAppendConsumeBuffers = false; limits.HasAppendConsumeBuffers = false;
limits.HasSeparateRenderTargetBlendState = false; limits.HasSeparateRenderTargetBlendState = false;
limits.HasDepthAsSRV = false; limits.HasDepthAsSRV = false;
limits.HasDepthClip = true;
limits.HasReadOnlyDepth = createdFeatureLevel == D3D_FEATURE_LEVEL_10_1; limits.HasReadOnlyDepth = createdFeatureLevel == D3D_FEATURE_LEVEL_10_1;
limits.HasMultisampleDepthAsSRV = false; limits.HasMultisampleDepthAsSRV = false;
limits.HasTypedUAVLoad = false; limits.HasTypedUAVLoad = false;

View File

@@ -381,6 +381,7 @@ bool GPUDeviceDX12::Init()
limits.HasAppendConsumeBuffers = true; limits.HasAppendConsumeBuffers = true;
limits.HasSeparateRenderTargetBlendState = true; limits.HasSeparateRenderTargetBlendState = true;
limits.HasDepthAsSRV = true; limits.HasDepthAsSRV = true;
limits.HasDepthClip = true;
limits.HasReadOnlyDepth = true; limits.HasReadOnlyDepth = true;
limits.HasMultisampleDepthAsSRV = true; limits.HasMultisampleDepthAsSRV = true;
limits.HasTypedUAVLoad = options.TypedUAVLoadAdditionalFormats != 0; limits.HasTypedUAVLoad = options.TypedUAVLoadAdditionalFormats != 0;

View File

@@ -50,18 +50,7 @@ bool GPUDeviceNull::Init()
// Init device limits // Init device limits
{ {
auto& limits = Limits; auto& limits = Limits;
limits.HasCompute = false; Platform::MemoryClear(&limits, sizeof(limits));
limits.HasTessellation = false;
limits.HasGeometryShaders = false;
limits.HasInstancing = false;
limits.HasVolumeTextureRendering = false;
limits.HasDrawIndirect = false;
limits.HasAppendConsumeBuffers = false;
limits.HasSeparateRenderTargetBlendState = false;
limits.HasDepthAsSRV = false;
limits.HasReadOnlyDepth = false;
limits.HasMultisampleDepthAsSRV = false;
limits.HasTypedUAVLoad = false;
limits.MaximumMipLevelsCount = 14; limits.MaximumMipLevelsCount = 14;
limits.MaximumTexture1DSize = 8192; limits.MaximumTexture1DSize = 8192;
limits.MaximumTexture1DArraySize = 512; limits.MaximumTexture1DArraySize = 512;
@@ -70,11 +59,8 @@ bool GPUDeviceNull::Init()
limits.MaximumTexture3DSize = 2048; limits.MaximumTexture3DSize = 2048;
limits.MaximumTextureCubeSize = 16384; limits.MaximumTextureCubeSize = 16384;
limits.MaximumSamplerAnisotropy = 1; limits.MaximumSamplerAnisotropy = 1;
for (int32 i = 0; i < static_cast<int32>(PixelFormat::MAX); i++) for (int32 i = 0; i < static_cast<int32>(PixelFormat::MAX); i++)
{
FeaturesPerFormat[i] = FormatFeatures(static_cast<PixelFormat>(i), MSAALevel::None, FormatSupport::None); FeaturesPerFormat[i] = FormatFeatures(static_cast<PixelFormat>(i), MSAALevel::None, FormatSupport::None);
}
} }
// Create main context // Create main context

View File

@@ -1210,16 +1210,16 @@ void GPUContextVulkan::ResolveMultisample(GPUTexture* sourceMultisampleTexture,
void GPUContextVulkan::DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex) void GPUContextVulkan::DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex)
{ {
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall(); OnDrawCall();
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
vkCmdDraw(cmdBuffer->GetHandle(), verticesCount, instanceCount, startVertex, startInstance); vkCmdDraw(cmdBuffer->GetHandle(), verticesCount, instanceCount, startVertex, startInstance);
RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3); RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3);
} }
void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex) void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex)
{ {
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall(); OnDrawCall();
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
vkCmdDrawIndexed(cmdBuffer->GetHandle(), indicesCount, instanceCount, startIndex, startVertex, startInstance); vkCmdDrawIndexed(cmdBuffer->GetHandle(), indicesCount, instanceCount, startIndex, startVertex, startInstance);
RENDER_STAT_DRAW_CALL(0, indicesCount / 3 * instanceCount); RENDER_STAT_DRAW_CALL(0, indicesCount / 3 * instanceCount);
} }
@@ -1227,10 +1227,9 @@ void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instance
void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs) void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
{ {
ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument)); ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
OnDrawCall();
auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs; auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
vkCmdDrawIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndirectCommand)); vkCmdDrawIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndirectCommand));
RENDER_STAT_DRAW_CALL(0, 0); RENDER_STAT_DRAW_CALL(0, 0);
} }
@@ -1238,10 +1237,9 @@ void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 of
void GPUContextVulkan::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs) void GPUContextVulkan::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
{ {
ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument)); ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
OnDrawCall();
auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs; auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
vkCmdDrawIndexedIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndexedIndirectCommand)); vkCmdDrawIndexedIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndexedIndirectCommand));
RENDER_STAT_DRAW_CALL(0, 0); RENDER_STAT_DRAW_CALL(0, 0);
} }

View File

@@ -1704,6 +1704,7 @@ bool GPUDeviceVulkan::Init()
limits.HasDrawIndirect = PhysicalDeviceLimits.maxDrawIndirectCount >= 1; limits.HasDrawIndirect = PhysicalDeviceLimits.maxDrawIndirectCount >= 1;
limits.HasAppendConsumeBuffers = false; // TODO: add Append Consume buffers support for Vulkan limits.HasAppendConsumeBuffers = false; // TODO: add Append Consume buffers support for Vulkan
limits.HasSeparateRenderTargetBlendState = true; limits.HasSeparateRenderTargetBlendState = true;
limits.HasDepthClip = PhysicalDeviceFeatures.depthClamp;
limits.HasDepthAsSRV = true; limits.HasDepthAsSRV = true;
limits.HasReadOnlyDepth = true; limits.HasReadOnlyDepth = true;
limits.HasMultisampleDepthAsSRV = !!PhysicalDeviceFeatures.sampleRateShading; limits.HasMultisampleDepthAsSRV = !!PhysicalDeviceFeatures.sampleRateShading;

View File

@@ -340,7 +340,7 @@ bool GPUPipelineStateVulkan::Init(const Description& desc)
break; break;
} }
_descRasterization.frontFace = VK_FRONT_FACE_CLOCKWISE; _descRasterization.frontFace = VK_FRONT_FACE_CLOCKWISE;
_descRasterization.depthClampEnable = !desc.DepthClipEnable; _descRasterization.depthClampEnable = !desc.DepthClipEnable && _device->Limits.HasDepthClip;
_descRasterization.lineWidth = 1.0f; _descRasterization.lineWidth = 1.0f;
_desc.pRasterizationState = &_descRasterization; _desc.pRasterizationState = &_descRasterization;

View File

@@ -16,7 +16,7 @@ void GPUTimerQueryVulkan::Interrupt(CmdBufferVulkan* cmdBuffer)
if (!_interrupted) if (!_interrupted)
{ {
_interrupted = true; _interrupted = true;
WriteTimestamp(cmdBuffer, _queries[_queryIndex].End); WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
} }
} }
@@ -28,7 +28,7 @@ void GPUTimerQueryVulkan::Resume(CmdBufferVulkan* cmdBuffer)
e.End.Pool = nullptr; e.End.Pool = nullptr;
_interrupted = false; _interrupted = false;
WriteTimestamp(cmdBuffer, e.Begin); WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
_queries.Add(e); _queries.Add(e);
_queryIndex++; _queryIndex++;
@@ -56,13 +56,13 @@ bool GPUTimerQueryVulkan::GetResult(Query& query)
return false; return false;
} }
void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query) const void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const
{ {
auto pool = _device->FindAvailableTimestampQueryPool(); auto pool = _device->FindAvailableTimestampQueryPool();
uint32 index; uint32 index;
pool->AcquireQuery(index); pool->AcquireQuery(index);
vkCmdWriteTimestamp(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, pool->GetHandle(), index); vkCmdWriteTimestamp(cmdBuffer->GetHandle(), stage, pool->GetHandle(), index);
pool->MarkQueryAsStarted(index); pool->MarkQueryAsStarted(index);
query.Pool = pool; query.Pool = pool;
@@ -168,7 +168,7 @@ void GPUTimerQueryVulkan::Begin()
_queryIndex = 0; _queryIndex = 0;
_interrupted = false; _interrupted = false;
WriteTimestamp(cmdBuffer, e.Begin); WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
context->GetCmdBufferManager()->OnQueryBegin(this); context->GetCmdBufferManager()->OnQueryBegin(this);
ASSERT(_queries.IsEmpty()); ASSERT(_queries.IsEmpty());
@@ -193,7 +193,7 @@ void GPUTimerQueryVulkan::End()
if (!_interrupted) if (!_interrupted)
{ {
WriteTimestamp(cmdBuffer, _queries[_queryIndex].End); WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
} }
context->GetCmdBufferManager()->OnQueryEnd(this); context->GetCmdBufferManager()->OnQueryEnd(this);
} }

View File

@@ -59,7 +59,7 @@ public:
private: private:
bool GetResult(Query& query); bool GetResult(Query& query);
void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query) const; void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const;
bool TryGetResult(); bool TryGetResult();
bool UseQueries(); bool UseQueries();

Some files were not shown because too many files have changed in this diff Show More