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

View File

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

View File

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

View File

@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
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 />
public override bool CanCreate(ContentFolder targetLocation)
{

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
var gizmos = gizmoOwner.Gizmos;
_gizmoMode = new ClothPaintingGizmoMode();
var projectCache = Editor.Instance.ProjectCache;
if (projectCache.TryGetCustomData("ClothGizmoPaintValue", out var cachedPaintValue))
_gizmoMode.PaintValue = JsonSerializer.Deserialize<float>(cachedPaintValue);
@@ -48,7 +48,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
_gizmoMode.BrushSize = JsonSerializer.Deserialize<float>(cachedBrushSize);
if (projectCache.TryGetCustomData("ClothGizmoBrushStrength", out var cachedBrushStrength))
_gizmoMode.BrushStrength = JsonSerializer.Deserialize<float>(cachedBrushStrength);
gizmos.AddMode(_gizmoMode);
_prevMode = gizmos.ActiveMode;
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.GUI;
using System.Collections.Generic;
namespace FlaxEditor.CustomEditors.Dedicated;
@@ -10,6 +17,10 @@ namespace FlaxEditor.CustomEditors.Dedicated;
[CustomEditor(typeof(MissingScript)), DefaultEditor]
public class MissingScriptEditor : GenericEditor
{
private DropPanel _dropPanel;
private Button _replaceScriptButton;
private CheckBox _shouldReplaceAllCheckbox;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
@@ -18,9 +29,137 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout);
return;
}
_dropPanel = dropPanel;
_dropPanel.HeaderTextColor = Color.OrangeRed;
dropPanel.HeaderTextColor = Color.OrangeRed;
var replaceScriptPanel = new Panel
{
Parent = _dropPanel,
Height = 64,
};
_replaceScriptButton = new Button
{
Text = "Replace Script",
TooltipText = "Replaces the missing script with a given script type",
AnchorPreset = AnchorPresets.TopCenter,
Width = 240,
Height = 24,
X = -120,
Y = 0,
Parent = replaceScriptPanel,
};
_replaceScriptButton.Clicked += OnReplaceScriptButtonClicked;
var replaceAllLabel = new Label
{
Text = "Replace all matching missing scripts",
TooltipText = "Whether or not to apply this script change to all scripts missing the same type.",
AnchorPreset = AnchorPresets.BottomCenter,
Y = -34,
Parent = replaceScriptPanel,
};
replaceAllLabel.X -= FlaxEngine.GUI.Style.Current.FontSmall.MeasureText(replaceAllLabel.Text).X;
_shouldReplaceAllCheckbox = new CheckBox
{
TooltipText = replaceAllLabel.TooltipText,
AnchorPreset = AnchorPresets.BottomCenter,
Y = -34,
Parent = replaceScriptPanel,
};
float centerDifference = (_shouldReplaceAllCheckbox.Right - replaceAllLabel.Left) / 2;
replaceAllLabel.X += centerDifference;
_shouldReplaceAllCheckbox.X += centerDifference;
base.Initialize(layout);
}
private void FindActorsWithMatchingMissingScript(List<MissingScript> missingScripts)
{
foreach (Actor actor in Level.GetActors(typeof(Actor)))
{
for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
{
Script actorScript = actor.Scripts[scriptIndex];
if (actorScript is not MissingScript missingActorScript)
continue;
MissingScript currentMissing = Values[0] as MissingScript;
if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
continue;
missingScripts.Add(missingActorScript);
}
}
}
private void RunReplacementMultiCast(List<IUndoAction> actions)
{
if (actions.Count == 0)
{
Editor.LogWarning("Failed to replace scripts!");
return;
}
var multiAction = new MultiUndoAction(actions);
multiAction.Do();
var presenter = ParentEditor.Presenter;
if (presenter != null)
{
presenter.Undo.AddAction(multiAction);
presenter.Control.Focus();
}
}
private void ReplaceScript(ScriptType script, bool replaceAllInScene)
{
var actions = new List<IUndoAction>(4);
var missingScripts = new List<MissingScript>();
if (!replaceAllInScene)
missingScripts.Add((MissingScript)Values[0]);
else
FindActorsWithMatchingMissingScript(missingScripts);
foreach (var missingScript in missingScripts)
actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
RunReplacementMultiCast(actions);
for (int actionIdx = 0; actionIdx < actions.Count; actionIdx++)
{
AddRemoveScript addRemoveScriptAction = (AddRemoveScript)actions[actionIdx];
int orderInParent = addRemoveScriptAction.GetOrderInParent();
Script newScript = missingScripts[actionIdx].Actor.Scripts[orderInParent];
missingScripts[actionIdx].ReferenceScript = newScript;
}
actions.Clear();
foreach (var missingScript in missingScripts)
actions.Add(AddRemoveScript.Remove(missingScript));
RunReplacementMultiCast(actions);
}
private void OnReplaceScriptButtonClicked()
{
var scripts = Editor.Instance.CodeEditing.Scripts.Get();
if (scripts.Count == 0)
{
// No scripts
var cm1 = new ContextMenu();
cm1.AddButton("No scripts in project");
cm1.Show(_dropPanel, _replaceScriptButton.BottomLeft);
return;
}
// Show context menu with list of scripts to add
var cm = new ItemsListContextMenu(180);
for (int i = 0; i < scripts.Count; i++)
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
cm.SortItems();
cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -191,6 +191,8 @@ namespace FlaxEditor.GUI.Tabs
get => _autoTabsSizeAuto;
set
{
if (_autoTabsSizeAuto == value)
return;
_autoTabsSizeAuto = value;
PerformLayout();
}
@@ -204,11 +206,11 @@ namespace FlaxEditor.GUI.Tabs
get => _orientation;
set
{
if (_orientation == value)
return;
_orientation = value;
if (UseScroll)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
PerformLayout();
}
}
@@ -402,6 +404,14 @@ namespace FlaxEditor.GUI.Tabs
tabHeader.Size = tabsSize;
}
}
else if (UseScroll)
{
// If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability
if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible)
tabsSize.Y += TabsPanel.HScrollBar.Height;
else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible)
tabsSize.X += TabsPanel.VScrollBar.Width;
}
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal

