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:
Wojtek Figat
2024-04-17 09:58:59 +02:00
99 changed files with 11337 additions and 1004 deletions

View File

@@ -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/=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/Abbreviations/=VS/@EntryIndexedValue">VS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</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_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_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_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_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_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> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002ENamespaceIndentationSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>

View File

@@ -133,6 +133,7 @@ namespace FlaxEditor.Content.Import
FileTypes["dds"] = ImportTexture; FileTypes["dds"] = ImportTexture;
FileTypes["hdr"] = ImportTexture; FileTypes["hdr"] = ImportTexture;
FileTypes["raw"] = ImportTexture; FileTypes["raw"] = ImportTexture;
FileTypes["exr"] = ImportTexture;
// Models // Models
FileTypes["obj"] = ImportModel; FileTypes["obj"] = ImportModel;

View File

@@ -128,6 +128,11 @@ namespace FlaxEditor.Content.Import
_settings.Settings.Type = TextureFormatType.HdrRGBA; _settings.Settings.Type = TextureFormatType.HdrRGBA;
_settings.Settings.Compress = false; _settings.Settings.Compress = false;
} }
else if (extension == ".exr")
{
// HDR image
_settings.Settings.Type = TextureFormatType.HdrRGBA;
}
else if (extension == ".hdr") else if (extension == ".hdr")
{ {
// HDR sky texture // HDR sky texture

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Actions; using FlaxEditor.Actions;
using FlaxEditor.CustomEditors.Editors; using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.Elements;
@@ -9,6 +10,8 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
@@ -111,6 +114,38 @@ namespace FlaxEditor.CustomEditors.Dedicated
var actor = (Actor)Values[0]; var actor = (Actor)Values[0];
var scriptType = TypeUtils.GetType(actor.TypeName); var scriptType = TypeUtils.GetType(actor.TypeName);
var item = scriptType.ContentItem; 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("Copy ID", OnClickCopyId);
cm.AddButton("Edit actor type", OnClickEditActorType).Enabled = item != null; cm.AddButton("Edit actor type", OnClickEditActorType).Enabled = item != null;
var showButton = cm.AddButton("Show in content window", OnClickShowActorType); var showButton = cm.AddButton("Show in content window", OnClickShowActorType);

View File

@@ -37,6 +37,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
return; return;
} }
EvaluateVisibleIf(itemLayout, item, GetLabelIndex(itemLayout, item));
// Add labels with a check box // Add labels with a check box
var label = new CheckablePropertyNameLabel(item.DisplayName); var label = new CheckablePropertyNameLabel(item.DisplayName);
label.CheckBox.Tag = setting.Bit; label.CheckBox.Tag = setting.Bit;

View File

@@ -582,55 +582,13 @@ namespace FlaxEditor.CustomEditors.Editors
} }
/// <summary> /// <summary>
/// Spawns the property for the given item. /// Evaluate the <see cref="VisibleIfAttribute"/> cache for a given property item.
/// </summary> /// </summary>
/// <param name="itemLayout">The item layout.</param> /// <param name="itemLayout">The item layout.</param>
/// <param name="itemValues">The item values.</param>
/// <param name="item">The item.</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) if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0)
{ {
PropertiesListElement list = null; 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 /> /// <inheritdoc />
internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values) internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
{ {

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.CustomEditors.Editors
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged; _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"); var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null; button.Clicked += () => _comboBox.SelectedItem = null;
@@ -106,7 +106,7 @@ namespace FlaxEditor.CustomEditors.Editors
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged; _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"); var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null; button.Clicked += () => _comboBox.SelectedItem = null;

View File

@@ -79,7 +79,7 @@ namespace FlaxEditor.CustomEditors
var theFirstType = TypeUtils.GetObjectType(this[0]); var theFirstType = TypeUtils.GetObjectType(this[0]);
for (int i = 1; i < Count; i++) for (int i = 1; i < Count; i++)
{ {
if (theFirstType != TypeUtils.GetObjectType(this[1])) if (theFirstType != TypeUtils.GetObjectType(this[i]))
return true; return true;
} }
return false; return false;

View File

@@ -47,7 +47,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
break; break;
default: throw new ArgumentOutOfRangeException(); 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); Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);
var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset); var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset);
Matrix3x3.Multiply(ref m1, ref m2, out var m3); 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.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6));
Render2D.PopTransform(); 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(); base.Draw();
} }

View File

@@ -42,7 +42,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f;
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap;
var moveColor = style.ProgressNormal; var moveColor = style.SelectionBorder;
var thickness = 2.0f; var thickness = 2.0f;
var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal); var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal);
Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor); Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor);

View File

@@ -100,7 +100,7 @@ namespace FlaxEditor.GUI.Timeline
private Track _tack; private Track _tack;
private int _startFrame, _durationFrames; private int _startFrame, _durationFrames;
private Float2 _mouseLocation = Float2.Minimum; private Float2 _mouseLocation = Float2.Minimum;
private bool _isMoving; internal bool _isMoving;
private Float2 _startMoveLocation; private Float2 _startMoveLocation;
private int _startMoveStartFrame; private int _startMoveStartFrame;
private int _startMoveDuration; private int _startMoveDuration;
@@ -347,7 +347,7 @@ namespace FlaxEditor.GUI.Timeline
var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge; var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge;
var borderHighlightColor = style.BorderHighlighted; var borderHighlightColor = style.BorderHighlighted;
var moveColor = style.ProgressNormal; var moveColor = style.SelectionBorder;
var selectedColor = style.BackgroundSelected; var selectedColor = style.BackgroundSelected;
var moveThickness = 2.0f; var moveThickness = 2.0f;
var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal)); var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal));

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -183,6 +182,49 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDurationFramesChanged(); 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 /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {

View File

@@ -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})"); _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator(); ToolStrip.AddSeparator();
// 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 // Play
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})"); _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})");
_toolStripPlay.ContextMenu = new ContextMenu(); _toolStripPlay.ContextMenu = new ContextMenu();
@@ -741,7 +728,6 @@ namespace FlaxEditor.Modules
playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction; playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction;
playActionGroup.SelectedChanged = SetPlayAction; playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
var windowModesGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.GameWindowMode>(); var windowModesGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.GameWindowMode>();
var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode"); var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode");
windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor"); 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; }; 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})"); _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(); UpdateToolstrip();
} }

View File

@@ -17,6 +17,11 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
public readonly InputActionsContainer InputActions; public readonly InputActionsContainer InputActions;
/// <summary>
/// Optional feature.
/// </summary>
public bool PanWithMiddleMouse = false;
private string _currentInputText = string.Empty; private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta; private Float2 _movingNodesDelta;
private HashSet<SurfaceNode> _movingNodes; private HashSet<SurfaceNode> _movingNodes;
@@ -223,15 +228,18 @@ namespace FlaxEditor.Surface
if (_middleMouseDown) if (_middleMouseDown)
{ {
// Calculate delta if (PanWithMiddleMouse)
var delta = location - _middleMouseDownPos;
if (delta.LengthSquared > 0.01f)
{ {
// Move view // Calculate delta
_mouseMoveAmount += delta.Length; var delta = location - _middleMouseDownPos;
_rootControl.Location += delta; if (delta.LengthSquared > 0.01f)
_middleMouseDownPos = location; {
Cursor = CursorType.SizeAll; // Move view
_mouseMoveAmount += delta.Length;
_rootControl.Location += delta;
_middleMouseDownPos = location;
Cursor = CursorType.SizeAll;
}
} }
// Handled // Handled
@@ -300,7 +308,8 @@ namespace FlaxEditor.Surface
if (_middleMouseDown) if (_middleMouseDown)
{ {
_middleMouseDown = false; _middleMouseDown = false;
Cursor = CursorType.Default; if (PanWithMiddleMouse)
Cursor = CursorType.Default;
} }
_isMovingSelection = false; _isMovingSelection = false;
ConnectingEnd(null); ConnectingEnd(null);
@@ -483,7 +492,7 @@ namespace FlaxEditor.Surface
Focus(); Focus();
return true; return true;
} }
if (_rightMouseDown || _middleMouseDown) if (_rightMouseDown || (_middleMouseDown && _middleMouseDown))
{ {
// Start navigating // Start navigating
StartMouseCapture(); StartMouseCapture();
@@ -555,9 +564,12 @@ namespace FlaxEditor.Surface
if (_middleMouseDown && button == MouseButton.Middle) if (_middleMouseDown && button == MouseButton.Middle)
{ {
_middleMouseDown = false; _middleMouseDown = false;
EndMouseCapture(); if (_middleMouseDown)
Cursor = CursorType.Default; {
if (_mouseMoveAmount > 0) EndMouseCapture();
Cursor = CursorType.Default;
}
if (_mouseMoveAmount > 0 && _middleMouseDown)
_mouseMoveAmount = 0; _mouseMoveAmount = 0;
else if (CanEdit) else if (CanEdit)
{ {

View File

@@ -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")] [EditorOrder(410), EditorDisplay("Transform", "Rotation"), DefaultValue(typeof(Quaternion), "0,0,0,1"), Tooltip("Orientation of the terrain")]
public Quaternion Orientation = Quaternion.Identity; 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")] [EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(1.0f), Limit(0.0001f, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
public Float3 Scale = Float3.One; public float Scale = 1.0f;
} }
private readonly Options _options = new Options(); private readonly Options _options = new Options();
@@ -147,7 +147,7 @@ namespace FlaxEditor.Tools.Terrain
// Create terrain object and setup some options // Create terrain object and setup some options
var terrain = new FlaxEngine.Terrain(); var terrain = new FlaxEngine.Terrain();
terrain.Setup(_options.LODCount, (int)_options.ChunkSize); 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.Material = _options.Material;
terrain.CollisionLOD = _options.CollisionLOD; terrain.CollisionLOD = _options.CollisionLOD;
if (_options.Heightmap) if (_options.Heightmap)
@@ -238,24 +238,5 @@ namespace FlaxEditor.Tools.Terrain
return base.CanCloseWindow(reason); 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);
}
} }
} }

View File

