Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts: # Source/Engine/Renderer/RenderList.cpp # Source/Engine/Renderer/RenderList.h
This commit is contained in:
@@ -79,6 +79,9 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SDK/@EntryIndexedValue">SDK</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=VS/@EntryIndexedValue">VS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue"><Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue"><Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue"><Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
@@ -220,6 +223,7 @@
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002EFunctionReturnStyleSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002ENamespaceIndentationSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace FlaxEditor.Content.Import
|
||||
FileTypes["dds"] = ImportTexture;
|
||||
FileTypes["hdr"] = ImportTexture;
|
||||
FileTypes["raw"] = ImportTexture;
|
||||
FileTypes["exr"] = ImportTexture;
|
||||
|
||||
// Models
|
||||
FileTypes["obj"] = ImportModel;
|
||||
|
||||
@@ -128,6 +128,11 @@ namespace FlaxEditor.Content.Import
|
||||
_settings.Settings.Type = TextureFormatType.HdrRGBA;
|
||||
_settings.Settings.Compress = false;
|
||||
}
|
||||
else if (extension == ".exr")
|
||||
{
|
||||
// HDR image
|
||||
_settings.Settings.Type = TextureFormatType.HdrRGBA;
|
||||
}
|
||||
else if (extension == ".hdr")
|
||||
{
|
||||
// HDR sky texture
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEditor.CustomEditors.Elements;
|
||||
@@ -9,6 +10,8 @@ using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Json;
|
||||
@@ -111,6 +114,38 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var actor = (Actor)Values[0];
|
||||
var scriptType = TypeUtils.GetType(actor.TypeName);
|
||||
var item = scriptType.ContentItem;
|
||||
if (Presenter.Owner is PropertiesWindow propertiesWindow)
|
||||
{
|
||||
var lockButton = cm.AddButton(propertiesWindow.LockObjects ? "Unlock" : "Lock");
|
||||
lockButton.ButtonClicked += button =>
|
||||
{
|
||||
propertiesWindow.LockObjects = !propertiesWindow.LockObjects;
|
||||
|
||||
// Reselect current selection
|
||||
if (!propertiesWindow.LockObjects && Editor.Instance.SceneEditing.SelectionCount > 0)
|
||||
{
|
||||
var cachedSelection = Editor.Instance.SceneEditing.Selection.ToArray();
|
||||
Editor.Instance.SceneEditing.Select(null);
|
||||
Editor.Instance.SceneEditing.Select(cachedSelection);
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (Presenter.Owner is PrefabWindow prefabWindow)
|
||||
{
|
||||
var lockButton = cm.AddButton(prefabWindow.LockSelectedObjects ? "Unlock" : "Lock");
|
||||
lockButton.ButtonClicked += button =>
|
||||
{
|
||||
prefabWindow.LockSelectedObjects = !prefabWindow.LockSelectedObjects;
|
||||
|
||||
// Reselect current selection
|
||||
if (!prefabWindow.LockSelectedObjects && prefabWindow.Selection.Count > 0)
|
||||
{
|
||||
var cachedSelection = prefabWindow.Selection.ToList();
|
||||
prefabWindow.Select(null);
|
||||
prefabWindow.Select(cachedSelection);
|
||||
}
|
||||
};
|
||||
}
|
||||
cm.AddButton("Copy ID", OnClickCopyId);
|
||||
cm.AddButton("Edit actor type", OnClickEditActorType).Enabled = item != null;
|
||||
var showButton = cm.AddButton("Show in content window", OnClickShowActorType);
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
return;
|
||||
}
|
||||
|
||||
EvaluateVisibleIf(itemLayout, item, GetLabelIndex(itemLayout, item));
|
||||
|
||||
// Add labels with a check box
|
||||
var label = new CheckablePropertyNameLabel(item.DisplayName);
|
||||
label.CheckBox.Tag = setting.Bit;
|
||||
|
||||
@@ -582,55 +582,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the property for the given item.
|
||||
/// Evaluate the <see cref="VisibleIfAttribute"/> cache for a given property item.
|
||||
/// </summary>
|
||||
/// <param name="itemLayout">The item layout.</param>
|
||||
/// <param name="itemValues">The item values.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
/// <param name="labelIndex">The label index.</param>
|
||||
protected virtual void EvaluateVisibleIf(LayoutElementsContainer itemLayout, ItemInfo item, int labelIndex)
|
||||
{
|
||||
int labelIndex = 0;
|
||||
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
|
||||
itemLayout.Children.Count > 0 &&
|
||||
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
{
|
||||
labelIndex = propertiesListElement.Labels.Count;
|
||||
}
|
||||
|
||||
itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
|
||||
|
||||
if (item.IsReadOnly && itemLayout.Children.Count > 0)
|
||||
{
|
||||
PropertiesListElement list = null;
|
||||
int firstChildControlIndex = 0;
|
||||
bool disableSingle = true;
|
||||
var control = itemLayout.Children[itemLayout.Children.Count - 1];
|
||||
if (control is GroupElement group && group.Children.Count > 0)
|
||||
{
|
||||
list = group.Children[0] as PropertiesListElement;
|
||||
disableSingle = false; // Disable all nested editors
|
||||
}
|
||||
else if (control is PropertiesListElement list1)
|
||||
{
|
||||
list = list1;
|
||||
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
|
||||
}
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
// Disable controls added to the editor
|
||||
var count = list.Properties.Children.Count;
|
||||
for (int j = firstChildControlIndex; j < count; j++)
|
||||
{
|
||||
var child = list.Properties.Children[j];
|
||||
if (disableSingle && child is PropertyNameLabel)
|
||||
break;
|
||||
|
||||
if (child != null)
|
||||
child.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0)
|
||||
{
|
||||
PropertiesListElement list = null;
|
||||
@@ -669,6 +627,73 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the label index.
|
||||
/// </summary>
|
||||
/// <param name="itemLayout">The item layout.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>The label index.</returns>
|
||||
protected virtual int GetLabelIndex(LayoutElementsContainer itemLayout, ItemInfo item)
|
||||
{
|
||||
int labelIndex = 0;
|
||||
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
|
||||
itemLayout.Children.Count > 0 &&
|
||||
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||
{
|
||||
labelIndex = propertiesListElement.Labels.Count;
|
||||
}
|
||||
|
||||
return labelIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the property for the given item.
|
||||
/// </summary>
|
||||
/// <param name="itemLayout">The item layout.</param>
|
||||
/// <param name="itemValues">The item values.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||
{
|
||||
int labelIndex = GetLabelIndex(itemLayout, item);
|
||||
|
||||
itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
|
||||
|
||||
if (item.IsReadOnly && itemLayout.Children.Count > 0)
|
||||
{
|
||||
PropertiesListElement list = null;
|
||||
int firstChildControlIndex = 0;
|
||||
bool disableSingle = true;
|
||||
var control = itemLayout.Children[itemLayout.Children.Count - 1];
|
||||
if (control is GroupElement group && group.Children.Count > 0)
|
||||
{
|
||||
list = group.Children[0] as PropertiesListElement;
|
||||
disableSingle = false; // Disable all nested editors
|
||||
}
|
||||
else if (control is PropertiesListElement list1)
|
||||
{
|
||||
list = list1;
|
||||
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
|
||||
}
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
// Disable controls added to the editor
|
||||
var count = list.Properties.Children.Count;
|
||||
for (int j = firstChildControlIndex; j < count; j++)
|
||||
{
|
||||
var child = list.Properties.Children[j];
|
||||
if (disableSingle && child is PropertyNameLabel)
|
||||
break;
|
||||
|
||||
if (child != null)
|
||||
child.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EvaluateVisibleIf(itemLayout, item, labelIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
}
|
||||
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||
{
|
||||
var button = menu.AddButton("Set to null");
|
||||
button.Clicked += () => _comboBox.SelectedItem = null;
|
||||
@@ -106,7 +106,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
}
|
||||
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
|
||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||
{
|
||||
var button = menu.AddButton("Set to null");
|
||||
button.Clicked += () => _comboBox.SelectedItem = null;
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace FlaxEditor.CustomEditors
|
||||
var theFirstType = TypeUtils.GetObjectType(this[0]);
|
||||
for (int i = 1; i < Count; i++)
|
||||
{
|
||||
if (theFirstType != TypeUtils.GetObjectType(this[1]))
|
||||
if (theFirstType != TypeUtils.GetObjectType(this[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f);
|
||||
var color = (_timeline.IsMovingPositionHandle ? style.SelectionBorder : style.Foreground).AlphaMultiplied(0.6f);
|
||||
Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);
|
||||
var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset);
|
||||
Matrix3x3.Multiply(ref m1, ref m2, out var m3);
|
||||
@@ -61,7 +61,8 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6));
|
||||
Render2D.PopTransform();
|
||||
|
||||
Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f));
|
||||
color = _timeline.IsMovingPositionHandle ? style.SelectionBorder : style.Foreground.RGBMultiplied(0.8f);
|
||||
Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), color);
|
||||
|
||||
base.Draw();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f;
|
||||
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap;
|
||||
|
||||
var moveColor = style.ProgressNormal;
|
||||
var moveColor = style.SelectionBorder;
|
||||
var thickness = 2.0f;
|
||||
var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal);
|
||||
Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor);
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
private Track _tack;
|
||||
private int _startFrame, _durationFrames;
|
||||
private Float2 _mouseLocation = Float2.Minimum;
|
||||
private bool _isMoving;
|
||||
internal bool _isMoving;
|
||||
private Float2 _startMoveLocation;
|
||||
private int _startMoveStartFrame;
|
||||
private int _startMoveDuration;
|
||||
@@ -347,7 +347,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
|
||||
var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge;
|
||||
var borderHighlightColor = style.BorderHighlighted;
|
||||
var moveColor = style.ProgressNormal;
|
||||
var moveColor = style.SelectionBorder;
|
||||
var selectedColor = style.BackgroundSelected;
|
||||
var moveThickness = 2.0f;
|
||||
var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -183,6 +182,49 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
base.OnDurationFramesChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||
{
|
||||
if (Timeline.Zoom > 0.5f && !IsContinuous)
|
||||
{
|
||||
// Hit-test dot
|
||||
var size = Height - 2.0f;
|
||||
var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
|
||||
return rect.Contains(ref location);
|
||||
}
|
||||
|
||||
return base.ContainsPoint(ref location, precise);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
if (Timeline.Zoom > 0.5f && !IsContinuous)
|
||||
{
|
||||
// Draw more visible dot for the event that maintains size even when zooming out
|
||||
var style = Style.Current;
|
||||
var icon = Editor.Instance.Icons.VisjectBoxClosed32;
|
||||
var size = Height - 2.0f;
|
||||
var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
|
||||
var outline = Color.Black; // Shadow
|
||||
if (_isMoving)
|
||||
outline = style.SelectionBorder;
|
||||
else if (IsMouseOver)
|
||||
outline = style.BorderHighlighted;
|
||||
else if (Timeline.SelectedMedia.Contains(this))
|
||||
outline = style.BackgroundSelected;
|
||||
Render2D.DrawSprite(icon, rect.MakeExpanded(6.0f), outline);
|
||||
Render2D.DrawSprite(icon, rect, BackgroundColor);
|
||||
|
||||
DrawChildren();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default drawing
|
||||
base.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
|
||||
@@ -717,19 +717,6 @@ 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();
|
||||
|
||||
// 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.CookAndRun})");
|
||||
_toolStripCook.ContextMenu = new ContextMenu();
|
||||
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
|
||||
_toolStripCook.ContextMenu.AddSeparator();
|
||||
var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
|
||||
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
|
||||
|
||||
ToolStrip.AddSeparator();
|
||||
|
||||
// Play
|
||||
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})");
|
||||
_toolStripPlay.ContextMenu = new ContextMenu();
|
||||
@@ -741,7 +728,6 @@ namespace FlaxEditor.Modules
|
||||
playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction;
|
||||
playActionGroup.SelectedChanged = SetPlayAction;
|
||||
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
|
||||
|
||||
var windowModesGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.GameWindowMode>();
|
||||
var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode");
|
||||
windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor");
|
||||
@@ -754,7 +740,20 @@ namespace FlaxEditor.Modules
|
||||
Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; };
|
||||
|
||||
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
|
||||
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
|
||||
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip($"Step one frame in game ({inputOptions.StepFrame})");
|
||||
|
||||
ToolStrip.AddSeparator();
|
||||
|
||||
// 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.CookAndRun})");
|
||||
_toolStripCook.ContextMenu = new ContextMenu();
|
||||
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
|
||||
_toolStripCook.ContextMenu.AddSeparator();
|
||||
var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
|
||||
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
|
||||
|
||||
UpdateToolstrip();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public readonly InputActionsContainer InputActions;
|
||||
|
||||
/// <summary>
|
||||
/// Optional feature.
|
||||
/// </summary>
|
||||
public bool PanWithMiddleMouse = false;
|
||||
|
||||
private string _currentInputText = string.Empty;
|
||||
private Float2 _movingNodesDelta;
|
||||
private HashSet<SurfaceNode> _movingNodes;
|
||||
@@ -223,15 +228,18 @@ namespace FlaxEditor.Surface
|
||||
|
||||
if (_middleMouseDown)
|
||||
{
|
||||
// Calculate delta
|
||||
var delta = location - _middleMouseDownPos;
|
||||
if (delta.LengthSquared > 0.01f)
|
||||
if (PanWithMiddleMouse)
|
||||
{
|
||||
// Move view
|
||||
_mouseMoveAmount += delta.Length;
|
||||
_rootControl.Location += delta;
|
||||
_middleMouseDownPos = location;
|
||||
Cursor = CursorType.SizeAll;
|
||||
// Calculate delta
|
||||
var delta = location - _middleMouseDownPos;
|
||||
if (delta.LengthSquared > 0.01f)
|
||||
{
|
||||
// Move view
|
||||
_mouseMoveAmount += delta.Length;
|
||||
_rootControl.Location += delta;
|
||||
_middleMouseDownPos = location;
|
||||
Cursor = CursorType.SizeAll;
|
||||
}
|
||||
}
|
||||
|
||||
// Handled
|
||||
@@ -300,7 +308,8 @@ namespace FlaxEditor.Surface
|
||||
if (_middleMouseDown)
|
||||
{
|
||||
_middleMouseDown = false;
|
||||
Cursor = CursorType.Default;
|
||||
if (PanWithMiddleMouse)
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
_isMovingSelection = false;
|
||||
ConnectingEnd(null);
|
||||
@@ -483,7 +492,7 @@ namespace FlaxEditor.Surface
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
if (_rightMouseDown || _middleMouseDown)
|
||||
if (_rightMouseDown || (_middleMouseDown && _middleMouseDown))
|
||||
{
|
||||
// Start navigating
|
||||
StartMouseCapture();
|
||||
@@ -555,9 +564,12 @@ namespace FlaxEditor.Surface
|
||||
if (_middleMouseDown && button == MouseButton.Middle)
|
||||
{
|
||||
_middleMouseDown = false;
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
if (_mouseMoveAmount > 0)
|
||||
if (_middleMouseDown)
|
||||
{
|
||||
EndMouseCapture();
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
if (_mouseMoveAmount > 0 && _middleMouseDown)
|
||||
_mouseMoveAmount = 0;
|
||||
else if (CanEdit)
|
||||
{
|
||||
|
||||
@@ -66,8 +66,8 @@ namespace FlaxEditor.Tools.Terrain
|
||||
[EditorOrder(410), EditorDisplay("Transform", "Rotation"), DefaultValue(typeof(Quaternion), "0,0,0,1"), Tooltip("Orientation of the terrain")]
|
||||
public Quaternion Orientation = Quaternion.Identity;
|
||||
|
||||
[EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(typeof(Float3), "1,1,1"), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
|
||||
public Float3 Scale = Float3.One;
|
||||
[EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(1.0f), Limit(0.0001f, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
|
||||
public float Scale = 1.0f;
|
||||
}
|
||||
|
||||
private readonly Options _options = new Options();
|
||||
@@ -147,7 +147,7 @@ namespace FlaxEditor.Tools.Terrain
|
||||
// Create terrain object and setup some options
|
||||
var terrain = new FlaxEngine.Terrain();
|
||||
terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
|
||||
terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
|
||||
terrain.Transform = new Transform(_options.Position, _options.Orientation, new Float3(_options.Scale));
|
||||
terrain.Material = _options.Material;
|
||||
terrain.CollisionLOD = _options.CollisionLOD;
|
||||
if (_options.Heightmap)
|
||||
@@ -238,24 +238,5 @@ namespace FlaxEditor.Tools.Terrain
|
||||
|
||||
return base.CanCloseWindow(reason);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (_isWorking)
|
||||
return true;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.Escape:
|
||||
OnCancel();
|
||||
return true;
|
||||
case KeyboardKeys.Return:
|
||||
OnSubmit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ struct TextureDataResult
|
||||
}
|
||||
};
|
||||
|
||||
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
|
||||
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
|
||||
{
|
||||
// Lock asset chunks (if not virtual)
|
||||
data.Lock = texture->LockData();
|
||||
@@ -103,7 +103,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
|
||||
|
||||
// Decompress or convert data if need to
|
||||
data.Mip0DataPtr = &data.Mip0Data;
|
||||
if (PixelFormatExtensions::IsCompressed(data.Format))
|
||||
if (PixelFormatExtensions::IsCompressed(data.Format) || TextureTool::GetSampler(data.Format) == nullptr)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Decompress");
|
||||
|
||||
@@ -122,7 +122,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
|
||||
srcMip.Lines = src.Height;
|
||||
|
||||
// Decompress texture
|
||||
if (TextureTool::Convert(data.Tmp, src, PixelFormat::R8G8B8A8_UNorm))
|
||||
if (TextureTool::Convert(data.Tmp, src, hdr ? PixelFormat::R16G16B16A16_Float : PixelFormat::R8G8B8A8_UNorm))
|
||||
{
|
||||
LOG(Warning, "Failed to decompress data.");
|
||||
return true;
|
||||
@@ -134,7 +134,6 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
|
||||
data.SlicePitch = data.Tmp.Items[0].Mips[0].DepthPitch;
|
||||
data.Mip0DataPtr = &data.Tmp.Items[0].Mips[0].Data;
|
||||
}
|
||||
// TODO: convert to RGBA from other formats that cannot be sampled?
|
||||
|
||||
// Check if can even sample the given format
|
||||
const auto sampler = TextureTool::GetSampler(data.Format);
|
||||
@@ -155,7 +154,6 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
LOG(Warning, "Cannot setup terain with no patches.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.GenerateTerrain");
|
||||
|
||||
// Wait for assets to be loaded
|
||||
@@ -188,7 +186,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
{
|
||||
// Get data
|
||||
TextureDataResult dataHeightmap;
|
||||
if (GetTextureDataForSampling(heightmap, dataHeightmap))
|
||||
if (GetTextureDataForSampling(heightmap, dataHeightmap, true))
|
||||
return true;
|
||||
const auto sampler = TextureTool::GetSampler(dataHeightmap.Format);
|
||||
|
||||
@@ -198,7 +196,6 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
|
||||
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
|
||||
{
|
||||
auto patch = terrain->GetPatch(patchIndex);
|
||||
|
||||
const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
|
||||
|
||||
// Sample heightmap pixels with interpolation to get actual heightmap vertices locations
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace FlaxEditor.Viewport
|
||||
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Float2 MouseDelta => _mouseDelta * 1000;
|
||||
public Float2 MouseDelta => _mouseDelta;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
|
||||
|
||||
@@ -65,6 +65,11 @@ namespace FlaxEditor.Viewport
|
||||
/// </summary>
|
||||
public bool IsAltDown;
|
||||
|
||||
/// <summary>
|
||||
/// The is alt down flag cached from the previous input. Used to make <see cref="IsControllingMouse"/> consistent when user releases Alt while orbiting with Alt+LMB.
|
||||
/// </summary>
|
||||
public bool WasAltDownBefore;
|
||||
|
||||
/// <summary>
|
||||
/// The is mouse right down flag.
|
||||
/// </summary>
|
||||
@@ -88,18 +93,20 @@ namespace FlaxEditor.Viewport
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether use is controlling mouse.
|
||||
/// </summary>
|
||||
public bool IsControllingMouse => IsMouseMiddleDown || IsMouseRightDown || (IsAltDown && IsMouseLeftDown) || Mathf.Abs(MouseWheelDelta) > 0.1f;
|
||||
public bool IsControllingMouse => IsMouseMiddleDown || IsMouseRightDown || ((IsAltDown || WasAltDownBefore) && IsMouseLeftDown) || Mathf.Abs(MouseWheelDelta) > 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gathers input from the specified window.
|
||||
/// </summary>
|
||||
/// <param name="window">The window.</param>
|
||||
/// <param name="useMouse">True if use mouse input, otherwise will skip mouse.</param>
|
||||
public void Gather(Window window, bool useMouse)
|
||||
/// <param name="prevInput">Previous input state.</param>
|
||||
public void Gather(Window window, bool useMouse, ref Input prevInput)
|
||||
{
|
||||
IsControlDown = window.GetKey(KeyboardKeys.Control);
|
||||
IsShiftDown = window.GetKey(KeyboardKeys.Shift);
|
||||
IsAltDown = window.GetKey(KeyboardKeys.Alt);
|
||||
WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown;
|
||||
|
||||
IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right);
|
||||
IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle);
|
||||
@@ -114,6 +121,7 @@ namespace FlaxEditor.Viewport
|
||||
IsControlDown = false;
|
||||
IsShiftDown = false;
|
||||
IsAltDown = false;
|
||||
WasAltDownBefore = false;
|
||||
|
||||
IsMouseRightDown = false;
|
||||
IsMouseMiddleDown = false;
|
||||
@@ -1540,7 +1548,7 @@ namespace FlaxEditor.Viewport
|
||||
_prevInput = _input;
|
||||
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
|
||||
if (canUseInput && ContainsFocus && hit == null)
|
||||
_input.Gather(win.Window, useMouse);
|
||||
_input.Gather(win.Window, useMouse, ref _prevInput);
|
||||
else
|
||||
_input.Clear();
|
||||
|
||||
@@ -1663,8 +1671,7 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
|
||||
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
|
||||
_mouseDelta = offset / size;
|
||||
_mouseDelta.Y *= size.Y / size.X;
|
||||
_mouseDelta = offset;
|
||||
|
||||
// Update delta filtering buffer
|
||||
_deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta;
|
||||
@@ -1682,8 +1689,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
else
|
||||
{
|
||||
_mouseDelta = offset / size;
|
||||
_mouseDelta.Y *= size.Y / size.X;
|
||||
_mouseDelta = offset;
|
||||
mouseDelta = _mouseDelta;
|
||||
}
|
||||
|
||||
@@ -1697,7 +1703,7 @@ namespace FlaxEditor.Viewport
|
||||
|
||||
// Update
|
||||
moveDelta *= dt * (60.0f * 4.0f);
|
||||
mouseDelta *= 200.0f * MouseSpeed * _mouseSensitivity;
|
||||
mouseDelta *= 0.1833f * MouseSpeed * _mouseSensitivity;
|
||||
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
|
||||
|
||||
// Move mouse back to the root position
|
||||
@@ -1723,7 +1729,7 @@ namespace FlaxEditor.Viewport
|
||||
var offset = _viewMousePos - _startPos;
|
||||
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
|
||||
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
|
||||
_mouseDelta = offset / size;
|
||||
_mouseDelta = offset;
|
||||
_startPos = _viewMousePos;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -291,7 +291,7 @@ namespace FlaxEditor.Viewport
|
||||
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Float2 MouseDelta => _mouseDelta * 1000;
|
||||
public Float2 MouseDelta => _mouseDelta;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);
|
||||
|
||||
@@ -336,7 +336,8 @@ namespace FlaxEditor.Viewport.Previews
|
||||
if (_showNodes)
|
||||
{
|
||||
// Draw bounding box at the node locations
|
||||
var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f));
|
||||
var boxSize = Mathf.Min(1.0f, _previewModel.Sphere.Radius / 100.0f);
|
||||
var localBox = new OrientedBoundingBox(new Vector3(-boxSize), new Vector3(boxSize));
|
||||
for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
|
||||
{
|
||||
if (nodesMask != null && !nodesMask[nodeIndex])
|
||||
|
||||
@@ -494,6 +494,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
_undo.Enabled = false;
|
||||
_undo.Clear();
|
||||
_propertiesEditor.BuildLayoutOnUpdate();
|
||||
UpdateToolstrip();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -504,6 +505,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
_undo.Enabled = true;
|
||||
_undo.Clear();
|
||||
_propertiesEditor.BuildLayoutOnUpdate();
|
||||
UpdateToolstrip();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -54,6 +54,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// <param name="before">The selection before the change.</param>
|
||||
public void OnSelectionChanged(SceneGraphNode[] before)
|
||||
{
|
||||
if (LockSelectedObjects)
|
||||
return;
|
||||
|
||||
Undo.AddAction(new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo));
|
||||
|
||||
OnSelectionChanges();
|
||||
|
||||
@@ -68,6 +68,11 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
public readonly LocalSceneGraph Graph;
|
||||
|
||||
/// <summary>
|
||||
/// Indication of if the prefab window selection is locked on specific objects.
|
||||
/// </summary>
|
||||
public bool LockSelectedObjects = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto).
|
||||
/// </summary>
|
||||
|
||||
@@ -665,7 +665,16 @@ namespace FlaxEditor.Windows
|
||||
|
||||
// Scroll to the new entry (if any added to view)
|
||||
if (scrollView && anyVisible)
|
||||
{
|
||||
panelScroll.ScrollViewTo(newEntry);
|
||||
|
||||
bool scrollViewNew = (panelScroll.VScrollBar.Maximum - panelScroll.VScrollBar.TargetValue) < LogEntry.DefaultHeight * 1.5f;
|
||||
if (scrollViewNew != scrollView)
|
||||
{
|
||||
// Make sure scrolling doesn't stop in case too many entries were added at once
|
||||
panelScroll.ScrollViewTo(new Float2(float.MaxValue, float.MaxValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ namespace FlaxEditor.Windows
|
||||
/// </summary>
|
||||
public bool UIPivotRelative = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indication of if the properties window is locked on specific objects.
|
||||
/// </summary>
|
||||
public bool LockObjects = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PropertiesWindow"/> class.
|
||||
/// </summary>
|
||||
@@ -62,6 +67,9 @@ namespace FlaxEditor.Windows
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
if (LockObjects)
|
||||
return;
|
||||
|
||||
// Update selected objects
|
||||
// TODO: use cached collection for less memory allocations
|
||||
undoRecordObjects = Editor.SceneEditing.Selection.ConvertAll(x => x.UndoRecordObject).Distinct();
|
||||
|
||||
@@ -196,6 +196,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
if (actorType.IsAbstract)
|
||||
continue;
|
||||
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType));
|
||||
ActorToolboxAttribute attribute = null;
|
||||
foreach (var e in actorType.GetAttributes(false))
|
||||
{
|
||||
@@ -235,6 +236,7 @@ namespace FlaxEditor.Windows
|
||||
group.AddChild(string.IsNullOrEmpty(attribute.Name) ? CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType) : CreateActorItem(attribute.Name, actorType));
|
||||
group.SortChildren();
|
||||
}
|
||||
_groupSearch.SortChildren();
|
||||
}
|
||||
|
||||
private void OnSearchBoxTextChanged()
|
||||
@@ -260,6 +262,10 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
|
||||
var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name;
|
||||
|
||||
// Display all actors on no search
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType));
|
||||
|
||||
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
|
||||
continue;
|
||||
@@ -278,6 +284,9 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
item.SetHighlights(highlights);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filterText))
|
||||
_groupSearch.SortChildren();
|
||||
|
||||
_groupSearch.UnlockChildrenRecursive();
|
||||
PerformLayout();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Animations/Curve.h"
|
||||
|
||||
@@ -68,6 +69,16 @@ public:
|
||||
uint64 GetMemoryUsage() const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Single track with events.
|
||||
/// </summary>
|
||||
struct EventAnimationData
|
||||
{
|
||||
float Duration = 0.0f;
|
||||
StringAnsi TypeName;
|
||||
StringAnsi JsonData;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
|
||||
/// </summary>
|
||||
@@ -120,10 +131,15 @@ struct AnimationData
|
||||
String RootNodeName;
|
||||
|
||||
/// <summary>
|
||||
/// The per skeleton node animation channels.
|
||||
/// The per-skeleton node animation channels.
|
||||
/// </summary>
|
||||
Array<NodeAnimationData> Channels;
|
||||
|
||||
/// <summary>
|
||||
/// The animation event tracks.
|
||||
/// </summary>
|
||||
Array<Pair<String, StepCurve<EventAnimationData>>> Events;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the length of the animation (in seconds).
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Engine/Visject/VisjectGraph.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#include "Engine/Core/Collections/BitArray.h"
|
||||
#include "Engine/Animations/AlphaBlend.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "../Config.h"
|
||||
@@ -892,7 +893,7 @@ private:
|
||||
|
||||
int32 GetRootNodeIndex(Animation* anim);
|
||||
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
|
||||
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
|
||||
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override, BitArray<InlinedAllocation<8>>* usedNodes = nullptr);
|
||||
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
|
||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha);
|
||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace
|
||||
{
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
nodes->Nodes[i].Orientation.Normalize();
|
||||
nodes->Nodes.Get()[i].Orientation.Normalize();
|
||||
}
|
||||
if (rootMotionMode != RootMotionExtraction::NoExtraction)
|
||||
{
|
||||
@@ -222,7 +222,7 @@ FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos,
|
||||
prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode)
|
||||
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode, BitArray<InlinedAllocation<8>>* usedNodes)
|
||||
{
|
||||
PROFILE_CPU_ASSET(anim);
|
||||
|
||||
@@ -240,9 +240,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
}
|
||||
|
||||
// Evaluate nested animations
|
||||
bool hasNested = false;
|
||||
BitArray<InlinedAllocation<8>> usedNodesThis;
|
||||
if (anim->NestedAnims.Count() != 0)
|
||||
{
|
||||
if (usedNodes == nullptr)
|
||||
{
|
||||
// Per-channel bit to indicate which channels were used by nested
|
||||
usedNodesThis.Resize(nodes->Nodes.Count());
|
||||
usedNodes = &usedNodesThis;
|
||||
}
|
||||
|
||||
for (auto& e : anim->NestedAnims)
|
||||
{
|
||||
const auto& nestedAnim = e.Second;
|
||||
@@ -262,8 +269,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
|
||||
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
|
||||
|
||||
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
|
||||
hasNested = true;
|
||||
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode, usedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,6 +301,15 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
{
|
||||
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
|
||||
}
|
||||
|
||||
// Mark node as used
|
||||
if (usedNodes)
|
||||
usedNodes->Set(i, true);
|
||||
}
|
||||
else if (usedNodes && usedNodes != &usedNodesThis)
|
||||
{
|
||||
// Skip for nested animations so other one or top-level anim will update remaining nodes
|
||||
continue;
|
||||
}
|
||||
|
||||
// Blend node
|
||||
@@ -316,7 +331,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
dstNode.Scale = srcNode.Scale * weight;
|
||||
dstNode.Orientation = srcNode.Orientation * weight;
|
||||
}
|
||||
else if (!hasNested)
|
||||
else
|
||||
{
|
||||
dstNode = srcNode;
|
||||
}
|
||||
@@ -1177,14 +1192,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
|
||||
auto mask = node->Assets[0].As<SkeletonMask>();
|
||||
auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node.
|
||||
|
||||
// Check if have some mask asset connected with the mask node
|
||||
if (maskAssetBox->HasConnection())
|
||||
// Use the mask connected with this node instead of default mask asset
|
||||
auto maskAssetBox = node->TryGetBox(4);
|
||||
if (maskAssetBox && maskAssetBox->HasConnection())
|
||||
{
|
||||
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
|
||||
|
||||
// Use the mask connected with this node instead of default mask asset
|
||||
if (assetBoxValue != Value::Null)
|
||||
mask = (SkeletonMask*)assetBoxValue.AsAsset;
|
||||
}
|
||||
|
||||
@@ -10,79 +10,102 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target,
|
||||
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection);
|
||||
}
|
||||
|
||||
void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode, Transform& targetNode, const Vector3& target, const Vector3& jointTarget, bool allowStretching, float maxStretchScale)
|
||||
{
|
||||
Real lowerLimbLength = (targetNode.Translation - jointNode.Translation).Length();
|
||||
Real upperLimbLength = (jointNode.Translation - rootNode.Translation).Length();
|
||||
Vector3 jointPos = jointNode.Translation;
|
||||
Vector3 desiredDelta = target - rootNode.Translation;
|
||||
Real desiredLength = desiredDelta.Length();
|
||||
Real limbLengthLimit = lowerLimbLength + upperLimbLength;
|
||||
|
||||
Vector3 desiredDir;
|
||||
if (desiredLength < ZeroTolerance)
|
||||
void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
|
||||
{
|
||||
// Calculate limb segment lengths
|
||||
Real lowerLimbLength = (endEffectorTransform.Translation - midJointTransform.Translation).Length();
|
||||
Real upperLimbLength = (midJointTransform.Translation - rootTransform.Translation).Length();
|
||||
Vector3 midJointPos = midJointTransform.Translation;
|
||||
|
||||
// Calculate the direction and length towards the target
|
||||
Vector3 toTargetVector = targetPosition - rootTransform.Translation;
|
||||
Real toTargetLength = toTargetVector.Length();
|
||||
Real totalLimbLength = lowerLimbLength + upperLimbLength;
|
||||
|
||||
// Normalize the direction vector or set a default direction if too small
|
||||
Vector3 toTargetDir;
|
||||
if (toTargetLength < ZeroTolerance)
|
||||
{
|
||||
desiredLength = ZeroTolerance;
|
||||
desiredDir = Vector3(1, 0, 0);
|
||||
toTargetLength = ZeroTolerance;
|
||||
toTargetDir = Vector3(1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredDir = desiredDelta.GetNormalized();
|
||||
toTargetDir = toTargetVector.GetNormalized();
|
||||
}
|
||||
|
||||
Vector3 jointTargetDelta = jointTarget - rootNode.Translation;
|
||||
const Real jointTargetLengthSqr = jointTargetDelta.LengthSquared();
|
||||
// Calculate the pole vector direction
|
||||
Vector3 poleVectorDelta = poleVector - rootTransform.Translation;
|
||||
const Real poleVectorLengthSqr = poleVectorDelta.LengthSquared();
|
||||
|
||||
Vector3 jointPlaneNormal, jointBendDir;
|
||||
if (jointTargetLengthSqr < ZeroTolerance * ZeroTolerance)
|
||||
Vector3 jointPlaneNormal, bendDirection;
|
||||
if (poleVectorLengthSqr < ZeroTolerance * ZeroTolerance)
|
||||
{
|
||||
jointBendDir = Vector3::Forward;
|
||||
bendDirection = Vector3::Forward;
|
||||
jointPlaneNormal = Vector3::Up;
|
||||
}
|
||||
else
|
||||
{
|
||||
jointPlaneNormal = desiredDir ^ jointTargetDelta;
|
||||
jointPlaneNormal = toTargetDir ^ poleVectorDelta;
|
||||
if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance)
|
||||
{
|
||||
desiredDir.FindBestAxisVectors(jointPlaneNormal, jointBendDir);
|
||||
toTargetDir.FindBestAxisVectors(jointPlaneNormal, bendDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
jointPlaneNormal.Normalize();
|
||||
jointBendDir = jointTargetDelta - (jointTargetDelta | desiredDir) * desiredDir;
|
||||
jointBendDir.Normalize();
|
||||
bendDirection = poleVectorDelta - (poleVectorDelta | toTargetDir) * toTargetDir;
|
||||
bendDirection.Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle limb stretching if allowed
|
||||
if (allowStretching)
|
||||
{
|
||||
const Real initialStretchRatio = 1.0f;
|
||||
const Real range = maxStretchScale - initialStretchRatio;
|
||||
if (range > ZeroTolerance && limbLengthLimit > ZeroTolerance)
|
||||
const Real stretchRange = maxStretchScale - initialStretchRatio;
|
||||
if (stretchRange > ZeroTolerance && totalLimbLength > ZeroTolerance)
|
||||
{
|
||||
const Real reachRatio = desiredLength / limbLengthLimit;
|
||||
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / range);
|
||||
const Real reachRatio = toTargetLength / totalLimbLength;
|
||||
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / stretchRange);
|
||||
if (scalingFactor > ZeroTolerance)
|
||||
{
|
||||
lowerLimbLength *= 1.0f + scalingFactor;
|
||||
upperLimbLength *= 1.0f + scalingFactor;
|
||||
limbLengthLimit *= 1.0f + scalingFactor;
|
||||
totalLimbLength *= 1.0f + scalingFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 resultEndPos = target;
|
||||
Vector3 resultJointPos = jointPos;
|
||||
// Calculate new positions for joint and end effector
|
||||
Vector3 newEndEffectorPos = targetPosition;
|
||||
Vector3 newMidJointPos = midJointPos;
|
||||
|
||||
if (desiredLength >= limbLengthLimit)
|
||||
{
|
||||
resultEndPos = rootNode.Translation + limbLengthLimit * desiredDir;
|
||||
resultJointPos = rootNode.Translation + upperLimbLength * desiredDir;
|
||||
if (toTargetLength >= totalLimbLength) {
|
||||
// Target is beyond the reach of the limb
|
||||
Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
|
||||
|
||||
// Calculate the slight offset towards the pole vector
|
||||
Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
|
||||
Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
|
||||
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) {
|
||||
slightBendDirection = Vector3::Up;
|
||||
}
|
||||
else {
|
||||
slightBendDirection.Normalize();
|
||||
}
|
||||
|
||||
// Calculate the direction from root to mid joint with a slight offset towards the pole vector
|
||||
Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized();
|
||||
Real slightOffset = upperLimbLength * 0.01f; // Small percentage of the limb length for slight offset
|
||||
newMidJointPos = rootTransform.Translation + rootToEnd * (upperLimbLength - slightOffset) + midJointDirection * slightOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Real twoAb = 2.0f * upperLimbLength * desiredLength;
|
||||
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + desiredLength * desiredLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
|
||||
// Target is within reach, calculate joint position
|
||||
const Real twoAb = 2.0f * upperLimbLength * toTargetLength;
|
||||
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + toTargetLength * toTargetLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
|
||||
const bool reverseUpperBone = cosAngle < 0.0f;
|
||||
const Real angle = Math::Acos(cosAngle);
|
||||
const Real jointLineDist = upperLimbLength * Math::Sin(angle);
|
||||
@@ -90,23 +113,66 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode
|
||||
Real projJointDist = projJointDistSqr > 0.0f ? Math::Sqrt(projJointDistSqr) : 0.0f;
|
||||
if (reverseUpperBone)
|
||||
projJointDist *= -1.0f;
|
||||
resultJointPos = rootNode.Translation + projJointDist * desiredDir + jointLineDist * jointBendDir;
|
||||
newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
|
||||
}
|
||||
|
||||
// Update root joint orientation
|
||||
{
|
||||
const Vector3 oldDir = (jointPos - rootNode.Translation).GetNormalized();
|
||||
const Vector3 newDir = (resultJointPos - rootNode.Translation).GetNormalized();
|
||||
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
|
||||
rootNode.Orientation = deltaRotation * rootNode.Orientation;
|
||||
// Vector from root joint to mid joint (local Y-axis direction)
|
||||
Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
|
||||
|
||||
// Vector from mid joint to end effector (used to calculate plane normal)
|
||||
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
||||
|
||||
// Calculate the plane normal (local Z-axis direction)
|
||||
Vector3 localZ = Vector3::Cross(localY, midToEnd).GetNormalized();
|
||||
|
||||
// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
|
||||
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
|
||||
|
||||
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
|
||||
localZ = Vector3::Cross(localX, localY).GetNormalized();
|
||||
|
||||
// Construct a rotation from the orthogonal basis vectors
|
||||
Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY);
|
||||
|
||||
// Apply the new rotation to the root joint
|
||||
rootTransform.Orientation = newRootJointOrientation;
|
||||
}
|
||||
|
||||
|
||||
// Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
|
||||
{
|
||||
const Vector3 oldDir = (targetNode.Translation - jointPos).GetNormalized();
|
||||
const Vector3 newDir = (resultEndPos - resultJointPos).GetNormalized();
|
||||
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
|
||||
jointNode.Orientation = deltaRotation * jointNode.Orientation;
|
||||
jointNode.Translation = resultJointPos;
|
||||
// Vector from mid joint to end effector (local Y-axis direction after rotation)
|
||||
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
||||
|
||||
// Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
|
||||
Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
|
||||
Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
|
||||
|
||||
|
||||
// Vector from mid joint to end effector (local Y-axis direction)
|
||||
Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
||||
|
||||
// Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction)
|
||||
Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized();
|
||||
|
||||
//// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
|
||||
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
|
||||
|
||||
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
|
||||
localZ = Vector3::Cross(localX, localY).GetNormalized();
|
||||
|
||||
|
||||
// Construct a rotation from the orthogonal basis vectors
|
||||
// The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
|
||||
Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up
|
||||
|
||||
// Apply the new rotation to the mid joint
|
||||
midJointTransform.Orientation = newMidJointOrientation;
|
||||
midJointTransform.Translation = newMidJointPos;
|
||||
}
|
||||
|
||||
targetNode.Translation = resultEndPos;
|
||||
// Update end effector transform
|
||||
endEffectorTransform.Translation = newEndEffectorPos;
|
||||
}
|
||||
|
||||
@@ -152,12 +152,17 @@ void AudioSource::Play()
|
||||
RequestStreamingBuffersUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (SourceIDs.HasItems())
|
||||
{
|
||||
// Play it right away
|
||||
SetNonStreamingBuffer();
|
||||
PlayInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source was nt properly added to the Audio Backend
|
||||
LOG(Warning, "Cannot play unitialized audio source.");
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSource::Pause()
|
||||
|
||||
@@ -184,9 +184,8 @@ void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
|
||||
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
|
||||
: ManagedScriptingObject(params)
|
||||
, _refCount(0)
|
||||
, _loadingTask(nullptr)
|
||||
, _isLoaded(false)
|
||||
, _loadFailed(false)
|
||||
, _loadState(0)
|
||||
, _loadingTask(0)
|
||||
, _deleteFileOnUnload(false)
|
||||
, _isVirtual(false)
|
||||
{
|
||||
@@ -225,10 +224,10 @@ void Asset::OnDeleteObject()
|
||||
|
||||
// Unload asset data (in a safe way to protect asset data)
|
||||
Locker.Lock();
|
||||
if (_isLoaded)
|
||||
if (IsLoaded())
|
||||
{
|
||||
unload(false);
|
||||
_isLoaded = false;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
|
||||
}
|
||||
Locker.Unlock();
|
||||
|
||||
@@ -319,11 +318,6 @@ void Asset::ChangeID(const Guid& newId)
|
||||
Content::onAssetChangeId(this, oldId, newId);
|
||||
}
|
||||
|
||||
bool Asset::LastLoadFailed() const
|
||||
{
|
||||
return _loadFailed != 0;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Asset::ShouldDeleteFileOnUnload() const
|
||||
@@ -337,7 +331,7 @@ uint64 Asset::GetMemoryUsage() const
|
||||
{
|
||||
uint64 result = sizeof(Asset);
|
||||
Locker.Lock();
|
||||
if (_loadingTask)
|
||||
if (Platform::AtomicRead(&_loadingTask))
|
||||
result += sizeof(ContentLoadTask);
|
||||
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
|
||||
Locker.Unlock();
|
||||
@@ -368,7 +362,7 @@ void Asset::Reload()
|
||||
{
|
||||
// Unload current data
|
||||
unload(true);
|
||||
_isLoaded = false;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
|
||||
}
|
||||
|
||||
// Start reloading process
|
||||
@@ -426,7 +420,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
|
||||
// Check if has missing loading task
|
||||
Platform::MemoryBarrier();
|
||||
const auto loadingTask = _loadingTask;
|
||||
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask == nullptr)
|
||||
{
|
||||
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
|
||||
@@ -516,7 +510,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
Content::tryCallOnLoaded((Asset*)this);
|
||||
}
|
||||
|
||||
return _isLoaded == 0;
|
||||
return !IsLoaded();
|
||||
}
|
||||
|
||||
void Asset::InitAsVirtual()
|
||||
@@ -525,14 +519,14 @@ void Asset::InitAsVirtual()
|
||||
_isVirtual = true;
|
||||
|
||||
// Be a loaded thing
|
||||
_isLoaded = true;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
|
||||
}
|
||||
|
||||
void Asset::CancelStreaming()
|
||||
{
|
||||
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
|
||||
Locker.Lock();
|
||||
ContentLoadTask* loadTask = _loadingTask;
|
||||
auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
Locker.Unlock();
|
||||
if (loadTask)
|
||||
{
|
||||
@@ -575,10 +569,11 @@ ContentLoadTask* Asset::createLoadingTask()
|
||||
void Asset::startLoading()
|
||||
{
|
||||
ASSERT(!IsLoaded());
|
||||
ASSERT(_loadingTask == nullptr);
|
||||
_loadingTask = createLoadingTask();
|
||||
ASSERT(_loadingTask != nullptr);
|
||||
_loadingTask->Start();
|
||||
ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
|
||||
auto loadingTask = createLoadingTask();
|
||||
ASSERT(loadingTask != nullptr);
|
||||
Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
|
||||
loadingTask->Start();
|
||||
}
|
||||
|
||||
void Asset::releaseStorage()
|
||||
@@ -593,7 +588,7 @@ bool Asset::IsInternalType() const
|
||||
bool Asset::onLoad(LoadAssetTask* task)
|
||||
{
|
||||
// It may fail when task is cancelled and new one is created later (don't crash but just end with an error)
|
||||
if (task->Asset.Get() != this || _loadingTask == nullptr)
|
||||
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
|
||||
return true;
|
||||
|
||||
Locker.Lock();
|
||||
@@ -606,15 +601,15 @@ bool Asset::onLoad(LoadAssetTask* task)
|
||||
}
|
||||
const bool isLoaded = result == LoadResult::Ok;
|
||||
const bool failed = !isLoaded;
|
||||
_loadFailed = failed;
|
||||
_isLoaded = !failed;
|
||||
LoadState state = LoadState::Loaded;
|
||||
Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
|
||||
if (failed)
|
||||
{
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result));
|
||||
}
|
||||
|
||||
// Unlink task
|
||||
_loadingTask = nullptr;
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
ASSERT(failed || IsLoaded() == isLoaded);
|
||||
|
||||
Locker.Unlock();
|
||||
@@ -663,12 +658,12 @@ void Asset::onUnload_MainThread()
|
||||
OnUnloaded(this);
|
||||
|
||||
// Check if is during loading
|
||||
if (_loadingTask != nullptr)
|
||||
auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask != nullptr)
|
||||
{
|
||||
// Cancel loading
|
||||
auto task = _loadingTask;
|
||||
_loadingTask = nullptr;
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
|
||||
task->Cancel();
|
||||
loadingTask->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,17 @@ public:
|
||||
DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData);
|
||||
|
||||
protected:
|
||||
volatile int64 _refCount;
|
||||
ContentLoadTask* _loadingTask;
|
||||
enum class LoadState : int64
|
||||
{
|
||||
Unloaded,
|
||||
Loaded,
|
||||
LoadFailed,
|
||||
};
|
||||
|
||||
volatile int64 _refCount;
|
||||
volatile int64 _loadState;
|
||||
volatile intptr _loadingTask;
|
||||
|
||||
int8 _isLoaded : 1; // Indicates that asset is loaded
|
||||
int8 _loadFailed : 1; // Indicates that last asset loading has failed
|
||||
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
|
||||
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
|
||||
|
||||
@@ -111,13 +117,16 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE bool IsLoaded() const
|
||||
{
|
||||
return _isLoaded != 0;
|
||||
return Platform::AtomicRead(&_loadState) == (int64)LoadState::Loaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if last asset loading failed, otherwise false.
|
||||
/// </summary>
|
||||
API_PROPERTY() bool LastLoadFailed() const;
|
||||
API_PROPERTY() bool LastLoadFailed() const
|
||||
{
|
||||
return Platform::AtomicRead(&_loadState) == (int64)LoadState::LoadFailed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved).
|
||||
|
||||
@@ -1434,8 +1434,7 @@ Asset::LoadResult VisualScript::load()
|
||||
if (_instances.HasItems())
|
||||
{
|
||||
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
|
||||
_loadFailed = false;
|
||||
_isLoaded = true;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
|
||||
|
||||
// Setup scripting type
|
||||
CacheScriptingType();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
@@ -688,101 +689,135 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
|
||||
class CloneAssetFileTask : public MainThreadTask
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
|
||||
public:
|
||||
StringView dstPath;
|
||||
StringView srcPath;
|
||||
Guid dstId;
|
||||
bool* output;
|
||||
|
||||
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
|
||||
|
||||
// Check source file
|
||||
if (!FileSystem::FileExists(srcPath))
|
||||
protected:
|
||||
bool Run() override
|
||||
{
|
||||
LOG(Warning, "Missing source file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special case for json resources
|
||||
if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
|
||||
{
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
if (JsonStorageProxy::ChangeId(dstPath, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
*output = Content::CloneAssetFile(dstPath, srcPath, dstId);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if destination file is missing
|
||||
if (!FileSystem::FileExists(dstPath))
|
||||
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
|
||||
{
|
||||
// Best to run this on the main thread to avoid clone conflicts.
|
||||
if (IsInMainThread())
|
||||
{
|
||||
// Use quick file copy
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
PROFILE_CPU();
|
||||
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
|
||||
|
||||
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
|
||||
|
||||
// Check source file
|
||||
if (!FileSystem::FileExists(srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
LOG(Warning, "Missing source file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change ID
|
||||
auto storage = ContentStorageManager::GetStorage(dstPath);
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
|
||||
// Special case for json resources
|
||||
if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
if (JsonStorageProxy::ChangeId(dstPath, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if destination file is missing
|
||||
if (!FileSystem::FileExists(dstPath))
|
||||
{
|
||||
// Use quick file copy
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change ID
|
||||
auto storage = ContentStorageManager::GetStorage(dstPath);
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use temporary file
|
||||
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
|
||||
if (FileSystem::CopyFile(tmpPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change asset ID
|
||||
{
|
||||
auto storage = ContentStorageManager::GetStorage(tmpPath);
|
||||
if (!storage)
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock destination file
|
||||
ContentStorageManager::EnsureAccess(dstPath);
|
||||
|
||||
// Copy temp file to the destination
|
||||
if (FileSystem::CopyFile(dstPath, tmpPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
|
||||
// Reload storage
|
||||
if (auto storage = ContentStorageManager::GetStorage(dstPath))
|
||||
{
|
||||
storage->Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use temporary file
|
||||
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
|
||||
if (FileSystem::CopyFile(tmpPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file.");
|
||||
return true;
|
||||
}
|
||||
CloneAssetFileTask* task = New<CloneAssetFileTask>();
|
||||
task->dstId = dstId;
|
||||
task->dstPath = dstPath;
|
||||
task->srcPath = srcPath;
|
||||
|
||||
// Change asset ID
|
||||
{
|
||||
auto storage = ContentStorageManager::GetStorage(tmpPath);
|
||||
if (!storage)
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool result = false;
|
||||
task->output = &result;
|
||||
task->Start();
|
||||
task->Wait();
|
||||
|
||||
// Unlock destination file
|
||||
ContentStorageManager::EnsureAccess(dstPath);
|
||||
|
||||
// Copy temp file to the destination
|
||||
if (FileSystem::CopyFile(dstPath, tmpPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
|
||||
// Reload storage
|
||||
if (auto storage = ContentStorageManager::GetStorage(dstPath))
|
||||
{
|
||||
storage->Reload();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -31,12 +31,11 @@ public:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
{
|
||||
Asset->_loadFailed = true;
|
||||
Asset->_isLoaded = false;
|
||||
Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
|
||||
Asset->_loadingTask = nullptr;
|
||||
}
|
||||
Asset->Locker.Unlock();
|
||||
}
|
||||
@@ -77,8 +76,8 @@ protected:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
@@ -91,8 +90,8 @@ protected:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
|
||||
@@ -413,6 +413,7 @@ bool AssetsImportingManagerService::Init()
|
||||
{ TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
{ TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
{ TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
{ TEXT("exr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
|
||||
// IES Profiles
|
||||
{ TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES },
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Engine/Content/Storage/ContentStorageManager.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Level/Actors/EmptyActor.h"
|
||||
#include "Engine/Level/Actors/StaticModel.h"
|
||||
#include "Engine/Level/Prefabs/Prefab.h"
|
||||
@@ -141,48 +142,6 @@ void RepackMeshLightmapUVs(ModelData& data)
|
||||
}
|
||||
}
|
||||
|
||||
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
|
||||
{
|
||||
// Skip if file is missing
|
||||
if (!FileSystem::FileExists(context.TargetAssetPath))
|
||||
return;
|
||||
|
||||
// Try to load asset that gets reimported
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset == nullptr)
|
||||
return;
|
||||
if (asset->WaitForLoaded())
|
||||
return;
|
||||
|
||||
// Get model object
|
||||
ModelBase* model = nullptr;
|
||||
if (asset.Get()->GetTypeName() == Model::TypeName)
|
||||
{
|
||||
model = ((Model*)asset.Get());
|
||||
}
|
||||
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
|
||||
{
|
||||
model = ((SkinnedModel*)asset.Get());
|
||||
}
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
// Peek materials
|
||||
for (int32 i = 0; i < modelData.Materials.Count(); i++)
|
||||
{
|
||||
auto& dstSlot = modelData.Materials[i];
|
||||
|
||||
if (model->MaterialSlots.Count() > i)
|
||||
{
|
||||
auto& srcSlot = model->MaterialSlots[i];
|
||||
|
||||
dstSlot.Name = srcSlot.Name;
|
||||
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
|
||||
dstSlot.AssetID = srcSlot.Material.GetID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
|
||||
{
|
||||
Array<int32> materialSlotsTable;
|
||||
@@ -458,10 +417,62 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
data = &dataThis;
|
||||
}
|
||||
|
||||
// Check if restore materials on model reimport
|
||||
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
|
||||
// Check if restore local changes on asset reimport
|
||||
constexpr bool RestoreAnimEventsOnReimport = true;
|
||||
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
|
||||
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
|
||||
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
|
||||
{
|
||||
TryRestoreMaterials(context, *data);
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset && !asset->WaitForLoaded())
|
||||
{
|
||||
auto* model = ScriptingObject::Cast<ModelBase>(asset);
|
||||
auto* animation = ScriptingObject::Cast<Animation>(asset);
|
||||
if (restoreMaterials && model)
|
||||
{
|
||||
// Copy material settings
|
||||
for (int32 i = 0; i < data->Materials.Count(); i++)
|
||||
{
|
||||
auto& dstSlot = data->Materials[i];
|
||||
if (model->MaterialSlots.Count() > i)
|
||||
{
|
||||
auto& srcSlot = model->MaterialSlots[i];
|
||||
dstSlot.Name = srcSlot.Name;
|
||||
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
|
||||
dstSlot.AssetID = srcSlot.Material.GetID();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (restoreAnimEvents && animation)
|
||||
{
|
||||
// Copy anim event tracks
|
||||
for (const auto& e : animation->Events)
|
||||
{
|
||||
auto& clone = data->Animations[0].Events.AddOne();
|
||||
clone.First = e.First;
|
||||
const auto& eKeys = e.Second.GetKeyframes();
|
||||
auto& cloneKeys = clone.Second.GetKeyframes();
|
||||
clone.Second.Resize(eKeys.Count());
|
||||
for (int32 i = 0; i < eKeys.Count(); i++)
|
||||
{
|
||||
const auto& eKey = eKeys[i];
|
||||
auto& cloneKey = cloneKeys[i];
|
||||
cloneKey.Time = eKey.Time;
|
||||
cloneKey.Value.Duration = eKey.Value.Duration;
|
||||
if (eKey.Value.Instance)
|
||||
{
|
||||
cloneKey.Value.TypeName = eKey.Value.Instance->GetType().Fullname;
|
||||
rapidjson_flax::StringBuffer buffer;
|
||||
CompactJsonWriter writer(buffer);
|
||||
writer.StartObject();
|
||||
eKey.Value.Instance->Serialize(writer, nullptr);
|
||||
writer.EndObject();
|
||||
cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space
|
||||
|
||||
@@ -44,6 +44,49 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
static void Merge(T* data, T* tmp, int32 start, int32 mid, int32 end)
|
||||
{
|
||||
int32 h = start;
|
||||
int32 i = start;
|
||||
int32 j = mid + 1;
|
||||
|
||||
while (h <= mid && j <= end)
|
||||
{
|
||||
if (data[h] < data[j])
|
||||
tmp[i] = data[h++];
|
||||
else
|
||||
tmp[i] = data[j++];
|
||||
i++;
|
||||
}
|
||||
|
||||
if (h > mid)
|
||||
{
|
||||
for (int32 k = j; k <= end; k++)
|
||||
tmp[i++] = data[k];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 k = h; k <= mid; k++)
|
||||
tmp[i++] = data[k];
|
||||
}
|
||||
|
||||
for (int32 k = start; k <= end; k++)
|
||||
data[k] = tmp[k];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void MergeSort(T* data, T* tmp, int32 start, int32 end)
|
||||
{
|
||||
if (start >= end)
|
||||
return;
|
||||
const int32 mid = (start + end) / 2;
|
||||
MergeSort(data, tmp, start, mid);
|
||||
MergeSort(data, tmp, mid + 1, end);
|
||||
Merge(data, tmp, start, mid, end);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
|
||||
@@ -263,6 +306,33 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Merge Sort algorithm (recursive version, uses temporary memory).
|
||||
/// </summary>
|
||||
/// <param name="data">The data pointer.</param>
|
||||
/// <param name="count">The elements count.</param>
|
||||
/// <param name="tmp">The additional temporary memory buffer for sorting data. If null then will be automatically allocated within this function call.</param>
|
||||
template<typename T>
|
||||
static void MergeSort(T* data, int32 count, T* tmp = nullptr)
|
||||
{
|
||||
if (count < 2)
|
||||
return;
|
||||
const bool alloc = tmp == nullptr;
|
||||
if (alloc)
|
||||
tmp = (T*)Platform::Allocate(sizeof(T) * count, 16);
|
||||
MergeSort(data, tmp, 0, count - 1);
|
||||
if (alloc)
|
||||
Platform::Free(tmp);
|
||||
}
|
||||
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
FORCE_INLINE static void MergeSort(Array<T, AllocationType>& data, Array<T, AllocationType>* tmp = nullptr)
|
||||
{
|
||||
if (tmp)
|
||||
tmp->Resize(data.Count());
|
||||
MergeSort(data.Get(), data.Count(), tmp ? tmp->Get() : nullptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection).
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#define FORCE_INLINE inline
|
||||
#define FORCE_NOINLINE __attribute__((noinline))
|
||||
#define NO_RETURN __attribute__((noreturn))
|
||||
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
|
||||
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
|
||||
#define PACK_BEGIN()
|
||||
#define PACK_END() __attribute__((__packed__))
|
||||
#define ALIGN_BEGIN(_align)
|
||||
@@ -44,6 +46,8 @@
|
||||
#define FORCE_INLINE inline
|
||||
#define FORCE_NOINLINE __attribute__((noinline))
|
||||
#define NO_RETURN __attribute__((noreturn))
|
||||
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
|
||||
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
|
||||
#define PACK_BEGIN()
|
||||
#define PACK_END() __attribute__((__packed__))
|
||||
#define ALIGN_BEGIN(_align)
|
||||
@@ -69,6 +73,8 @@
|
||||
#define FORCE_INLINE __forceinline
|
||||
#define FORCE_NOINLINE __declspec(noinline)
|
||||
#define NO_RETURN __declspec(noreturn)
|
||||
#define NO_SANITIZE_ADDRESS
|
||||
#define NO_SANITIZE_THREAD
|
||||
#define PACK_BEGIN() __pragma(pack(push, 1))
|
||||
#define PACK_END() ; __pragma(pack(pop))
|
||||
#define ALIGN_BEGIN(_align) __declspec(align(_align))
|
||||
|
||||
@@ -94,4 +94,15 @@ namespace Utilities
|
||||
return (x * 0x01010101) >> 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Copy memory region but ignoring address sanatizer checks for memory regions.
|
||||
NO_SANITIZE_ADDRESS static void UnsafeMemoryCopy(void* dst, const void* src, uint64 size)
|
||||
{
|
||||
#if BUILD_RELEASE
|
||||
memcpy(dst, src, static_cast<size_t>(size));
|
||||
#else
|
||||
for (uint64 i = 0; i < size; i++)
|
||||
((byte*)dst)[i] = ((byte*)src)[i];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ PACK_STRUCT(struct Data {
|
||||
Matrix ViewProjection;
|
||||
Float2 Padding;
|
||||
float ClipPosZBias;
|
||||
bool EnableDepthTest;
|
||||
uint32 EnableDepthTest;
|
||||
});
|
||||
|
||||
struct PsData
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CommandLine.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Utilities.h"
|
||||
#include <iostream>
|
||||
|
||||
CommandLine::OptionsData CommandLine::Options;
|
||||
@@ -81,7 +82,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
if (pos) \
|
||||
{ \
|
||||
len = ARRAY_COUNT(text) - 1; \
|
||||
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
*(end - len) = 0; \
|
||||
end -= len; \
|
||||
Options.field = true; \
|
||||
@@ -98,7 +99,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
} \
|
||||
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
|
||||
len = static_cast<int32>((argEnd - pos) + 1); \
|
||||
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
*(end - len) = 0; \
|
||||
end -= len; \
|
||||
}
|
||||
@@ -114,7 +115,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
{ \
|
||||
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
|
||||
len = static_cast<int32>((argEnd - pos) + 1); \
|
||||
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
*(end - len) = 0; \
|
||||
end -= len; \
|
||||
} \
|
||||
|
||||
@@ -324,10 +324,12 @@ void Engine::OnUpdate()
|
||||
|
||||
// Call event
|
||||
Update();
|
||||
UpdateGraph->Execute();
|
||||
|
||||
// Update services
|
||||
EngineService::OnUpdate();
|
||||
|
||||
// Run async
|
||||
UpdateGraph->Execute();
|
||||
}
|
||||
|
||||
void Engine::OnLateUpdate()
|
||||
|
||||
@@ -221,9 +221,7 @@ Asset::LoadResult GameplayGlobals::load()
|
||||
// Get data
|
||||
const auto chunk = GetChunk(0);
|
||||
if (!chunk || !chunk->IsLoaded())
|
||||
{
|
||||
return LoadResult::MissingDataChunk;
|
||||
}
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
|
||||
// Load all variables
|
||||
@@ -234,15 +232,16 @@ Asset::LoadResult GameplayGlobals::load()
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
stream.ReadString(&name, 71);
|
||||
if (name.IsEmpty())
|
||||
{
|
||||
LOG(Warning, "Empty variable name");
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
auto& e = Variables[name];
|
||||
stream.ReadVariant(&e.DefaultValue);
|
||||
e.Value = e.DefaultValue;
|
||||
}
|
||||
if (stream.HasError())
|
||||
{
|
||||
// Failed to load data
|
||||
Variables.Clear();
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
|
||||
return LoadResult::Ok;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
|
||||
// Rollback state and cancel
|
||||
_context = nullptr;
|
||||
_state = TaskState::Queued;
|
||||
SetState(TaskState::Queued);
|
||||
Cancel();
|
||||
}
|
||||
|
||||
@@ -148,8 +148,7 @@ protected:
|
||||
ASSERT(_context != nullptr);
|
||||
_context->OnCancelSync(this);
|
||||
_context = nullptr;
|
||||
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
|
||||
void GPUTask::Execute(GPUTasksContext* context)
|
||||
{
|
||||
// Begin
|
||||
ASSERT(IsQueued() && _context == nullptr);
|
||||
_state = TaskState::Running;
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
const auto result = run(context);
|
||||
@@ -19,7 +18,7 @@ void GPUTask::Execute(GPUTasksContext* context)
|
||||
// Process result
|
||||
if (IsCancelRequested())
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else if (result != Result::Ok)
|
||||
{
|
||||
|
||||
@@ -941,7 +941,19 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
|
||||
}
|
||||
|
||||
// Animation events
|
||||
stream->WriteInt32(0);
|
||||
stream->WriteInt32(anim.Events.Count());
|
||||
for (auto& e : anim.Events)
|
||||
{
|
||||
stream->WriteString(e.First, 172);
|
||||
stream->WriteInt32(e.Second.GetKeyframes().Count());
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream->WriteFloat(k.Time);
|
||||
stream->WriteFloat(k.Value.Duration);
|
||||
stream->WriteStringAnsi(k.Value.TypeName, 17);
|
||||
stream->WriteJson(k.Value.JsonData);
|
||||
}
|
||||
}
|
||||
|
||||
// Nested animations
|
||||
stream->WriteInt32(0);
|
||||
|
||||
@@ -13,4 +13,12 @@ namespace FlaxEngine
|
||||
Bit = bit;
|
||||
}
|
||||
}
|
||||
|
||||
public partial struct AntiAliasingSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not to show the TAA settings.
|
||||
/// </summary>
|
||||
public bool ShowTAASettings => (Mode == AntialiasingMode.TemporalAntialiasing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1888,25 +1888,25 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
|
||||
/// <summary>
|
||||
/// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\")")
|
||||
API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_JitterSpread = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\")")
|
||||
API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_Sharpness = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\")")
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_StationaryBlending = 0.95f;
|
||||
|
||||
/// <summary>
|
||||
/// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")")
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_MotionBlending = 0.85f;
|
||||
|
||||
public:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "GPUTimerQueryVulkan.h"
|
||||
#endif
|
||||
#include "DescriptorSetVulkan.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
|
||||
void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore)
|
||||
@@ -32,7 +33,7 @@ void CmdBufferVulkan::Begin()
|
||||
// Acquire a descriptor pool set on
|
||||
if (_descriptorPoolSetContainer == nullptr)
|
||||
{
|
||||
_descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer();
|
||||
_descriptorPoolSetContainer = _device->DescriptorPoolsManager->AcquirePoolSetContainer();
|
||||
}
|
||||
|
||||
_state = State::IsInsideBegin;
|
||||
@@ -138,7 +139,7 @@ void CmdBufferVulkan::RefreshFenceStatus()
|
||||
|
||||
if (_descriptorPoolSetContainer)
|
||||
{
|
||||
_device->DescriptorPoolsManager->ReleasePoolSet(*_descriptorPoolSetContainer);
|
||||
_descriptorPoolSetContainer->LastFrameUsed = Engine::FrameCount;
|
||||
_descriptorPoolSetContainer = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -279,6 +280,7 @@ void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float
|
||||
void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT_LOW_LAYER(_activeCmdBuffer == nullptr)
|
||||
for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++)
|
||||
{
|
||||
auto cmdBuffer = _pool._cmdBuffers.Get()[i];
|
||||
@@ -286,8 +288,7 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
|
||||
if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin)
|
||||
{
|
||||
_activeCmdBuffer = cmdBuffer;
|
||||
_activeCmdBuffer->Begin();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -295,8 +296,12 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
// Always begin fresh command buffer for rendering
|
||||
_activeCmdBuffer = _pool.Create();
|
||||
if (_activeCmdBuffer == nullptr)
|
||||
{
|
||||
// Always begin fresh command buffer for rendering
|
||||
_activeCmdBuffer = _pool.Create();
|
||||
}
|
||||
|
||||
_activeCmdBuffer->Begin();
|
||||
|
||||
#if VULKAN_USE_QUERIES
|
||||
|
||||
@@ -247,8 +247,7 @@ void TypedDescriptorPoolSetVulkan::Reset()
|
||||
|
||||
DescriptorPoolSetContainerVulkan::DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device)
|
||||
: _device(device)
|
||||
, _lastFrameUsed(Engine::FrameCount)
|
||||
, _used(true)
|
||||
, LastFrameUsed(Engine::FrameCount)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -278,12 +277,6 @@ void DescriptorPoolSetContainerVulkan::Reset()
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorPoolSetContainerVulkan::SetUsed(bool used)
|
||||
{
|
||||
_used = used;
|
||||
_lastFrameUsed = used ? Engine::FrameCount : _lastFrameUsed;
|
||||
}
|
||||
|
||||
DescriptorPoolsManagerVulkan::DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device)
|
||||
: _device(device)
|
||||
{
|
||||
@@ -294,26 +287,21 @@ DescriptorPoolsManagerVulkan::~DescriptorPoolsManagerVulkan()
|
||||
_poolSets.ClearDelete();
|
||||
}
|
||||
|
||||
DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
|
||||
DescriptorPoolSetContainerVulkan* DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
|
||||
{
|
||||
ScopeLock lock(_locker);
|
||||
for (auto* poolSet : _poolSets)
|
||||
{
|
||||
if (poolSet->IsUnused())
|
||||
if (poolSet->Refs == 0)
|
||||
{
|
||||
poolSet->SetUsed(true);
|
||||
poolSet->LastFrameUsed = Engine::FrameCount;
|
||||
poolSet->Reset();
|
||||
return *poolSet;
|
||||
return poolSet;
|
||||
}
|
||||
}
|
||||
const auto poolSet = New<DescriptorPoolSetContainerVulkan>(_device);
|
||||
_poolSets.Add(poolSet);
|
||||
return *poolSet;
|
||||
}
|
||||
|
||||
void DescriptorPoolsManagerVulkan::ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet)
|
||||
{
|
||||
poolSet.SetUsed(false);
|
||||
return poolSet;
|
||||
}
|
||||
|
||||
void DescriptorPoolsManagerVulkan::GC()
|
||||
@@ -322,7 +310,7 @@ void DescriptorPoolsManagerVulkan::GC()
|
||||
for (int32 i = _poolSets.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
const auto poolSet = _poolSets[i];
|
||||
if (poolSet->IsUnused() && Engine::FrameCount - poolSet->GetLastFrameUsed() > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
|
||||
if (poolSet->Refs == 0 && Engine::FrameCount - poolSet->LastFrameUsed > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
|
||||
{
|
||||
_poolSets.RemoveAt(i);
|
||||
Delete(poolSet);
|
||||
|
||||
@@ -212,8 +212,6 @@ class DescriptorPoolSetContainerVulkan
|
||||
private:
|
||||
GPUDeviceVulkan* _device;
|
||||
Dictionary<uint32, TypedDescriptorPoolSetVulkan*> _typedDescriptorPools;
|
||||
uint64 _lastFrameUsed;
|
||||
bool _used;
|
||||
|
||||
public:
|
||||
DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device);
|
||||
@@ -222,17 +220,9 @@ public:
|
||||
public:
|
||||
TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout);
|
||||
void Reset();
|
||||
void SetUsed(bool used);
|
||||
|
||||
bool IsUnused() const
|
||||
{
|
||||
return !_used;
|
||||
}
|
||||
|
||||
uint64 GetLastFrameUsed() const
|
||||
{
|
||||
return _lastFrameUsed;
|
||||
}
|
||||
mutable uint64 Refs = 0;
|
||||
mutable uint64 LastFrameUsed;
|
||||
};
|
||||
|
||||
class DescriptorPoolsManagerVulkan
|
||||
@@ -246,8 +236,7 @@ public:
|
||||
DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device);
|
||||
~DescriptorPoolsManagerVulkan();
|
||||
|
||||
DescriptorPoolSetContainerVulkan& AcquirePoolSetContainer();
|
||||
void ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet);
|
||||
DescriptorPoolSetContainerVulkan* AcquirePoolSetContainer();
|
||||
void GC();
|
||||
};
|
||||
|
||||
|
||||
@@ -112,7 +112,11 @@ ComputePipelineStateVulkan::ComputePipelineStateVulkan(GPUDeviceVulkan* device,
|
||||
ComputePipelineStateVulkan::~ComputePipelineStateVulkan()
|
||||
{
|
||||
DSWriteContainer.Release();
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
{
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
}
|
||||
DescriptorSetsLayout = nullptr;
|
||||
DescriptorSetHandles.Resize(0);
|
||||
DynamicOffsets.Resize(0);
|
||||
@@ -206,7 +210,11 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass)
|
||||
void GPUPipelineStateVulkan::OnReleaseGPU()
|
||||
{
|
||||
DSWriteContainer.Release();
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
{
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
}
|
||||
DescriptorSetsLayout = nullptr;
|
||||
DescriptorSetHandles.Resize(0);
|
||||
DynamicOffsets.Resize(0);
|
||||
|
||||
@@ -41,17 +41,17 @@ public:
|
||||
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
|
||||
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
|
||||
{
|
||||
ASSERT(cmdBufferPoolSet);
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool AllocateDescriptorSets()
|
||||
{
|
||||
ASSERT(CurrentTypedDescriptorPoolSet);
|
||||
return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get());
|
||||
}
|
||||
|
||||
@@ -165,7 +165,10 @@ public:
|
||||
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
|
||||
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
|
||||
{
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "Engine/Animations/Animations.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Math/OrientedBoundingBox.h"
|
||||
#include "Engine/Core/Math/Matrix3x3.h"
|
||||
#include "Editor/Editor.h"
|
||||
#endif
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
@@ -1018,6 +1020,45 @@ void AnimatedModel::OnDebugDrawSelected()
|
||||
ModelInstanceActor::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
void AnimatedModel::OnDebugDraw()
|
||||
{
|
||||
if (ShowDebugDrawSkeleton && SkinnedModel && AnimationGraph)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
PreInitSkinningData();
|
||||
Matrix world;
|
||||
GetLocalToWorldMatrix(world);
|
||||
|
||||
// Draw bounding box at the node locations
|
||||
const float boxSize = Math::Min(1.0f, (float)_sphere.Radius / 100.0f);
|
||||
OrientedBoundingBox localBox(Vector3(-boxSize), Vector3(boxSize));
|
||||
for (int32 nodeIndex = 0; nodeIndex < GraphInstance.NodesPose.Count(); nodeIndex++)
|
||||
{
|
||||
Matrix transform = GraphInstance.NodesPose[nodeIndex] * world;
|
||||
Float3 scale, translation;
|
||||
Matrix3x3 rotation;
|
||||
transform.Decompose(scale, rotation, translation);
|
||||
transform = Matrix::Invert(Matrix::Scaling(scale)) * transform;
|
||||
OrientedBoundingBox box = localBox * transform;
|
||||
DEBUG_DRAW_WIRE_BOX(box, Color::Green, 0, false);
|
||||
}
|
||||
|
||||
// Nodes connections
|
||||
for (int32 nodeIndex = 0; nodeIndex < SkinnedModel->Skeleton.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
int32 parentIndex = SkinnedModel->Skeleton.Nodes[nodeIndex].ParentIndex;
|
||||
if (parentIndex != -1)
|
||||
{
|
||||
Float3 parentPos = (GraphInstance.NodesPose[parentIndex] * world).GetTranslation();
|
||||
Float3 bonePos = (GraphInstance.NodesPose[nodeIndex] * world).GetTranslation();
|
||||
DEBUG_DRAW_LINE(parentPos, bonePos, Color::Green, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModelInstanceActor::OnDebugDraw();
|
||||
}
|
||||
|
||||
BoundingBox AnimatedModel::GetEditorBox() const
|
||||
{
|
||||
if (SkinnedModel)
|
||||
|
||||
@@ -168,6 +168,13 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
|
||||
ScriptingObjectReference<Actor> RootMotionTarget;
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// If checked, the skeleton pose will be shawn during debug shapes drawing.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The graph instance data container. For dynamic usage only at runtime, not serialized.
|
||||
@@ -416,6 +423,7 @@ public:
|
||||
void Draw(RenderContextBatch& renderContextBatch) override;
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
void OnDebugDraw() override;
|
||||
BoundingBox GetEditorBox() const override;
|
||||
#endif
|
||||
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
|
||||
|
||||
@@ -105,10 +105,8 @@ void NavMesh::OnDataAssetLoaded()
|
||||
if (Data.Tiles.HasItems())
|
||||
return;
|
||||
|
||||
const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy();
|
||||
|
||||
// Remove added tiles
|
||||
if (isEnabled)
|
||||
if (_navMeshActive)
|
||||
{
|
||||
RemoveTiles();
|
||||
}
|
||||
@@ -120,7 +118,7 @@ void NavMesh::OnDataAssetLoaded()
|
||||
IsDataDirty = false;
|
||||
|
||||
// Add loaded tiles
|
||||
if (isEnabled)
|
||||
if (_navMeshActive)
|
||||
{
|
||||
AddTiles();
|
||||
}
|
||||
@@ -156,15 +154,36 @@ void NavMesh::OnEnable()
|
||||
// Base
|
||||
Actor::OnEnable();
|
||||
|
||||
GetScene()->Navigation.Meshes.Add(this);
|
||||
AddTiles();
|
||||
if (!_navMeshActive)
|
||||
{
|
||||
GetScene()->Navigation.Meshes.Add(this);
|
||||
AddTiles();
|
||||
_navMeshActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NavMesh::OnDisable()
|
||||
{
|
||||
RemoveTiles();
|
||||
GetScene()->Navigation.Meshes.Remove(this);
|
||||
if (_navMeshActive)
|
||||
{
|
||||
RemoveTiles();
|
||||
GetScene()->Navigation.Meshes.Remove(this);
|
||||
_navMeshActive = false;
|
||||
}
|
||||
|
||||
// Base
|
||||
Actor::OnDisable();
|
||||
}
|
||||
|
||||
void NavMesh::Initialize()
|
||||
{
|
||||
// Base
|
||||
Actor::Initialize();
|
||||
|
||||
if (!_navMeshActive && IsActiveInHierarchy())
|
||||
{
|
||||
GetScene()->Navigation.Meshes.Add(this);
|
||||
AddTiles();
|
||||
_navMeshActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ private:
|
||||
void RemoveTiles();
|
||||
void OnDataAssetLoaded();
|
||||
|
||||
private:
|
||||
bool _navMeshActive = false;
|
||||
|
||||
public:
|
||||
// [Actor]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
@@ -77,4 +80,5 @@ protected:
|
||||
// [Actor]
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
void Initialize() override;
|
||||
};
|
||||
|
||||
@@ -55,13 +55,13 @@ public:
|
||||
{
|
||||
return __sync_fetch_and_add(dst, value);
|
||||
}
|
||||
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
|
||||
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
int32 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_RELAXED);
|
||||
return result;
|
||||
}
|
||||
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
|
||||
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
int64 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_RELAXED);
|
||||
|
||||
@@ -45,21 +45,21 @@ public:
|
||||
{
|
||||
return __sync_fetch_and_add(dst, value);
|
||||
}
|
||||
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
|
||||
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
return __atomic_load_n(dst, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
|
||||
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
return __atomic_load_n(dst, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value)
|
||||
{
|
||||
__atomic_store(dst, &value, __ATOMIC_SEQ_CST);
|
||||
__atomic_store_n((volatile int32*)dst, value, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value)
|
||||
{
|
||||
__atomic_store(dst, &value, __ATOMIC_SEQ_CST);
|
||||
__atomic_store_n((volatile int64*)dst, value, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static void Prefetch(void const* ptr)
|
||||
{
|
||||
|
||||
@@ -270,14 +270,14 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="dst">A pointer to the destination value.</param>
|
||||
/// <returns>The function returns the value of the destination parameter.</returns>
|
||||
static int32 AtomicRead(int32 volatile* dst) = delete;
|
||||
static int32 AtomicRead(int32 const volatile* dst) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Performs an atomic 64-bit variable read operation on the specified values.
|
||||
/// </summary>
|
||||
/// <param name="dst">A pointer to the destination value.</param>
|
||||
/// <returns>The function returns the value of the destination parameter.</returns>
|
||||
static int64 AtomicRead(int64 volatile* dst) = delete;
|
||||
static int64 AtomicRead(int64 const volatile* dst) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a 32-bit variable to the specified value as an atomic operation.
|
||||
|
||||
@@ -69,13 +69,13 @@ public:
|
||||
{
|
||||
return __sync_fetch_and_add(dst, value);
|
||||
}
|
||||
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
|
||||
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
int32 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
|
||||
return result;
|
||||
}
|
||||
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
|
||||
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
int64 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
/// <summary>
|
||||
/// Locks the critical section.
|
||||
/// </summary>
|
||||
void Lock() const
|
||||
NO_SANITIZE_THREAD void Lock() const
|
||||
{
|
||||
pthread_mutex_lock(_mutexPtr);
|
||||
#if BUILD_DEBUG
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
/// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
|
||||
/// </summary>
|
||||
/// <returns>True if calling thread took ownership of the critical section.</returns>
|
||||
bool TryLock() const
|
||||
NO_SANITIZE_THREAD bool TryLock() const
|
||||
{
|
||||
return pthread_mutex_trylock(_mutexPtr) == 0;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
/// <summary>
|
||||
/// Releases the lock on the critical section.
|
||||
/// </summary>
|
||||
void Unlock() const
|
||||
NO_SANITIZE_THREAD void Unlock() const
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
((UnixCriticalSection*)this)->_owningThreadId = 0;
|
||||
|
||||
@@ -63,13 +63,13 @@ public:
|
||||
return _interlockedexchangeadd64(dst, value);
|
||||
#endif
|
||||
}
|
||||
static int32 AtomicRead(int32 volatile* dst)
|
||||
static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
return (int32)_InterlockedCompareExchange((long volatile*)dst, 0, 0);
|
||||
}
|
||||
static int64 AtomicRead(int64 volatile* dst)
|
||||
static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
return _InterlockedCompareExchange64(dst, 0, 0);
|
||||
return _InterlockedCompareExchange64((int64 volatile*)dst, 0, 0);
|
||||
}
|
||||
static void AtomicStore(int32 volatile* dst, int32 value)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace
|
||||
// Cached data for the draw calls sorting
|
||||
Array<uint64> SortingKeys[2];
|
||||
Array<int32> SortingIndices;
|
||||
Array<DrawBatch> SortingBatches;
|
||||
Array<RenderList*> FreeRenderList;
|
||||
|
||||
struct MemPoolEntry
|
||||
@@ -594,12 +595,13 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass)
|
||||
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass, bool stable)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
const auto* drawCallsData = drawCalls.Get();
|
||||
const auto* listData = list.Indices.Get();
|
||||
const int32 listSize = list.Indices.Count();
|
||||
ZoneValue(listSize);
|
||||
|
||||
// Peek shared memory
|
||||
#define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize)
|
||||
@@ -656,6 +658,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
|
||||
}
|
||||
|
||||
DrawBatch batch;
|
||||
static_assert(sizeof(DrawBatch) == sizeof(uint64) * 2, "Fix the size of draw batch to optimize memory access.");
|
||||
batch.SortKey = sortedKeys[i];
|
||||
batch.StartIndex = i;
|
||||
batch.BatchSize = batchSize;
|
||||
@@ -665,8 +668,12 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
|
||||
i += batchSize;
|
||||
}
|
||||
|
||||
// Sort draw calls batches by depth
|
||||
Sorting::QuickSort(list.Batches);
|
||||
// When using depth buffer draw calls are already almost ideally sorted by Radix Sort but transparency needs more stability to prevent flickering
|
||||
if (stable)
|
||||
{
|
||||
// Sort draw calls batches by depth
|
||||
Sorting::MergeSort(list.Batches, &SortingBatches);
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_INLINE bool CanUseInstancing(DrawPass pass)
|
||||
|
||||
@@ -217,17 +217,17 @@ struct DrawBatch
|
||||
/// <summary>
|
||||
/// The first draw call index.
|
||||
/// </summary>
|
||||
int32 StartIndex;
|
||||
uint16 StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// A number of draw calls to be submitted at once.
|
||||
/// </summary>
|
||||
int32 BatchSize;
|
||||
uint16 BatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of instances (sum from all draw calls in this batch).
|
||||
/// </summary>
|
||||
int32 InstanceCount;
|
||||
uint32 InstanceCount;
|
||||
|
||||
bool operator<(const DrawBatch& other) const
|
||||
{
|
||||
@@ -525,7 +525,8 @@ public:
|
||||
/// <param name="pass">The draw pass (optional).</param>
|
||||
API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType, DrawPass pass = DrawPass::All)
|
||||
{
|
||||
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass);
|
||||
const bool stable = listType == DrawCallsListType::Forward;
|
||||
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass, stable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -536,7 +537,8 @@ public:
|
||||
/// <param name="list">The collected draw calls indices list.</param>
|
||||
/// <param name="drawCalls">The collected draw calls list.</param>
|
||||
/// <param name="pass">The draw pass (optional).</param>
|
||||
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All);
|
||||
/// <param name="stable">If set to <c>true</c> draw batches will be additionally sorted to prevent any flickering, otherwise Depth Buffer will smooth out any non-stability in sorting.</param>
|
||||
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All, bool stable = false);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the collected draw calls.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "BinaryModule.h"
|
||||
#include "ScriptingObject.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Utilities.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "ManagedCLR/MAssembly.h"
|
||||
@@ -436,6 +437,7 @@ void ScriptingType::SetupScriptVTable(ScriptingTypeHandle baseTypeHandle)
|
||||
}
|
||||
}
|
||||
|
||||
NO_SANITIZE_ADDRESS
|
||||
void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
|
||||
{
|
||||
// Analyze vtable size
|
||||
@@ -475,7 +477,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
|
||||
|
||||
// Duplicate vtable
|
||||
Script.VTable = (void**)((byte*)Platform::Allocate(totalSize, 16) + prefixSize);
|
||||
Platform::MemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
|
||||
Utilities::UnsafeMemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
|
||||
|
||||
// Override vtable entries
|
||||
if (interfacesCount)
|
||||
@@ -508,7 +510,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
|
||||
const int32 interfaceSize = interfaceCount * sizeof(void*);
|
||||
|
||||
// Duplicate interface vtable
|
||||
Platform::MemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
|
||||
Utilities::UnsafeMemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
|
||||
|
||||
// Override interface vtable entries
|
||||
const auto scriptOffset = interfaces->ScriptVTableOffset;
|
||||
|
||||
@@ -497,7 +497,9 @@ void ReadStream::Read(Variant& data)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CRASH;
|
||||
_hasError = true;
|
||||
LOG(Error, "Invalid Variant type. Corrupted data.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,6 +948,13 @@ void WriteStream::WriteJson(ISerializable* obj, const void* otherObj)
|
||||
WriteInt32(0);
|
||||
}
|
||||
|
||||
void WriteStream::WriteJson(const StringAnsiView& json)
|
||||
{
|
||||
WriteInt32(FLAXENGINE_VERSION_BUILD);
|
||||
WriteInt32((int32)json.Length());
|
||||
WriteBytes((byte*)json.Get(), (int32)json.Length());
|
||||
}
|
||||
|
||||
void WriteStream::WriteString(const StringView& data)
|
||||
{
|
||||
Write(data);
|
||||
|
||||
@@ -233,6 +233,7 @@ public:
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
|
||||
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
|
||||
void WriteJson(const StringAnsiView& json);
|
||||
|
||||
public:
|
||||
// Writes String to the stream
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
void Task::Start()
|
||||
{
|
||||
if (_state != TaskState::Created)
|
||||
if (GetState() != TaskState::Created)
|
||||
return;
|
||||
|
||||
OnStart();
|
||||
|
||||
// Change state
|
||||
_state = TaskState::Queued;
|
||||
SetState(TaskState::Queued);
|
||||
|
||||
// Add task to the execution queue
|
||||
Enqueue();
|
||||
@@ -110,7 +110,6 @@ Task* Task::ContinueWith(const Function<bool()>& action, Object* target)
|
||||
Task* Task::StartNew(Task* task)
|
||||
{
|
||||
ASSERT(task);
|
||||
|
||||
task->Start();
|
||||
return task;
|
||||
}
|
||||
@@ -137,11 +136,10 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
|
||||
|
||||
void Task::Execute()
|
||||
{
|
||||
// Begin
|
||||
if (IsCanceled())
|
||||
return;
|
||||
ASSERT(IsQueued());
|
||||
_state = TaskState::Running;
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
bool failed = Run();
|
||||
@@ -149,7 +147,7 @@ void Task::Execute()
|
||||
// Process result
|
||||
if (IsCancelRequested())
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else if (failed)
|
||||
{
|
||||
@@ -167,10 +165,8 @@ void Task::OnStart()
|
||||
|
||||
void Task::OnFinish()
|
||||
{
|
||||
ASSERT(IsRunning());
|
||||
ASSERT(!IsCancelRequested());
|
||||
|
||||
_state = TaskState::Finished;
|
||||
ASSERT(IsRunning() && !IsCancelRequested());
|
||||
SetState(TaskState::Finished);
|
||||
|
||||
// Send event further
|
||||
if (_continueWith)
|
||||
@@ -181,7 +177,7 @@ void Task::OnFinish()
|
||||
|
||||
void Task::OnFail()
|
||||
{
|
||||
_state = TaskState::Failed;
|
||||
SetState(TaskState::Failed);
|
||||
|
||||
// Send event further
|
||||
if (_continueWith)
|
||||
@@ -209,8 +205,7 @@ void Task::OnCancel()
|
||||
const auto state = GetState();
|
||||
if (state != TaskState::Finished && state != TaskState::Failed)
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
|
||||
SetState(TaskState::Canceled);
|
||||
OnEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class FLAXENGINE_API Task : public Object, public NonCopyable
|
||||
//
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// The cancel flag used to indicate that there is request to cancel task operation.
|
||||
/// </summary>
|
||||
@@ -65,14 +64,18 @@ protected:
|
||||
/// </summary>
|
||||
Task* _continueWith = nullptr;
|
||||
|
||||
public:
|
||||
void SetState(TaskState state)
|
||||
{
|
||||
Platform::AtomicStore((int64 volatile*)&_state, (uint64)state);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the task state.
|
||||
/// </summary>
|
||||
FORCE_INLINE TaskState GetState() const
|
||||
{
|
||||
return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state));
|
||||
return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +97,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Checks if operation failed.
|
||||
/// </summary>
|
||||
@@ -153,7 +155,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Starts this task execution (and will continue with all children).
|
||||
/// </summary>
|
||||
@@ -199,7 +200,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Continues that task execution with a given task (will call Start on given task after finishing that one).
|
||||
/// </summary>
|
||||
@@ -232,7 +232,6 @@ public:
|
||||
Task* ContinueWith(const Function<bool()>& action, Object* target = nullptr);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Starts the new task.
|
||||
/// </summary>
|
||||
@@ -312,7 +311,6 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// Executes this task.
|
||||
/// It should be called by the task consumer (thread pool or other executor of this task type).
|
||||
@@ -328,7 +326,6 @@ protected:
|
||||
virtual bool Run() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void Enqueue() = 0;
|
||||
virtual void OnStart();
|
||||
virtual void OnFinish();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
|
||||
// Import Assimp library
|
||||
// Source: https://github.com/assimp/assimp
|
||||
@@ -157,7 +158,7 @@ struct AssimpImporterData
|
||||
Array<AssimpBone> Bones;
|
||||
Dictionary<int32, Array<int32>> MeshIndexToNodeIndex;
|
||||
|
||||
AssimpImporterData(const char* path, const ModelTool::Options& options)
|
||||
AssimpImporterData(const StringView& path, const ModelTool::Options& options)
|
||||
: Path(path)
|
||||
, Options(options)
|
||||
{
|
||||
@@ -735,7 +736,7 @@ void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerD
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
static bool AssimpInited = false;
|
||||
if (!AssimpInited)
|
||||
@@ -784,7 +785,10 @@ bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& opt
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
|
||||
|
||||
// Import file
|
||||
context.Scene = context.AssimpImporter.ReadFile(path, flags);
|
||||
{
|
||||
AnsiPathTempFile tempFile(path);
|
||||
context.Scene = context.AssimpImporter.ReadFile(tempFile.Path.Get(), flags);
|
||||
}
|
||||
if (context.Scene == nullptr)
|
||||
{
|
||||
LOG_STR(Warning, String(context.AssimpImporter.GetErrorString()));
|
||||
|
||||
@@ -816,7 +816,7 @@ void BakeTransforms(FbxScene* scene)
|
||||
scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate, false);
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataAutodeskFbxSdk(const String& path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
ScopeLock lock(FbxSdkManager::Locker);
|
||||
|
||||
@@ -836,7 +836,7 @@ bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& da
|
||||
auto ios = FbxSdkManager::Manager->GetIOSettings();
|
||||
ios->SetBoolProp(IMP_FBX_MODEL, importMeshes);
|
||||
ios->SetBoolProp(IMP_FBX_ANIMATION, importAnimations);
|
||||
if (!importer->Initialize(path, -1, ios))
|
||||
if (!importer->Initialize(StringAnsi(path), -1, ios))
|
||||
{
|
||||
errorMsg = String::Format(TEXT("Failed to initialize FBX importer. {0}"), String(importer->GetStatus().GetErrorString()));
|
||||
return false;
|
||||
|
||||
@@ -103,7 +103,7 @@ struct OpenFbxImporterData
|
||||
Array<const ofbx::Material*> Materials;
|
||||
Array<MaterialSlotEntry> ImportedMaterials;
|
||||
|
||||
OpenFbxImporterData(const char* path, const ModelTool::Options& options, ofbx::IScene* scene)
|
||||
OpenFbxImporterData(const String& path, const ModelTool::Options& options, ofbx::IScene* scene)
|
||||
: Scene(scene)
|
||||
, ScenePtr(scene)
|
||||
, Path(path)
|
||||
@@ -1114,11 +1114,11 @@ static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
|
||||
return { 0.f, 0.f, 0.f };
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
// Import file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(String(path), fileData))
|
||||
if (File::ReadAllBytes(path, fileData))
|
||||
{
|
||||
errorMsg = TEXT("Cannot load file.");
|
||||
return true;
|
||||
|
||||
@@ -471,64 +471,37 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
|
||||
options.MergeMeshes = false; // Meshes merging doesn't make sense when we want to import each mesh individually
|
||||
// TODO: maybe we could update meshes merger to collapse meshes within the same name if splitting is enabled?
|
||||
|
||||
// Validate path
|
||||
// Note: Assimp/Autodesk supports only ANSI characters in imported file path
|
||||
StringAnsi importPath;
|
||||
String tmpPath;
|
||||
if (path.IsANSI() == false)
|
||||
{
|
||||
// Use temporary file
|
||||
LOG(Warning, "Model Tool doesn't support importing files from paths using non ASNI characters. Using temporary file.");
|
||||
FileSystem::GetTempFilePath(tmpPath);
|
||||
if (tmpPath.IsANSI() == false || FileSystem::CopyFile(tmpPath, path))
|
||||
{
|
||||
errorMsg = TEXT("Path with non ANSI characters is invalid.");
|
||||
return true;
|
||||
}
|
||||
importPath = tmpPath.ToStringAnsi();
|
||||
}
|
||||
else
|
||||
{
|
||||
importPath = path.ToStringAnsi();
|
||||
}
|
||||
|
||||
// Call importing backend
|
||||
#if (USE_AUTODESK_FBX_SDK || USE_OPEN_FBX) && USE_ASSIMP
|
||||
if (path.EndsWith(TEXT(".fbx"), StringSearchCase::IgnoreCase))
|
||||
{
|
||||
#if USE_AUTODESK_FBX_SDK
|
||||
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
|
||||
return true;
|
||||
#elif USE_OPEN_FBX
|
||||
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataOpenFBX(path, data, options, errorMsg))
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAssimp(path, data, options, errorMsg))
|
||||
return true;
|
||||
}
|
||||
#elif USE_ASSIMP
|
||||
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAssimp(path, data, options, errorMsg))
|
||||
return true;
|
||||
#elif USE_AUTODESK_FBX_SDK
|
||||
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
|
||||
return true;
|
||||
#elif USE_OPEN_FBX
|
||||
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataOpenFBX(path, data, options, errorMsg))
|
||||
return true;
|
||||
#else
|
||||
LOG(Error, "Compiled without model importing backend.");
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// Remove temporary file
|
||||
if (tmpPath.HasChars() && FileSystem::FileExists(tmpPath))
|
||||
{
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
}
|
||||
|
||||
// Remove namespace prefixes from the nodes names
|
||||
{
|
||||
for (auto& node : data.Nodes)
|
||||
@@ -1248,7 +1221,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
}
|
||||
|
||||
// Apply the import transformation
|
||||
if (!importTransform.IsIdentity())
|
||||
if (!importTransform.IsIdentity() && data.Nodes.HasItems())
|
||||
{
|
||||
if (options.Type == ModelType::SkinnedModel)
|
||||
{
|
||||
|
||||
@@ -386,13 +386,13 @@ public:
|
||||
private:
|
||||
static void CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex);
|
||||
#if USE_ASSIMP
|
||||
static bool ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg);
|
||||
static bool ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
#if USE_AUTODESK_FBX_SDK
|
||||
static bool ImportDataAutodeskFbxSdk(const char* path, ModelData& data, Options& options, String& errorMsg);
|
||||
static bool ImportDataAutodeskFbxSdk(const String& path, ModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
#if USE_OPEN_FBX
|
||||
static bool ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg);
|
||||
static bool ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ public class TextureTool : EngineModule
|
||||
|
||||
bool useDirectXTex = false;
|
||||
bool useStb = false;
|
||||
bool useExr = options.Target.IsEditor;
|
||||
|
||||
switch (options.Platform.Target)
|
||||
{
|
||||
@@ -58,6 +59,10 @@ public class TextureTool : EngineModule
|
||||
options.PrivateDependencies.Add("bc7enc16");
|
||||
}
|
||||
}
|
||||
if (useExr)
|
||||
{
|
||||
options.PrivateDependencies.Add("tinyexr");
|
||||
}
|
||||
if (options.Target.IsEditor && astc.IsSupported(options))
|
||||
{
|
||||
// ASTC for mobile (iOS and Android)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#endif
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
|
||||
// Import DirectXTex library
|
||||
// Source: https://github.com/Microsoft/DirectXTex
|
||||
@@ -24,6 +25,19 @@ DECLARE_HANDLE(HMONITOR);
|
||||
#endif
|
||||
#include <ThirdParty/DirectXTex/DirectXTex.h>
|
||||
|
||||
#if USE_EDITOR
|
||||
// Import tinyexr library
|
||||
// Source: https://github.com/syoyo/tinyexr
|
||||
#define TINYEXR_IMPLEMENTATION
|
||||
#define TINYEXR_USE_MINIZ 1
|
||||
#define TINYEXR_USE_STB_ZLIB 0
|
||||
#define TINYEXR_USE_THREAD 0
|
||||
#define TINYEXR_USE_OPENMP 0
|
||||
#undef min
|
||||
#undef max
|
||||
#include <ThirdParty/tinyexr/tinyexr.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format)
|
||||
@@ -36,7 +50,7 @@ namespace
|
||||
return static_cast<DXGI_FORMAT>(format);
|
||||
}
|
||||
|
||||
HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, float threshold, DirectX::ScratchImage& cImages)
|
||||
HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, float threshold, DirectX::ScratchImage& cImages)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
if ((format == DXGI_FORMAT_BC7_UNORM || format == DXGI_FORMAT_BC7_UNORM_SRGB || format == DXGI_FORMAT_BC6H_UF16 || format == DXGI_FORMAT_BC6H_SF16) &&
|
||||
@@ -60,12 +74,12 @@ namespace
|
||||
size_t _nimages;
|
||||
const DirectX::TexMetadata& _metadata;
|
||||
DXGI_FORMAT _format;
|
||||
DWORD _compress;
|
||||
DirectX::TEX_COMPRESS_FLAGS _compress;
|
||||
DirectX::ScratchImage& _cImages;
|
||||
public:
|
||||
HRESULT CompressResult = E_FAIL;
|
||||
|
||||
GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, DirectX::ScratchImage& cImages)
|
||||
GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, DirectX::ScratchImage& cImages)
|
||||
: GPUTask(Type::Custom)
|
||||
, _signal(&signal)
|
||||
, _srcImages(srcImages)
|
||||
@@ -276,6 +290,46 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
|
||||
return image.InitializeFromImage(img);
|
||||
}
|
||||
|
||||
HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
// Load exr file
|
||||
AnsiPathTempFile tempFile(path);
|
||||
float* pixels;
|
||||
int width, height;
|
||||
const char* err = nullptr;
|
||||
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
|
||||
if (ret != TINYEXR_SUCCESS)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
LOG_STR(Warning, String(err));
|
||||
FreeEXRErrorMessage(err);
|
||||
}
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Setup image
|
||||
DirectX::Image img;
|
||||
img.format = DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
img.width = width;
|
||||
img.height = height;
|
||||
img.rowPitch = width * sizeof(Float4);
|
||||
img.slicePitch = img.rowPitch * height;
|
||||
|
||||
// Link data
|
||||
img.pixels = (uint8_t*)pixels;
|
||||
|
||||
// Init
|
||||
HRESULT result = image.InitializeFromImage(img);
|
||||
free(pixels);
|
||||
return result;
|
||||
#else
|
||||
LOG(Warning, "EXR format is not supported.");
|
||||
return S_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
|
||||
{
|
||||
// Load image data
|
||||
@@ -302,6 +356,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
case ImageType::RAW:
|
||||
result = LoadFromRAWFile(path, image);
|
||||
break;
|
||||
case ImageType::EXR:
|
||||
result = LoadFromEXRFile(path, image);
|
||||
break;
|
||||
default:
|
||||
result = DXGI_ERROR_INVALID_CALL;
|
||||
break;
|
||||
@@ -518,6 +575,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
case ImageType::RAW:
|
||||
result = LoadFromRAWFile(path, image1);
|
||||
break;
|
||||
case ImageType::EXR:
|
||||
result = LoadFromEXRFile(path, image1);
|
||||
break;
|
||||
case ImageType::Internal:
|
||||
{
|
||||
if (options.InternalLoad.IsBinded())
|
||||
@@ -688,7 +748,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
if (!keepAsIs && options.FlipY)
|
||||
{
|
||||
auto& tmpImg = GET_TMP_IMG();
|
||||
DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL;
|
||||
DirectX::TEX_FR_FLAGS flags = DirectX::TEX_FR_FLIP_VERTICAL;
|
||||
result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
|
||||
if (FAILED(result))
|
||||
{
|
||||
@@ -698,7 +758,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
SET_CURRENT_IMG(tmpImg);
|
||||
}
|
||||
|
||||
// Check if it invert green channel
|
||||
// Check if invert green channel
|
||||
if (!keepAsIs && options.InvertGreenChannel)
|
||||
{
|
||||
auto& timage = GET_TMP_IMG();
|
||||
|
||||
@@ -767,6 +767,10 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type)
|
||||
{
|
||||
type = ImageType::RAW;
|
||||
}
|
||||
else if (extension == TEXT("exr"))
|
||||
{
|
||||
type = ImageType::EXR;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Unknown file type.");
|
||||
|
||||
@@ -252,6 +252,7 @@ private:
|
||||
JPEG,
|
||||
HDR,
|
||||
RAW,
|
||||
EXR,
|
||||
Internal,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
|
||||
#define STBI_ASSERT(x) ASSERT(x)
|
||||
@@ -48,6 +49,18 @@
|
||||
// Compression libs for Editor
|
||||
#include <ThirdParty/detex/detex.h>
|
||||
#include <ThirdParty/bc7enc16/bc7enc16.h>
|
||||
|
||||
// Import tinyexr library
|
||||
// Source: https://github.com/syoyo/tinyexr
|
||||
#define TINYEXR_IMPLEMENTATION
|
||||
#define TINYEXR_USE_MINIZ 1
|
||||
#define TINYEXR_USE_STB_ZLIB 0
|
||||
#define TINYEXR_USE_THREAD 0
|
||||
#define TINYEXR_USE_OPENMP 0
|
||||
#undef min
|
||||
#undef max
|
||||
#include <ThirdParty/tinyexr/tinyexr.h>
|
||||
|
||||
#endif
|
||||
|
||||
static void stbWrite(void* context, void* data, int size)
|
||||
@@ -173,7 +186,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
{
|
||||
if (textureData.GetArraySize() != 1)
|
||||
{
|
||||
LOG(Warning, "Exporting texture arrays and cubemaps is not supported by stb library.");
|
||||
LOG(Warning, "Exporting texture arrays and cubemaps is not supported.");
|
||||
}
|
||||
TextureData const* texture = &textureData;
|
||||
|
||||
@@ -189,7 +202,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
const auto sampler = GetSampler(texture->Format);
|
||||
if (sampler == nullptr)
|
||||
{
|
||||
LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format);
|
||||
LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format);
|
||||
return true;
|
||||
}
|
||||
const auto srcData = texture->GetData(0, 0);
|
||||
@@ -272,16 +285,19 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
break;
|
||||
}
|
||||
case ImageType::GIF:
|
||||
LOG(Warning, "GIF format is not supported by stb library.");
|
||||
LOG(Warning, "GIF format is not supported.");
|
||||
break;
|
||||
case ImageType::TIFF:
|
||||
LOG(Warning, "GIF format is not supported by stb library.");
|
||||
LOG(Warning, "GIF format is not supported.");
|
||||
break;
|
||||
case ImageType::DDS:
|
||||
LOG(Warning, "DDS format is not supported by stb library.");
|
||||
LOG(Warning, "DDS format is not supported.");
|
||||
break;
|
||||
case ImageType::RAW:
|
||||
LOG(Warning, "RAW format is not supported by stb library.");
|
||||
LOG(Warning, "RAW format is not supported.");
|
||||
break;
|
||||
case ImageType::EXR:
|
||||
LOG(Warning, "EXR format is not supported.");
|
||||
break;
|
||||
default:
|
||||
LOG(Warning, "Unknown format.");
|
||||
@@ -383,11 +399,49 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
|
||||
|
||||
break;
|
||||
}
|
||||
case ImageType::EXR:
|
||||
{
|
||||
#if USE_EDITOR
|
||||
// Load exr file
|
||||
AnsiPathTempFile tempFile(path);
|
||||
float* pixels;
|
||||
int width, height;
|
||||
const char* err = nullptr;
|
||||
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
|
||||
if (ret != TINYEXR_SUCCESS)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
LOG_STR(Warning, String(err));
|
||||
FreeEXRErrorMessage(err);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup texture data
|
||||
textureData.Width = width;
|
||||
textureData.Height = height;
|
||||
textureData.Depth = 1;
|
||||
textureData.Format = PixelFormat::R32G32B32A32_Float;
|
||||
textureData.Items.Resize(1);
|
||||
textureData.Items[0].Mips.Resize(1);
|
||||
auto& mip = textureData.Items[0].Mips[0];
|
||||
mip.RowPitch = width * sizeof(Float4);
|
||||
mip.DepthPitch = mip.RowPitch * height;
|
||||
mip.Lines = height;
|
||||
mip.Data.Copy((const byte*)pixels, mip.DepthPitch);
|
||||
|
||||
free(pixels);
|
||||
#else
|
||||
LOG(Warning, "EXR format is not supported.");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ImageType::DDS:
|
||||
LOG(Warning, "DDS format is not supported by stb library.");
|
||||
LOG(Warning, "DDS format is not supported.");
|
||||
break;
|
||||
case ImageType::TIFF:
|
||||
LOG(Warning, "TIFF format is not supported by stb library.");
|
||||
LOG(Warning, "TIFF format is not supported.");
|
||||
break;
|
||||
default:
|
||||
LOG(Warning, "Unknown format.");
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace FlaxEngine.GUI
|
||||
{
|
||||
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
|
||||
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
|
||||
float height = font.Height / DpiScale;
|
||||
float height = font.Height;
|
||||
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
|
||||
alpha *= alpha;
|
||||
Color selectionColor = Color.White * alpha;
|
||||
@@ -340,7 +340,7 @@ namespace FlaxEngine.GUI
|
||||
if (textBlock.Style.UnderlineBrush != null)
|
||||
{
|
||||
var underLineHeight = 2.0f;
|
||||
var height = font.Height / DpiScale;
|
||||
var height = font.Height;
|
||||
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
|
||||
textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
|
||||
}
|
||||
|
||||
37
Source/Engine/Utilities/AnsiPathTempFile.h
Normal file
37
Source/Engine/Utilities/AnsiPathTempFile.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
|
||||
// Small utility that uses temporary file to properly handle non-ANSI paths for 3rd party libs.
|
||||
struct AnsiPathTempFile
|
||||
{
|
||||
StringAnsi Path;
|
||||
String TempPath;
|
||||
bool Temp;
|
||||
|
||||
AnsiPathTempFile(const String& path)
|
||||
{
|
||||
if (path.IsANSI() == false)
|
||||
{
|
||||
// Use temporary file
|
||||
FileSystem::GetTempFilePath(TempPath);
|
||||
if (TempPath.IsANSI() && !FileSystem::CopyFile(TempPath, path))
|
||||
{
|
||||
Path = TempPath.ToStringAnsi();
|
||||
return;
|
||||
}
|
||||
TempPath.Clear();
|
||||
}
|
||||
Path = path.ToStringAnsi();
|
||||
}
|
||||
|
||||
~AnsiPathTempFile()
|
||||
{
|
||||
// Cleanup temporary file after use
|
||||
if (TempPath.HasChars())
|
||||
FileSystem::DeleteFile(TempPath);
|
||||
}
|
||||
};
|
||||
@@ -47,6 +47,12 @@ namespace FlaxEngine.Utilities
|
||||
/// True if this tag contained a leading or trailing forward slash.
|
||||
/// </summary>
|
||||
public bool IsSlash => IsLeadingSlash || IsEndingSlash;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -231,6 +237,9 @@ namespace FlaxEngine.Utilities
|
||||
tag.Attributes[s] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (EOF)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip over closing '>'
|
||||
@@ -264,8 +273,13 @@ namespace FlaxEngine.Utilities
|
||||
private string ParseAttributeName()
|
||||
{
|
||||
int start = _pos;
|
||||
while (!EOF && char.IsLetterOrDigit(Peek()))
|
||||
while (!EOF)
|
||||
{
|
||||
var c = Peek();
|
||||
if (!char.IsLetterOrDigit(c) && c != '-')
|
||||
break;
|
||||
Move();
|
||||
}
|
||||
return _html.Substring(start, _pos - start);
|
||||
}
|
||||
|
||||
|
||||
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.lib
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.lib
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.pdb
(Stored with Git LFS)
vendored
BIN
Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.pdb
(Stored with Git LFS)
vendored
Binary file not shown.
955
Source/ThirdParty/DirectXTex/DirectXTex.h
vendored
955
Source/ThirdParty/DirectXTex/DirectXTex.h
vendored
File diff suppressed because it is too large
Load Diff
168
Source/ThirdParty/DirectXTex/DirectXTex.inl
vendored
168
Source/ThirdParty/DirectXTex/DirectXTex.inl
vendored
@@ -1,9 +1,9 @@
|
||||
//-------------------------------------------------------------------------------------
|
||||
// DirectXTex.inl
|
||||
//
|
||||
//
|
||||
// DirectX Texture Library
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
// http://go.microsoft.com/fwlink/?LinkId=248926
|
||||
@@ -11,81 +11,109 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
//=====================================================================================
|
||||
// Bitmask flags enumerator operators
|
||||
//=====================================================================================
|
||||
DEFINE_ENUM_FLAG_OPERATORS(CP_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(DDS_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(TGA_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(WIC_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(TEX_FR_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(TEX_FILTER_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(TEX_PMALPHA_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(TEX_COMPRESS_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(CNMAP_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(CMSE_FLAGS);
|
||||
DEFINE_ENUM_FLAG_OPERATORS(CREATETEX_FLAGS);
|
||||
|
||||
// WIC_FILTER modes match TEX_FILTER modes
|
||||
constexpr WIC_FLAGS operator|(WIC_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast<WIC_FLAGS>(static_cast<unsigned long>(a) | static_cast<unsigned long>(b & TEX_FILTER_MODE_MASK)); }
|
||||
constexpr WIC_FLAGS operator|(TEX_FILTER_FLAGS a, WIC_FLAGS b) { return static_cast<WIC_FLAGS>(static_cast<unsigned long>(a & TEX_FILTER_MODE_MASK) | static_cast<unsigned long>(b)); }
|
||||
|
||||
// TEX_PMALPHA_SRGB match TEX_FILTER_SRGB
|
||||
constexpr TEX_PMALPHA_FLAGS operator|(TEX_PMALPHA_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast<TEX_PMALPHA_FLAGS>(static_cast<unsigned long>(a) | static_cast<unsigned long>(b & TEX_FILTER_SRGB_MASK)); }
|
||||
constexpr TEX_PMALPHA_FLAGS operator|(TEX_FILTER_FLAGS a, TEX_PMALPHA_FLAGS b) { return static_cast<TEX_PMALPHA_FLAGS>(static_cast<unsigned long>(a & TEX_FILTER_SRGB_MASK) | static_cast<unsigned long>(b)); }
|
||||
|
||||
// TEX_COMPRESS_SRGB match TEX_FILTER_SRGB
|
||||
constexpr TEX_COMPRESS_FLAGS operator|(TEX_COMPRESS_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast<TEX_COMPRESS_FLAGS>(static_cast<unsigned long>(a) | static_cast<unsigned long>(b & TEX_FILTER_SRGB_MASK)); }
|
||||
constexpr TEX_COMPRESS_FLAGS operator|(TEX_FILTER_FLAGS a, TEX_COMPRESS_FLAGS b) { return static_cast<TEX_COMPRESS_FLAGS>(static_cast<unsigned long>(a & TEX_FILTER_SRGB_MASK) | static_cast<unsigned long>(b)); }
|
||||
|
||||
|
||||
//=====================================================================================
|
||||
// DXGI Format Utilities
|
||||
//=====================================================================================
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline bool __cdecl IsValid(DXGI_FORMAT fmt)
|
||||
constexpr bool __cdecl IsValid(DXGI_FORMAT fmt) noexcept
|
||||
{
|
||||
return (static_cast<size_t>(fmt) >= 1 && static_cast<size_t>(fmt) <= 190);
|
||||
return (static_cast<size_t>(fmt) >= 1 && static_cast<size_t>(fmt) <= 191);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline bool __cdecl IsCompressed(DXGI_FORMAT fmt)
|
||||
inline bool __cdecl IsCompressed(DXGI_FORMAT fmt) noexcept
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case DXGI_FORMAT_BC1_TYPELESS:
|
||||
case DXGI_FORMAT_BC1_UNORM:
|
||||
case DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC2_TYPELESS:
|
||||
case DXGI_FORMAT_BC2_UNORM:
|
||||
case DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC3_TYPELESS:
|
||||
case DXGI_FORMAT_BC3_UNORM:
|
||||
case DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC4_TYPELESS:
|
||||
case DXGI_FORMAT_BC4_UNORM:
|
||||
case DXGI_FORMAT_BC4_SNORM:
|
||||
case DXGI_FORMAT_BC5_TYPELESS:
|
||||
case DXGI_FORMAT_BC5_UNORM:
|
||||
case DXGI_FORMAT_BC5_SNORM:
|
||||
case DXGI_FORMAT_BC6H_TYPELESS:
|
||||
case DXGI_FORMAT_BC6H_UF16:
|
||||
case DXGI_FORMAT_BC6H_SF16:
|
||||
case DXGI_FORMAT_BC7_TYPELESS:
|
||||
case DXGI_FORMAT_BC7_UNORM:
|
||||
case DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
return true;
|
||||
case DXGI_FORMAT_BC1_TYPELESS:
|
||||
case DXGI_FORMAT_BC1_UNORM:
|
||||
case DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC2_TYPELESS:
|
||||
case DXGI_FORMAT_BC2_UNORM:
|
||||
case DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC3_TYPELESS:
|
||||
case DXGI_FORMAT_BC3_UNORM:
|
||||
case DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC4_TYPELESS:
|
||||
case DXGI_FORMAT_BC4_UNORM:
|
||||
case DXGI_FORMAT_BC4_SNORM:
|
||||
case DXGI_FORMAT_BC5_TYPELESS:
|
||||
case DXGI_FORMAT_BC5_UNORM:
|
||||
case DXGI_FORMAT_BC5_SNORM:
|
||||
case DXGI_FORMAT_BC6H_TYPELESS:
|
||||
case DXGI_FORMAT_BC6H_UF16:
|
||||
case DXGI_FORMAT_BC6H_SF16:
|
||||
case DXGI_FORMAT_BC7_TYPELESS:
|
||||
case DXGI_FORMAT_BC7_UNORM:
|
||||
case DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline bool __cdecl IsPalettized(DXGI_FORMAT fmt)
|
||||
inline bool __cdecl IsPalettized(DXGI_FORMAT fmt) noexcept
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case DXGI_FORMAT_AI44:
|
||||
case DXGI_FORMAT_IA44:
|
||||
case DXGI_FORMAT_P8:
|
||||
case DXGI_FORMAT_A8P8:
|
||||
return true;
|
||||
case DXGI_FORMAT_AI44:
|
||||
case DXGI_FORMAT_IA44:
|
||||
case DXGI_FORMAT_P8:
|
||||
case DXGI_FORMAT_A8P8:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
|
||||
inline bool __cdecl IsSRGB(DXGI_FORMAT fmt) noexcept
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
return true;
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +122,7 @@ inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
|
||||
// Image I/O
|
||||
//=====================================================================================
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& blob)
|
||||
inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DDS_FLAGS flags, Blob& blob) noexcept
|
||||
{
|
||||
TexMetadata mdata = {};
|
||||
mdata.width = image.width;
|
||||
@@ -109,7 +137,7 @@ inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& bl
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wchar_t* szFile)
|
||||
inline HRESULT __cdecl SaveToDDSFile(const Image& image, DDS_FLAGS flags, const wchar_t* szFile) noexcept
|
||||
{
|
||||
TexMetadata mdata = {};
|
||||
mdata.width = image.width;
|
||||
@@ -122,3 +150,43 @@ inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wcha
|
||||
|
||||
return SaveToDDSFile(&image, 1, mdata, flags, szFile);
|
||||
}
|
||||
|
||||
|
||||
//=====================================================================================
|
||||
// Compatability helpers
|
||||
//=====================================================================================
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl GetMetadataFromTGAMemory(const void* pSource, size_t size, TexMetadata& metadata) noexcept
|
||||
{
|
||||
return GetMetadataFromTGAMemory(pSource, size, TGA_FLAGS_NONE, metadata);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl GetMetadataFromTGAFile(const wchar_t* szFile, TexMetadata& metadata) noexcept
|
||||
{
|
||||
return GetMetadataFromTGAFile(szFile, TGA_FLAGS_NONE, metadata);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl LoadFromTGAMemory(const void* pSource, size_t size, TexMetadata* metadata, ScratchImage& image) noexcept
|
||||
{
|
||||
return LoadFromTGAMemory(pSource, size, TGA_FLAGS_NONE, metadata, image);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl LoadFromTGAFile(const wchar_t* szFile, TexMetadata* metadata, ScratchImage& image) noexcept
|
||||
{
|
||||
return LoadFromTGAFile(szFile, TGA_FLAGS_NONE, metadata, image);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl SaveToTGAMemory(const Image& image, Blob& blob, const TexMetadata* metadata) noexcept
|
||||
{
|
||||
return SaveToTGAMemory(image, TGA_FLAGS_NONE, blob, metadata);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
inline HRESULT __cdecl SaveToTGAFile(const Image& image, const wchar_t* szFile, const TexMetadata* metadata) noexcept
|
||||
{
|
||||
return SaveToTGAFile(image, TGA_FLAGS_NONE, szFile, metadata);
|
||||
}
|
||||
|
||||
34
Source/ThirdParty/DirectXTex/LICENSE
vendored
34
Source/ThirdParty/DirectXTex/LICENSE
vendored
@@ -1,21 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2011-2019 Microsoft Corp
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies
|
||||
or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
|
||||
5
Source/ThirdParty/tinyexr/LICENSE
vendored
Normal file
5
Source/ThirdParty/tinyexr/LICENSE
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
3-clause BSD
|
||||
|
||||
tinyexr uses miniz, which is developed by Rich Geldreich richgel99@gmail.com, and licensed under public domain.
|
||||
|
||||
tinyexr tools uses stb, which is licensed under public domain: https://github.com/nothings/stb tinyexr uses some code from OpenEXR, which is licensed under 3-clause BSD license. tinyexr uses nanozlib and wuffs, whose are licensed unnder Apache 2.0 license.
|
||||
22
Source/ThirdParty/tinyexr/tinyexr.Build.cs
vendored
Normal file
22
Source/ThirdParty/tinyexr/tinyexr.Build.cs
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// https://github.com/syoyo/tinyexr
|
||||
/// </summary>
|
||||
public class tinyexr : HeaderOnlyModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
LicenseType = LicenseTypes.BSD3Clause;
|
||||
LicenseFilePath = "LICENSE";
|
||||
|
||||
// Merge third-party modules into engine binary
|
||||
BinaryModuleName = "FlaxEngine";
|
||||
}
|
||||
}
|
||||
9307
Source/ThirdParty/tinyexr/tinyexr.h
vendored
Normal file
9307
Source/ThirdParty/tinyexr/tinyexr.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -269,8 +269,9 @@ namespace Flax.Build
|
||||
dotnetSdkVersions = MergeVersions(dotnetSdkVersions, GetVersions(Path.Combine(dotnetPath, "sdk")));
|
||||
dotnetRuntimeVersions = MergeVersions(dotnetRuntimeVersions, GetVersions(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App")));
|
||||
|
||||
dotnetSdkVersions = dotnetSdkVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "sdk", x)));
|
||||
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x)));
|
||||
dotnetSdkVersions = dotnetSdkVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "sdk", x, ".version")));
|
||||
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x, ".version")));
|
||||
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => Directory.Exists(Path.Combine(dotnetPath, "packs", "Microsoft.NETCore.App.Ref", x)));
|
||||
|
||||
dotnetSdkVersions = dotnetSdkVersions.OrderByDescending(ParseVersion);
|
||||
dotnetRuntimeVersions = dotnetRuntimeVersions.OrderByDescending(ParseVersion);
|
||||
@@ -543,11 +544,6 @@ namespace Flax.Build
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsValidVersion(string versionPath)
|
||||
{
|
||||
return File.Exists(Path.Combine(versionPath, ".version"));
|
||||
}
|
||||
|
||||
private static string SearchForDotnetLocationLinux()
|
||||
{
|
||||
if (File.Exists("/etc/dotnet/install_location")) // Officially recommended dotnet location file
|
||||
|
||||
@@ -26,6 +26,38 @@ namespace Flax.Build.NativeCpp
|
||||
SmallCode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The code sanitizers for core errors detection by compiler-supported checks.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Sanitizer
|
||||
{
|
||||
/// <summary>
|
||||
/// No sanitizers in use.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Memory errors detector,
|
||||
/// </summary>
|
||||
Address = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Data races and deadlocks detector.
|
||||
/// </summary>
|
||||
Thread = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Uninitialized memory reads detector.
|
||||
/// </summary>
|
||||
Memory = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Undefined behavior (UB) detector.
|
||||
/// </summary>
|
||||
Undefined = 8,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The compilation optimization hint.
|
||||
/// </summary>
|
||||
@@ -67,6 +99,11 @@ namespace Flax.Build.NativeCpp
|
||||
/// </summary>
|
||||
public FavorSizeOrSpeed FavorSizeOrSpeed = FavorSizeOrSpeed.Neither;
|
||||
|
||||
/// <summary>
|
||||
/// Selects a sanitizers to use (as flags).
|
||||
/// </summary>
|
||||
public Sanitizer Sanitizers = Sanitizer.None;
|
||||
|
||||
/// <summary>
|
||||
/// Enables exceptions support.
|
||||
/// </summary>
|
||||
@@ -184,6 +221,7 @@ namespace Flax.Build.NativeCpp
|
||||
{
|
||||
CppVersion = CppVersion,
|
||||
FavorSizeOrSpeed = FavorSizeOrSpeed,
|
||||
Sanitizers = Sanitizers,
|
||||
EnableExceptions = EnableExceptions,
|
||||
RuntimeTypeInfo = RuntimeTypeInfo,
|
||||
Inlining = Inlining,
|
||||
|
||||
@@ -249,6 +249,7 @@ namespace Flax.Build
|
||||
}
|
||||
|
||||
options.CompileEnv.EnableExceptions = true; // TODO: try to disable this!
|
||||
options.CompileEnv.Sanitizers = Configuration.Sanitizers;
|
||||
switch (options.Configuration)
|
||||
{
|
||||
case TargetConfiguration.Debug:
|
||||
|
||||
@@ -225,6 +225,12 @@ namespace Flax.Build
|
||||
[CommandLine("compiler", "<name>", "Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows.")]
|
||||
public static string Compiler = null;
|
||||
|
||||
/// <summary>
|
||||
/// Selects a sanitizers to use (as flags). Options: Address, Thread.
|
||||
/// </summary>
|
||||
[CommandLine("sanitizers", "<name>", "Selects a sanitizers to use (as flags). Options: Address, Thread.")]
|
||||
public static Flax.Build.NativeCpp.Sanitizer Sanitizers = Flax.Build.NativeCpp.Sanitizer.None;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the dotnet SDK version to use for the build. Eg. set to '7' to use .NET 7 even if .NET 8 is installed.
|
||||
/// </summary>
|
||||
@@ -242,6 +248,8 @@ namespace Flax.Build
|
||||
cmdLine += " -compiler=" + Compiler;
|
||||
if (!string.IsNullOrEmpty(Dotnet))
|
||||
cmdLine += " -dotnet=" + Dotnet;
|
||||
if (Sanitizers != Flax.Build.NativeCpp.Sanitizer.None)
|
||||
cmdLine += " -sanitizers=" + Sanitizers.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Flax.Deps.Dependencies
|
||||
|
||||
// Get the source
|
||||
CloneGitRepo(root, "https://github.com/Microsoft/DirectXTex.git");
|
||||
GitCheckout(root, "master", "9a417f506c43e087b84c017260ad673abd6c64e1");
|
||||
GitCheckout(root, "main", "5cfd711dc5d64cde1e8b27670036535df5c3f922");
|
||||
|
||||
foreach (var platform in options.Platforms)
|
||||
{
|
||||
@@ -52,60 +52,39 @@ namespace Flax.Deps.Dependencies
|
||||
{
|
||||
case TargetPlatform.Windows:
|
||||
{
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2015.sln");
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2015");
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2022.sln");
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2022");
|
||||
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64");
|
||||
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
|
||||
foreach (var file in outputFileNames)
|
||||
{
|
||||
Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TargetPlatform.UWP:
|
||||
{
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2017.sln");
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2017");
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2019.sln");
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2019");
|
||||
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64");
|
||||
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
|
||||
foreach (var file in outputFileNames)
|
||||
{
|
||||
Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TargetPlatform.XboxOne:
|
||||
{
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln");
|
||||
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true);
|
||||
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj"));
|
||||
projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142");
|
||||
var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2017.vcxproj");
|
||||
File.WriteAllText(projectPath, projectFileContents);
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017");
|
||||
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.XboxOne.x64");
|
||||
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
|
||||
foreach (var file in outputFileNames)
|
||||
{
|
||||
Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.XboxOne.x64", configuration, file), Path.Combine(depsFolder, file));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TargetPlatform.XboxScarlett:
|
||||
{
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln");
|
||||
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true);
|
||||
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj"));
|
||||
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2019.sln");
|
||||
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.sln"), solutionPath, true);
|
||||
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.vcxproj"));
|
||||
projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142");
|
||||
var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2017.vcxproj");
|
||||
var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2019.vcxproj");
|
||||
File.WriteAllText(projectPath, projectFileContents);
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017");
|
||||
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.Scarlett.x64");
|
||||
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2019");
|
||||
var xboxName = platform == TargetPlatform.XboxOne ? "Gaming.Xbox.XboxOne.x64" : "Gaming.Xbox.Scarlett.x64";
|
||||
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, xboxName);
|
||||
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
|
||||
foreach (var file in outputFileNames)
|
||||
{
|
||||
Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.Scarlett.x64", configuration, file), Path.Combine(depsFolder, file));
|
||||
}
|
||||
Utilities.FileCopy(Path.Combine(binFolder, xboxName, configuration, file), Path.Combine(depsFolder, file));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Flax.Build.Platforms
|
||||
commonArgs.Add("objective-c++");
|
||||
commonArgs.Add("-stdlib=libc++");
|
||||
AddArgsCommon(options, commonArgs);
|
||||
AddArgsSanitizer(compileEnvironment.Sanitizers, commonArgs);
|
||||
|
||||
switch (compileEnvironment.CppVersion)
|
||||
{
|
||||
@@ -155,14 +156,24 @@ namespace Flax.Build.Platforms
|
||||
|
||||
commonArgs.Add("-pthread");
|
||||
|
||||
if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode)
|
||||
commonArgs.Add("-Ofast");
|
||||
else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode)
|
||||
commonArgs.Add("-Os");
|
||||
if (compileEnvironment.Optimization)
|
||||
commonArgs.Add("-O3");
|
||||
if (compileEnvironment.Sanitizers.HasFlag(Sanitizer.Address))
|
||||
{
|
||||
commonArgs.Add("-fno-optimize-sibling-calls");
|
||||
commonArgs.Add("-fno-omit-frame-pointer");
|
||||
if (compileEnvironment.Optimization)
|
||||
commonArgs.Add("-O1");
|
||||
}
|
||||
else
|
||||
commonArgs.Add("-O0");
|
||||
{
|
||||
if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode)
|
||||
commonArgs.Add("-Ofast");
|
||||
else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode)
|
||||
commonArgs.Add("-Os");
|
||||
if (compileEnvironment.Optimization)
|
||||
commonArgs.Add("-O3");
|
||||
else
|
||||
commonArgs.Add("-O0");
|
||||
}
|
||||
|
||||
if (compileEnvironment.BufferSecurityCheck)
|
||||
commonArgs.Add("-fstack-protector");
|
||||
@@ -240,6 +251,7 @@ namespace Flax.Build.Platforms
|
||||
{
|
||||
args.Add(string.Format("-o \"{0}\"", outputFilePath));
|
||||
AddArgsCommon(options, args);
|
||||
AddArgsSanitizer(options.CompileEnv.Sanitizers, args);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
@@ -426,5 +438,15 @@ namespace Flax.Build.Platforms
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddArgsSanitizer(Sanitizer sanitizers, List<string> args)
|
||||
{
|
||||
if (sanitizers.HasFlag(Sanitizer.Address))
|
||||
args.Add("-fsanitize=address");
|
||||
if (sanitizers.HasFlag(Sanitizer.Thread))
|
||||
args.Add("-fsanitize=thread");
|
||||
if (sanitizers.HasFlag(Sanitizer.Undefined))
|
||||
args.Add("-fsanitize=undefined");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user