View File

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

View File

@@ -162,10 +162,23 @@ namespace FlaxEditor.Gizmo
// Scale gizmo to fit on-screen
Vector3 position = Position;
Vector3 vLength = Owner.ViewPosition - position;
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
if (Owner.Viewport.UseOrthographicProjection)
{
//[hack] this is far form ideal the View Position is in wrong location, any think using the View Position will have problem
//the camera system needs rewrite the to be a camera on springarm, similar how the ArcBallCamera is handled
//the ortho projection cannot exist with fps camera because there is no
// - focus point to calculate correct View Position with Orthographic Scale as a reference and Orthographic Scale from View Position
// with make the camera jump
// - and deaph so w and s movment in orto mode moves the cliping plane now
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = gizmoSize * (50 * Owner.Viewport.OrthographicScale);
}
else
{
Vector3 vLength = Owner.ViewPosition - position;
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
}
// Setup world
Quaternion orientation = GetSelectedObject(0).Orientation;
_gizmoWorld = new Transform(position, orientation, new Float3(_screenScale));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,10 +29,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool CheckFunc(ScriptType scriptType)
{
if (scriptType.IsStatic ||
scriptType.IsGenericType ||
!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
if (scriptType.IsStatic ||
scriptType.IsGenericType ||
!scriptType.IsPublic ||
scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
var managedType = TypeUtils.GetType(scriptType);
@@ -410,9 +410,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
base.OnUpdate();
// Automatic project files generation after workspace modifications
if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty)
if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling)
{
Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
// Try to delay generation when a lot of files are added at once
if (ScriptsBuilder.IsSourceDirtyFor(TimeSpan.FromMilliseconds(150)))
Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
}
}

View File

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

View File

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

View File

@@ -117,7 +117,7 @@ namespace FlaxEditor.Options
/// <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).
/// </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; } =
{
BuildAction.CSG,

View File

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

View File

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

View File

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

View File

@@ -286,81 +286,17 @@ namespace VisualStudio
return "Visual Studio open timout";
}
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::ProjectItems>& projectItems, BSTR filePath)
{
long count;
projectItems->get_Count(&count);
if (count == 0)
return nullptr;
for (long i = 1; i <= count; i++) // They are counting from [1..Count]
{
ComPtr<EnvDTE::ProjectItem> projectItem;
projectItems->Item(_variant_t(i), &projectItem);
if (!projectItem)
continue;
short fileCount = 0;
projectItem->get_FileCount(&fileCount);
for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++)
{
_bstr_t filename;
projectItem->get_FileNames(fileIndex, filename.GetAddress());
if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename))
{
return projectItem;
}
}
ComPtr<EnvDTE::ProjectItems> childProjectItems;
projectItem->get_ProjectItems(&childProjectItems);
if (childProjectItems)
{
ComPtr<EnvDTE::ProjectItem> result = FindItem(childProjectItems, filePath);
if (result)
return result;
}
}
return nullptr;
}
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
{
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
{
HRESULT result;
ComPtr<EnvDTE::Projects> projects;
result = solution->get_Projects(&projects);
ComPtr<EnvDTE::ProjectItem> projectItem;
result = solution->FindProjectItem(filePath, &projectItem);
if (FAILED(result))
return nullptr;
long projectsCount = 0;
result = projects->get_Count(&projectsCount);
if (FAILED(result))
return nullptr;
for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
{
ComPtr<EnvDTE::Project> project;
result = projects->Item(_variant_t(projectIndex), &project);
if (FAILED(result) || !project)
continue;
ComPtr<EnvDTE::ProjectItems> projectItems;
result = project->get_ProjectItems(&projectItems);
if (FAILED(result) || !projectItems)
continue;
auto projectItem = FindItem(projectItems, filePath);
if (projectItem)
{
return projectItem;
}
}
return nullptr;
}
return projectItem;
}
// Opens a file on a specific line in a running Visual Studio instance.
//

View File

@@ -170,7 +170,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty()
bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout)
{
ScopeLock scopeLock(_locker);
return _lastSourceCodeEdited > (_lastCompileAction + timeout);
return _lastSourceCodeEdited > _lastCompileAction && DateTime::Now() > _lastSourceCodeEdited + timeout;
}
bool ScriptsBuilder::IsCompiling()
@@ -266,7 +266,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
{
String args(TEXT("-log -genproject "));
String args(TEXT("-log -mutex -genproject "));
args += customArgs;
_wasProjectStructureChanged = false;
return RunBuildTool(args);
@@ -669,7 +669,7 @@ void ScriptsBuilderService::Update()
}
// Check if compile code (if has been edited)
const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50);
const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(150);
auto mainWindow = Engine::MainWindow;
if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused())
{

View File

@@ -68,7 +68,7 @@ public:
/// </summary>
/// <param name="timeout">Time to use for checking.</param>
/// <returns>True if source code is dirty, otherwise false.</returns>
static bool IsSourceDirtyFor(const TimeSpan& timeout);
API_FUNCTION() static bool IsSourceDirtyFor(const TimeSpan& timeout);
/// <summary>
/// Returns true if scripts are being now compiled/reloaded.

View File

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

View File

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

View File

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

View File

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

View File

@@ -150,6 +150,12 @@ namespace FlaxEditor.Tools.Terrain
return;
}
// Increase or decrease brush size with scroll
if (Input.GetKey(KeyboardKeys.Shift))
{
Mode.CurrentBrush.Size += dt * Mode.CurrentBrush.Size * Input.Mouse.ScrollDelta * 5f;
}
// Check if no terrain is selected
var terrain = SelectedTerrain;
if (!terrain)