@@ -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) // Lock asset chunks (if not virtual)
data.Lock = texture->LockData(); data.Lock = texture->LockData();
@@ -103,7 +103,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
// Decompress or convert data if need to // Decompress or convert data if need to
data.Mip0DataPtr = &data.Mip0Data; data.Mip0DataPtr = &data.Mip0Data;
if (PixelFormatExtensions::IsCompressed(data.Format)) if (PixelFormatExtensions::IsCompressed(data.Format) || TextureTool::GetSampler(data.Format) == nullptr)
{ {
PROFILE_CPU_NAMED("Decompress"); PROFILE_CPU_NAMED("Decompress");
@@ -122,7 +122,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
srcMip.Lines = src.Height; srcMip.Lines = src.Height;
// Decompress texture // 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."); LOG(Warning, "Failed to decompress data.");
return true; return true;
@@ -134,7 +134,6 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
data.SlicePitch = data.Tmp.Items[0].Mips[0].DepthPitch; data.SlicePitch = data.Tmp.Items[0].Mips[0].DepthPitch;
data.Mip0DataPtr = &data.Tmp.Items[0].Mips[0].Data; 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 // Check if can even sample the given format
const auto sampler = TextureTool::GetSampler(data.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."); LOG(Warning, "Cannot setup terain with no patches.");
return false; return false;
} }
PROFILE_CPU_NAMED("Terrain.GenerateTerrain"); PROFILE_CPU_NAMED("Terrain.GenerateTerrain");
// Wait for assets to be loaded // Wait for assets to be loaded
@@ -188,7 +186,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
{ {
// Get data // Get data
TextureDataResult dataHeightmap; TextureDataResult dataHeightmap;
if (GetTextureDataForSampling(heightmap, dataHeightmap)) if (GetTextureDataForSampling(heightmap, dataHeightmap, true))
return true; return true;
const auto sampler = TextureTool::GetSampler(dataHeightmap.Format); 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++) for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
{ {
auto patch = terrain->GetPatch(patchIndex); auto patch = terrain->GetPatch(patchIndex);
const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch; const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
// Sample heightmap pixels with interpolation to get actual heightmap vertices locations // Sample heightmap pixels with interpolation to get actual heightmap vertices locations

View File

@@ -76,7 +76,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc /> /// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000; public Float2 MouseDelta => _mouseDelta;
/// <inheritdoc /> /// <inheritdoc />
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false; public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;

View File

@@ -65,6 +65,11 @@ namespace FlaxEditor.Viewport
/// </summary> /// </summary>
public bool IsAltDown; 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> /// <summary>
/// The is mouse right down flag. /// The is mouse right down flag.
/// </summary> /// </summary>
@@ -88,18 +93,20 @@ namespace FlaxEditor.Viewport
/// <summary> /// <summary>
/// Gets a value indicating whether use is controlling mouse. /// Gets a value indicating whether use is controlling mouse.
/// </summary> /// </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> /// <summary>
/// Gathers input from the specified window. /// Gathers input from the specified window.
/// </summary> /// </summary>
/// <param name="window">The window.</param> /// <param name="window">The window.</param>
/// <param name="useMouse">True if use mouse input, otherwise will skip mouse.</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); IsControlDown = window.GetKey(KeyboardKeys.Control);
IsShiftDown = window.GetKey(KeyboardKeys.Shift); IsShiftDown = window.GetKey(KeyboardKeys.Shift);
IsAltDown = window.GetKey(KeyboardKeys.Alt); IsAltDown = window.GetKey(KeyboardKeys.Alt);
WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown;
IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right); IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right);
IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle); IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle);
@@ -114,6 +121,7 @@ namespace FlaxEditor.Viewport
IsControlDown = false; IsControlDown = false;
IsShiftDown = false; IsShiftDown = false;
IsAltDown = false; IsAltDown = false;
WasAltDownBefore = false;
IsMouseRightDown = false; IsMouseRightDown = false;
IsMouseMiddleDown = false; IsMouseMiddleDown = false;
@@ -1540,7 +1548,7 @@ namespace FlaxEditor.Viewport
_prevInput = _input; _prevInput = _input;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot)); var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
if (canUseInput && ContainsFocus && hit == null) if (canUseInput && ContainsFocus && hit == null)
_input.Gather(win.Window, useMouse); _input.Gather(win.Window, useMouse, ref _prevInput);
else else
_input.Clear(); _input.Clear();
@@ -1663,8 +1671,7 @@ namespace FlaxEditor.Viewport
{ {
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); 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); offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
_mouseDelta = offset / size; _mouseDelta = offset;
_mouseDelta.Y *= size.Y / size.X;
// Update delta filtering buffer // Update delta filtering buffer
_deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta; _deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta;
@@ -1682,8 +1689,7 @@ namespace FlaxEditor.Viewport
} }
else else
{ {
_mouseDelta = offset / size; _mouseDelta = offset;
_mouseDelta.Y *= size.Y / size.X;
mouseDelta = _mouseDelta; mouseDelta = _mouseDelta;
} }
@@ -1697,7 +1703,7 @@ namespace FlaxEditor.Viewport
// Update // Update
moveDelta *= dt * (60.0f * 4.0f); 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); UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
// Move mouse back to the root position // Move mouse back to the root position
@@ -1723,7 +1729,7 @@ namespace FlaxEditor.Viewport
var offset = _viewMousePos - _startPos; var offset = _viewMousePos - _startPos;
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X); 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); offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
_mouseDelta = offset / size; _mouseDelta = offset;
_startPos = _viewMousePos; _startPos = _viewMousePos;
} }
else else

View File

@@ -291,7 +291,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc /> /// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000; public Float2 MouseDelta => _mouseDelta;
/// <inheritdoc /> /// <inheritdoc />
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);

View File

@@ -336,7 +336,8 @@ namespace FlaxEditor.Viewport.Previews
if (_showNodes) if (_showNodes)
{ {
// Draw bounding box at the node locations // 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++) for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
{ {
if (nodesMask != null && !nodesMask[nodeIndex]) if (nodesMask != null && !nodesMask[nodeIndex])

View File

@@ -494,6 +494,7 @@ namespace FlaxEditor.Windows.Assets
_undo.Enabled = false; _undo.Enabled = false;
_undo.Clear(); _undo.Clear();
_propertiesEditor.BuildLayoutOnUpdate(); _propertiesEditor.BuildLayoutOnUpdate();
UpdateToolstrip();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -504,6 +505,7 @@ namespace FlaxEditor.Windows.Assets
_undo.Enabled = true; _undo.Enabled = true;
_undo.Clear(); _undo.Clear();
_propertiesEditor.BuildLayoutOnUpdate(); _propertiesEditor.BuildLayoutOnUpdate();
UpdateToolstrip();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -54,6 +54,9 @@ namespace FlaxEditor.Windows.Assets
/// <param name="before">The selection before the change.</param> /// <param name="before">The selection before the change.</param>
public void OnSelectionChanged(SceneGraphNode[] before) public void OnSelectionChanged(SceneGraphNode[] before)
{ {
if (LockSelectedObjects)
return;
Undo.AddAction(new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo)); Undo.AddAction(new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo));
OnSelectionChanges(); OnSelectionChanges();

View File

@@ -68,6 +68,11 @@ namespace FlaxEditor.Windows.Assets
/// </summary> /// </summary>
public readonly LocalSceneGraph Graph; public readonly LocalSceneGraph Graph;
/// <summary>
/// Indication of if the prefab window selection is locked on specific objects.
/// </summary>
public bool LockSelectedObjects = false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto). /// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto).
/// </summary> /// </summary>

View File

@@ -665,7 +665,16 @@ namespace FlaxEditor.Windows
// Scroll to the new entry (if any added to view) // Scroll to the new entry (if any added to view)
if (scrollView && anyVisible) if (scrollView && anyVisible)
{
panelScroll.ScrollViewTo(newEntry); 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));
}
}
} }
} }

View File

@@ -37,6 +37,11 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
public bool UIPivotRelative = true; public bool UIPivotRelative = true;
/// <summary>
/// Indication of if the properties window is locked on specific objects.
/// </summary>
public bool LockObjects = false;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PropertiesWindow"/> class. /// Initializes a new instance of the <see cref="PropertiesWindow"/> class.
/// </summary> /// </summary>
@@ -62,6 +67,9 @@ namespace FlaxEditor.Windows
private void OnSelectionChanged() private void OnSelectionChanged()
{ {
if (LockObjects)
return;
// Update selected objects // Update selected objects
// TODO: use cached collection for less memory allocations // TODO: use cached collection for less memory allocations
undoRecordObjects = Editor.SceneEditing.Selection.ConvertAll(x => x.UndoRecordObject).Distinct(); undoRecordObjects = Editor.SceneEditing.Selection.ConvertAll(x => x.UndoRecordObject).Distinct();

View File

@@ -196,6 +196,7 @@ namespace FlaxEditor.Windows
{ {
if (actorType.IsAbstract) if (actorType.IsAbstract)
continue; continue;
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType));
ActorToolboxAttribute attribute = null; ActorToolboxAttribute attribute = null;
foreach (var e in actorType.GetAttributes(false)) 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.AddChild(string.IsNullOrEmpty(attribute.Name) ? CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType) : CreateActorItem(attribute.Name, actorType));
group.SortChildren(); group.SortChildren();
} }
_groupSearch.SortChildren();
} }
private void OnSearchBoxTextChanged() private void OnSearchBoxTextChanged()
@@ -260,6 +262,10 @@ namespace FlaxEditor.Windows
} }
var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name; 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)) if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
continue; continue;
@@ -278,6 +284,9 @@ namespace FlaxEditor.Windows
} }
item.SetHighlights(highlights); item.SetHighlights(highlights);
} }
if (string.IsNullOrEmpty(filterText))
_groupSearch.SortChildren();
_groupSearch.UnlockChildrenRecursive(); _groupSearch.UnlockChildrenRecursive();
PerformLayout(); PerformLayout();

View File

@@ -3,6 +3,7 @@
#pragma once #pragma once
#include "Engine/Core/Types/String.h" #include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Math/Transform.h" #include "Engine/Core/Math/Transform.h"
#include "Engine/Animations/Curve.h" #include "Engine/Animations/Curve.h"
@@ -68,6 +69,16 @@ public:
uint64 GetMemoryUsage() const; uint64 GetMemoryUsage() const;
}; };
/// <summary>
/// Single track with events.
/// </summary>
struct EventAnimationData
{
float Duration = 0.0f;
StringAnsi TypeName;
StringAnsi JsonData;
};
/// <summary> /// <summary>
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior. /// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
/// </summary> /// </summary>
@@ -120,10 +131,15 @@ struct AnimationData
String RootNodeName; String RootNodeName;
/// <summary> /// <summary>
/// The per skeleton node animation channels. /// The per-skeleton node animation channels.
/// </summary> /// </summary>
Array<NodeAnimationData> Channels; Array<NodeAnimationData> Channels;
/// <summary>
/// The animation event tracks.
/// </summary>
Array<Pair<String, StepCurve<EventAnimationData>>> Events;
public: public:
/// <summary> /// <summary>
/// Gets the length of the animation (in seconds). /// Gets the length of the animation (in seconds).

View File

@@ -5,6 +5,7 @@
#include "Engine/Visject/VisjectGraph.h" #include "Engine/Visject/VisjectGraph.h"
#include "Engine/Content/Assets/Animation.h" #include "Engine/Content/Assets/Animation.h"
#include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Core/Collections/ChunkedArray.h"
#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Animations/AlphaBlend.h" #include "Engine/Animations/AlphaBlend.h"
#include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Matrix.h"
#include "../Config.h" #include "../Config.h"
@@ -892,7 +893,7 @@ private:
int32 GetRootNodeIndex(Animation* anim); int32 GetRootNodeIndex(Animation* anim);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed); 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 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, 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); 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);

View File

@@ -25,7 +25,7 @@ namespace
{ {
for (int32 i = 0; i < nodes->Nodes.Count(); i++) for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{ {
nodes->Nodes[i].Orientation.Normalize(); nodes->Nodes.Get()[i].Orientation.Normalize();
} }
if (rootMotionMode != RootMotionExtraction::NoExtraction) if (rootMotionMode != RootMotionExtraction::NoExtraction)
{ {
@@ -222,7 +222,7 @@ FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos,
prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length); 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); PROFILE_CPU_ASSET(anim);
@@ -240,9 +240,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
} }
// Evaluate nested animations // Evaluate nested animations
bool hasNested = false; BitArray<InlinedAllocation<8>> usedNodesThis;
if (anim->NestedAnims.Count() != 0) 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) for (auto& e : anim->NestedAnims)
{ {
const auto& nestedAnim = e.Second; const auto& nestedAnim = e.Second;
@@ -262,8 +269,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale; nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos); GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode); ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode, usedNodes);
hasNested = true;
} }
} }
} }
@@ -295,6 +301,15 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{ {
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i); 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 // Blend node
@@ -316,7 +331,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
dstNode.Scale = srcNode.Scale * weight; dstNode.Scale = srcNode.Scale * weight;
dstNode.Orientation = srcNode.Orientation * weight; dstNode.Orientation = srcNode.Orientation * weight;
} }
else if (!hasNested) else
{ {
dstNode = srcNode; 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])); const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
auto mask = node->Assets[0].As<SkeletonMask>(); 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 // Use the mask connected with this node instead of default mask asset
if (maskAssetBox->HasConnection()) auto maskAssetBox = node->TryGetBox(4);
if (maskAssetBox && maskAssetBox->HasConnection())
{ {
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null); const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
// Use the mask connected with this node instead of default mask asset
if (assetBoxValue != Value::Null) if (assetBoxValue != Value::Null)
mask = (SkeletonMask*)assetBoxValue.AsAsset; mask = (SkeletonMask*)assetBoxValue.AsAsset;
} }