View File

@@ -158,6 +158,12 @@ namespace FlaxEditor.Tools.Terrain
return;
}
// Increase or decrease brush size with scroll
if (Input.GetKey(KeyboardKeys.Shift))
{
Mode.CurrentBrush.Size += dt * Mode.CurrentBrush.Size * Input.Mouse.ScrollDelta * 5f;
}
// Check if selected terrain was changed during painting
if (terrain != _paintTerrain && IsPainting)
{

View File

@@ -49,7 +49,16 @@ namespace FlaxEditor.Actions
_scriptTypeName = script.TypeName;
_prefabId = script.PrefabID;
_prefabObjectId = script.PrefabObjectID;
_scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script);
try
{
_scriptData = FlaxEngine.Json.JsonSerializer.Serialize(script);
}
catch (Exception ex)
{
_scriptData = null;
Debug.LogError("Failed to serialize script data for Undo due to exception");
Debug.LogException(ex);
}
_parentId = script.Actor.ID;
_orderInParent = script.OrderInParent;
_enabled = script.Enabled;
@@ -66,6 +75,11 @@ namespace FlaxEditor.Actions
_enabled = true;
}
public int GetOrderInParent()
{
return _orderInParent;
}
/// <summary>
/// Creates a new added script undo action.
/// </summary>
@@ -175,6 +189,7 @@ namespace FlaxEditor.Actions
script.Parent = parentActor;
if (_orderInParent != -1)
script.OrderInParent = _orderInParent;
_orderInParent = script.OrderInParent; // Ensure order is correct for script that want to use it later
if (_prefabObjectId != Guid.Empty)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId);
Editor.Instance.Scene.MarkSceneEdited(parentActor.Scene);

View File

@@ -394,8 +394,12 @@ bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon)
// - icon/cursor/etc data
std::fstream stream;
#if PLATFORM_WINDOWS
stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
#else
StringAsANSI<> pathAnsi(path.Get());
stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
#endif
if (!stream.is_open())
{
LOG(Warning, "Cannot open file");

View File

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

View File

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

View File

@@ -593,12 +593,14 @@ namespace FlaxEditor.Viewport
{
ref var vv = ref v.Options[j];
var button = childMenu.AddButton(vv.Name);
button.CloseMenuOnClick = false;
button.Tag = vv.Mode;
}
}
else
{
var button = debugView.AddButton(v.Name);
button.CloseMenuOnClick = false;
button.Tag = v.Mode;
}
}
@@ -1119,7 +1121,12 @@ namespace FlaxEditor.Viewport
var win = (WindowRootControl)Root;
// Get current mouse position in the view
_viewMousePos = PointFromWindow(win.MousePosition);
{
// When the window is not focused, the position in window does not return sane values
Float2 pos = PointFromWindow(win.MousePosition);
if (!float.IsInfinity(pos.LengthSquared))
_viewMousePos = pos;
}
// Update input
var window = win.Window;
@@ -1582,7 +1589,14 @@ namespace FlaxEditor.Viewport
private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{
if (button.Tag is ViewMode v)
{
Task.ViewMode = v;
var cm = button.ParentContextMenu;
WidgetViewModeShowHide(cm);
var mainCM = ViewWidgetButtonMenu.GetChildMenu("Debug View").ContextMenu;
if (mainCM != null && cm != mainCM)
WidgetViewModeShowHide(mainCM);
}
}
private void WidgetViewModeShowHide(Control cm)
@@ -1594,7 +1608,7 @@ namespace FlaxEditor.Viewport
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b && b.Tag is ViewMode v)
b.Icon = Task.View.Mode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
b.Icon = Task.ViewMode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}

View File

@@ -306,6 +306,8 @@ namespace FlaxEditor.Viewport
var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
}
/// <inheritdoc />
public EditorViewport Viewport => this;
/// <inheritdoc />
public GizmosCollection Gizmos { get; }

View File

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

View File

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

View File

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

View File

@@ -335,6 +335,22 @@ namespace FlaxEditor.Windows.Assets
}
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
if (key == KeyboardKeys.Spacebar)
{
if (_previewSource?.State == AudioSource.States.Playing)
OnPause();
else
OnPlay();
}
return false;
}
/// <inheritdoc />
public override bool UseLayoutData => true;

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
@@ -249,6 +250,10 @@ namespace FlaxEditor.Windows
});
}
// Remove any leftover separator
if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
cm.ItemsContainer.Children.Last().Dispose();
// Show it
cm.Show(this, location);
}

View File