View File

@@ -10,79 +10,102 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target,
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection); 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; void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
if (desiredLength < ZeroTolerance) {
// 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; toTargetLength = ZeroTolerance;
desiredDir = Vector3(1, 0, 0); toTargetDir = Vector3(1, 0, 0);
} }
else else
{ {
desiredDir = desiredDelta.GetNormalized(); toTargetDir = toTargetVector.GetNormalized();
} }
Vector3 jointTargetDelta = jointTarget - rootNode.Translation; // Calculate the pole vector direction
const Real jointTargetLengthSqr = jointTargetDelta.LengthSquared(); Vector3 poleVectorDelta = poleVector - rootTransform.Translation;
const Real poleVectorLengthSqr = poleVectorDelta.LengthSquared();
Vector3 jointPlaneNormal, jointBendDir; Vector3 jointPlaneNormal, bendDirection;
if (jointTargetLengthSqr < ZeroTolerance * ZeroTolerance) if (poleVectorLengthSqr < ZeroTolerance * ZeroTolerance)
{ {
jointBendDir = Vector3::Forward; bendDirection = Vector3::Forward;
jointPlaneNormal = Vector3::Up; jointPlaneNormal = Vector3::Up;
} }
else else
{ {
jointPlaneNormal = desiredDir ^ jointTargetDelta; jointPlaneNormal = toTargetDir ^ poleVectorDelta;
if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance) if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance)
{ {
desiredDir.FindBestAxisVectors(jointPlaneNormal, jointBendDir); toTargetDir.FindBestAxisVectors(jointPlaneNormal, bendDirection);
} }
else else
{ {
jointPlaneNormal.Normalize(); jointPlaneNormal.Normalize();
jointBendDir = jointTargetDelta - (jointTargetDelta | desiredDir) * desiredDir; bendDirection = poleVectorDelta - (poleVectorDelta | toTargetDir) * toTargetDir;
jointBendDir.Normalize(); bendDirection.Normalize();
} }
} }
// Handle limb stretching if allowed
if (allowStretching) if (allowStretching)
{ {
const Real initialStretchRatio = 1.0f; const Real initialStretchRatio = 1.0f;
const Real range = maxStretchScale - initialStretchRatio; const Real stretchRange = maxStretchScale - initialStretchRatio;
if (range > ZeroTolerance && limbLengthLimit > ZeroTolerance) if (stretchRange > ZeroTolerance && totalLimbLength > ZeroTolerance)
{ {
const Real reachRatio = desiredLength / limbLengthLimit; const Real reachRatio = toTargetLength / totalLimbLength;
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / range); const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / stretchRange);
if (scalingFactor > ZeroTolerance) if (scalingFactor > ZeroTolerance)
{ {
lowerLimbLength *= 1.0f + scalingFactor; lowerLimbLength *= 1.0f + scalingFactor;
upperLimbLength *= 1.0f + scalingFactor; upperLimbLength *= 1.0f + scalingFactor;
limbLengthLimit *= 1.0f + scalingFactor; totalLimbLength *= 1.0f + scalingFactor;
} }
} }
} }
Vector3 resultEndPos = target; // Calculate new positions for joint and end effector
Vector3 resultJointPos = jointPos; Vector3 newEndEffectorPos = targetPosition;
Vector3 newMidJointPos = midJointPos;
if (desiredLength >= limbLengthLimit) if (toTargetLength >= totalLimbLength) {
{ // Target is beyond the reach of the limb
resultEndPos = rootNode.Translation + limbLengthLimit * desiredDir; Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
resultJointPos = rootNode.Translation + upperLimbLength * desiredDir;
// 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 else
{ {
const Real twoAb = 2.0f * upperLimbLength * desiredLength; // Target is within reach, calculate joint position
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + desiredLength * desiredLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f; 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 bool reverseUpperBone = cosAngle < 0.0f;
const Real angle = Math::Acos(cosAngle); const Real angle = Math::Acos(cosAngle);
const Real jointLineDist = upperLimbLength * Math::Sin(angle); 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; Real projJointDist = projJointDistSqr > 0.0f ? Math::Sqrt(projJointDistSqr) : 0.0f;
if (reverseUpperBone) if (reverseUpperBone)
projJointDist *= -1.0f; 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(); // Vector from root joint to mid joint (local Y-axis direction)
const Vector3 newDir = (resultJointPos - rootNode.Translation).GetNormalized(); Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
rootNode.Orientation = deltaRotation * rootNode.Orientation; // 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(); // Vector from mid joint to end effector (local Y-axis direction after rotation)
const Vector3 newDir = (resultEndPos - resultJointPos).GetNormalized(); Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
jointNode.Orientation = deltaRotation * jointNode.Orientation; // Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
jointNode.Translation = resultJointPos; 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;
} }

View File

@@ -152,12 +152,17 @@ void AudioSource::Play()
RequestStreamingBuffersUpdate(); RequestStreamingBuffersUpdate();
} }
} }
else else if (SourceIDs.HasItems())
{ {
// Play it right away // Play it right away
SetNonStreamingBuffer(); SetNonStreamingBuffer();
PlayInternal(); PlayInternal();
} }
else
{
// Source was nt properly added to the Audio Backend
LOG(Warning, "Cannot play unitialized audio source.");
}
} }
void AudioSource::Pause() void AudioSource::Pause()

View File

@@ -184,9 +184,8 @@ void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
Asset::Asset(const SpawnParams& params, const AssetInfo* info) Asset::Asset(const SpawnParams& params, const AssetInfo* info)
: ManagedScriptingObject(params) : ManagedScriptingObject(params)
, _refCount(0) , _refCount(0)
, _loadingTask(nullptr) , _loadState(0)
, _isLoaded(false) , _loadingTask(0)
, _loadFailed(false)
, _deleteFileOnUnload(false) , _deleteFileOnUnload(false)
, _isVirtual(false) , _isVirtual(false)
{ {
@@ -225,10 +224,10 @@ void Asset::OnDeleteObject()
// Unload asset data (in a safe way to protect asset data) // Unload asset data (in a safe way to protect asset data)
Locker.Lock(); Locker.Lock();
if (_isLoaded) if (IsLoaded())
{ {
unload(false); unload(false);
_isLoaded = false; Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
} }
Locker.Unlock(); Locker.Unlock();
@@ -319,11 +318,6 @@ void Asset::ChangeID(const Guid& newId)
Content::onAssetChangeId(this, oldId, newId); Content::onAssetChangeId(this, oldId, newId);
} }
bool Asset::LastLoadFailed() const
{
return _loadFailed != 0;
}
#if USE_EDITOR #if USE_EDITOR
bool Asset::ShouldDeleteFileOnUnload() const bool Asset::ShouldDeleteFileOnUnload() const
@@ -337,7 +331,7 @@ uint64 Asset::GetMemoryUsage() const
{ {
uint64 result = sizeof(Asset); uint64 result = sizeof(Asset);
Locker.Lock(); Locker.Lock();
if (_loadingTask) if (Platform::AtomicRead(&_loadingTask))
result += sizeof(ContentLoadTask); result += sizeof(ContentLoadTask);
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType); result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
Locker.Unlock(); Locker.Unlock();
@@ -368,7 +362,7 @@ void Asset::Reload()
{ {
// Unload current data // Unload current data
unload(true); unload(true);
_isLoaded = false; Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
} }
// Start reloading process // Start reloading process
@@ -426,7 +420,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
// Check if has missing loading task // Check if has missing loading task
Platform::MemoryBarrier(); Platform::MemoryBarrier();
const auto loadingTask = _loadingTask; const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask == nullptr) if (loadingTask == nullptr)
{ {
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString()); 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); Content::tryCallOnLoaded((Asset*)this);
} }
return _isLoaded == 0; return !IsLoaded();
} }
void Asset::InitAsVirtual() void Asset::InitAsVirtual()
@@ -525,14 +519,14 @@ void Asset::InitAsVirtual()
_isVirtual = true; _isVirtual = true;
// Be a loaded thing // Be a loaded thing
_isLoaded = true; Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
} }
void Asset::CancelStreaming() 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 // 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(); Locker.Lock();
ContentLoadTask* loadTask = _loadingTask; auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
Locker.Unlock(); Locker.Unlock();
if (loadTask) if (loadTask)
{ {
@@ -575,10 +569,11 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading() void Asset::startLoading()
{ {
ASSERT(!IsLoaded()); ASSERT(!IsLoaded());
ASSERT(_loadingTask == nullptr); ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
_loadingTask = createLoadingTask(); auto loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr); ASSERT(loadingTask != nullptr);
_loadingTask->Start(); Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
loadingTask->Start();
} }
void Asset::releaseStorage() void Asset::releaseStorage()
@@ -593,7 +588,7 @@ bool Asset::IsInternalType() const
bool Asset::onLoad(LoadAssetTask* task) 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) // 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; return true;
Locker.Lock(); Locker.Lock();
@@ -606,15 +601,15 @@ bool Asset::onLoad(LoadAssetTask* task)
} }
const bool isLoaded = result == LoadResult::Ok; const bool isLoaded = result == LoadResult::Ok;
const bool failed = !isLoaded; const bool failed = !isLoaded;
_loadFailed = failed; LoadState state = LoadState::Loaded;
_isLoaded = !failed; Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
if (failed) if (failed)
{ {
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result)); LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result));
} }
// Unlink task // Unlink task
_loadingTask = nullptr; Platform::AtomicStore(&_loadingTask, 0);
ASSERT(failed || IsLoaded() == isLoaded); ASSERT(failed || IsLoaded() == isLoaded);
Locker.Unlock(); Locker.Unlock();
@@ -663,12 +658,12 @@ void Asset::onUnload_MainThread()
OnUnloaded(this); OnUnloaded(this);
// Check if is during loading // Check if is during loading
if (_loadingTask != nullptr) auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask != nullptr)
{ {
// Cancel loading // Cancel loading
auto task = _loadingTask; Platform::AtomicStore(&_loadingTask, 0);
_loadingTask = nullptr;
LOG(Warning, "Cancel loading task for \'{0}\'", ToString()); LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
task->Cancel(); loadingTask->Cancel();
} }
} }

View File

@@ -34,11 +34,17 @@ public:
DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData); DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData);
protected: protected:
volatile int64 _refCount; enum class LoadState : int64
ContentLoadTask* _loadingTask; {
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 _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) 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> /// </summary>
API_PROPERTY() FORCE_INLINE bool IsLoaded() const API_PROPERTY() FORCE_INLINE bool IsLoaded() const
{ {
return _isLoaded != 0; return Platform::AtomicRead(&_loadState) == (int64)LoadState::Loaded;
} }
/// <summary> /// <summary>
/// Returns true if last asset loading failed, otherwise false. /// Returns true if last asset loading failed, otherwise false.
/// </summary> /// </summary>
API_PROPERTY() bool LastLoadFailed() const; API_PROPERTY() bool LastLoadFailed() const
{
return Platform::AtomicRead(&_loadState) == (int64)LoadState::LoadFailed;
}
/// <summary> /// <summary>
/// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved). /// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved).

View File

@@ -1434,8 +1434,7 @@ Asset::LoadResult VisualScript::load()
if (_instances.HasItems()) if (_instances.HasItems())
{ {
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use // Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
_loadFailed = false; Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
_isLoaded = true;
// Setup scripting type // Setup scripting type
CacheScriptingType(); CacheScriptingType();

View File

@@ -14,6 +14,7 @@
#include "Engine/Engine/EngineService.h" #include "Engine/Engine/EngineService.h"
#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystem.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/Graphics.h"
#include "Engine/Engine/Time.h" #include "Engine/Engine/Time.h"
#include "Engine/Engine/Globals.h" #include "Engine/Engine/Globals.h"
@@ -688,101 +689,135 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
return false; return false;
} }
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId) class CloneAssetFileTask : public MainThreadTask
{ {
PROFILE_CPU(); public:
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid()); StringView dstPath;
StringView srcPath;
Guid dstId;
bool* output;
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId); protected:
bool Run() override
// Check source file
if (!FileSystem::FileExists(srcPath))
{ {
LOG(Warning, "Missing source file."); *output = Content::CloneAssetFile(dstPath, srcPath, dstId);
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;
}
return false; return false;
} }
};
// Check if destination file is missing bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
if (!FileSystem::FileExists(dstPath)) {
// Best to run this on the main thread to avoid clone conflicts.
if (IsInMainThread())
{ {
// Use quick file copy PROFILE_CPU();
if (FileSystem::CopyFile(dstPath, srcPath)) 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; return true;
} }
// Change ID // Special case for json resources
auto storage = ContentStorageManager::GetStorage(dstPath); if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
FlaxStorage::Entry e;
storage->GetEntry(0, e);
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
{ {
LOG(Warning, "Cannot change asset ID."); if (FileSystem::CopyFile(dstPath, srcPath))
return true; {
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 else
{ {
// Use temporary file CloneAssetFileTask* task = New<CloneAssetFileTask>();
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D); task->dstId = dstId;
if (FileSystem::CopyFile(tmpPath, srcPath)) task->dstPath = dstPath;
{ task->srcPath = srcPath;
LOG(Warning, "Cannot copy file.");
return true;
}
// Change asset ID bool result = false;
{ task->output = &result;
auto storage = ContentStorageManager::GetStorage(tmpPath); task->Start();
if (!storage) task->Wait();
{
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 return result;
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 false; return false;

View File

@@ -31,12 +31,11 @@ public:
if (Asset) if (Asset)
{ {
Asset->Locker.Lock(); Asset->Locker.Lock();
if (Asset->_loadingTask == this) if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
{ {
Asset->_loadFailed = true; Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
Asset->_isLoaded = false; Platform::AtomicStore(&Asset->_loadingTask, 0);
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed)); LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
Asset->_loadingTask = nullptr;
} }
Asset->Locker.Unlock(); Asset->Locker.Unlock();
} }
@@ -77,8 +76,8 @@ protected:
if (Asset) if (Asset)
{ {
Asset->Locker.Lock(); Asset->Locker.Lock();
if (Asset->_loadingTask == this) if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
Asset->_loadingTask = nullptr; Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock(); Asset->Locker.Unlock();
Asset = nullptr; Asset = nullptr;
} }
@@ -91,8 +90,8 @@ protected:
if (Asset) if (Asset)
{ {
Asset->Locker.Lock(); Asset->Locker.Lock();
if (Asset->_loadingTask == this) if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
Asset->_loadingTask = nullptr; Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock(); Asset->Locker.Unlock();
Asset = nullptr; Asset = nullptr;
} }

View File

@@ -413,6 +413,7 @@ bool AssetsImportingManagerService::Init()
{ TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import }, { TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import }, { TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import }, { TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("exr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
// IES Profiles // IES Profiles
{ TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES }, { TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES },

View File

@@ -15,6 +15,7 @@
#include "Engine/Content/Storage/ContentStorageManager.h" #include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h" #include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Animations/AnimEvent.h"
#include "Engine/Level/Actors/EmptyActor.h" #include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/Prefabs/Prefab.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) void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
{ {
Array<int32> materialSlotsTable; Array<int32> materialSlotsTable;
@@ -458,10 +417,62 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
data = &dataThis; data = &dataThis;
} }
// Check if restore materials on model reimport // Check if restore local changes on asset reimport
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems()) 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 // 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

View File

@@ -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: public:
/// <summary> /// <summary>
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection). /// 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> /// <summary>
/// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection). /// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection).
/// </summary> /// </summary>

View File

@@ -14,6 +14,8 @@
#define FORCE_INLINE inline #define FORCE_INLINE inline
#define FORCE_NOINLINE __attribute__((noinline)) #define FORCE_NOINLINE __attribute__((noinline))
#define NO_RETURN __attribute__((noreturn)) #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_BEGIN()
#define PACK_END() __attribute__((__packed__)) #define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align) #define ALIGN_BEGIN(_align)
@@ -44,6 +46,8 @@
#define FORCE_INLINE inline #define FORCE_INLINE inline
#define FORCE_NOINLINE __attribute__((noinline)) #define FORCE_NOINLINE __attribute__((noinline))
#define NO_RETURN __attribute__((noreturn)) #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_BEGIN()
#define PACK_END() __attribute__((__packed__)) #define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align) #define ALIGN_BEGIN(_align)
@@ -69,6 +73,8 @@
#define FORCE_INLINE __forceinline #define FORCE_INLINE __forceinline
#define FORCE_NOINLINE __declspec(noinline) #define FORCE_NOINLINE __declspec(noinline)
#define NO_RETURN __declspec(noreturn) #define NO_RETURN __declspec(noreturn)
#define NO_SANITIZE_ADDRESS
#define NO_SANITIZE_THREAD
#define PACK_BEGIN() __pragma(pack(push, 1)) #define PACK_BEGIN() __pragma(pack(push, 1))
#define PACK_END() ; __pragma(pack(pop)) #define PACK_END() ; __pragma(pack(pop))
#define ALIGN_BEGIN(_align) __declspec(align(_align)) #define ALIGN_BEGIN(_align) __declspec(align(_align))

View File

@@ -94,4 +94,15 @@ namespace Utilities
return (x * 0x01010101) >> 24; return (x * 0x01010101) >> 24;
#endif #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
}
} }

View File

@@ -129,7 +129,7 @@ PACK_STRUCT(struct Data {
Matrix ViewProjection; Matrix ViewProjection;
Float2 Padding; Float2 Padding;
float ClipPosZBias; float ClipPosZBias;
bool EnableDepthTest; uint32 EnableDepthTest;
}); });
struct PsData struct PsData

View File

@@ -2,6 +2,7 @@
#include "CommandLine.h" #include "CommandLine.h"
#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Utilities.h"
#include <iostream> #include <iostream>
CommandLine::OptionsData CommandLine::Options; CommandLine::OptionsData CommandLine::Options;
@@ -81,7 +82,7 @@ bool CommandLine::Parse(const Char* cmdLine)
if (pos) \ if (pos) \
{ \ { \
len = ARRAY_COUNT(text) - 1; \ 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) = 0; \
end -= len; \ end -= len; \
Options.field = true; \ Options.field = true; \
@@ -98,7 +99,7 @@ bool CommandLine::Parse(const Char* cmdLine)
} \ } \
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \ Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
len = static_cast<int32>((argEnd - pos) + 1); \ 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) = 0; \
end -= len; \ end -= len; \
} }
@@ -114,7 +115,7 @@ bool CommandLine::Parse(const Char* cmdLine)
{ \ { \
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \ Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
len = static_cast<int32>((argEnd - pos) + 1); \ 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) = 0; \
end -= len; \ end -= len; \
} \ } \

View File

@@ -324,10 +324,12 @@ void Engine::OnUpdate()
// Call event // Call event
Update(); Update();
UpdateGraph->Execute();
// Update services // Update services
EngineService::OnUpdate(); EngineService::OnUpdate();
// Run async
UpdateGraph->Execute();
} }
void Engine::OnLateUpdate() void Engine::OnLateUpdate()

View File

@@ -221,9 +221,7 @@ Asset::LoadResult GameplayGlobals::load()
// Get data // Get data
const auto chunk = GetChunk(0); const auto chunk = GetChunk(0);
if (!chunk || !chunk->IsLoaded()) if (!chunk || !chunk->IsLoaded())
{
return LoadResult::MissingDataChunk; return LoadResult::MissingDataChunk;
}
MemoryReadStream stream(chunk->Get(), chunk->Size()); MemoryReadStream stream(chunk->Get(), chunk->Size());
// Load all variables // Load all variables
@@ -234,15 +232,16 @@ Asset::LoadResult GameplayGlobals::load()
for (int32 i = 0; i < count; i++) for (int32 i = 0; i < count; i++)
{ {
stream.ReadString(&name, 71); stream.ReadString(&name, 71);
if (name.IsEmpty())
{
LOG(Warning, "Empty variable name");
return LoadResult::InvalidData;
}
auto& e = Variables[name]; auto& e = Variables[name];
stream.ReadVariant(&e.DefaultValue); stream.ReadVariant(&e.DefaultValue);
e.Value = e.DefaultValue; e.Value = e.DefaultValue;
} }
if (stream.HasError())
{
// Failed to load data
Variables.Clear();
return LoadResult::InvalidData;
}
return LoadResult::Ok; return LoadResult::Ok;
} }

View File

@@ -115,7 +115,7 @@ public:
// Rollback state and cancel // Rollback state and cancel
_context = nullptr; _context = nullptr;
_state = TaskState::Queued; SetState(TaskState::Queued);
Cancel(); Cancel();
} }
@@ -148,8 +148,7 @@ protected:
ASSERT(_context != nullptr); ASSERT(_context != nullptr);
_context->OnCancelSync(this); _context->OnCancelSync(this);
_context = nullptr; _context = nullptr;
SetState(TaskState::Canceled);
_state = TaskState::Canceled;
} }
else else
{ {

View File

@@ -9,9 +9,8 @@
void GPUTask::Execute(GPUTasksContext* context) void GPUTask::Execute(GPUTasksContext* context)
{ {
// Begin
ASSERT(IsQueued() && _context == nullptr); ASSERT(IsQueued() && _context == nullptr);
_state = TaskState::Running; SetState(TaskState::Running);
// Perform an operation // Perform an operation
const auto result = run(context); const auto result = run(context);
@@ -19,7 +18,7 @@ void GPUTask::Execute(GPUTasksContext* context)
// Process result // Process result
if (IsCancelRequested()) if (IsCancelRequested())
{ {
_state = TaskState::Canceled; SetState(TaskState::Canceled);
} }
else if (result != Result::Ok) else if (result != Result::Ok)
{ {

View File

@@ -941,7 +941,19 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
} }
// Animation events // 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 // Nested animations
stream->WriteInt32(0); stream->WriteInt32(0);

View File

@@ -13,4 +13,12 @@ namespace FlaxEngine
Bit = bit; Bit = bit;
} }
} }
public partial struct AntiAliasingSettings
{
/// <summary>
/// Whether or not to show the TAA settings.
/// </summary>
public bool ShowTAASettings => (Mode == AntialiasingMode.TemporalAntialiasing);
}
} }

View File

@@ -1888,25 +1888,25 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
/// <summary> /// <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. /// 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> /// </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; float TAA_JitterSpread = 1.0f;
/// <summary> /// <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. /// 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> /// </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; float TAA_Sharpness = 0.1f;
/// <summary> /// <summary>
/// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion. /// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
/// </summary> /// </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; float TAA_StationaryBlending = 0.95f;
/// <summary> /// <summary>
/// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion. /// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
/// </summary> /// </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; float TAA_MotionBlending = 0.85f;
public: public:

View File