@@ -629,8 +629,9 @@ namespace FlaxEditor.Windows
if (items.Count == 0)
return;
// TODO: remove items that depend on different items in the list: use wants to remove `folderA` and `folderA/asset.x`, we should just remove `folderA`
// Sort items to remove files first, then folders
var toDelete = new List<ContentItem>(items);
toDelete.Sort((a, b) => a.IsFolder ? 1 : b.IsFolder ? -1 : a.Compare(b));
string msg = toDelete.Count == 1
? string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path)

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Networking;
namespace FlaxEngine
{
@@ -37,11 +38,14 @@ namespace FlaxEditor.Windows.Profiler
{
private readonly SingleChart _dataSentChart;
private readonly SingleChart _dataReceivedChart;
private readonly SingleChart _dataSentRateChart;
private readonly SingleChart _dataReceivedRateChart;
private readonly Table _tableRpc;
private readonly Table _tableRep;
private SamplesBuffer<ProfilingTools.NetworkEventStat[]> _events;
private List<Row> _tableRowsCache;
private FlaxEngine.Networking.NetworkDriverStats _prevStats;
private SamplesBuffer<ProfilingTools.NetworkEventStat[]> _events;
private NetworkDriverStats _prevStats;
private List<NetworkDriverStats> _stats;
public Network()
: base("Network")
@@ -76,6 +80,20 @@ namespace FlaxEditor.Windows.Profiler
Parent = layout,
};
_dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged;
_dataSentRateChart = new SingleChart
{
Title = "Data Sent Rate",
FormatSample = FormatSampleBytesRate,
Parent = layout,
};
_dataSentRateChart.SelectedSampleChanged += OnSelectedSampleChanged;
_dataReceivedRateChart = new SingleChart
{
Title = "Data Received Rate",
FormatSample = FormatSampleBytesRate,
Parent = layout,
};
_dataReceivedRateChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Tables
_tableRpc = InitTable(layout, "RPC Name");
@@ -87,24 +105,52 @@ namespace FlaxEditor.Windows.Profiler
{
_dataSentChart.Clear();
_dataReceivedChart.Clear();
_dataSentRateChart.Clear();
_dataReceivedRateChart.Clear();
_events?.Clear();
_stats?.Clear();
_prevStats = new NetworkDriverStats();
}
/// <inheritdoc />
public override void Update(ref SharedUpdateData sharedData)
{
// Gather peer stats
var peers = FlaxEngine.Networking.NetworkPeer.Peers;
var stats = new FlaxEngine.Networking.NetworkDriverStats();
var peers = NetworkPeer.Peers;
var thisStats = new NetworkDriverStats();
thisStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT
foreach (var peer in peers)
{
var peerStats = peer.NetworkDriver.GetStats();
stats.TotalDataSent += peerStats.TotalDataSent;
stats.TotalDataReceived += peerStats.TotalDataReceived;
thisStats.TotalDataSent += peerStats.TotalDataSent;
thisStats.TotalDataReceived += peerStats.TotalDataReceived;
}
_dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0));
_dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0));
_prevStats = stats;
var stats = thisStats;
stats.TotalDataSent = (uint)Mathf.Max((long)thisStats.TotalDataSent - (long)_prevStats.TotalDataSent, 0);
stats.TotalDataReceived = (uint)Mathf.Max((long)thisStats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0);
_dataSentChart.AddSample(stats.TotalDataSent);
_dataReceivedChart.AddSample(stats.TotalDataReceived);
_prevStats = thisStats;
if (_stats == null)
_stats = new List<NetworkDriverStats>();
_stats.Add(stats);
// Remove all stats older than 1 second
while (_stats.Count > 0 && thisStats.RTT - _stats[0].RTT >= 1.0f)
_stats.RemoveAt(0);
// Calculate average data rates (from last second)
var avgStats = new NetworkDriverStats();
foreach (var e in _stats)
{
avgStats.TotalDataSent += e.TotalDataSent;
avgStats.TotalDataReceived += e.TotalDataReceived;
}
avgStats.TotalDataSent /= (uint)_stats.Count;
avgStats.TotalDataReceived /= (uint)_stats.Count;
_dataSentRateChart.AddSample(avgStats.TotalDataSent);
_dataReceivedRateChart.AddSample(avgStats.TotalDataReceived);
// Gather network events
var events = ProfilingTools.EventsNetwork;
@@ -118,6 +164,8 @@ namespace FlaxEditor.Windows.Profiler
{
_dataSentChart.SelectedSampleIndex = selectedFrame;
_dataReceivedChart.SelectedSampleIndex = selectedFrame;
_dataSentRateChart.SelectedSampleIndex = selectedFrame;
_dataReceivedRateChart.SelectedSampleIndex = selectedFrame;
// Update events tables
if (_events != null)
@@ -128,7 +176,7 @@ namespace FlaxEditor.Windows.Profiler
_tableRep.IsLayoutLocked = true;
RecycleTableRows(_tableRpc, _tableRowsCache);
RecycleTableRows(_tableRep, _tableRowsCache);
var events = _events.Get(selectedFrame);
var rowCount = Int2.Zero;
if (events != null && events.Length != 0)
@@ -186,7 +234,7 @@ namespace FlaxEditor.Windows.Profiler
_tableRep.Visible = rowCount.Y != 0;
_tableRpc.Children.Sort(SortRows);
_tableRep.Children.Sort(SortRows);
_tableRpc.UnlockChildrenRecursive();
_tableRpc.PerformLayout();
_tableRep.UnlockChildrenRecursive();
@@ -257,6 +305,11 @@ namespace FlaxEditor.Windows.Profiler
return Utilities.Utils.FormatBytesCount((ulong)v);
}
private static string FormatSampleBytesRate(float v)
{
return Utilities.Utils.FormatBytesCount((ulong)v) + "/s";
}
private static string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,8 @@
int alError = alGetError(); \
if (alError != 0) \
{ \
LOG(Error, "OpenAL method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), alError, __LINE__ - 1); \
const Char* errorStr = GetOpenALErrorString(alError); \
LOG(Error, "OpenAL method {0} failed with error 0x{1:X}:{2} (at line {3})", TEXT(#method), alError, errorStr, __LINE__ - 1); \
} \
}
#endif
@@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return 0;
}
const Char* GetOpenALErrorString(int error)
{
switch (error)
{
case AL_NO_ERROR:
return TEXT("AL_NO_ERROR");
case AL_INVALID_NAME:
return TEXT("AL_INVALID_NAME");
case AL_INVALID_ENUM:
return TEXT("AL_INVALID_ENUM");
case AL_INVALID_VALUE:
return TEXT("AL_INVALID_VALUE");
case AL_INVALID_OPERATION:
return TEXT("AL_INVALID_OPERATION");
case AL_OUT_OF_MEMORY:
return TEXT("AL_OUT_OF_MEMORY");
default:
break;
}
return TEXT("???");
}
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
{
#if ALC_MULTIPLE_LISTENERS
@@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init()
// Init
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
ALC::RebuildContexts(true);
int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
if (clampedIndex == Audio::GetActiveDeviceIndex())
{
ALC::RebuildContexts(true);
}
Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))

View File