@@ -10,6 +10,7 @@
#include "GPUTimerQueryVulkan.h" #include "GPUTimerQueryVulkan.h"
#endif #endif
#include "DescriptorSetVulkan.h" #include "DescriptorSetVulkan.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore) void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore)
@@ -32,7 +33,7 @@ void CmdBufferVulkan::Begin()
// Acquire a descriptor pool set on // Acquire a descriptor pool set on
if (_descriptorPoolSetContainer == nullptr) if (_descriptorPoolSetContainer == nullptr)
{ {
_descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer(); _descriptorPoolSetContainer = _device->DescriptorPoolsManager->AcquirePoolSetContainer();
} }
_state = State::IsInsideBegin; _state = State::IsInsideBegin;
@@ -138,7 +139,7 @@ void CmdBufferVulkan::RefreshFenceStatus()
if (_descriptorPoolSetContainer) if (_descriptorPoolSetContainer)
{ {
_device->DescriptorPoolsManager->ReleasePoolSet(*_descriptorPoolSetContainer); _descriptorPoolSetContainer->LastFrameUsed = Engine::FrameCount;
_descriptorPoolSetContainer = nullptr; _descriptorPoolSetContainer = nullptr;
} }
} }
@@ -279,6 +280,7 @@ void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float
void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer() void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
{ {
PROFILE_CPU(); PROFILE_CPU();
ASSERT_LOW_LAYER(_activeCmdBuffer == nullptr)
for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++) for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++)
{ {
auto cmdBuffer = _pool._cmdBuffers.Get()[i]; auto cmdBuffer = _pool._cmdBuffers.Get()[i];
@@ -286,8 +288,7 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin) if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin)
{ {
_activeCmdBuffer = cmdBuffer; _activeCmdBuffer = cmdBuffer;
_activeCmdBuffer->Begin(); break;
return;
} }
else else
{ {
@@ -295,8 +296,12 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
} }
} }
// Always begin fresh command buffer for rendering if (_activeCmdBuffer == nullptr)
_activeCmdBuffer = _pool.Create(); {
// Always begin fresh command buffer for rendering
_activeCmdBuffer = _pool.Create();
}
_activeCmdBuffer->Begin(); _activeCmdBuffer->Begin();
#if VULKAN_USE_QUERIES #if VULKAN_USE_QUERIES

View File

@@ -247,8 +247,7 @@ void TypedDescriptorPoolSetVulkan::Reset()
DescriptorPoolSetContainerVulkan::DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device) DescriptorPoolSetContainerVulkan::DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device)
: _device(device) : _device(device)
, _lastFrameUsed(Engine::FrameCount) , LastFrameUsed(Engine::FrameCount)
, _used(true)
{ {
} }
@@ -278,12 +277,6 @@ void DescriptorPoolSetContainerVulkan::Reset()
} }
} }
void DescriptorPoolSetContainerVulkan::SetUsed(bool used)
{
_used = used;
_lastFrameUsed = used ? Engine::FrameCount : _lastFrameUsed;
}
DescriptorPoolsManagerVulkan::DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device) DescriptorPoolsManagerVulkan::DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device)
: _device(device) : _device(device)
{ {
@@ -294,26 +287,21 @@ DescriptorPoolsManagerVulkan::~DescriptorPoolsManagerVulkan()
_poolSets.ClearDelete(); _poolSets.ClearDelete();
} }
DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetContainer() DescriptorPoolSetContainerVulkan* DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
{ {
ScopeLock lock(_locker); ScopeLock lock(_locker);
for (auto* poolSet : _poolSets) for (auto* poolSet : _poolSets)
{ {
if (poolSet->IsUnused()) if (poolSet->Refs == 0)
{ {
poolSet->SetUsed(true); poolSet->LastFrameUsed = Engine::FrameCount;
poolSet->Reset(); poolSet->Reset();
return *poolSet; return poolSet;
} }
} }
const auto poolSet = New<DescriptorPoolSetContainerVulkan>(_device); const auto poolSet = New<DescriptorPoolSetContainerVulkan>(_device);
_poolSets.Add(poolSet); _poolSets.Add(poolSet);
return *poolSet; return poolSet;
}
void DescriptorPoolsManagerVulkan::ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet)
{
poolSet.SetUsed(false);
} }
void DescriptorPoolsManagerVulkan::GC() void DescriptorPoolsManagerVulkan::GC()
@@ -322,7 +310,7 @@ void DescriptorPoolsManagerVulkan::GC()
for (int32 i = _poolSets.Count() - 1; i >= 0; i--) for (int32 i = _poolSets.Count() - 1; i >= 0; i--)
{ {
const auto poolSet = _poolSets[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); _poolSets.RemoveAt(i);
Delete(poolSet); Delete(poolSet);

View File

@@ -212,8 +212,6 @@ class DescriptorPoolSetContainerVulkan
private: private:
GPUDeviceVulkan* _device; GPUDeviceVulkan* _device;
Dictionary<uint32, TypedDescriptorPoolSetVulkan*> _typedDescriptorPools; Dictionary<uint32, TypedDescriptorPoolSetVulkan*> _typedDescriptorPools;
uint64 _lastFrameUsed;
bool _used;
public: public:
DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device); DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device);
@@ -222,17 +220,9 @@ public:
public: public:
TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout); TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout);
void Reset(); void Reset();
void SetUsed(bool used);
bool IsUnused() const mutable uint64 Refs = 0;
{ mutable uint64 LastFrameUsed;
return !_used;
}
uint64 GetLastFrameUsed() const
{
return _lastFrameUsed;
}
}; };
class DescriptorPoolsManagerVulkan class DescriptorPoolsManagerVulkan
@@ -246,8 +236,7 @@ public:
DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device); DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device);
~DescriptorPoolsManagerVulkan(); ~DescriptorPoolsManagerVulkan();
DescriptorPoolSetContainerVulkan& AcquirePoolSetContainer(); DescriptorPoolSetContainerVulkan* AcquirePoolSetContainer();
void ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet);
void GC(); void GC();
}; };

View File

@@ -112,7 +112,11 @@ ComputePipelineStateVulkan::ComputePipelineStateVulkan(GPUDeviceVulkan* device,
ComputePipelineStateVulkan::~ComputePipelineStateVulkan() ComputePipelineStateVulkan::~ComputePipelineStateVulkan()
{ {
DSWriteContainer.Release(); DSWriteContainer.Release();
CurrentTypedDescriptorPoolSet = nullptr; if (CurrentTypedDescriptorPoolSet)
{
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = nullptr;
}
DescriptorSetsLayout = nullptr; DescriptorSetsLayout = nullptr;
DescriptorSetHandles.Resize(0); DescriptorSetHandles.Resize(0);
DynamicOffsets.Resize(0); DynamicOffsets.Resize(0);
@@ -206,7 +210,11 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass)
void GPUPipelineStateVulkan::OnReleaseGPU() void GPUPipelineStateVulkan::OnReleaseGPU()
{ {
DSWriteContainer.Release(); DSWriteContainer.Release();
CurrentTypedDescriptorPoolSet = nullptr; if (CurrentTypedDescriptorPoolSet)
{
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = nullptr;
}
DescriptorSetsLayout = nullptr; DescriptorSetsLayout = nullptr;
DescriptorSetHandles.Resize(0); DescriptorSetHandles.Resize(0);
DynamicOffsets.Resize(0); DynamicOffsets.Resize(0);

View File

@@ -41,17 +41,17 @@ public:
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet(); DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet) if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
{ {
ASSERT(cmdBufferPoolSet); if (CurrentTypedDescriptorPoolSet)
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout); CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
return true; return true;
} }
return false; return false;
} }
inline bool AllocateDescriptorSets() inline bool AllocateDescriptorSets()
{ {
ASSERT(CurrentTypedDescriptorPoolSet);
return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get()); return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get());
} }
@@ -165,7 +165,10 @@ public:
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet(); DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet) if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
{ {
if (CurrentTypedDescriptorPoolSet)
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout); CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
return true; return true;
} }
return false; return false;

View File

@@ -7,6 +7,8 @@
#include "Engine/Animations/Animations.h" #include "Engine/Animations/Animations.h"
#include "Engine/Engine/Engine.h" #include "Engine/Engine/Engine.h"
#if USE_EDITOR #if USE_EDITOR
#include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Core/Math/Matrix3x3.h"
#include "Editor/Editor.h" #include "Editor/Editor.h"
#endif #endif
#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUContext.h"
@@ -1018,6 +1020,45 @@ void AnimatedModel::OnDebugDrawSelected()
ModelInstanceActor::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 BoundingBox AnimatedModel::GetEditorBox() const
{ {
if (SkinnedModel) if (SkinnedModel)

View File

@@ -168,6 +168,13 @@ public:
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")") API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
ScriptingObjectReference<Actor> RootMotionTarget; 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: public:
/// <summary> /// <summary>
/// The graph instance data container. For dynamic usage only at runtime, not serialized. /// The graph instance data container. For dynamic usage only at runtime, not serialized.
@@ -416,6 +423,7 @@ public:
void Draw(RenderContextBatch& renderContextBatch) override; void Draw(RenderContextBatch& renderContextBatch) override;
#if USE_EDITOR #if USE_EDITOR
void OnDebugDrawSelected() override; void OnDebugDrawSelected() override;
void OnDebugDraw() override;
BoundingBox GetEditorBox() const override; BoundingBox GetEditorBox() const override;
#endif #endif
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;

View File

@@ -105,10 +105,8 @@ void NavMesh::OnDataAssetLoaded()
if (Data.Tiles.HasItems()) if (Data.Tiles.HasItems())
return; return;
const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy();
// Remove added tiles // Remove added tiles
if (isEnabled) if (_navMeshActive)
{ {
RemoveTiles(); RemoveTiles();
} }
@@ -120,7 +118,7 @@ void NavMesh::OnDataAssetLoaded()
IsDataDirty = false; IsDataDirty = false;
// Add loaded tiles // Add loaded tiles
if (isEnabled) if (_navMeshActive)
{ {
AddTiles(); AddTiles();
} }
@@ -156,15 +154,36 @@ void NavMesh::OnEnable()
// Base // Base
Actor::OnEnable(); Actor::OnEnable();
GetScene()->Navigation.Meshes.Add(this); if (!_navMeshActive)
AddTiles(); {
GetScene()->Navigation.Meshes.Add(this);
AddTiles();
_navMeshActive = true;
}
} }
void NavMesh::OnDisable() void NavMesh::OnDisable()
{ {
RemoveTiles(); if (_navMeshActive)
GetScene()->Navigation.Meshes.Remove(this); {
RemoveTiles();
GetScene()->Navigation.Meshes.Remove(this);
_navMeshActive = false;
}
// Base // Base
Actor::OnDisable(); Actor::OnDisable();
} }
void NavMesh::Initialize()
{
// Base
Actor::Initialize();
if (!_navMeshActive && IsActiveInHierarchy())
{
GetScene()->Navigation.Meshes.Add(this);
AddTiles();
_navMeshActive = true;
}
}

View File

@@ -68,6 +68,9 @@ private:
void RemoveTiles(); void RemoveTiles();
void OnDataAssetLoaded(); void OnDataAssetLoaded();
private:
bool _navMeshActive = false;
public: public:
// [Actor] // [Actor]
void Serialize(SerializeStream& stream, const void* otherObj) override; void Serialize(SerializeStream& stream, const void* otherObj) override;
@@ -77,4 +80,5 @@ protected:
// [Actor] // [Actor]
void OnEnable() override; void OnEnable() override;
void OnDisable() override; void OnDisable() override;
void Initialize() override;
}; };

View File

@@ -55,13 +55,13 @@ public:
{ {
return __sync_fetch_and_add(dst, value); 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; int32 result;
__atomic_load(dst, &result, __ATOMIC_RELAXED); __atomic_load(dst, &result, __ATOMIC_RELAXED);
return result; return result;
} }
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst) FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{ {
int64 result; int64 result;
__atomic_load(dst, &result, __ATOMIC_RELAXED); __atomic_load(dst, &result, __ATOMIC_RELAXED);

View File

@@ -45,21 +45,21 @@ public:
{ {
return __sync_fetch_and_add(dst, value); 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); 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); return __atomic_load_n(dst, __ATOMIC_RELAXED);
} }
FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value) 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) 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) FORCE_INLINE static void Prefetch(void const* ptr)
{ {

View File

@@ -270,14 +270,14 @@ public:
/// </summary> /// </summary>
/// <param name="dst">A pointer to the destination value.</param> /// <param name="dst">A pointer to the destination value.</param>
/// <returns>The function returns the value of the destination parameter.</returns> /// <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> /// <summary>
/// Performs an atomic 64-bit variable read operation on the specified values. /// Performs an atomic 64-bit variable read operation on the specified values.
/// </summary> /// </summary>
/// <param name="dst">A pointer to the destination value.</param> /// <param name="dst">A pointer to the destination value.</param>
/// <returns>The function returns the value of the destination parameter.</returns> /// <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> /// <summary>
/// Sets a 32-bit variable to the specified value as an atomic operation. /// Sets a 32-bit variable to the specified value as an atomic operation.

View File

@@ -69,13 +69,13 @@ public:
{ {
return __sync_fetch_and_add(dst, value); 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; int32 result;
__atomic_load(dst, &result, __ATOMIC_SEQ_CST); __atomic_load(dst, &result, __ATOMIC_SEQ_CST);
return result; return result;
} }
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst) FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{ {
int64 result; int64 result;
__atomic_load(dst, &result, __ATOMIC_SEQ_CST); __atomic_load(dst, &result, __ATOMIC_SEQ_CST);

View File

@@ -57,7 +57,7 @@ public:
/// <summary> /// <summary>
/// Locks the critical section. /// Locks the critical section.
/// </summary> /// </summary>
void Lock() const NO_SANITIZE_THREAD void Lock() const
{ {
pthread_mutex_lock(_mutexPtr); pthread_mutex_lock(_mutexPtr);
#if BUILD_DEBUG #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. /// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
/// </summary> /// </summary>
/// <returns>True if calling thread took ownership of the critical section.</returns> /// <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; return pthread_mutex_trylock(_mutexPtr) == 0;
} }
@@ -77,7 +77,7 @@ public:
/// <summary> /// <summary>
/// Releases the lock on the critical section. /// Releases the lock on the critical section.
/// </summary> /// </summary>
void Unlock() const NO_SANITIZE_THREAD void Unlock() const
{ {
#if BUILD_DEBUG #if BUILD_DEBUG
((UnixCriticalSection*)this)->_owningThreadId = 0; ((UnixCriticalSection*)this)->_owningThreadId = 0;

View File

@@ -63,13 +63,13 @@ public:
return _interlockedexchangeadd64(dst, value); return _interlockedexchangeadd64(dst, value);
#endif #endif
} }
static int32 AtomicRead(int32 volatile* dst) static int32 AtomicRead(int32 const volatile* dst)
{ {
return (int32)_InterlockedCompareExchange((long volatile*)dst, 0, 0); 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) static void AtomicStore(int32 volatile* dst, int32 value)
{ {

View File

@@ -27,6 +27,7 @@ namespace
// Cached data for the draw calls sorting // Cached data for the draw calls sorting
Array<uint64> SortingKeys[2]; Array<uint64> SortingKeys[2];
Array<int32> SortingIndices; Array<int32> SortingIndices;
Array<DrawBatch> SortingBatches;
Array<RenderList*> FreeRenderList; Array<RenderList*> FreeRenderList;
struct MemPoolEntry 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(); PROFILE_CPU();
const auto* drawCallsData = drawCalls.Get(); const auto* drawCallsData = drawCalls.Get();
const auto* listData = list.Indices.Get(); const auto* listData = list.Indices.Get();
const int32 listSize = list.Indices.Count(); const int32 listSize = list.Indices.Count();
ZoneValue(listSize);
// Peek shared memory // Peek shared memory
#define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize) #define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize)
@@ -656,6 +658,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
} }
DrawBatch batch; DrawBatch batch;
static_assert(sizeof(DrawBatch) == sizeof(uint64) * 2, "Fix the size of draw batch to optimize memory access.");
batch.SortKey = sortedKeys[i]; batch.SortKey = sortedKeys[i];
batch.StartIndex = i; batch.StartIndex = i;
batch.BatchSize = batchSize; batch.BatchSize = batchSize;
@@ -665,8 +668,12 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
i += batchSize; i += batchSize;
} }
// Sort draw calls batches by depth // When using depth buffer draw calls are already almost ideally sorted by Radix Sort but transparency needs more stability to prevent flickering
Sorting::QuickSort(list.Batches); if (stable)
{
// Sort draw calls batches by depth
Sorting::MergeSort(list.Batches, &SortingBatches);
}
} }
FORCE_INLINE bool CanUseInstancing(DrawPass pass) FORCE_INLINE bool CanUseInstancing(DrawPass pass)

View File

@@ -217,17 +217,17 @@ struct DrawBatch
/// <summary> /// <summary>
/// The first draw call index. /// The first draw call index.
/// </summary> /// </summary>
int32 StartIndex; uint16 StartIndex;
/// <summary> /// <summary>
/// A number of draw calls to be submitted at once. /// A number of draw calls to be submitted at once.
/// </summary> /// </summary>
int32 BatchSize; uint16 BatchSize;
/// <summary> /// <summary>
/// The total amount of instances (sum from all draw calls in this batch). /// The total amount of instances (sum from all draw calls in this batch).
/// </summary> /// </summary>
int32 InstanceCount; uint32 InstanceCount;
bool operator<(const DrawBatch& other) const bool operator<(const DrawBatch& other) const
{ {
@@ -525,7 +525,8 @@ public:
/// <param name="pass">The draw pass (optional).</param> /// <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) 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> /// <summary>
@@ -536,7 +537,8 @@ public:
/// <param name="list">The collected draw calls indices list.</param> /// <param name="list">The collected draw calls indices list.</param>
/// <param name="drawCalls">The collected draw calls list.</param> /// <param name="drawCalls">The collected draw calls list.</param>
/// <param name="pass">The draw pass (optional).</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> /// <summary>
/// Executes the collected draw calls. /// Executes the collected draw calls.

View File

@@ -3,6 +3,7 @@
#include "BinaryModule.h" #include "BinaryModule.h"
#include "ScriptingObject.h" #include "ScriptingObject.h"
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "ManagedCLR/MAssembly.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) void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
{ {
// Analyze vtable size // Analyze vtable size
@@ -475,7 +477,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
// Duplicate vtable // Duplicate vtable
Script.VTable = (void**)((byte*)Platform::Allocate(totalSize, 16) + prefixSize); 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 // Override vtable entries
if (interfacesCount) if (interfacesCount)
@@ -508,7 +510,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
const int32 interfaceSize = interfaceCount * sizeof(void*); const int32 interfaceSize = interfaceCount * sizeof(void*);
// Duplicate interface vtable // 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 // Override interface vtable entries
const auto scriptOffset = interfaces->ScriptVTableOffset; const auto scriptOffset = interfaces->ScriptVTableOffset;

View File

@@ -497,7 +497,9 @@ void ReadStream::Read(Variant& data)
break; break;
} }
default: 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); 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) void WriteStream::WriteString(const StringView& data)
{ {
Write(data); Write(data);

View File

@@ -233,6 +233,7 @@ public:
/// <param name="obj">The object to serialize.</param> /// <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> /// <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(ISerializable* obj, const void* otherObj = nullptr);
void WriteJson(const StringAnsiView& json);
public: public:
// Writes String to the stream // Writes String to the stream

View File

@@ -11,13 +11,13 @@
void Task::Start() void Task::Start()
{ {
if (_state != TaskState::Created) if (GetState() != TaskState::Created)
return; return;
OnStart(); OnStart();
// Change state // Change state
_state = TaskState::Queued; SetState(TaskState::Queued);
// Add task to the execution queue // Add task to the execution queue
Enqueue(); Enqueue();
@@ -110,7 +110,6 @@ Task* Task::ContinueWith(const Function<bool()>& action, Object* target)
Task* Task::StartNew(Task* task) Task* Task::StartNew(Task* task)
{ {
ASSERT(task); ASSERT(task);
task->Start(); task->Start();
return task; return task;
} }
@@ -137,11 +136,10 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
void Task::Execute() void Task::Execute()
{ {
// Begin
if (IsCanceled()) if (IsCanceled())
return; return;
ASSERT(IsQueued()); ASSERT(IsQueued());
_state = TaskState::Running; SetState(TaskState::Running);
// Perform an operation // Perform an operation
bool failed = Run(); bool failed = Run();
@@ -149,7 +147,7 @@ void Task::Execute()
// Process result // Process result
if (IsCancelRequested()) if (IsCancelRequested())
{ {
_state = TaskState::Canceled; SetState(TaskState::Canceled);
} }
else if (failed) else if (failed)
{ {
@@ -167,10 +165,8 @@ void Task::OnStart()
void Task::OnFinish() void Task::OnFinish()
{ {
ASSERT(IsRunning()); ASSERT(IsRunning() && !IsCancelRequested());
ASSERT(!IsCancelRequested()); SetState(TaskState::Finished);
_state = TaskState::Finished;
// Send event further // Send event further
if (_continueWith) if (_continueWith)
@@ -181,7 +177,7 @@ void Task::OnFinish()
void Task::OnFail() void Task::OnFail()
{ {
_state = TaskState::Failed; SetState(TaskState::Failed);
// Send event further // Send event further
if (_continueWith) if (_continueWith)
@@ -209,8 +205,7 @@ void Task::OnCancel()
const auto state = GetState(); const auto state = GetState();
if (state != TaskState::Finished && state != TaskState::Failed) if (state != TaskState::Finished && state != TaskState::Failed)
{ {
_state = TaskState::Canceled; SetState(TaskState::Canceled);
OnEnd(); OnEnd();
} }
} }

View File

@@ -49,7 +49,6 @@ class FLAXENGINE_API Task : public Object, public NonCopyable
// //
protected: protected:
/// <summary> /// <summary>
/// The cancel flag used to indicate that there is request to cancel task operation. /// The cancel flag used to indicate that there is request to cancel task operation.
/// </summary> /// </summary>
@@ -65,14 +64,18 @@ protected:
/// </summary> /// </summary>
Task* _continueWith = nullptr; Task* _continueWith = nullptr;
public: void SetState(TaskState state)
{
Platform::AtomicStore((int64 volatile*)&_state, (uint64)state);
}
public:
/// <summary> /// <summary>
/// Gets the task state. /// Gets the task state.
/// </summary> /// </summary>
FORCE_INLINE TaskState GetState() const FORCE_INLINE TaskState GetState() const
{ {
return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state)); return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state);
} }
/// <summary> /// <summary>
@@ -94,7 +97,6 @@ public:
} }
public: public:
/// <summary> /// <summary>
/// Checks if operation failed. /// Checks if operation failed.
/// </summary> /// </summary>
@@ -153,7 +155,6 @@ public:
} }
public: public:
/// <summary> /// <summary>
/// Starts this task execution (and will continue with all children). /// Starts this task execution (and will continue with all children).
/// </summary> /// </summary>
@@ -199,7 +200,6 @@ public:
} }
public: public:
/// <summary> /// <summary>
/// Continues that task execution with a given task (will call Start on given task after finishing that one). /// Continues that task execution with a given task (will call Start on given task after finishing that one).
/// </summary> /// </summary>
@@ -232,7 +232,6 @@ public:
Task* ContinueWith(const Function<bool()>& action, Object* target = nullptr); Task* ContinueWith(const Function<bool()>& action, Object* target = nullptr);
public: public:
/// <summary> /// <summary>
/// Starts the new task. /// Starts the new task.
/// </summary> /// </summary>
@@ -312,7 +311,6 @@ public:
} }
protected: protected:
/// <summary> /// <summary>
/// Executes this task. /// Executes this task.
/// It should be called by the task consumer (thread pool or other executor of this task type). /// 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; virtual bool Run() = 0;
protected: protected:
virtual void Enqueue() = 0; virtual void Enqueue() = 0;
virtual void OnStart(); virtual void OnStart();
virtual void OnFinish(); virtual void OnFinish();

View File