@@ -27,7 +27,15 @@
#define MAX_INPUT_CHANNELS 2
#define MAX_OUTPUT_CHANNELS 8
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
#if ENABLE_ASSERTION
#define XAUDIO2_CHECK_ERROR(method) \
if (hr != 0) \
{ \
LOG(Error, "XAudio2 method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), (uint32)hr, __LINE__ - 1); \
}
#else
#define XAUDIO2_CHECK_ERROR(method)
#endif
#define FLAX_COORD_SCALE 0.01f // units are meters
#define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE
#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * FLAX_COORD_SCALE, vec.Y * FLAX_COORD_SCALE, vec.Z * FLAX_COORD_SCALE)
@@ -104,7 +112,9 @@ namespace XAudio2
COM_DECLSPEC_NOTHROW void STDMETHODCALLTYPE OnVoiceError(THIS_ void* pBufferContext, HRESULT Error) override
{
#if ENABLE_ASSERTION
LOG(Warning, "IXAudio2VoiceCallback::OnVoiceError! Error: 0x{0:x}", Error);
#endif
}
public:
@@ -121,7 +131,8 @@ namespace XAudio2
XAUDIO2_SEND_DESCRIPTOR Destination;
float Pitch;
float Pan;
float StartTime;
float StartTimeForQueueBuffer;
float LastBufferStartTime;
float DopplerFactor;
uint64 LastBufferStartSamplesPlayed;
int32 BuffersProcessed;
@@ -145,7 +156,8 @@ namespace XAudio2
Destination.pOutputVoice = nullptr;
Pitch = 1.0f;
Pan = 0.0f;
StartTime = 0.0f;
StartTimeForQueueBuffer = 0.0f;
LastBufferStartTime = 0.0f;
IsDirty = false;
Is3D = false;
IsPlaying = false;
@@ -255,18 +267,18 @@ namespace XAudio2
buffer.pAudioData = aBuffer->Data.Get();
buffer.AudioBytes = aBuffer->Data.Count();
if (aSource->StartTime > ZeroTolerance)
if (aSource->StartTimeForQueueBuffer > ZeroTolerance)
{
buffer.PlayBegin = (UINT32)(aSource->StartTime * (aBuffer->Info.SampleRate * aBuffer->Info.NumChannels));
buffer.PlayLength = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels - buffer.PlayBegin;
aSource->StartTime = 0;
// Offset start position when playing buffer with a custom time offset
const uint32 bytesPerSample = aBuffer->Info.BitDepth / 8 * aBuffer->Info.NumChannels;
buffer.PlayBegin = (UINT32)(aSource->StartTimeForQueueBuffer * aBuffer->Info.SampleRate);
buffer.PlayLength = (buffer.AudioBytes / bytesPerSample) - buffer.PlayBegin;
aSource->LastBufferStartTime = aSource->StartTimeForQueueBuffer;
aSource->StartTimeForQueueBuffer = 0;
}
const HRESULT hr = aSource->Voice->SubmitSourceBuffer(&buffer);
if (FAILED(hr))
{
LOG(Warning, "XAudio2: Failed to submit source buffer (error: 0x{0:x})", hr);
}
XAUDIO2_CHECK_ERROR(SubmitSourceBuffer);
}
void VoiceCallback::OnBufferEnd(void* pBufferContext)
@@ -375,7 +387,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
const auto& header = clip->AudioHeader;
auto& format = aSource->Format;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write)
format.nSamplesPerSec = header.Info.SampleRate;
format.wBitsPerSample = header.Info.BitDepth;
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8));
@@ -391,12 +403,10 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
1,
&aSource->Destination
};
const HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList);
HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList);
XAUDIO2_CHECK_ERROR(CreateSourceVoice);
if (FAILED(hr))
{
LOG(Error, "Failed to create XAudio2 voice. Error: 0x{0:x}", hr);
return;
}
// Prepare source state
aSource->Callback.Source = source;
@@ -410,7 +420,8 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
aSource->DopplerFactor = source->GetDopplerFactor();
aSource->UpdateTransform(source);
aSource->UpdateVelocity(source);
aSource->Voice->SetVolume(source->GetVolume());
hr = aSource->Voice->SetVolume(source->GetVolume());
XAUDIO2_CHECK_ERROR(SetVolume);
// 0 is invalid ID so shift them
sourceID++;
@@ -451,7 +462,8 @@ void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source)
auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice)
{
aSource->Voice->SetVolume(source->GetVolume());
const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume());
XAUDIO2_CHECK_ERROR(SetVolume);
}
}
@@ -494,12 +506,18 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
HRESULT hr;
const bool isPlaying = source->IsActuallyPlayingSth();
if (isPlaying)
aSource->Voice->Stop();
{
hr = aSource->Voice->Stop();
XAUDIO2_CHECK_ERROR(Stop);
}
aSource->Voice->FlushSourceBuffers();
hr = aSource->Voice->FlushSourceBuffers();
XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
aSource->LastBufferStartSamplesPlayed = 0;
aSource->LastBufferStartTime = 0;
aSource->BuffersProcessed = 0;
XAUDIO2_BUFFER buffer = { 0 };
@@ -512,12 +530,15 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels;
buffer.PlayBegin = state.SamplesPlayed % totalSamples;
buffer.PlayLength = totalSamples - buffer.PlayBegin;
aSource->StartTime = 0;
aSource->StartTimeForQueueBuffer = 0;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
if (isPlaying)
aSource->Voice->Start();
{
hr = aSource->Voice->Start();
XAUDIO2_CHECK_ERROR(Start);
}
}
void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source)
@@ -572,7 +593,8 @@ void AudioBackendXAudio2::Source_Play(AudioSource* source)
if (aSource && aSource->Voice && !aSource->IsPlaying)
{
// Play
aSource->Voice->Start();
const HRESULT hr = aSource->Voice->Start();
XAUDIO2_CHECK_ERROR(Start);
aSource->IsPlaying = true;
}
}
@@ -583,7 +605,8 @@ void AudioBackendXAudio2::Source_Pause(AudioSource* source)
if (aSource && aSource->Voice && aSource->IsPlaying)
{
// Pause
aSource->Voice->Stop();
const HRESULT hr = aSource->Voice->Stop();
XAUDIO2_CHECK_ERROR(Stop);
aSource->IsPlaying = false;
}
}
@@ -593,14 +616,18 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice)
{
aSource->StartTime = 0.0f;
aSource->StartTimeForQueueBuffer = 0.0f;
aSource->LastBufferStartTime = 0.0f;
// Pause
aSource->Voice->Stop();
HRESULT hr = aSource->Voice->Stop();
XAUDIO2_CHECK_ERROR(Stop);
aSource->IsPlaying = false;
// Unset streaming buffers to rewind
aSource->Voice->FlushSourceBuffers();
hr = aSource->Voice->FlushSourceBuffers();
XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
Platform::Sleep(10); // TODO: find a better way to handle case when VoiceCallback::OnBufferEnd is called after source was stopped thus BuffersProcessed != 0, probably via buffers contexts ptrs
aSource->BuffersProcessed = 0;
aSource->Callback.PeekSamples();
}
@@ -612,7 +639,7 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float
if (aSource)
{
// Store start time so next buffer submitted will start from here (assumes audio is stopped)
aSource->StartTime = value;
aSource->StartTimeForQueueBuffer = value;
}
}
@@ -628,8 +655,9 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source
aSource->Voice->GetState(&state);
const uint32 numChannels = clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / numChannels;
const uint32 sampleRate = clipInfo.SampleRate;// / clipInfo.NumChannels;
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
time = aSource->StartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, clipInfo.SampleRate));
time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, sampleRate));
}
return time;
}
@@ -697,10 +725,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
if (aSource && aSource->Voice)
{
const HRESULT hr = aSource->Voice->FlushSourceBuffers();
if (FAILED(hr))
{
LOG(Warning, "XAudio2: FlushSourceBuffers failed. Error: 0x{0:x}", hr);
}
XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
aSource->BuffersProcessed = 0;
}
}
@@ -749,8 +774,7 @@ void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const Aud
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
const uint32 bytesPerSample = info.BitDepth / 8;
const int32 samplesLength = info.NumSamples * bytesPerSample;
const uint32 samplesLength = info.NumSamples * info.BitDepth / 8;
aBuffer->Info = info;
aBuffer->Data.Set(samples, samplesLength);
@@ -779,7 +803,8 @@ void AudioBackendXAudio2::Base_SetVolume(float value)
{
if (XAudio2::MasteringVoice)
{
XAudio2::MasteringVoice->SetVolume(value);
const HRESULT hr = XAudio2::MasteringVoice->SetVolume(value);
XAUDIO2_CHECK_ERROR(SetVolume);
}
}