@@ -9,6 +9,7 @@
#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h" #include "Engine/Platform/File.h"
#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Utilities/AnsiPathTempFile.h"
// Import Assimp library // Import Assimp library
// Source: https://github.com/assimp/assimp // Source: https://github.com/assimp/assimp
@@ -157,7 +158,7 @@ struct AssimpImporterData
Array<AssimpBone> Bones; Array<AssimpBone> Bones;
Dictionary<int32, Array<int32>> MeshIndexToNodeIndex; Dictionary<int32, Array<int32>> MeshIndexToNodeIndex;
AssimpImporterData(const char* path, const ModelTool::Options& options) AssimpImporterData(const StringView& path, const ModelTool::Options& options)
: Path(path) : Path(path)
, Options(options) , 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; static bool AssimpInited = false;
if (!AssimpInited) 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); context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
// Import file // 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) if (context.Scene == nullptr)
{ {
LOG_STR(Warning, String(context.AssimpImporter.GetErrorString())); LOG_STR(Warning, String(context.AssimpImporter.GetErrorString()));

View File

@@ -816,7 +816,7 @@ void BakeTransforms(FbxScene* scene)
scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate, false); 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); ScopeLock lock(FbxSdkManager::Locker);
@@ -836,7 +836,7 @@ bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& da
auto ios = FbxSdkManager::Manager->GetIOSettings(); auto ios = FbxSdkManager::Manager->GetIOSettings();
ios->SetBoolProp(IMP_FBX_MODEL, importMeshes); ios->SetBoolProp(IMP_FBX_MODEL, importMeshes);
ios->SetBoolProp(IMP_FBX_ANIMATION, importAnimations); 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())); errorMsg = String::Format(TEXT("Failed to initialize FBX importer. {0}"), String(importer->GetStatus().GetErrorString()));
return false; return false;

View File

@@ -103,7 +103,7 @@ struct OpenFbxImporterData
Array<const ofbx::Material*> Materials; Array<const ofbx::Material*> Materials;
Array<MaterialSlotEntry> ImportedMaterials; 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) : Scene(scene)
, ScenePtr(scene) , ScenePtr(scene)
, Path(path) , Path(path)
@@ -1114,11 +1114,11 @@ static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
return { 0.f, 0.f, 0.f }; 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 // Import file
Array<byte> fileData; Array<byte> fileData;
if (File::ReadAllBytes(String(path), fileData)) if (File::ReadAllBytes(path, fileData))
{ {
errorMsg = TEXT("Cannot load file."); errorMsg = TEXT("Cannot load file.");
return true; return true;

View File

@@ -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 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? // 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 // Call importing backend
#if (USE_AUTODESK_FBX_SDK || USE_OPEN_FBX) && USE_ASSIMP #if (USE_AUTODESK_FBX_SDK || USE_OPEN_FBX) && USE_ASSIMP
if (path.EndsWith(TEXT(".fbx"), StringSearchCase::IgnoreCase)) if (path.EndsWith(TEXT(".fbx"), StringSearchCase::IgnoreCase))
{ {
#if USE_AUTODESK_FBX_SDK #if USE_AUTODESK_FBX_SDK
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg)) if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
return true; return true;
#elif USE_OPEN_FBX #elif USE_OPEN_FBX
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg)) if (ImportDataOpenFBX(path, data, options, errorMsg))
return true; return true;
#endif #endif
} }
else else
{ {
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg)) if (ImportDataAssimp(path, data, options, errorMsg))
return true; return true;
} }
#elif USE_ASSIMP #elif USE_ASSIMP
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg)) if (ImportDataAssimp(path, data, options, errorMsg))
return true; return true;
#elif USE_AUTODESK_FBX_SDK #elif USE_AUTODESK_FBX_SDK
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg)) if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
return true; return true;
#elif USE_OPEN_FBX #elif USE_OPEN_FBX
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg)) if (ImportDataOpenFBX(path, data, options, errorMsg))
return true; return true;
#else #else
LOG(Error, "Compiled without model importing backend."); LOG(Error, "Compiled without model importing backend.");
return true; return true;
#endif #endif
// Remove temporary file
if (tmpPath.HasChars() && FileSystem::FileExists(tmpPath))
{
FileSystem::DeleteFile(tmpPath);
}
// Remove namespace prefixes from the nodes names // Remove namespace prefixes from the nodes names
{ {
for (auto& node : data.Nodes) for (auto& node : data.Nodes)
@@ -1248,7 +1221,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
} }
// Apply the import transformation // Apply the import transformation
if (!importTransform.IsIdentity()) if (!importTransform.IsIdentity() && data.Nodes.HasItems())
{ {
if (options.Type == ModelType::SkinnedModel) if (options.Type == ModelType::SkinnedModel)
{ {

View File

@@ -386,13 +386,13 @@ public:
private: private:
static void CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex); static void CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex);
#if USE_ASSIMP #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 #endif
#if USE_AUTODESK_FBX_SDK #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 #endif
#if USE_OPEN_FBX #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
#endif #endif
}; };

View File

@@ -21,6 +21,7 @@ public class TextureTool : EngineModule
bool useDirectXTex = false; bool useDirectXTex = false;
bool useStb = false; bool useStb = false;
bool useExr = options.Target.IsEditor;
switch (options.Platform.Target) switch (options.Platform.Target)
{ {
@@ -58,6 +59,10 @@ public class TextureTool : EngineModule
options.PrivateDependencies.Add("bc7enc16"); options.PrivateDependencies.Add("bc7enc16");
} }
} }
if (useExr)
{
options.PrivateDependencies.Add("tinyexr");
}
if (options.Target.IsEditor && astc.IsSupported(options)) if (options.Target.IsEditor && astc.IsSupported(options))
{ {
// ASTC for mobile (iOS and Android) // ASTC for mobile (iOS and Android)

View File

@@ -15,6 +15,7 @@
#if USE_EDITOR #if USE_EDITOR
#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUDevice.h"
#endif #endif
#include "Engine/Utilities/AnsiPathTempFile.h"
// Import DirectXTex library // Import DirectXTex library
// Source: https://github.com/Microsoft/DirectXTex // Source: https://github.com/Microsoft/DirectXTex
@@ -24,6 +25,19 @@ DECLARE_HANDLE(HMONITOR);
#endif #endif
#include <ThirdParty/DirectXTex/DirectXTex.h> #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 namespace
{ {
FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format) FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format)
@@ -36,7 +50,7 @@ namespace
return static_cast<DXGI_FORMAT>(format); 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 USE_EDITOR
if ((format == DXGI_FORMAT_BC7_UNORM || format == DXGI_FORMAT_BC7_UNORM_SRGB || format == DXGI_FORMAT_BC6H_UF16 || format == DXGI_FORMAT_BC6H_SF16) && 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; size_t _nimages;
const DirectX::TexMetadata& _metadata; const DirectX::TexMetadata& _metadata;
DXGI_FORMAT _format; DXGI_FORMAT _format;
DWORD _compress; DirectX::TEX_COMPRESS_FLAGS _compress;
DirectX::ScratchImage& _cImages; DirectX::ScratchImage& _cImages;
public: public:
HRESULT CompressResult = E_FAIL; 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) : GPUTask(Type::Custom)
, _signal(&signal) , _signal(&signal)
, _srcImages(srcImages) , _srcImages(srcImages)
@@ -276,6 +290,46 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
return image.InitializeFromImage(img); 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) bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
{ {
// Load image data // Load image data
@@ -302,6 +356,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::RAW: case ImageType::RAW:
result = LoadFromRAWFile(path, image); result = LoadFromRAWFile(path, image);
break; break;
case ImageType::EXR:
result = LoadFromEXRFile(path, image);
break;
default: default:
result = DXGI_ERROR_INVALID_CALL; result = DXGI_ERROR_INVALID_CALL;
break; break;
@@ -518,6 +575,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::RAW: case ImageType::RAW:
result = LoadFromRAWFile(path, image1); result = LoadFromRAWFile(path, image1);
break; break;
case ImageType::EXR:
result = LoadFromEXRFile(path, image1);
break;
case ImageType::Internal: case ImageType::Internal:
{ {
if (options.InternalLoad.IsBinded()) if (options.InternalLoad.IsBinded())
@@ -688,7 +748,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (!keepAsIs && options.FlipY) if (!keepAsIs && options.FlipY)
{ {
auto& tmpImg = GET_TMP_IMG(); 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); result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
if (FAILED(result)) if (FAILED(result))
{ {
@@ -698,7 +758,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg); SET_CURRENT_IMG(tmpImg);
} }
// Check if it invert green channel // Check if invert green channel
if (!keepAsIs && options.InvertGreenChannel) if (!keepAsIs && options.InvertGreenChannel)
{ {
auto& timage = GET_TMP_IMG(); auto& timage = GET_TMP_IMG();

View File

@@ -767,6 +767,10 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type)
{ {
type = ImageType::RAW; type = ImageType::RAW;
} }
else if (extension == TEXT("exr"))
{
type = ImageType::EXR;
}
else else
{ {
LOG(Warning, "Unknown file type."); LOG(Warning, "Unknown file type.");

View File

@@ -252,6 +252,7 @@ private:
JPEG, JPEG,
HDR, HDR,
RAW, RAW,
EXR,
Internal, Internal,
}; };

View File

@@ -10,6 +10,7 @@
#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Utilities/AnsiPathTempFile.h"
#include "Engine/Platform/File.h" #include "Engine/Platform/File.h"
#define STBI_ASSERT(x) ASSERT(x) #define STBI_ASSERT(x) ASSERT(x)
@@ -48,6 +49,18 @@
// Compression libs for Editor // Compression libs for Editor
#include <ThirdParty/detex/detex.h> #include <ThirdParty/detex/detex.h>
#include <ThirdParty/bc7enc16/bc7enc16.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 #endif
static void stbWrite(void* context, void* data, int size) 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) 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; TextureData const* texture = &textureData;
@@ -189,7 +202,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
const auto sampler = GetSampler(texture->Format); const auto sampler = GetSampler(texture->Format);
if (sampler == nullptr) 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; return true;
} }
const auto srcData = texture->GetData(0, 0); const auto srcData = texture->GetData(0, 0);
@@ -272,16 +285,19 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
break; break;
} }
case ImageType::GIF: case ImageType::GIF:
LOG(Warning, "GIF format is not supported by stb library."); LOG(Warning, "GIF format is not supported.");
break; break;
case ImageType::TIFF: case ImageType::TIFF:
LOG(Warning, "GIF format is not supported by stb library."); LOG(Warning, "GIF format is not supported.");
break; break;
case ImageType::DDS: case ImageType::DDS:
LOG(Warning, "DDS format is not supported by stb library."); LOG(Warning, "DDS format is not supported.");
break; break;
case ImageType::RAW: 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; break;
default: default:
LOG(Warning, "Unknown format."); LOG(Warning, "Unknown format.");
@@ -383,11 +399,49 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
break; 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: case ImageType::DDS:
LOG(Warning, "DDS format is not supported by stb library."); LOG(Warning, "DDS format is not supported.");
break; break;
case ImageType::TIFF: case ImageType::TIFF:
LOG(Warning, "TIFF format is not supported by stb library."); LOG(Warning, "TIFF format is not supported.");
break; break;
default: default:
LOG(Warning, "Unknown format."); LOG(Warning, "Unknown format.");

View File

@@ -290,7 +290,7 @@ namespace FlaxEngine.GUI
{ {
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); 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); 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); float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha; alpha *= alpha;
Color selectionColor = Color.White * alpha; Color selectionColor = Color.White * alpha;
@@ -340,7 +340,7 @@ namespace FlaxEngine.GUI
if (textBlock.Style.UnderlineBrush != null) if (textBlock.Style.UnderlineBrush != null)
{ {
var underLineHeight = 2.0f; 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); 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); textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
} }

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

View File