View File

@@ -538,11 +538,7 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
// Check if is already loaded
if (IsLoaded())
return;
// Start loading (using async tasks)
ASSERT(!IsLoaded());
ASSERT(_loadingTask == nullptr);
_loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/SceneReference.h"
@@ -76,6 +77,12 @@ public:
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
bool ShadersGenerateDebugData = false;
/// <summary>
/// If checked, skips bundling default engine fonts for UI. Use if to reduce build size if you don't use default engine fonts but custom ones only.
/// </summary>
API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
bool SkipDefaultFonts = false;
/// <summary>
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
/// </summary>
@@ -106,6 +113,7 @@ public:
DESERIALIZE(AdditionalAssetFolders);
DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData);
DESERIALIZE(SkipDefaultFonts);
DESERIALIZE(SkipDotnetPackaging);
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
}

View File

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

View File

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

View File

@@ -1,10 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Half.h"
#include "Rectangle.h"
#include "Vector2.h"
#include "Vector3.h"
#include "Vector4.h"
#include "Rectangle.h"
#include "Color.h"
static_assert(sizeof(Half) == 2, "Invalid Half type size.");
@@ -16,12 +14,47 @@ Half2 Half2::Zero(0.0f, 0.0f);
Half3 Half3::Zero(0.0f, 0.0f, 0.0f);
Half4 Half4::Zero(0.0f, 0.0f, 0.0f, 0.0f);
Half2::Half2(const Float2& v)
#if !USE_SSE_HALF_CONVERSION
Half Float16Compressor::Compress(float value)
{
X = Float16Compressor::Compress(v.X);
Y = Float16Compressor::Compress(v.Y);
Bits v, s;
v.f = value;
uint32 sign = v.si & signN;
v.si ^= sign;
sign >>= shiftSign; // logical shift
s.si = mulN;
s.si = static_cast<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
{
return Float2(
@@ -30,13 +63,6 @@ Float2 Half2::ToFloat2() const
);
}
Half3::Half3(const Float3& v)
{
X = Float16Compressor::Compress(v.X);
Y = Float16Compressor::Compress(v.Y);
Z = Float16Compressor::Compress(v.Z);
}
Float3 Half3::ToFloat3() const
{
return Float3(

View File

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

View File

@@ -5,7 +5,6 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
@@ -14,16 +13,15 @@ const Char* HertzSizesData[] = { TEXT("Hz"), TEXT("KHz"), TEXT("MHz"), TEXT("GHz
Span<const Char*> Utilities::Private::BytesSizes(BytesSizesData, ARRAY_COUNT(BytesSizesData));
Span<const Char*> Utilities::Private::HertzSizes(HertzSizesData, ARRAY_COUNT(HertzSizesData));
namespace ObjectsRemovalServiceImpl
namespace
{
CriticalSection PoolLocker;
DateTime LastUpdate;
float LastUpdateGameTime;
Dictionary<Object*, float> Pool(8192);
uint64 PoolCounter = 0;
}
using namespace ObjectsRemovalServiceImpl;
class ObjectsRemoval : public EngineService
{
public:
@@ -64,6 +62,7 @@ void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime)
PoolLocker.Lock();
Pool[obj] = timeToLive;
PoolCounter++;
PoolLocker.Unlock();
}
@@ -72,6 +71,7 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
PROFILE_CPU();
PoolLocker.Lock();
PoolCounter = 0;
// Update timeouts and delete objects that timed out
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();
}
@@ -121,7 +139,7 @@ void ObjectsRemoval::Dispose()
// Delete all remaining objects
{
ScopeLock lock(PoolLocker);
PoolLocker.Lock();
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{
Object* obj = i->Key;
@@ -129,6 +147,7 @@ void ObjectsRemoval::Dispose()
obj->OnDeleteObject();
}
Pool.Clear();
PoolLocker.Unlock();
}
}

View File

@@ -107,6 +107,26 @@ public:
ASSERT(index >= 0 && index < _length);
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>

View File

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

View File

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

View File

@@ -119,6 +119,7 @@ namespace FlaxEngine.Interop
{
}
#if !USE_AOT
// 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 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);
return ManagedHandle.Alloc(obj);
}
#endif
internal static void* NativeAlloc(int byteCount)
{
@@ -429,6 +431,9 @@ namespace FlaxEngine.Interop
/// </summary>
internal static int GetFieldOffset(FieldInfo field, Type type)
{
if (field.IsLiteral)
return 0;
// Get the address of the field, source: https://stackoverflow.com/a/56512720
int fieldOffset = Unsafe.Read<int>((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF;
if (!type.IsValueType)
@@ -436,6 +441,23 @@ namespace FlaxEngine.Interop
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>
/// Returns a reference to the value of the field.
/// </summary>
@@ -462,6 +484,7 @@ namespace FlaxEngine.Interop
byte* fieldPtr = (byte*)Unsafe.As<T, IntPtr>(ref fieldOwner) + fieldOffset;
return ref Unsafe.AsRef<TField>(fieldPtr);
}
#endif
}
/// <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
{
#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);
fieldValueRef = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
#endif
fieldSize = IntPtr.Size;
}
private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class
{
#if USE_AOT
IntPtr fieldValue = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue);
#else
ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner);
fieldValueRef = Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer());
#endif
fieldSize = IntPtr.Size;
}
private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct
{
ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, IntPtr>(fieldOffset, ref fieldOwner);
Unsafe.Write<IntPtr>(nativeFieldPtr.ToPointer(), fieldValueRef);
#if USE_AOT
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;
}
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);
Unsafe.Write<IntPtr>(nativeFieldPtr.ToPointer(), fieldValueRef);
#if USE_AOT
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;
}
@@ -799,8 +842,15 @@ namespace FlaxEngine.Interop
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
}
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
MarshalHelper<TField>.ToManaged(ref fieldValueRef, nativeFieldPtr, false);
#if USE_AOT
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
@@ -813,8 +863,15 @@ namespace FlaxEngine.Interop
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
}
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
MarshalHelper<TField>.ToManaged(ref fieldValueRef, nativeFieldPtr, false);
#if USE_AOT
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
@@ -825,8 +882,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
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
@@ -837,8 +901,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
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
@@ -852,11 +923,11 @@ namespace FlaxEngine.Interop
}
#if USE_AOT
TField fieldValueRef = (TField)field.GetValue(fieldOwner);
TField fieldValue = (TField)field.GetValue(fieldOwner);
#else
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#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
@@ -870,11 +941,11 @@ namespace FlaxEngine.Interop
}
#if USE_AOT
TField fieldValueRef = (TField)field.GetValue(fieldOwner);
TField fieldValue = (TField)field.GetValue(fieldOwner);
#else
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
#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);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
MarshalHelper<TField>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
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
@@ -915,8 +993,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
MarshalHelper<TField>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
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
@@ -926,8 +1011,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
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
@@ -937,8 +1029,15 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField[]>(fieldOffset, ref fieldOwner);
MarshalHelper<TField[]>.ToManaged(ref fieldValueRef, Unsafe.Read<IntPtr>(nativeFieldPtr.ToPointer()), false);
#if USE_AOT
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
@@ -948,8 +1047,12 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
MarshalHelper<TField>.ToNative(ref fieldValueRef, nativeFieldPtr);
#if USE_AOT
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
@@ -959,8 +1062,12 @@ namespace FlaxEngine.Interop
nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size);
fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32();
ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference<T, TField>(fieldOffset, ref fieldOwner);
MarshalHelper<TField>.ToNative(ref fieldValueRef, nativeFieldPtr);
#if USE_AOT
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 offsets = MarshalHelper<T>.marshallableFieldOffsets;
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);
nativePtr += fieldSize;
@@ -1306,11 +1413,13 @@ namespace FlaxEngine.Interop
return RuntimeHelpers.GetUninitializedObject(wrappedType);
}
#if !USE_AOT
internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr)
{
object obj = CreateObject();
object obj = RuntimeHelpers.GetUninitializedObject(wrappedType);
if (obj is Object)
{
// TODO: use UnsafeAccessorAttribute on .NET 8 and use this path on all platforms (including non-Desktop, see MCore::ScriptingObject::CreateScriptingObject)
{
ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference<IntPtr>(unmanagedPtrFieldOffset, ref obj);
fieldRef = unmanagedPtr;
@@ -1331,8 +1440,9 @@ namespace FlaxEngine.Interop
return obj;
}
#endif
public static implicit operator Type(TypeHolder holder) => holder?.type ?? null;
public static implicit operator Type(TypeHolder holder) => holder?.type;
public bool Equals(TypeHolder other) => type == other.type;
public bool Equals(Type other) => type == other;
public override int GetHashCode() => type.GetHashCode();

View File

@@ -22,7 +22,7 @@ class ScreenService : public EngineService
{
public:
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
{
PROFILE_CPU();
int32 result = 0;
for (auto i = Instances.Begin(); i.IsNotEnd(); i++)
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
if (i->Type == index)
result++;
}
return result;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -174,6 +174,8 @@ bool ForwardMaterialShader::Load()
// Forward Pass
psDesc.VS = _shader->GetVS("VS");
if (psDesc.VS == nullptr)
return true;
psDesc.PS = _shader->GetPS("PS_Forward");
psDesc.DepthWriteEnable = false;
psDesc.BlendMode = BlendingMode::AlphaBlend;

View File

@@ -339,7 +339,7 @@ namespace FlaxEngine
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, int[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -357,7 +357,7 @@ namespace FlaxEngine
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(List<Vector3> vertices, List<int> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -375,7 +375,7 @@ namespace FlaxEngine
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, uint[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -393,7 +393,7 @@ namespace FlaxEngine
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(List<Vector3> vertices, List<uint> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -411,7 +411,7 @@ namespace FlaxEngine
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
@@ -429,7 +429,7 @@ namespace FlaxEngine
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(List<Vector3> vertices, List<ushort> triangles, List<Vector3> normals = null, List<Vector3> tangents = null, List<Vector2> uv = null, List<Color32> colors = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);

View File

@@ -609,7 +609,7 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c
ScopeLock lock(model->Locker);
if (model->IsVirtual())
{
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download");
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
return true;
}

View File

@@ -318,7 +318,9 @@ void MeshData::BuildIndexBuffer()
}
const auto endTime = Platform::GetTimeSeconds();
LOG(Info, "Generated index buffer for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), Positions.Count(), Indices.Count());
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
if (time > 0.5f) // Don't log if generation was fast enough
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, Indices.Count(), TEXT("indices"));
}
void MeshData::FindPositions(const Float3& position, float epsilon, Array<int32>& result)
@@ -449,7 +451,9 @@ bool MeshData::GenerateNormals(float smoothingAngle)
}
const auto endTime = Platform::GetTimeSeconds();
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
if (time > 0.5f) // Don't log if generation was fast enough
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("normals"));
return false;
}
@@ -685,7 +689,10 @@ bool MeshData::GenerateTangents(float smoothingAngle)
#endif
const auto endTime = Platform::GetTimeSeconds();
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
if (time > 0.5f) // Don't log if generation was fast enough
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("tangents"));
return false;
}
@@ -872,7 +879,9 @@ void MeshData::ImproveCacheLocality()
Allocator::Free(piCandidates);
const auto endTime = Platform::GetTimeSeconds();
LOG(Info, "Cache relevant optimize for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime));
const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
if (time > 0.5f) // Don't log if generation was fast enough
LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("optimized indices"));
}
float MeshData::CalculateTrianglesArea() const