@@ -47,6 +47,12 @@ namespace FlaxEngine.Utilities
/// True if this tag contained a leading or trailing forward slash. /// True if this tag contained a leading or trailing forward slash.
/// </summary> /// </summary>
public bool IsSlash => IsLeadingSlash || IsEndingSlash; public bool IsSlash => IsLeadingSlash || IsEndingSlash;
/// <inheritdoc />
public override string ToString()
{
return Name;
}
}; };
/// <summary> /// <summary>
@@ -231,6 +237,9 @@ namespace FlaxEngine.Utilities
tag.Attributes[s] = value; tag.Attributes[s] = value;
} }
} }
if (EOF)
return false;
} }
// Skip over closing '>' // Skip over closing '>'
@@ -264,8 +273,13 @@ namespace FlaxEngine.Utilities
private string ParseAttributeName() private string ParseAttributeName()
{ {
int start = _pos; int start = _pos;
while (!EOF && char.IsLetterOrDigit(Peek())) while (!EOF)
{
var c = Peek();
if (!char.IsLetterOrDigit(c) && c != '-')
break;
Move(); Move();
}
return _html.Substring(start, _pos - start); return _html.Substring(start, _pos - start);
} }

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// DirectXTex.inl // DirectXTex.inl
// //
// DirectX Texture Library // DirectX Texture Library
// //
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
// //
// http://go.microsoft.com/fwlink/?LinkId=248926 // http://go.microsoft.com/fwlink/?LinkId=248926
@@ -11,81 +11,109 @@
#pragma once #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 // DXGI Format Utilities
//===================================================================================== //=====================================================================================
_Use_decl_annotations_ _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_ _Use_decl_annotations_
inline bool __cdecl IsCompressed(DXGI_FORMAT fmt) inline bool __cdecl IsCompressed(DXGI_FORMAT fmt) noexcept
{ {
switch (fmt) switch (fmt)
{ {
case DXGI_FORMAT_BC1_TYPELESS: case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM: case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB: case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_TYPELESS: case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM: case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB: case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS: case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM: case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB: case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS: case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM: case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM: case DXGI_FORMAT_BC4_SNORM:
case DXGI_FORMAT_BC5_TYPELESS: case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM: case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM: case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS: case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16: case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16: case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS: case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM: case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB: case DXGI_FORMAT_BC7_UNORM_SRGB:
return true; return true;
default: default:
return false; return false;
} }
} }
_Use_decl_annotations_ _Use_decl_annotations_
inline bool __cdecl IsPalettized(DXGI_FORMAT fmt) inline bool __cdecl IsPalettized(DXGI_FORMAT fmt) noexcept
{ {
switch (fmt) switch (fmt)
{ {
case DXGI_FORMAT_AI44: case DXGI_FORMAT_AI44:
case DXGI_FORMAT_IA44: case DXGI_FORMAT_IA44:
case DXGI_FORMAT_P8: case DXGI_FORMAT_P8:
case DXGI_FORMAT_A8P8: case DXGI_FORMAT_A8P8:
return true; return true;
default: default:
return false; return false;
} }
} }
_Use_decl_annotations_ _Use_decl_annotations_
inline bool __cdecl IsSRGB(DXGI_FORMAT fmt) inline bool __cdecl IsSRGB(DXGI_FORMAT fmt) noexcept
{ {
switch (fmt) switch (fmt)
{ {
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI_FORMAT_BC1_UNORM_SRGB: case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_UNORM_SRGB: case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_UNORM_SRGB: case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
case DXGI_FORMAT_BC7_UNORM_SRGB: case DXGI_FORMAT_BC7_UNORM_SRGB:
return true; return true;
default: default:
return false; return false;
} }
} }
@@ -94,7 +122,7 @@ inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
// Image I/O // Image I/O
//===================================================================================== //=====================================================================================
_Use_decl_annotations_ _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 = {}; TexMetadata mdata = {};
mdata.width = image.width; mdata.width = image.width;
@@ -109,7 +137,7 @@ inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& bl
} }
_Use_decl_annotations_ _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 = {}; TexMetadata mdata = {};
mdata.width = image.width; 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); 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);
}

View File

@@ -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 Permission is hereby granted, free of charge, to any person obtaining a copy
software and associated documentation files (the "Software"), to deal in the Software of this software and associated documentation files (the "Software"), to deal
without restriction, including without limitation the rights to use, copy, modify, in the Software without restriction, including without limitation the rights
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
permit persons to whom the Software is furnished to do so, subject to the following copies of the Software, and to permit persons to whom the Software is
conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies The above copyright notice and this permission notice shall be included in all
or substantial portions of the Software. 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 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
View 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.

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -269,8 +269,9 @@ namespace Flax.Build
dotnetSdkVersions = MergeVersions(dotnetSdkVersions, GetVersions(Path.Combine(dotnetPath, "sdk"))); dotnetSdkVersions = MergeVersions(dotnetSdkVersions, GetVersions(Path.Combine(dotnetPath, "sdk")));
dotnetRuntimeVersions = MergeVersions(dotnetRuntimeVersions, GetVersions(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App"))); dotnetRuntimeVersions = MergeVersions(dotnetRuntimeVersions, GetVersions(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App")));
dotnetSdkVersions = dotnetSdkVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "sdk", x))); dotnetSdkVersions = dotnetSdkVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "sdk", x, ".version")));
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x))); 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); dotnetSdkVersions = dotnetSdkVersions.OrderByDescending(ParseVersion);
dotnetRuntimeVersions = dotnetRuntimeVersions.OrderByDescending(ParseVersion); dotnetRuntimeVersions = dotnetRuntimeVersions.OrderByDescending(ParseVersion);
@@ -543,11 +544,6 @@ namespace Flax.Build
return null; return null;
} }
private static bool IsValidVersion(string versionPath)
{
return File.Exists(Path.Combine(versionPath, ".version"));
}
private static string SearchForDotnetLocationLinux() private static string SearchForDotnetLocationLinux()
{ {
if (File.Exists("/etc/dotnet/install_location")) // Officially recommended dotnet location file if (File.Exists("/etc/dotnet/install_location")) // Officially recommended dotnet location file

View File

@@ -26,6 +26,38 @@ namespace Flax.Build.NativeCpp
SmallCode 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> /// <summary>
/// The compilation optimization hint. /// The compilation optimization hint.
/// </summary> /// </summary>
@@ -67,6 +99,11 @@ namespace Flax.Build.NativeCpp
/// </summary> /// </summary>
public FavorSizeOrSpeed FavorSizeOrSpeed = FavorSizeOrSpeed.Neither; public FavorSizeOrSpeed FavorSizeOrSpeed = FavorSizeOrSpeed.Neither;
/// <summary>
/// Selects a sanitizers to use (as flags).
/// </summary>
public Sanitizer Sanitizers = Sanitizer.None;
/// <summary> /// <summary>
/// Enables exceptions support. /// Enables exceptions support.
/// </summary> /// </summary>
@@ -184,6 +221,7 @@ namespace Flax.Build.NativeCpp
{ {
CppVersion = CppVersion, CppVersion = CppVersion,
FavorSizeOrSpeed = FavorSizeOrSpeed, FavorSizeOrSpeed = FavorSizeOrSpeed,
Sanitizers = Sanitizers,
EnableExceptions = EnableExceptions, EnableExceptions = EnableExceptions,
RuntimeTypeInfo = RuntimeTypeInfo, RuntimeTypeInfo = RuntimeTypeInfo,
Inlining = Inlining, Inlining = Inlining,

View File

@@ -249,6 +249,7 @@ namespace Flax.Build
} }
options.CompileEnv.EnableExceptions = true; // TODO: try to disable this! options.CompileEnv.EnableExceptions = true; // TODO: try to disable this!
options.CompileEnv.Sanitizers = Configuration.Sanitizers;
switch (options.Configuration) switch (options.Configuration)
{ {
case TargetConfiguration.Debug: case TargetConfiguration.Debug:

View File

@@ -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.")] [CommandLine("compiler", "<name>", "Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows.")]
public static string Compiler = null; 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> /// <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. /// 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> /// </summary>
@@ -242,6 +248,8 @@ namespace Flax.Build
cmdLine += " -compiler=" + Compiler; cmdLine += " -compiler=" + Compiler;
if (!string.IsNullOrEmpty(Dotnet)) if (!string.IsNullOrEmpty(Dotnet))
cmdLine += " -dotnet=" + Dotnet; cmdLine += " -dotnet=" + Dotnet;
if (Sanitizers != Flax.Build.NativeCpp.Sanitizer.None)
cmdLine += " -sanitizers=" + Sanitizers.ToString();
} }
} }

View File

@@ -44,7 +44,7 @@ namespace Flax.Deps.Dependencies
// Get the source // Get the source
CloneGitRepo(root, "https://github.com/Microsoft/DirectXTex.git"); CloneGitRepo(root, "https://github.com/Microsoft/DirectXTex.git");
GitCheckout(root, "master", "9a417f506c43e087b84c017260ad673abd6c64e1"); GitCheckout(root, "main", "5cfd711dc5d64cde1e8b27670036535df5c3f922");
foreach (var platform in options.Platforms) foreach (var platform in options.Platforms)
{ {
@@ -52,60 +52,39 @@ namespace Flax.Deps.Dependencies
{ {
case TargetPlatform.Windows: case TargetPlatform.Windows:
{ {
var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2015.sln"); var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2022.sln");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2015"); var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2022");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64"); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64");
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames) foreach (var file in outputFileNames)
{
Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file)); Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file));
}
break; break;
} }
case TargetPlatform.UWP: case TargetPlatform.UWP:
{ {
var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2017.sln"); var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2019.sln");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2017"); var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2019");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64"); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64");
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames) foreach (var file in outputFileNames)
{
Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file)); Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file));
}
break; break;
} }
case TargetPlatform.XboxOne: 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: case TargetPlatform.XboxScarlett:
{ {
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln"); var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2019.sln");
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true); File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.sln"), solutionPath, true);
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj")); var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.vcxproj"));
projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142"); 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); File.WriteAllText(projectPath, projectFileContents);
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017"); var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2019");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.Scarlett.x64"); 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); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames) foreach (var file in outputFileNames)
{ Utilities.FileCopy(Path.Combine(binFolder, xboxName, configuration, file), Path.Combine(depsFolder, file));
Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.Scarlett.x64", configuration, file), Path.Combine(depsFolder, file));
}
break; break;
} }
} }

View File

@@ -105,6 +105,7 @@ namespace Flax.Build.Platforms
commonArgs.Add("objective-c++"); commonArgs.Add("objective-c++");
commonArgs.Add("-stdlib=libc++"); commonArgs.Add("-stdlib=libc++");
AddArgsCommon(options, commonArgs); AddArgsCommon(options, commonArgs);
AddArgsSanitizer(compileEnvironment.Sanitizers, commonArgs);
switch (compileEnvironment.CppVersion) switch (compileEnvironment.CppVersion)
{ {
@@ -155,14 +156,24 @@ namespace Flax.Build.Platforms
commonArgs.Add("-pthread"); commonArgs.Add("-pthread");
if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode) if (compileEnvironment.Sanitizers.HasFlag(Sanitizer.Address))
commonArgs.Add("-Ofast"); {
else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode) commonArgs.Add("-fno-optimize-sibling-calls");
commonArgs.Add("-Os"); commonArgs.Add("-fno-omit-frame-pointer");
if (compileEnvironment.Optimization) if (compileEnvironment.Optimization)
commonArgs.Add("-O3"); commonArgs.Add("-O1");
}
else 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) if (compileEnvironment.BufferSecurityCheck)
commonArgs.Add("-fstack-protector"); commonArgs.Add("-fstack-protector");
@@ -240,6 +251,7 @@ namespace Flax.Build.Platforms
{ {
args.Add(string.Format("-o \"{0}\"", outputFilePath)); args.Add(string.Format("-o \"{0}\"", outputFilePath));
AddArgsCommon(options, args); AddArgsCommon(options, args);
AddArgsSanitizer(options.CompileEnv.Sanitizers, args);
if (isArchive) if (isArchive)
{ {
@@ -426,5 +438,15 @@ namespace Flax.Build.Platforms
break; 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");
}
} }
} }