View File

@@ -216,7 +216,7 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, int[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
@@ -235,7 +235,7 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
@@ -254,7 +254,7 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
[Obsolete("Deprecated in 1.4")]
[Obsolete("Deprecated in 1.4, use overload with Float3 and Float2 parameters")]
public void UpdateMesh(Vector3[] vertices, ushort[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null)
{
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));

View File

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

View File

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

View File

@@ -50,18 +50,7 @@ bool GPUDeviceNull::Init()
// Init device limits
{
auto& limits = Limits;
limits.HasCompute = false;
limits.HasTessellation = false;
limits.HasGeometryShaders = false;
limits.HasInstancing = false;
limits.HasVolumeTextureRendering = false;
limits.HasDrawIndirect = false;
limits.HasAppendConsumeBuffers = false;
limits.HasSeparateRenderTargetBlendState = false;
limits.HasDepthAsSRV = false;
limits.HasReadOnlyDepth = false;
limits.HasMultisampleDepthAsSRV = false;
limits.HasTypedUAVLoad = false;
Platform::MemoryClear(&limits, sizeof(limits));
limits.MaximumMipLevelsCount = 14;
limits.MaximumTexture1DSize = 8192;
limits.MaximumTexture1DArraySize = 512;
@@ -70,11 +59,8 @@ bool GPUDeviceNull::Init()
limits.MaximumTexture3DSize = 2048;
limits.MaximumTextureCubeSize = 16384;
limits.MaximumSamplerAnisotropy = 1;
for (int32 i = 0; i < static_cast<int32>(PixelFormat::MAX); i++)
{
FeaturesPerFormat[i] = FormatFeatures(static_cast<PixelFormat>(i), MSAALevel::None, FormatSupport::None);
}
}
// 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)
{
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
vkCmdDraw(cmdBuffer->GetHandle(), verticesCount, instanceCount, startVertex, startInstance);
RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3);
}
void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex)
{
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
vkCmdDrawIndexed(cmdBuffer->GetHandle(), indicesCount, instanceCount, startIndex, startVertex, startInstance);
RENDER_STAT_DRAW_CALL(0, indicesCount / 3 * instanceCount);
}
@@ -1227,10 +1227,9 @@ void GPUContextVulkan::DrawIndexedInstanced(uint32 indicesCount, uint32 instance
void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
{
ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
OnDrawCall();
auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
vkCmdDrawIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndirectCommand));
RENDER_STAT_DRAW_CALL(0, 0);
}
@@ -1238,10 +1237,9 @@ void GPUContextVulkan::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 of
void GPUContextVulkan::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
{
ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
OnDrawCall();
auto bufferForArgsVK = (GPUBufferVulkan*)bufferForArgs;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
OnDrawCall();
vkCmdDrawIndexedIndirect(cmdBuffer->GetHandle(), bufferForArgsVK->GetHandle(), (VkDeviceSize)offsetForArgs, 1, sizeof(VkDrawIndexedIndirectCommand));
RENDER_STAT_DRAW_CALL(0, 0);
}

View File

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

View File

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

View File

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

View File

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

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