diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index 3b786a7cd..abc444316 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -438,7 +438,6 @@ VertexOutput VS(TerrainVertexInput input) // Apply world position offset per-vertex #if USE_POSITION_OFFSET output.Geometry.WorldPosition += material.PositionOffset; - output.Geometry.PrevWorldPosition += material.PositionOffset; output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); #endif diff --git a/README.md b/README.md index 36c562006..8e9de0424 100644 --- a/README.md +++ b/README.md @@ -46,21 +46,26 @@ Follow the instructions below to compile and run the engine from source. * Install Visual Studio Code * Install .NET 8 or 9 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)) * Ubuntu: `sudo apt install dotnet-sdk-8.0` + * Fedora: `sudo dnf install dotnet-sdk-8.0` * Arch: `sudo pacman -S dotnet-sdk-8.0 dotnet-runtime-8.0 dotnet-targeting-pack-8.0 dotnet-host` * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Ubuntu: `sudo apt install vulkan-sdk` - * Arch: `sudo pacman -S spirv-tools vulkan-headers vulkan-tools vulkan-validation-layers` + * Fedora: `sudo dnf install vulkan-headers vulkan-tools vulkan-validation-layers` + * Arch: `sudo pacman -S vulkan-headers vulkan-tools vulkan-validation-layers` * Install Git with LFS * Ubuntu: `sudo apt-get install git git-lfs` * Arch: `sudo pacman -S git git-lfs` * `git-lfs install` * Install the required packages: * Ubuntu: `sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev zlib1g-dev` + * Fedora: `sudo dnf install libX11-devel libXcursor-devel libXinerama-devel ghc-zlib-devel` * Arch: `sudo pacman -S base-devel libx11 libxcursor libxinerama zlib` * Install Clang compiler (version 6 or later): * Ubuntu: `sudo apt-get install clang lldb lld` + * Fedora: `sudo dnf install clang llvm lldb lld` * Arch: `sudo pacman -S clang lldb lld` * Clone the repository (with LFS) + * git-lfs clone https://github.com/FlaxEngine/FlaxEngine.git * Run `./GenerateProjectFiles.sh` * Open workspace with Visual Code * Build and run (configuration and task named `Flax|Editor.Linux.Development|x64`) diff --git a/Source/Editor/Content/Create/PrefabCreateEntry.cs b/Source/Editor/Content/Create/PrefabCreateEntry.cs index 2ddfccfde..90cca263d 100644 --- a/Source/Editor/Content/Create/PrefabCreateEntry.cs +++ b/Source/Editor/Content/Create/PrefabCreateEntry.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.Content.Create /// public class PrefabCreateEntry : CreateFileEntry { + /// public override bool CanBeCreated => _options.RootActorType != null; /// diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index b56672f44..4928722b6 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -145,7 +145,7 @@ namespace FlaxEditor.Content.GUI set { value = Mathf.Clamp(value, 0.3f, 3.0f); - if (!Mathf.NearEqual(value, _viewScale)) + if (value != _viewScale) { _viewScale = value; ViewScaleChanged?.Invoke(); diff --git a/Source/Editor/Content/Proxy/ShaderSourceProxy.cs b/Source/Editor/Content/Proxy/ShaderSourceProxy.cs index 803ff66c8..2749c8d0e 100644 --- a/Source/Editor/Content/Proxy/ShaderSourceProxy.cs +++ b/Source/Editor/Content/Proxy/ShaderSourceProxy.cs @@ -10,11 +10,9 @@ using FlaxEngine; namespace FlaxEditor.Content { /// - /// Context proxy object for shader source files (represented by ). + /// Base class for shader source files. /// - /// - [ContentContextMenu("New/Shader Source")] - public class ShaderSourceProxy : ContentProxy + public abstract class ShaderBaseProxy : ContentProxy { /// public override bool CanCreate(ContentFolder targetLocation) @@ -29,6 +27,21 @@ namespace FlaxEditor.Content return targetLocation.ShortName == "Source" && prevTargetLocation.ShortName == "Shaders"; } + /// + public override EditorWindow Open(Editor editor, ContentItem item) + { + Editor.Instance.CodeEditing.OpenFile(item.Path); + return null; + } + } + + /// + /// Context proxy object for shader source files (represented by ). + /// + /// + [ContentContextMenu("New/Shader Source (.shader)")] + public class ShaderSourceProxy : ShaderBaseProxy + { /// public override void Create(string outputPath, object arg) { @@ -44,13 +57,6 @@ namespace FlaxEditor.Content File.WriteAllText(outputPath, shaderTemplate, Encoding.UTF8); } - /// - public override EditorWindow Open(Editor editor, ContentItem item) - { - Editor.Instance.CodeEditing.OpenFile(item.Path); - return null; - } - /// public override Color AccentColor => Color.FromRGB(0x7542f5); @@ -66,4 +72,33 @@ namespace FlaxEditor.Content return item is ShaderSourceItem; } } + + /// + /// Context proxy object for shader header files. + /// + /// + [ContentContextMenu("New/Shader Header (.hlsl)")] + public class ShaderHeaderProxy : ShaderBaseProxy + { + /// + public override void Create(string outputPath, object arg) + { + File.WriteAllText(outputPath, "\n", Encoding.UTF8); + } + + /// + public override Color AccentColor => Color.FromRGB(0x2545a5); + + /// + public override string FileExtension => "hlsl"; + + /// + public override string Name => "Shader Header"; + + /// + public override bool IsProxyFor(ContentItem item) + { + return false; + } + } } diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 62db53cab..1030abfda 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -52,6 +53,16 @@ namespace FlaxEditor.CustomEditors /// /// The nodes to select public void Select(List nodes); + + /// + /// Gets the current selection. + /// + public List Selection { get; } + + /// + /// Indication of if the properties window is locked on specific objects. + /// + public bool LockSelection { get; set; } } /// @@ -81,6 +92,8 @@ namespace FlaxEditor.CustomEditors Offsets = Margin.Zero; Pivot = Float2.Zero; IsScrollable = true; + Spacing = Utilities.Constants.UIMargin; + Margin = new Margin(Utilities.Constants.UIMargin); } /// @@ -95,7 +108,7 @@ namespace FlaxEditor.CustomEditors { FlaxEditor.Editor.LogWarning(ex); - // Refresh layout on errors to reduce lgo spam + // Refresh layout on errors to reduce log spam _presenter.BuildLayout(); } diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index 062aeeedd..540b602ec 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -9,7 +9,6 @@ using FlaxEditor.CustomEditors.Elements; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tree; -using FlaxEditor.Modules; using FlaxEditor.Scripting; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; @@ -71,14 +70,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Display prefab UI (when displaying object inside Prefab Window then display only nested prefabs) prefab.GetNestedObject(ref prefabObjectId, out var nestedPrefabId, out var nestedPrefabObjectId); var nestedPrefab = FlaxEngine.Content.Load(nestedPrefabId); - var panel = layout.CustomContainer(); + var panel = layout.UniformGrid(); panel.CustomControl.Height = 20.0f; panel.CustomControl.SlotsVertically = 1; if (Presenter == Editor.Instance.Windows.PropertiesWin.Presenter || nestedPrefab) { var targetPrefab = nestedPrefab ?? prefab; panel.CustomControl.SlotsHorizontally = 3; - + // Selecting actor prefab asset var selectPrefab = panel.Button("Select Prefab"); selectPrefab.Button.Clicked += () => @@ -133,35 +132,22 @@ namespace FlaxEditor.CustomEditors.Dedicated var actor = (Actor)Values[0]; var scriptType = TypeUtils.GetType(actor.TypeName); var item = scriptType.ContentItem; - if (Presenter.Owner is PropertiesWindow propertiesWindow) + if (Presenter.Owner != null) { - var lockButton = cm.AddButton(propertiesWindow.LockObjects ? "Unlock" : "Lock"); + var lockButton = cm.AddButton(Presenter.Owner.LockSelection ? "Unlock" : "Lock"); lockButton.ButtonClicked += button => { - propertiesWindow.LockObjects = !propertiesWindow.LockObjects; + var owner = Presenter?.Owner; + if (owner == null) + return; + owner.LockSelection = !owner.LockSelection; // Reselect current selection - if (!propertiesWindow.LockObjects && Editor.Instance.SceneEditing.SelectionCount > 0) + if (!owner.LockSelection && owner.Selection.Count > 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); + var cachedSelection = owner.Selection.ToList(); + owner.Select(null); + owner.Select(cachedSelection); } }; } @@ -258,7 +244,17 @@ namespace FlaxEditor.CustomEditors.Dedicated else if (editor.Values[0] is SceneObject sceneObject) { node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected; - node.Text = Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name); + if (editor.Values.Info != ScriptMemberInfo.Null) + { + if (editor.Values.GetAttributes().FirstOrDefault(x => x is EditorDisplayAttribute) is EditorDisplayAttribute editorDisplayAttribute && !string.IsNullOrEmpty(editorDisplayAttribute.Name)) + node.Text = $"{Utilities.Utils.GetPropertyNameUI(editorDisplayAttribute.Name)} ({Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name)})"; + else + node.Text = Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name); + } + else if (sceneObject is Actor actor) + node.Text = $"{actor.Name} ({Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name)})"; + else + node.Text = Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name); } // Array Item else if (editor.ParentEditor is CollectionEditor) @@ -268,7 +264,12 @@ namespace FlaxEditor.CustomEditors.Dedicated // Common type else if (editor.Values.Info != ScriptMemberInfo.Null) { - node.Text = Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name); + if (editor.Values.GetAttributes().FirstOrDefault(x => x is EditorDisplayAttribute) is EditorDisplayAttribute editorDisplayAttribute + && !string.IsNullOrEmpty(editorDisplayAttribute.Name) + && !editorDisplayAttribute.Name.Contains("_inline")) + node.Text = $"{Utilities.Utils.GetPropertyNameUI(editorDisplayAttribute.Name)} ({Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name)})"; + else + node.Text = Utilities.Utils.GetPropertyNameUI(editor.Values.Info.Name); } // Custom type else if (editor.Values[0] != null) @@ -316,7 +317,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var childEditor = editor.ChildrenEditors[i]; // Special case for root actor transformation (can be applied only in Prefab editor, not in Level) - if (isActorEditorInLevel && childEditor.Values.Info.Name is "LocalPosition" or "LocalOrientation" or "LocalScale") + if (isActorEditorInLevel && childEditor.Values.Info.Name is "LocalPosition" or "LocalOrientation" or "LocalScale" or "Name") continue; var child = ProcessDiff(childEditor, !isScriptEditorWithRefValue); @@ -361,13 +362,39 @@ namespace FlaxEditor.CustomEditors.Dedicated return result; } + private TreeNode CreateDiffTree(Actor actor, CustomEditorPresenter presenter, LayoutElementsContainer layout) + { + var actorNode = Editor.Instance.Scene.GetActorNode(actor); + ValueContainer vc = new ValueContainer(ScriptMemberInfo.Null); + vc.SetType(new ScriptType(actorNode.EditableObject.GetType())); + vc.Add(actorNode.EditableObject); + var editor = CustomEditorsUtil.CreateEditor(vc, null, false); + editor.Initialize(presenter, layout, vc); + var node = ProcessDiff(editor, false); + layout.ClearLayout(); + foreach (var child in actor.Children) + { + var childNode = CreateDiffTree(child, presenter, layout); + if (childNode == null) + continue; + if (node == null) + node = CreateDiffNode(editor); + node.AddChild(childNode); + } + return node; + } + private void ViewChanges(Control target, Float2 targetLocation) { // Build a tree out of modified properties - var rootNode = ProcessDiff(this, false); + var thisActor = (Actor)Values[0]; + var rootActor = thisActor.IsPrefabRoot ? thisActor : thisActor.GetPrefabRoot(); + var presenter = new CustomEditorPresenter(null); + var layout = new CustomElementsContainer(); + var rootNode = CreateDiffTree(rootActor, presenter, layout); // Skip if no changes detected - if (rootNode == null || rootNode.ChildrenCount == 0) + if (rootNode == null) { var cm1 = new ContextMenu(); cm1.AddButton("No changes detected"); diff --git a/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs b/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs index 4f18baa84..1ddc1c144 100644 --- a/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/AudioSourceEditor.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.CustomEditors.Dedicated _infoLabel = playbackGroup.Label(string.Empty).Label; _infoLabel.AutoHeight = true; - var grid = playbackGroup.CustomContainer(); + var grid = playbackGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs index e16f1141c..abb27867a 100644 --- a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs @@ -59,7 +59,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var paintValue = new ReadOnlyValueContainer(new ScriptType(typeof(ClothPaintingGizmoMode)), _gizmoMode); paintGroup.Object(paintValue); { - var grid = paintGroup.CustomContainer(); + var grid = paintGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs index bfe3d5efe..db7434125 100644 --- a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs @@ -92,12 +92,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Update add button var update = group.Button("Update").Button; + group.Space(0); update.TooltipText = "Refreshes the dashboard statistics"; update.Height = 16.0f; update.Clicked += RebuildLayout; // New locale add button var addLocale = group.Button("Add Locale...").Button; + group.Space(0); addLocale.TooltipText = "Shows a locale picker and creates new localization for it with not translated string tables"; addLocale.Height = 16.0f; addLocale.ButtonClicked += delegate(Button button) @@ -167,12 +169,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Export button var exportLocalization = group.Button("Export...").Button; + group.Space(0); exportLocalization.TooltipText = "Exports the localization strings into .pot file for translation"; exportLocalization.Height = 16.0f; exportLocalization.Clicked += () => Export(tableEntries, allKeys); // Find localized strings in code button var findStringsCode = group.Button("Find localized strings in code").Button; + group.Space(0); findStringsCode.TooltipText = "Searches for localized string usage in inside a project source files"; findStringsCode.Height = 16.0f; findStringsCode.Clicked += delegate diff --git a/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs b/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs index f57548258..22c5d7ece 100644 --- a/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs @@ -55,7 +55,7 @@ public class ModelPrefabEditor : GenericEditor // Creates the import path UI var group = layout.Group("Import Path"); - Utilities.Utils.CreateImportPathUI(group, modelPrefab.ImportPath, false); + Utilities.Utils.CreateImportPathUI(group, modelPrefab.ImportPath); var button = layout.Button("Reimport", "Reimports the source asset as prefab."); _reimportButton = button.Button; diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs index 783b787ae..3de013bfb 100644 --- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs @@ -92,7 +92,7 @@ namespace FlaxEditor.CustomEditors.Dedicated _infoLabel = playbackGroup.Label(string.Empty).Label; _infoLabel.AutoHeight = true; - var grid = playbackGroup.CustomContainer(); + var grid = playbackGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index b68ade40b..ce157240a 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (ragdoll.Parent is AnimatedModel animatedModel && animatedModel.SkinnedModel) { // Builder - var grid = editorGroup.CustomContainer(); + var grid = editorGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; @@ -53,7 +53,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (Presenter.Owner != null) { // Selection - var grid = editorGroup.CustomContainer(); + var grid = editorGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Dedicated/SceneAnimationPlayerEditor.cs b/Source/Editor/CustomEditors/Dedicated/SceneAnimationPlayerEditor.cs index aa2b42edc..e4496601b 100644 --- a/Source/Editor/CustomEditors/Dedicated/SceneAnimationPlayerEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SceneAnimationPlayerEditor.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.CustomEditors.Dedicated _infoLabel = playbackGroup.Label(string.Empty).Label; _infoLabel.AutoHeight = true; - var grid = playbackGroup.CustomContainer(); + var grid = playbackGroup.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 15566a631..614d2c160 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -682,7 +682,7 @@ namespace FlaxEditor.CustomEditors.Dedicated private CustomElementsContainer UniformGridTwoByOne(LayoutElementsContainer cont) { - var grid = cont.CustomContainer(); + var grid = cont.UniformGrid(); grid.CustomControl.SlotsHorizontally = 2; grid.CustomControl.SlotsVertically = 1; grid.CustomControl.SlotPadding = Margin.Zero; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 355321329..2daf2f6af 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -41,13 +41,9 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); - + if (XElement.ValueBox.Parent is UniformGridPanel ug) - { - ug.Height += 2; - ug.SlotSpacing = new Float2(4); - ug.SlotPadding = new Margin(0, 0, 1, 1); - } + CheckLayout(ug); // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; @@ -75,11 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors base.Initialize(layout); if (XElement.ValueBox.Parent is UniformGridPanel ug) - { - ug.Height += 2; - ug.SlotSpacing = new Float2(4); - ug.SlotPadding = new Margin(0, 0, 1, 1); - } + CheckLayout(ug); // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; @@ -136,13 +128,9 @@ namespace FlaxEditor.CustomEditors.Editors menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling"); }; } - + if (XElement.ValueBox.Parent is UniformGridPanel ug) - { - ug.Height += 2; - ug.SlotSpacing = new Float2(4); - ug.SlotPadding = new Margin(0, 0, 1, 1); - } + CheckLayout(ug); // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; @@ -203,5 +191,13 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.TooltipText = LinkValues ? "Unlinks scale components from uniform scaling" : "Links scale components for uniform scaling"; } } + + private static void CheckLayout(UniformGridPanel ug) + { + // Enlarge to fix border visibility + ug.Height += 2; + ug.SlotSpacing += new Float2(2); + ug.SlotPadding += new Margin(0, 0, 1, 1); + } } } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 6ebdb9a58..cfc11d5a5 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -642,10 +642,10 @@ namespace FlaxEditor.CustomEditors.Editors if (_canResize && !_readOnly) { var panel = dragArea.HorizontalPanel(); - panel.Panel.Size = new Float2(0, 20); - panel.Panel.Margin = new Margin(2); + panel.Panel.Size = new Float2(0, 18); + panel.Panel.Margin = new Margin(0, 0, Utilities.Constants.UIMargin, 0); - var removeButton = panel.Button("-", "Remove last item"); + var removeButton = panel.Button("-", "Remove the last item"); removeButton.Button.Size = new Float2(16, 16); removeButton.Button.Enabled = size > _minCount; removeButton.Button.AnchorPreset = AnchorPresets.TopRight; @@ -656,7 +656,7 @@ namespace FlaxEditor.CustomEditors.Editors Resize(Count - 1); }; - var addButton = panel.Button("+", "Add new item"); + var addButton = panel.Button("+", "Add a new item"); addButton.Button.Size = new Float2(16, 16); addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount; addButton.Button.AnchorPreset = AnchorPresets.TopRight; diff --git a/Source/Editor/CustomEditors/Editors/ColorTrackball.cs b/Source/Editor/CustomEditors/Editors/ColorTrackball.cs index d0259102d..87b9a9e0c 100644 --- a/Source/Editor/CustomEditors/Editors/ColorTrackball.cs +++ b/Source/Editor/CustomEditors/Editors/ColorTrackball.cs @@ -27,7 +27,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - float trackBallSize = 80.0f; + float trackBallSize = 100f; float margin = 4.0f; // Panel @@ -50,7 +50,7 @@ namespace FlaxEditor.CustomEditors.Editors // Scale editor { - var grid = masterPanel.CustomContainer(); + var grid = masterPanel.UniformGrid(); var gridControl = grid.CustomControl; gridControl.SlotPadding = new Margin(4, 2, 2, 2); gridControl.ClipChildren = false; diff --git a/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs index 76ceb7488..221bfd7a9 100644 --- a/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs +++ b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs @@ -21,6 +21,7 @@ namespace FlaxEditor.CustomEditors.Editors public sealed class LocalizedStringEditor : GenericEditor { private TextBoxElement _idElement, _valueElement; + private Button _viewStringButton; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -70,6 +71,21 @@ namespace FlaxEditor.CustomEditors.Editors }; addString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); addString.ButtonClicked += OnAddStringClicked; + + var viewString = new Button + { + Visible = false, + Width = 16.0f, + BackgroundColor = Color.White, + BackgroundColorHighlighted = Color.Gray, + BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Search12), + TooltipText = "Find localized text in Localized String Table asset for the current locale...", + Parent = valueElement.TextBox, + }; + viewString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); + viewString.LocalX -= 16.0f; + viewString.ButtonClicked += OnViewStringClicked; + _viewStringButton = viewString; } /// @@ -80,6 +96,7 @@ namespace FlaxEditor.CustomEditors.Editors if (_valueElement != null) { _valueElement.TextBox.WatermarkText = Localization.GetString(_idElement.Text); + _viewStringButton.Visible = !string.IsNullOrEmpty(_valueElement.TextBox.WatermarkText); } } @@ -92,14 +109,21 @@ namespace FlaxEditor.CustomEditors.Editors _valueElement = null; } - private void OnSelectStringClicked(Button button) + private bool GetSettings(out LocalizationSettings settings) { - var settings = GameSettings.Load(); + settings = GameSettings.Load(); if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0) { MessageBox.Show("No valid localization settings setup."); - return; + return true; } + return false; + } + + private void OnSelectStringClicked(Button button) + { + if (GetSettings(out var settings)) + return; Profiler.BeginEvent("LocalizedStringEditor.OnSelectStringClicked"); var allKeys = new HashSet(); for (int i = 0; i < settings.LocalizedStringTables.Length; i++) @@ -136,6 +160,7 @@ namespace FlaxEditor.CustomEditors.Editors { menu.Hide(); _idElement.TextBox.SetTextAsUser(after[0].Text); + _valueElement.TextBox.SetTextAsUser(string.Empty); } }; searchBox.TextChanged += delegate @@ -158,12 +183,8 @@ namespace FlaxEditor.CustomEditors.Editors private void OnAddStringClicked(Button button) { - var settings = GameSettings.Load(); - if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0) - { - MessageBox.Show("No valid localization settings setup."); + if (GetSettings(out var settings)) return; - } Profiler.BeginEvent("LocalizedStringEditor.OnAddStringClicked"); var allKeys = new HashSet(); for (int i = 0; i < settings.LocalizedStringTables.Length; i++) @@ -231,5 +252,30 @@ namespace FlaxEditor.CustomEditors.Editors _idElement.TextBox.SetTextAsUser(newKey); Profiler.EndEvent(); } + + private void OnViewStringClicked(Button button) + { + if (GetSettings(out var settings)) + return; + var id = _idElement.TextBox.Text; + var value = _valueElement.TextBox.WatermarkText; + for (int i = 0; i < settings.LocalizedStringTables.Length; i++) + { + var table = settings.LocalizedStringTables[i]; + if (table && !table.WaitForLoaded()) + { + var entries = table.Entries; + if (entries.TryGetValue(id, out var messages)) + { + if (messages.Length != 0 && messages[0] == value) + { + Editor.Instance.ContentEditing.Open(table); + return; + } + } + } + } + MessageBox.Show("Unable to find localized string table."); + } } } diff --git a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs index feaada5df..3baad685d 100644 --- a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs @@ -46,7 +46,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Editors/StringEditor.cs b/Source/Editor/CustomEditors/Editors/StringEditor.cs index 12cd29b8b..7eaf26186 100644 --- a/Source/Editor/CustomEditors/Editors/StringEditor.cs +++ b/Source/Editor/CustomEditors/Editors/StringEditor.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.CustomEditors.Editors } _element = layout.TextBox(isMultiLine); - _defaultWatermarkColor = _element.TextBox.WatermarkTextColor; + _watermarkColor = _defaultWatermarkColor = _element.TextBox.WatermarkTextColor; if (watermarkAttribute is WatermarkAttribute watermark) { _watermarkText = watermark.WatermarkText; diff --git a/Source/Editor/CustomEditors/Editors/Vector2Editor.cs b/Source/Editor/CustomEditors/Editors/Vector2Editor.cs index 6ae8e608f..8c09298c7 100644 --- a/Source/Editor/CustomEditors/Editors/Vector2Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector2Editor.cs @@ -42,7 +42,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; @@ -131,7 +131,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; @@ -220,7 +220,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs index cb44c60c8..d81cae199 100644 --- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs @@ -82,7 +82,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; @@ -469,7 +469,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; @@ -783,7 +783,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Editors/Vector4Editor.cs b/Source/Editor/CustomEditors/Editors/Vector4Editor.cs index 01328c34b..34d96a80a 100644 --- a/Source/Editor/CustomEditors/Editors/Vector4Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector4Editor.cs @@ -52,7 +52,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; @@ -163,7 +163,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; @@ -274,7 +274,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Editors/VersionEditor.cs b/Source/Editor/CustomEditors/Editors/VersionEditor.cs index 5fc047fed..de9781dc4 100644 --- a/Source/Editor/CustomEditors/Editors/VersionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/VersionEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var grid = layout.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = TextBox.DefaultHeight; diff --git a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs index e3226ef2b..64bc9080b 100644 --- a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs +++ b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs @@ -22,7 +22,8 @@ namespace FlaxEditor.CustomEditors.Elements ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight), ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown), EnableDropDownIcon = true, - ItemsMargin = new Margin(7, 7, 3, 3), + ItemsMargin = new Margin(Utilities.Constants.UIMargin), + ItemsSpacing = Utilities.Constants.UIMargin, HeaderHeight = 18.0f, EnableContainmentLines = true, }; diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs index 02efbb2a3..ef90fc706 100644 --- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs +++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs @@ -20,13 +20,6 @@ namespace FlaxEditor.CustomEditors.GUI /// public const int SplitterSize = 2; - /// - /// The splitter margin (in pixels). - /// - public const int SplitterMargin = 4; - - private const int SplitterSizeHalf = SplitterSize / 2; - private PropertiesListElement _element; private float _splitterValue; private Rectangle _splitterRect; @@ -65,16 +58,18 @@ namespace FlaxEditor.CustomEditors.GUI /// The element. public PropertiesList(PropertiesListElement element) { + ClipChildren = false; _element = element; _splitterValue = 0.4f; - BottomMargin = TopMargin = RightMargin = SplitterMargin; + Margin = new Margin(); + Spacing = Utilities.Constants.UIMargin; UpdateSplitRect(); } private void UpdateSplitRect() { - _splitterRect = new Rectangle(Mathf.Clamp(_splitterValue * Width - SplitterSizeHalf, 0.0f, Width), 0, SplitterSize, Height); - LeftMargin = _splitterValue * Width + SplitterMargin; + _splitterRect = new Rectangle(Mathf.Clamp(_splitterValue * Width - SplitterSize * 0.5f, 0.0f, Width), 0, SplitterSize, Height); + LeftMargin = _splitterValue * Width + _spacing; } private void StartTracking() @@ -222,23 +217,33 @@ namespace FlaxEditor.CustomEditors.GUI /// protected override void PerformLayoutAfterChildren() { - // Sort controls from up to down into two columns: one for labels and one for the rest of the stuff - + // Place non-label controls from top to down float y = _margin.Top; float w = Width - _margin.Width; + bool firstItem = true; for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; if (!(c is PropertyNameLabel)) { - var h = c.Height; - c.Bounds = new Rectangle(_margin.Left, y + _spacing, w, h); + var rect = new Rectangle(_margin.Left, y, w, c.Height); + if (c.Visible) + { + if (firstItem) + firstItem = false; + else + rect.Y += _spacing; + } + else if (!firstItem) + rect.Y += _spacing; + c.Bounds = rect; if (c.Visible) y = c.Bottom; } } y += _margin.Bottom; + // Place labels accordingly to their respective controls placement float namesWidth = _splitterValue * Width; int count = _element.Labels.Count; float[] yStarts = new float[count + 1]; @@ -271,7 +276,9 @@ namespace FlaxEditor.CustomEditors.GUI { var label = _element.Labels[i]; - var rect = new Rectangle(0, yStarts[i] + 1, namesWidth, yStarts[i + 1] - yStarts[i] - 2); + var rect = new Rectangle(0, yStarts[i], namesWidth, yStarts[i + 1] - yStarts[i]); + if (i != count - 1) + rect.Height -= _spacing; //label.Parent = this; label.Bounds = rect; } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 855a730df..68ac9f47a 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -202,6 +202,17 @@ namespace FlaxEditor.CustomEditors return element; } + /// + /// Adds new uniform grid control. + /// + /// The created element. + public CustomElementsContainer UniformGrid() + { + var grid = CustomContainer(); + grid.CustomControl.SlotSpacing = new Float2(Utilities.Constants.UIMargin); + return grid; + } + /// /// Adds new custom element. /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 394f907d9..c6ead7a76 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1031,6 +1031,8 @@ namespace FlaxEditor { Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box); BoundingSphere.FromBox(ref box, out sphere); + if (sphere == BoundingSphere.Empty) + sphere = new BoundingSphere(actor.Position, sphere.Radius); sphere.Radius = Math.Max(sphere.Radius, 15.0f); } else diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs index d1cffa75d..d7534b15b 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs @@ -285,6 +285,17 @@ namespace FlaxEditor.GUI.ContextMenu } } + private static void ForceDefocus(ContainerControl c) + { + foreach (var cc in c.Children) + { + if (cc.ContainsFocus) + cc.Defocus(); + if (cc is ContainerControl ccc) + ForceDefocus(ccc); + } + } + /// /// Hide popup menu and all child menus. /// @@ -299,6 +310,9 @@ namespace FlaxEditor.GUI.ContextMenu // Close child HideChild(); + // Force defocus + ForceDefocus(this); + // Unlink from window Parent = null; diff --git a/Source/Editor/GUI/Input/DoubleValueBox.cs b/Source/Editor/GUI/Input/DoubleValueBox.cs index 7399f4621..a50fa86cc 100644 --- a/Source/Editor/GUI/Input/DoubleValueBox.cs +++ b/Source/Editor/GUI/Input/DoubleValueBox.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.GUI.Input get => _min; set { - if (!Mathd.NearEqual(_min, value)) + if (_min != value) { if (value > _max) throw new ArgumentException(); @@ -58,7 +58,7 @@ namespace FlaxEditor.GUI.Input get => _max; set { - if (!Mathd.NearEqual(_max, value)) + if (_max != value) { if (value < _min) throw new ArgumentException(); diff --git a/Source/Editor/GUI/Input/FloatValueBox.cs b/Source/Editor/GUI/Input/FloatValueBox.cs index 46e9e6502..d49f277d3 100644 --- a/Source/Editor/GUI/Input/FloatValueBox.cs +++ b/Source/Editor/GUI/Input/FloatValueBox.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.GUI.Input get => _min; set { - if (!Mathf.NearEqual(_min, value)) + if (_min != value) { if (value > _max) throw new ArgumentException(); @@ -54,7 +54,7 @@ namespace FlaxEditor.GUI.Input get => _max; set { - if (!Mathf.NearEqual(_max, value)) + if (_max != value) { if (value < _min) throw new ArgumentException(); diff --git a/Source/Editor/GUI/Input/SliderControl.cs b/Source/Editor/GUI/Input/SliderControl.cs index bc2523ae5..8e3efe956 100644 --- a/Source/Editor/GUI/Input/SliderControl.cs +++ b/Source/Editor/GUI/Input/SliderControl.cs @@ -54,7 +54,7 @@ namespace FlaxEditor.GUI.Input set { value = Mathf.Clamp(value, Minimum, Maximum); - if (!Mathf.NearEqual(value, _value)) + if (value != _value) { _value = value; @@ -311,7 +311,7 @@ namespace FlaxEditor.GUI.Input get => _min; set { - if (!Mathf.NearEqual(_min, value)) + if (_min != value) { if (value > _max) throw new ArgumentException(); @@ -330,7 +330,7 @@ namespace FlaxEditor.GUI.Input get => _max; set { - if (!Mathf.NearEqual(_max, value)) + if (_max != value) { if (value < _min) throw new ArgumentException(); diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index f2bd260d4..7555185e6 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -47,6 +47,7 @@ namespace FlaxEditor.GUI.Tabs if (EnabledInHierarchy && Tab.Enabled) { Tabs.SelectedTab = Tab; + Tab.PerformLayout(true); Tabs.Focus(); } return true; diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 02a9f47cb..e36f0ccd5 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -40,6 +40,7 @@ namespace FlaxEditor.GUI.Tree private readonly bool _supportMultiSelect; private Margin _margin; private bool _autoSize = true; + private bool _deferLayoutUpdate = false; /// /// The TreeNode that is being dragged over. This could have a value when not dragging. @@ -66,6 +67,11 @@ namespace FlaxEditor.GUI.Tree /// Gets the first selected node or null. /// public TreeNode SelectedNode => Selection.Count > 0 ? Selection[0] : null; + + /// + /// Allow nodes to Draw the root tree line. + /// + public bool DrawRootTreeLine = true; /// /// Gets or sets the margin for the child tree nodes. @@ -353,9 +359,25 @@ namespace FlaxEditor.GUI.Tree BulkSelectUpdateExpanded(false); } + /// + public override void PerformLayout(bool force = false) + { + if (_isLayoutLocked && !force) + return; + + // In case the tree was fully expanded or collapsed along its children, avoid calculating the layout multiple times for each child + _deferLayoutUpdate = true; + } + /// public override void Update(float deltaTime) { + if (_deferLayoutUpdate) + { + base.PerformLayout(); + _deferLayoutUpdate = false; + } + var node = SelectedNode; // Check if has focus and if any node is focused and it isn't a root diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index eb7f345cf..40c276bf4 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -760,20 +760,21 @@ namespace FlaxEditor.GUI.Tree // Show tree guidelines if (Editor.Instance.Options.Options.Interface.ShowTreeLines) { - TreeNode parentNode = Parent as TreeNode; + ContainerControl parent = Parent; + TreeNode parentNode = parent as TreeNode; bool thisNodeIsLast = false; - while (parentNode != null && parentNode != ParentTree.Children[0]) + while (parentNode != null && (parentNode != tree.Children[0] || tree.DrawRootTreeLine)) { float bottomOffset = 0; float topOffset = 0; - if (Parent == parentNode && this == Parent.Children[0]) + if (parent == parentNode && this == parent.Children[0]) topOffset = 2; if (thisNodeIsLast && parentNode.Children.Count == 1) bottomOffset = topOffset != 0 ? 4 : 2; - if (Parent == parentNode && this == Parent.Children[Parent.Children.Count - 1] && !_opened) + if (parent == parentNode && this == parent.Children[^1] && !_opened) { thisNodeIsLast = true; bottomOffset = topOffset != 0 ? 4 : 2; @@ -784,6 +785,8 @@ namespace FlaxEditor.GUI.Tree if (_iconCollaped.IsValid) leftOffset += 18; var lineRect1 = new Rectangle(parentNode.TextRect.Left - leftOffset, parentNode.HeaderRect.Top + topOffset, 1, parentNode.HeaderRect.Height - bottomOffset); + if (HasAnyVisibleChild && CustomArrowRect.HasValue && CustomArrowRect.Value.Intersects(lineRect1)) + lineRect1 = Rectangle.Empty; // Skip drawing line if it's overlapping the arrow rectangle Render2D.FillRectangle(lineRect1, isSelected ? style.ForegroundGrey : style.LightBackground); parentNode = parentNode.Parent as TreeNode; } diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index bd8f3c036..53e45ff25 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -1135,6 +1135,7 @@ namespace FlaxEditor.Modules Proxy.Add(new FontProxy()); Proxy.Add(new ShaderProxy()); Proxy.Add(new ShaderSourceProxy()); + Proxy.Add(new ShaderHeaderProxy()); Proxy.Add(new ParticleEmitterProxy()); Proxy.Add(new ParticleEmitterFunctionProxy()); Proxy.Add(new ParticleSystemProxy()); diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index d1b254200..617202062 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -255,12 +255,17 @@ namespace FlaxEditor.Modules // When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff) var originalTransform = instance.LocalTransform; + var originalName = instance.Name; if (instance.IsPrefabRoot && instance.HasScene) + { instance.LocalTransform = prefab.GetDefaultInstance().Transform; + instance.Name = prefab.GetDefaultInstance().Name; + } // Call backend var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)); instance.LocalTransform = originalTransform; + instance.Name = originalName; if (failed) throw new Exception("Failed to apply the prefab. See log to learn more."); diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index 4b0951b5f..fb1fb61d1 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -306,19 +306,21 @@ namespace FlaxEditor.Modules public override void OnPlayEnd() { var gameWin = Editor.Windows.GameWin; - - switch (gameWin.FocusOnPlayOption) + if (gameWin != null) { - case Options.InterfaceOptions.PlayModeFocus.None: break; - case Options.InterfaceOptions.PlayModeFocus.GameWindow: break; - case Options.InterfaceOptions.PlayModeFocus.GameWindowThenRestore: - if (_previousWindow != null && !_previousWindow.IsDisposing) + switch (gameWin.FocusOnPlayOption) { - if (!Editor.Windows.GameWin.ParentDockPanel.ContainsTab(_previousWindow)) - break; - _previousWindow.Focus(); + case Options.InterfaceOptions.PlayModeFocus.None: break; + case Options.InterfaceOptions.PlayModeFocus.GameWindow: break; + case Options.InterfaceOptions.PlayModeFocus.GameWindowThenRestore: + if (_previousWindow != null && !_previousWindow.IsDisposing) + { + if (!Editor.Windows.GameWin.ParentDockPanel.ContainsTab(_previousWindow)) + break; + _previousWindow.Focus(); + } + break; } - break; } Editor.UI.UncheckPauseButton(); diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index da9eaf89a..19a0c1142 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -628,19 +628,19 @@ namespace FlaxEditor.Modules MenuWindow = MainMenu.AddButton("Window"); cm = MenuWindow.ContextMenu; cm.VisibleChanged += OnMenuWindowVisibleChanged; - cm.AddButton("Content", Editor.Windows.ContentWin.FocusOrShow); - cm.AddButton("Scene", Editor.Windows.SceneWin.FocusOrShow); - cm.AddButton("Toolbox", Editor.Windows.ToolboxWin.FocusOrShow); - cm.AddButton("Properties", Editor.Windows.PropertiesWin.FocusOrShow); - cm.AddButton("Game", Editor.Windows.GameWin.FocusOrShow); - cm.AddButton("Editor", Editor.Windows.EditWin.FocusOrShow); - cm.AddButton("Debug Log", Editor.Windows.DebugLogWin.FocusOrShow); - cm.AddButton("Output Log", Editor.Windows.OutputLogWin.FocusOrShow); - cm.AddButton("Graphics Quality", Editor.Windows.GraphicsQualityWin.FocusOrShow); - cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); + cm.AddButton("Content", inputOptions.ContentWindow,Editor.Windows.ContentWin.FocusOrShow); + cm.AddButton("Scene", inputOptions.SceneWindow, Editor.Windows.SceneWin.FocusOrShow); + cm.AddButton("Toolbox", inputOptions.ToolboxWindow, Editor.Windows.ToolboxWin.FocusOrShow); + cm.AddButton("Properties", inputOptions.PropertiesWindow, Editor.Windows.PropertiesWin.FocusOrShow); + cm.AddButton("Game", inputOptions.GameWindow, Editor.Windows.GameWin.FocusOrShow); + cm.AddButton("Editor", inputOptions.EditorWindow, Editor.Windows.EditWin.FocusOrShow); + cm.AddButton("Debug Log", inputOptions.DebugLogWindow, Editor.Windows.DebugLogWin.FocusOrShow); + cm.AddButton("Output Log", inputOptions.OutputLogWindow, Editor.Windows.OutputLogWin.FocusOrShow); + cm.AddButton("Graphics Quality", inputOptions.GraphicsQualityWindow, Editor.Windows.GraphicsQualityWin.FocusOrShow); + cm.AddButton("Game Cooker", inputOptions.GameCookerWindow, Editor.Windows.GameCookerWin.FocusOrShow); cm.AddButton("Profiler", inputOptions.ProfilerWindow, Editor.Windows.ProfilerWin.FocusOrShow); - cm.AddButton("Content Search", Editor.ContentFinding.ShowSearch); - cm.AddButton("Visual Script Debugger", Editor.Windows.VisualScriptDebuggerWin.FocusOrShow); + cm.AddButton("Content Search", inputOptions.ContentSearchWindow, Editor.ContentFinding.ShowSearch); + cm.AddButton("Visual Script Debugger", inputOptions.VisualScriptDebuggerWindow, Editor.Windows.VisualScriptDebuggerWin.FocusOrShow); cm.AddSeparator(); cm.AddButton("Save window layout", Editor.Windows.SaveLayout); _menuWindowApplyWindowLayout = cm.AddChildMenu("Window layouts"); diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 5168e03d0..ff7971667 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -33,6 +33,25 @@ namespace FlaxEditor.Options OpenPrefab, } + /// + /// Shortcut availability in play mode. + /// + public enum PlayModeShortcutAvailability + { + /// + /// None of the window shortcuts will be available in play mode. + /// + None, + /// + /// Only the profiler window shortcut will be available in play mode. + /// + ProfilerOnly, + /// + /// All window shortcuts will be available in play mode. + /// + All, + } + /// /// Input editor options data container. /// @@ -40,6 +59,16 @@ namespace FlaxEditor.Options [HideInEditor] public sealed class InputOptions { + /// + /// Gets a value based on the current settings that indicates wether window shortcuts will be avaliable during play mode. + /// + public static bool WindowShortcutsAvaliable => !Editor.IsPlayMode || Editor.Instance.Options.Options.Input.PlayModeWindowShortcutAvaliability == PlayModeShortcutAvailability.All; + + /// + /// Gets a value based on the current settings that indicates wether the profiler window shortcut will be avaliable during play mode. + /// + public static bool ProfilerShortcutAvaliable => WindowShortcutsAvaliable || Editor.Instance.Options.Options.Input.PlayModeWindowShortcutAvaliability == PlayModeShortcutAvailability.ProfilerOnly; + #region Common [DefaultValue(typeof(InputBinding), "Ctrl+S")] @@ -230,9 +259,9 @@ namespace FlaxEditor.Options #region Profiler - [DefaultValue(typeof(InputBinding), "None")] + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha7")] [EditorDisplay("Profiler", "Open Profiler Window"), EditorOrder(630)] - public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.None); + public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.Alpha7, KeyboardKeys.Control); [DefaultValue(typeof(InputBinding), "None")] [EditorDisplay("Profiler", "Start/Stop Profiler"), EditorOrder(631)] @@ -356,24 +385,267 @@ namespace FlaxEditor.Options #endregion + #region Debug Views + + [DefaultValue(typeof(InputBinding), "Alt+Alpha4")] + [EditorDisplay("Debug Views"), EditorOrder(2000)] + public InputBinding Default = new InputBinding(KeyboardKeys.Alpha4, KeyboardKeys.Alt); + + [DefaultValue(typeof(InputBinding), "Alt+Alpha3")] + [EditorDisplay("Debug Views"), EditorOrder(2010)] + public InputBinding Unlit = new InputBinding(KeyboardKeys.Alpha3, KeyboardKeys.Alt); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2020)] + public InputBinding NoPostFX = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Alt+Alpha2")] + [EditorDisplay("Debug Views"), EditorOrder(2030)] + public InputBinding Wireframe = new InputBinding(KeyboardKeys.Alpha2, KeyboardKeys.Alt); + + [DefaultValue(typeof(InputBinding), "Alt+Alpha5")] + [EditorDisplay("Debug Views"), EditorOrder(2040)] + public InputBinding LightBuffer = new InputBinding(KeyboardKeys.Alpha5, KeyboardKeys.Alt); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2050)] + public InputBinding ReflectionsBuffer = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2060)] + public InputBinding DepthBuffer = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2070)] + public InputBinding MotionVectors = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2080)] + public InputBinding LightmapUVDensity = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2090)] + public InputBinding VertexColors = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Alt+Alpha1")] + [EditorDisplay("Debug Views"), EditorOrder(2100)] + public InputBinding PhysicsColliders = new InputBinding(KeyboardKeys.Alpha1, KeyboardKeys.Alt); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2110)] + public InputBinding LODPreview = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2120)] + public InputBinding MaterialComplexity = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2130)] + public InputBinding QuadOverdraw = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2140)] + public InputBinding GloablSDF = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2150)] + public InputBinding GlobalSurfaceAtlas = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Debug Views"), EditorOrder(2160)] + public InputBinding GlobalIllumination = new InputBinding(KeyboardKeys.None); + + #endregion + + #region View Flags + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3000)] + public InputBinding AntiAliasing = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3010)] + public InputBinding Shadows = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha7")] + [EditorDisplay("View Flags"), EditorOrder(3020)] + public InputBinding EditorSprites = new InputBinding(KeyboardKeys.Alpha7, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3030)] + public InputBinding Reflections = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3040)] + public InputBinding ScreenSpaceReflections = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3050)] + public InputBinding AmbientOcclusion = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha6")] + [EditorDisplay("View Flags", "Global Illumination"), EditorOrder(3060)] + public InputBinding GlobalIlluminationViewFlag = new InputBinding(KeyboardKeys.Alpha6, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3070)] + public InputBinding DirectionalLights = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3080)] + public InputBinding PointLights = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3090)] + public InputBinding SpotLights = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3100)] + public InputBinding SkyLights = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3110)] + public InputBinding Sky = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3120)] + public InputBinding Fog = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3130)] + public InputBinding SpecularLight = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3140)] + public InputBinding Decals = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha3")] + [EditorDisplay("View Flags"), EditorOrder(3150)] + public InputBinding CustomPostProcess = new InputBinding(KeyboardKeys.Alpha3, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3160)] + public InputBinding Bloom = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3170)] + public InputBinding ToneMapping = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha2")] + [EditorDisplay("View Flags"), EditorOrder(3180)] + public InputBinding EyeAdaptation = new InputBinding(KeyboardKeys.Alpha2, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3190)] + public InputBinding CameraArtifacts = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3200)] + public InputBinding LensFlares = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3210)] + public InputBinding DepthOfField = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3220)] + public InputBinding MotionBlur = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("View Flags"), EditorOrder(3230)] + public InputBinding ContactShadows = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha1")] + [EditorDisplay("View Flags"), EditorOrder(3240)] + public InputBinding PhysicsDebug = new InputBinding(KeyboardKeys.Alpha1, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha5")] + [EditorDisplay("View Flags"), EditorOrder(3250)] + public InputBinding LightsDebug = new InputBinding(KeyboardKeys.Alpha5, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Alpha4")] + [EditorDisplay("View Flags"), EditorOrder(3260)] + public InputBinding DebugDraw = new InputBinding(KeyboardKeys.Alpha4, KeyboardKeys.Control, KeyboardKeys.Shift); + + #endregion + #region Interface [DefaultValue(typeof(InputBinding), "Ctrl+W")] - [EditorDisplay("Interface"), EditorOrder(2000)] + [EditorDisplay("Interface"), EditorOrder(3500)] public InputBinding CloseTab = new InputBinding(KeyboardKeys.W, KeyboardKeys.Control); [DefaultValue(typeof(InputBinding), "Ctrl+Tab")] - [EditorDisplay("Interface"), EditorOrder(2010)] + [EditorDisplay("Interface"), EditorOrder(3510)] public InputBinding NextTab = new InputBinding(KeyboardKeys.Tab, KeyboardKeys.Control); [DefaultValue(typeof(InputBinding), "Shift+Ctrl+Tab")] - [EditorDisplay("Interface"), EditorOrder(2020)] + [EditorDisplay("Interface"), EditorOrder(3520)] public InputBinding PreviousTab = new InputBinding(KeyboardKeys.Tab, KeyboardKeys.Control, KeyboardKeys.Shift); [DefaultValue(SceneNodeDoubleClick.Expand)] - [EditorDisplay("Interface"), EditorOrder(2030)] + [EditorDisplay("Interface"), EditorOrder(3530)] public SceneNodeDoubleClick DoubleClickSceneNode = SceneNodeDoubleClick.Expand; #endregion + + #region Windows + + /// + /// Gets or sets a value indicating what window shortcuts will be available during play mode. + /// + [DefaultValue(PlayModeShortcutAvailability.ProfilerOnly)] + [EditorDisplay("Windows", "Avaliability in Play Mode"), EditorOrder(3000)] + public PlayModeShortcutAvailability PlayModeWindowShortcutAvaliability { get; set; } = PlayModeShortcutAvailability.ProfilerOnly; + + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha5")] + [EditorDisplay("Windows"), EditorOrder(3010)] + public InputBinding ContentWindow = new InputBinding(KeyboardKeys.Alpha5, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha4")] + [EditorDisplay("Windows"), EditorOrder(3020)] + public InputBinding SceneWindow = new InputBinding(KeyboardKeys.Alpha4, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(3030)] + public InputBinding ToolboxWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha3")] + [EditorDisplay("Windows"), EditorOrder(3040)] + public InputBinding PropertiesWindow = new InputBinding(KeyboardKeys.Alpha3, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha2")] + [EditorDisplay("Windows"), EditorOrder(3050)] + public InputBinding GameWindow = new InputBinding(KeyboardKeys.Alpha2, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "Ctrl+Alpha1")] + [EditorDisplay("Windows"), EditorOrder(3060)] + public InputBinding EditorWindow = new InputBinding(KeyboardKeys.Alpha1, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(3070)] + public InputBinding DebugLogWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(3080)] + public InputBinding OutputLogWindow = new InputBinding(KeyboardKeys.C, KeyboardKeys.Control, KeyboardKeys.Shift); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(3090)] + public InputBinding GraphicsQualityWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(4000)] + public InputBinding GameCookerWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(4010)] + public InputBinding ContentSearchWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Windows"), EditorOrder(4020)] + public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None); + + #endregion } } diff --git a/Source/Editor/SceneGraph/ActorNodeWithIcon.cs b/Source/Editor/SceneGraph/ActorNodeWithIcon.cs index 9e66f5d5a..f9820bb55 100644 --- a/Source/Editor/SceneGraph/ActorNodeWithIcon.cs +++ b/Source/Editor/SceneGraph/ActorNodeWithIcon.cs @@ -35,7 +35,8 @@ namespace FlaxEditor.SceneGraph return false; } - BoundingSphere sphere = new BoundingSphere(Transform.Translation, 7.0f); + var center = _actor.Transform.Translation; + ViewportIconsRenderer.GetBounds(ref center, ref ray.Ray.Position, out var sphere); return CollisionsHelper.RayIntersectsSphere(ref ray.Ray, ref sphere, out distance); } } diff --git a/Source/Editor/SceneGraph/Actors/VideoPlayerEditor.cs b/Source/Editor/SceneGraph/Actors/VideoPlayerEditor.cs index 7e50de084..e5f120e04 100644 --- a/Source/Editor/SceneGraph/Actors/VideoPlayerEditor.cs +++ b/Source/Editor/SceneGraph/Actors/VideoPlayerEditor.cs @@ -28,7 +28,7 @@ namespace FlaxEditor.CustomEditors.Dedicated _infoLabel = playbackGroup.Label(string.Empty).Label; _infoLabel.AutoHeight = true; - var grid = playbackGroup.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index bf3f4b830..5f56e918a 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -320,7 +320,7 @@ namespace FlaxEditor.SceneGraph.GUI if (noFilter && actor != null) { // Pick the correct id when inside a prefab window. - var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID; + var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID; isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id); } diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 831ce8d9a..e46b1c6fb 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -534,7 +534,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Tangent Vector", Description = "World space tangent vector", Flags = NodeFlags.MaterialGraph, - Size = new Float2(160, 40), + Size = new Float2(160, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, "Tangent", typeof(Float3), 0), @@ -546,7 +546,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Bitangent Vector", Description = "World space bitangent vector", Flags = NodeFlags.MaterialGraph, - Size = new Float2(160, 40), + Size = new Float2(160, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, "Bitangent", typeof(Float3), 0), @@ -558,7 +558,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Camera Position", Description = "World space camera location", Flags = NodeFlags.MaterialGraph, - Size = new Float2(160, 40), + Size = new Float2(160, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, "XYZ", typeof(Float3), 0), @@ -570,7 +570,7 @@ namespace FlaxEditor.Surface.Archetypes Title = "Per Instance Random", Description = "Per object instance random value (normalized to range 0-1)", Flags = NodeFlags.MaterialGraph, - Size = new Float2(200, 40), + Size = new Float2(200, 30), Elements = new[] { NodeElementArchetype.Factory.Output(0, "", typeof(float), 0), @@ -607,14 +607,14 @@ namespace FlaxEditor.Surface.Archetypes Title = "Terrain Layer Weight", Description = "Terrain layer weight mask used for blending terrain layers", Flags = NodeFlags.MaterialGraph, - Size = new Float2(220, 30), + Size = new Float2(200, 30), DefaultValues = new object[] { 0, }, Elements = new[] { - NodeElementArchetype.Factory.ComboBox(0, 0, 70.0f, 0, LayersAndTagsSettings.GetCurrentTerrainLayers()), + NodeElementArchetype.Factory.ComboBox(0, 0, 175.0f, 0, LayersAndTagsSettings.GetCurrentTerrainLayers()), NodeElementArchetype.Factory.Output(0, "", typeof(float), 0), } }, diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 59f379bde..8a42a7a92 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -409,7 +409,7 @@ namespace FlaxEditor.Surface /// /// Called after adding the control to the surface after paste. /// - /// The nodes IDs mapping (original node ID to pasted node ID). Can be sued to update internal node's data after paste operation from the original data. + /// The nodes IDs mapping (original node ID to pasted node ID). Can be used to update internal node's data after paste operation from the original data. public virtual void OnPasted(System.Collections.Generic.Dictionary idsMapping) { } diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index e49bbc047..a874db681 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -369,24 +369,14 @@ namespace FlaxEditor.Surface } // Change scale (disable scaling during selecting nodes) - if (IsMouseOver && !_leftMouseDown && !IsPrimaryMenuOpened) + if (IsMouseOver && !_leftMouseDown && !_rightMouseDown && !IsPrimaryMenuOpened) { var nextViewScale = ViewScale + delta * 0.1f; - if (delta > 0 && !_rightMouseDown) - { - // Scale towards mouse when zooming in - var nextCenterPosition = ViewPosition + location / ViewScale; - ViewScale = nextViewScale; - ViewPosition = nextCenterPosition - (location / ViewScale); - } - else - { - // Scale while keeping center position when zooming out or when dragging view - var viewCenter = ViewCenterPosition; - ViewScale = nextViewScale; - ViewCenterPosition = viewCenter; - } + // Scale towards/ away from mouse when zooming in/ out + var nextCenterPosition = ViewPosition + location / ViewScale; + ViewScale = nextViewScale; + ViewPosition = nextCenterPosition - (location / ViewScale); return true; } @@ -498,11 +488,9 @@ namespace FlaxEditor.Surface // Check if user is pressing control if (Root.GetKey(KeyboardKeys.Control)) { - // Add to selection - if (!controlUnderMouse.IsSelected) - { - AddToSelection(controlUnderMouse); - } + // Add/remove from selection + controlUnderMouse.IsSelected = !controlUnderMouse.IsSelected; + SelectionChanged?.Invoke(); } // Check if node isn't selected else if (!controlUnderMouse.IsSelected) diff --git a/Source/Editor/Utilities/Constants.cs b/Source/Editor/Utilities/Constants.cs index cbdb22a66..40fbbb5d7 100644 --- a/Source/Editor/Utilities/Constants.cs +++ b/Source/Editor/Utilities/Constants.cs @@ -20,5 +20,7 @@ namespace FlaxEditor.Utilities #else public const string ShowInExplorer = "Show in explorer"; #endif + + public const float UIMargin = 3.0f; } } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 7c7d3d2d2..179e50ebb 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -415,13 +415,10 @@ namespace FlaxEditor.Utilities /// /// The parent layout element. /// The import path. - /// Whether to use an initial layout space of 5 for separation. - public static void CreateImportPathUI(CustomEditors.LayoutElementsContainer parentLayout, string path, bool useInitialSpacing = true) + public static void CreateImportPathUI(CustomEditors.LayoutElementsContainer parentLayout, string path) { if (!string.IsNullOrEmpty(path)) { - if (useInitialSpacing) - parentLayout.Space(0); var textBox = parentLayout.TextBox().TextBox; textBox.TooltipText = "Source asset path. Can be relative or absolute to the project. Path is not editable here."; textBox.IsReadOnly = true; @@ -1503,7 +1500,6 @@ namespace FlaxEditor.Utilities inputActions.Add(options => options.BuildNav, Editor.Instance.BuildNavMesh); inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF); inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot); - inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow()); #if USE_PROFILER inputActions.Add(options => options.ProfilerStartStop, () => { diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 6bfdbb5f8..59c980156 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -24,8 +24,6 @@ #include "Engine/Level/Actors/SpotLight.h" #include "Engine/Video/VideoPlayer.h" -#define ICON_RADIUS 7.0f - enum class IconTypes { PointLight, @@ -67,6 +65,16 @@ public: }; ViewportIconsRendererService ViewportIconsRendererServiceInstance; +float ViewportIconsRenderer::Scale = 1.0f; + +void ViewportIconsRenderer::GetBounds(const Vector3& position, const Vector3& viewPosition, BoundingSphere& bounds) +{ + constexpr Real minSize = 7.0; + constexpr Real maxSize = 30.0; + Real scale = Math::Square(Vector3::Distance(position, viewPosition) / 1000.0f); + Real radius = minSize + Math::Min(scale, 1.0f) * (maxSize - minSize); + bounds = BoundingSphere(position, radius * Scale); +} void ViewportIconsRenderer::DrawIcons(RenderContext& renderContext, Actor* actor) { @@ -134,7 +142,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene AssetReference texture; for (Actor* icon : icons) { - BoundingSphere sphere(icon->GetPosition() - renderContext.View.Origin, ICON_RADIUS); + BoundingSphere sphere; + ViewportIconsRenderer::GetBounds(icon->GetPosition() - renderContext.View.Origin, renderContext.View.Position, sphere); if (!frustum.Intersects(sphere)) continue; IconTypes iconType; @@ -174,7 +183,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene if (draw.Buffer) { // Create world matrix - Matrix::Scaling(ICON_RADIUS * 2.0f, m2); + Matrix::Scaling((float)sphere.Radius * 2.0f, m2); Matrix::RotationY(PI, world); Matrix::Multiply(m2, world, m1); Matrix::Billboard(sphere.Center, view.Position, Vector3::Up, view.Direction, m2); @@ -194,14 +203,15 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor auto& view = renderContext.View; const BoundingFrustum frustum = view.Frustum; Matrix m1, m2, world; - BoundingSphere sphere(actor->GetPosition() - renderContext.View.Origin, ICON_RADIUS); + BoundingSphere sphere; + ViewportIconsRenderer::GetBounds(actor->GetPosition() - renderContext.View.Origin, renderContext.View.Position, sphere); IconTypes iconType; AssetReference texture; if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(actor->GetTypeHandle(), iconType)) { // Create world matrix - Matrix::Scaling(ICON_RADIUS * 2.0f, m2); + Matrix::Scaling((float)sphere.Radius * 2.0f, m2); Matrix::RotationY(PI, world); Matrix::Multiply(m2, world, m1); Matrix::Billboard(sphere.Center, view.Position, Vector3::Up, view.Direction, m2); diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.h b/Source/Editor/Utilities/ViewportIconsRenderer.h index 8dab1bbf4..c7bf7e1c3 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.h +++ b/Source/Editor/Utilities/ViewportIconsRenderer.h @@ -17,6 +17,19 @@ API_CLASS(Static, Namespace="FlaxEditor") class FLAXENGINE_API ViewportIconsRend DECLARE_SCRIPTING_TYPE_NO_SPAWN(ViewportIconsRenderer); public: + /// + /// Global scale of the icons. + /// + API_FIELD() static float Scale; + + /// + /// Draws the icons for the actors in the given scene (or actor tree). + /// + /// The icon position. + /// The viewer position. + /// The computed bounds for the icon. + API_FUNCTION() static void GetBounds(API_PARAM(Ref) const Vector3& position, API_PARAM(Ref) const Vector3& viewPosition, API_PARAM(Out) BoundingSphere& bounds); + /// /// Draws the icons for the actors in the given scene (or actor tree). /// diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index eaf243726..b4af43281 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -910,7 +910,7 @@ namespace FlaxEditor.Viewport for (int i = 0; i < ViewFlagsValues.Length; i++) { var v = ViewFlagsValues[i]; - var button = viewFlags.AddButton(v.Name); + var button = viewFlags.AddButton(v.Name, v.InputBinding.ToString()); button.CloseMenuOnClick = false; button.Tag = v.Mode; } @@ -959,7 +959,7 @@ namespace FlaxEditor.Viewport } else { - var button = debugView.AddButton(v.Name); + var button = debugView.AddButton(v.Name, v.InputBinding.ToString()); button.CloseMenuOnClick = false; button.Tag = v.Mode; } @@ -1002,19 +1002,79 @@ namespace FlaxEditor.Viewport ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale; } + // Icons Scale + { + var icons = ViewWidgetButtonMenu.AddButton("Icons"); + icons.CloseMenuOnClick = false; + var iconsValue = new FloatValueBox(ViewportIconsRenderer.Scale, xLocationForExtras, 2, 70.0f, 0.01f, 100.0f, 0.001f) + { + Parent = icons + }; + iconsValue.ValueChanged += () => ViewportIconsRenderer.Scale = iconsValue.Value; + ViewWidgetButtonMenu.VisibleChanged += control => iconsValue.Value = ViewportIconsRenderer.Scale; + } + #endregion View mode widget } + // Viewpoints InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + // Editor camera InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); InputActions.Add(options => options.ToggleOrthographic, () => OnOrthographicModeToggled(null)); + // Debug views + InputActions.Add(options => options.Default, () => Task.ViewMode = ViewMode.Default); + InputActions.Add(options => options.Unlit, () => Task.ViewMode = ViewMode.Unlit); + InputActions.Add(options => options.NoPostFX, () => Task.ViewMode = ViewMode.NoPostFx); + InputActions.Add(options => options.Wireframe, () => Task.ViewMode = ViewMode.Wireframe); + InputActions.Add(options => options.LightBuffer, () => Task.ViewMode = ViewMode.LightBuffer); + InputActions.Add(options => options.ReflectionsBuffer, () => Task.ViewMode = ViewMode.Reflections); + InputActions.Add(options => options.DepthBuffer, () => Task.ViewMode = ViewMode.Depth); + InputActions.Add(options => options.MotionVectors, () => Task.ViewMode = ViewMode.MotionVectors); + InputActions.Add(options => options.LightmapUVDensity, () => Task.ViewMode = ViewMode.LightmapUVsDensity); + InputActions.Add(options => options.VertexColors, () => Task.ViewMode = ViewMode.VertexColors); + InputActions.Add(options => options.PhysicsColliders, () => Task.ViewMode = ViewMode.PhysicsColliders); + InputActions.Add(options => options.LODPreview, () => Task.ViewMode = ViewMode.LODPreview); + InputActions.Add(options => options.MaterialComplexity, () => Task.ViewMode = ViewMode.MaterialComplexity); + InputActions.Add(options => options.QuadOverdraw, () => Task.ViewMode = ViewMode.QuadOverdraw); + InputActions.Add(options => options.GloablSDF, () => Task.ViewMode = ViewMode.GlobalSDF); + InputActions.Add(options => options.GlobalSurfaceAtlas, () => Task.ViewMode = ViewMode.GlobalSurfaceAtlas); + InputActions.Add(options => options.GlobalIllumination, () => Task.ViewMode = ViewMode.GlobalIllumination); + // View flags + InputActions.Add(options => options.AntiAliasing, () => Task.ViewFlags ^= ViewFlags.AntiAliasing); + InputActions.Add(options => options.Shadows, () => Task.ViewFlags ^= ViewFlags.Shadows); + InputActions.Add(options => options.EditorSprites, () => Task.ViewFlags ^= ViewFlags.EditorSprites); + InputActions.Add(options => options.Reflections, () => Task.ViewFlags ^= ViewFlags.Reflections); + InputActions.Add(options => options.ScreenSpaceReflections, () => Task.ViewFlags ^= ViewFlags.SSR); + InputActions.Add(options => options.AmbientOcclusion, () => Task.ViewFlags ^= ViewFlags.AO); + InputActions.Add(options => options.GlobalIllumination, () => Task.ViewFlags ^= ViewFlags.GI); + InputActions.Add(options => options.DirectionalLights, () => Task.ViewFlags ^= ViewFlags.DirectionalLights); + InputActions.Add(options => options.PointLights, () => Task.ViewFlags ^= ViewFlags.PointLights); + InputActions.Add(options => options.SpotLights, () => Task.ViewFlags ^= ViewFlags.SpotLights); + InputActions.Add(options => options.SkyLights, () => Task.ViewFlags ^= ViewFlags.SkyLights); + InputActions.Add(options => options.Sky, () => Task.ViewFlags ^= ViewFlags.Sky); + InputActions.Add(options => options.Fog, () => Task.ViewFlags ^= ViewFlags.Fog); + InputActions.Add(options => options.SpecularLight, () => Task.ViewFlags ^= ViewFlags.SpecularLight); + InputActions.Add(options => options.Decals, () => Task.ViewFlags ^= ViewFlags.Decals); + InputActions.Add(options => options.CustomPostProcess, () => Task.ViewFlags ^= ViewFlags.CustomPostProcess); + InputActions.Add(options => options.Bloom, () => Task.ViewFlags ^= ViewFlags.Bloom); + InputActions.Add(options => options.ToneMapping, () => Task.ViewFlags ^= ViewFlags.ToneMapping); + InputActions.Add(options => options.EyeAdaptation, () => Task.ViewFlags ^= ViewFlags.EyeAdaptation); + InputActions.Add(options => options.CameraArtifacts, () => Task.ViewFlags ^= ViewFlags.CameraArtifacts); + InputActions.Add(options => options.LensFlares, () => Task.ViewFlags ^= ViewFlags.LensFlares); + InputActions.Add(options => options.DepthOfField, () => Task.ViewFlags ^= ViewFlags.DepthOfField); + InputActions.Add(options => options.MotionBlur, () => Task.ViewFlags ^= ViewFlags.MotionBlur); + InputActions.Add(options => options.ContactShadows, () => Task.ViewFlags ^= ViewFlags.ContactShadows); + InputActions.Add(options => options.PhysicsDebug, () => Task.ViewFlags ^= ViewFlags.PhysicsDebug); + InputActions.Add(options => options.LightsDebug, () => Task.ViewFlags ^= ViewFlags.LightsDebug); + InputActions.Add(options => options.DebugDraw, () => Task.ViewFlags ^= ViewFlags.DebugDraw); // Link for task event task.Begin += OnRenderBegin; @@ -1933,8 +1993,17 @@ namespace FlaxEditor.Viewport { public readonly string Name; public readonly ViewMode Mode; + public readonly InputBinding InputBinding; public readonly ViewModeOptions[] Options; + public ViewModeOptions(ViewMode mode, string name, InputBinding inputBinding) + { + Mode = mode; + Name = name; + InputBinding = inputBinding; + Options = null; + } + public ViewModeOptions(ViewMode mode, string name) { Mode = mode; @@ -1952,13 +2021,13 @@ namespace FlaxEditor.Viewport private static readonly ViewModeOptions[] ViewModeValues = { - new ViewModeOptions(ViewMode.Default, "Default"), - new ViewModeOptions(ViewMode.Unlit, "Unlit"), - new ViewModeOptions(ViewMode.NoPostFx, "No PostFx"), - new ViewModeOptions(ViewMode.Wireframe, "Wireframe"), - new ViewModeOptions(ViewMode.LightBuffer, "Light Buffer"), - new ViewModeOptions(ViewMode.Reflections, "Reflections Buffer"), - new ViewModeOptions(ViewMode.Depth, "Depth Buffer"), + new ViewModeOptions(ViewMode.Default, "Default", Editor.Instance.Options.Options.Input.Default), + new ViewModeOptions(ViewMode.Unlit, "Unlit", Editor.Instance.Options.Options.Input.Unlit), + new ViewModeOptions(ViewMode.NoPostFx, "No PostFx", Editor.Instance.Options.Options.Input.NoPostFX), + new ViewModeOptions(ViewMode.Wireframe, "Wireframe", Editor.Instance.Options.Options.Input.Wireframe), + new ViewModeOptions(ViewMode.LightBuffer, "Light Buffer", Editor.Instance.Options.Options.Input.LightBuffer), + new ViewModeOptions(ViewMode.Reflections, "Reflections Buffer", Editor.Instance.Options.Options.Input.ReflectionsBuffer), + new ViewModeOptions(ViewMode.Depth, "Depth Buffer", Editor.Instance.Options.Options.Input.DepthBuffer), new ViewModeOptions("GBuffer", new[] { new ViewModeOptions(ViewMode.Diffuse, "Diffuse"), @@ -1972,16 +2041,16 @@ namespace FlaxEditor.Viewport new ViewModeOptions(ViewMode.Normals, "Normals"), new ViewModeOptions(ViewMode.AmbientOcclusion, "Ambient Occlusion"), }), - new ViewModeOptions(ViewMode.MotionVectors, "Motion Vectors"), - new ViewModeOptions(ViewMode.LightmapUVsDensity, "Lightmap UVs Density"), - new ViewModeOptions(ViewMode.VertexColors, "Vertex Colors"), - new ViewModeOptions(ViewMode.PhysicsColliders, "Physics Colliders"), - new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"), - new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"), - new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"), - new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"), - new ViewModeOptions(ViewMode.GlobalSurfaceAtlas, "Global Surface Atlas"), - new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination"), + new ViewModeOptions(ViewMode.MotionVectors, "Motion Vectors", Editor.Instance.Options.Options.Input.MotionVectors), + new ViewModeOptions(ViewMode.LightmapUVsDensity, "Lightmap UVs Density", Editor.Instance.Options.Options.Input.LightmapUVDensity), + new ViewModeOptions(ViewMode.VertexColors, "Vertex Colors", Editor.Instance.Options.Options.Input.VertexColors), + new ViewModeOptions(ViewMode.PhysicsColliders, "Physics Colliders", Editor.Instance.Options.Options.Input.PhysicsColliders), + new ViewModeOptions(ViewMode.LODPreview, "LOD Preview", Editor.Instance.Options.Options.Input.LODPreview), + new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity", Editor.Instance.Options.Options.Input.MaterialComplexity), + new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw", Editor.Instance.Options.Options.Input.QuadOverdraw), + new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF", Editor.Instance.Options.Options.Input.GloablSDF), + new ViewModeOptions(ViewMode.GlobalSurfaceAtlas, "Global Surface Atlas", Editor.Instance.Options.Options.Input.GlobalSurfaceAtlas), + new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination", Editor.Instance.Options.Options.Input.GlobalIllumination), }; private void WidgetViewModeShowHideClicked(ContextMenuButton button) @@ -2014,43 +2083,45 @@ namespace FlaxEditor.Viewport { public readonly ViewFlags Mode; public readonly string Name; + public readonly InputBinding InputBinding; - public ViewFlagOptions(ViewFlags mode, string name) + public ViewFlagOptions(ViewFlags mode, string name, InputBinding inputBinding) { Mode = mode; Name = name; + InputBinding = inputBinding; } } private static readonly ViewFlagOptions[] ViewFlagsValues = { - new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"), - new ViewFlagOptions(ViewFlags.Shadows, "Shadows"), - new ViewFlagOptions(ViewFlags.EditorSprites, "Editor Sprites"), - new ViewFlagOptions(ViewFlags.Reflections, "Reflections"), - new ViewFlagOptions(ViewFlags.SSR, "Screen Space Reflections"), - new ViewFlagOptions(ViewFlags.AO, "Ambient Occlusion"), - new ViewFlagOptions(ViewFlags.GI, "Global Illumination"), - new ViewFlagOptions(ViewFlags.DirectionalLights, "Directional Lights"), - new ViewFlagOptions(ViewFlags.PointLights, "Point Lights"), - new ViewFlagOptions(ViewFlags.SpotLights, "Spot Lights"), - new ViewFlagOptions(ViewFlags.SkyLights, "Sky Lights"), - new ViewFlagOptions(ViewFlags.Sky, "Sky"), - new ViewFlagOptions(ViewFlags.Fog, "Fog"), - new ViewFlagOptions(ViewFlags.SpecularLight, "Specular Light"), - new ViewFlagOptions(ViewFlags.Decals, "Decals"), - new ViewFlagOptions(ViewFlags.CustomPostProcess, "Custom Post Process"), - new ViewFlagOptions(ViewFlags.Bloom, "Bloom"), - new ViewFlagOptions(ViewFlags.ToneMapping, "Tone Mapping"), - new ViewFlagOptions(ViewFlags.EyeAdaptation, "Eye Adaptation"), - new ViewFlagOptions(ViewFlags.CameraArtifacts, "Camera Artifacts"), - new ViewFlagOptions(ViewFlags.LensFlares, "Lens Flares"), - new ViewFlagOptions(ViewFlags.DepthOfField, "Depth of Field"), - new ViewFlagOptions(ViewFlags.MotionBlur, "Motion Blur"), - new ViewFlagOptions(ViewFlags.ContactShadows, "Contact Shadows"), - new ViewFlagOptions(ViewFlags.PhysicsDebug, "Physics Debug"), - new ViewFlagOptions(ViewFlags.LightsDebug, "Lights Debug"), - new ViewFlagOptions(ViewFlags.DebugDraw, "Debug Draw"), + new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing", Editor.Instance.Options.Options.Input.AntiAliasing), + new ViewFlagOptions(ViewFlags.Shadows, "Shadows", Editor.Instance.Options.Options.Input.Shadows), + new ViewFlagOptions(ViewFlags.EditorSprites, "Editor Sprites", Editor.Instance.Options.Options.Input.EditorSprites), + new ViewFlagOptions(ViewFlags.Reflections, "Reflections", Editor.Instance.Options.Options.Input.Reflections), + new ViewFlagOptions(ViewFlags.SSR, "Screen Space Reflections", Editor.Instance.Options.Options.Input.ScreenSpaceReflections), + new ViewFlagOptions(ViewFlags.AO, "Ambient Occlusion", Editor.Instance.Options.Options.Input.AmbientOcclusion), + new ViewFlagOptions(ViewFlags.GI, "Global Illumination", Editor.Instance.Options.Options.Input.GlobalIlluminationViewFlag), + new ViewFlagOptions(ViewFlags.DirectionalLights, "Directional Lights", Editor.Instance.Options.Options.Input.DirectionalLights), + new ViewFlagOptions(ViewFlags.PointLights, "Point Lights", Editor.Instance.Options.Options.Input.PointLights), + new ViewFlagOptions(ViewFlags.SpotLights, "Spot Lights", Editor.Instance.Options.Options.Input.SpotLights), + new ViewFlagOptions(ViewFlags.SkyLights, "Sky Lights", Editor.Instance.Options.Options.Input.SkyLights), + new ViewFlagOptions(ViewFlags.Sky, "Sky", Editor.Instance.Options.Options.Input.Sky), + new ViewFlagOptions(ViewFlags.Fog, "Fog", Editor.Instance.Options.Options.Input.Fog), + new ViewFlagOptions(ViewFlags.SpecularLight, "Specular Light", Editor.Instance.Options.Options.Input.SpecularLight), + new ViewFlagOptions(ViewFlags.Decals, "Decals", Editor.Instance.Options.Options.Input.Decals), + new ViewFlagOptions(ViewFlags.CustomPostProcess, "Custom Post Process", Editor.Instance.Options.Options.Input.CustomPostProcess), + new ViewFlagOptions(ViewFlags.Bloom, "Bloom", Editor.Instance.Options.Options.Input.Bloom), + new ViewFlagOptions(ViewFlags.ToneMapping, "Tone Mapping", Editor.Instance.Options.Options.Input.ToneMapping), + new ViewFlagOptions(ViewFlags.EyeAdaptation, "Eye Adaptation", Editor.Instance.Options.Options.Input.EyeAdaptation), + new ViewFlagOptions(ViewFlags.CameraArtifacts, "Camera Artifacts", Editor.Instance.Options.Options.Input.CameraArtifacts), + new ViewFlagOptions(ViewFlags.LensFlares, "Lens Flares", Editor.Instance.Options.Options.Input.LensFlares), + new ViewFlagOptions(ViewFlags.DepthOfField, "Depth of Field", Editor.Instance.Options.Options.Input.DepthOfField), + new ViewFlagOptions(ViewFlags.MotionBlur, "Motion Blur", Editor.Instance.Options.Options.Input.MotionBlur), + new ViewFlagOptions(ViewFlags.ContactShadows, "Contact Shadows", Editor.Instance.Options.Options.Input.ContactShadows), + new ViewFlagOptions(ViewFlags.PhysicsDebug, "Physics Debug", Editor.Instance.Options.Options.Input.PhysicsDebug), + new ViewFlagOptions(ViewFlags.LightsDebug, "Lights Debug", Editor.Instance.Options.Options.Input.LightsDebug), + new ViewFlagOptions(ViewFlags.DebugDraw, "Debug Draw", Editor.Instance.Options.Options.Input.DebugDraw), }; private void WidgetViewFlagsShowHide(Control cm) diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 995f510a9..a678e868a 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -185,7 +185,7 @@ namespace FlaxEditor.Viewport.Previews { UseTimeScale = false, UpdateWhenOffscreen = true, - BoundsScale = 100.0f, + BoundsScale = 1.0f, UpdateMode = AnimatedModel.AnimationUpdateMode.Manual, }; Task.AddCustomActor(_previewModel); diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 2fd71d860..2b86664f2 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -635,5 +635,11 @@ namespace FlaxEditor.Windows.Assets public void Select(List nodes) { } + + /// + public List Selection => new List(); + + /// + public bool LockSelection { get; set; } } } diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index c6fb5675a..df227c733 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -76,29 +76,29 @@ namespace FlaxEditor.Windows.Assets // Transparency - [EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")] + [EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")] public MaterialTransparentLightingMode TransparentLightingMode; - [EditorOrder(205), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")] + [EditorOrder(205), DefaultValue(true), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")] public bool EnableReflections; [VisibleIf(nameof(EnableReflections))] - [EditorOrder(210), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")] + [EditorOrder(210), DefaultValue(false), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")] public bool EnableScreenSpaceReflections; - [EditorOrder(210), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")] + [EditorOrder(210), DefaultValue(true), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")] public bool EnableFog; - [EditorOrder(220), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")] + [EditorOrder(220), DefaultValue(true), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")] public bool EnableDistortion; - [EditorOrder(224), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")] + [EditorOrder(224), DefaultValue(false), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")] public bool EnableGlobalIllumination; - [EditorOrder(225), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")] + [EditorOrder(225), DefaultValue(false), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")] public bool PixelNormalOffsetRefraction; - [EditorOrder(230), DefaultValue(0.12f), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)] + [EditorOrder(230), DefaultValue(0.12f), VisibleIf(nameof(IsForward)), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)] public float OpacityThreshold; // Tessellation @@ -146,6 +146,7 @@ namespace FlaxEditor.Windows.Assets private bool IsDecal => Domain == MaterialDomain.Decal; private bool IsGUI => Domain == MaterialDomain.GUI; private bool IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable; + private bool IsForward => Domain == MaterialDomain.Particle || ((Domain == MaterialDomain.Deformable || Domain == MaterialDomain.Surface) && BlendMode != MaterialBlendMode.Opaque); private bool IsStandardOrGUI => IsStandard || IsGUI; /// diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs index 216dede67..14344ef71 100644 --- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs +++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs @@ -254,7 +254,8 @@ namespace FlaxEditor.Windows.Assets if (lodIndex >= countLODs - loadedLODs) { var mesh = lod.GetMesh(0); - vertexLayout = mesh.VertexLayout; + if (mesh != null) + vertexLayout = mesh.VertexLayout; if (vertexLayout != null && vertexLayout.Elements.Length != 0) break; vertexLayout = null; @@ -759,7 +760,6 @@ namespace FlaxEditor.Windows.Assets var importSettingsField = typeof(ImportPropertiesProxyBase).GetField(nameof(ImportSettings), BindingFlags.NonPublic | BindingFlags.Instance); var importSettingsValues = new ValueContainer(new ScriptMemberInfo(importSettingsField)) { proxy.ImportSettings }; importSettingsGroup.Object(importSettingsValues); - importSettingsGroup.Space(3); // Creates the import path UI var group = layout.Group("Import Path"); diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index d28c0141b..9a0b1ad82 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -114,7 +114,7 @@ namespace FlaxEditor.Windows.Assets lodIndex.IntValue.Value = sdf.Texture != null ? sdf.LOD : 6; _sdfModelLodIndex = lodIndex; - var buttons = group.CustomContainer(); + var buttons = layout.UniformGrid(); var gridControl = buttons.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 95e6b7a46..24854afb9 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -56,6 +56,7 @@ namespace FlaxEditor.Windows.Assets public PrefabTree() : base(true) { + DrawRootTreeLine = false; } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs index f6d3f1669..03e2a9652 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Windows.Assets /// The selection before the change. public void OnSelectionChanged(SceneGraphNode[] before) { - if (LockSelectedObjects) + if (LockSelection) return; Undo.AddAction(new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo)); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 1b2ca48bc..44c21d863 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -78,7 +78,7 @@ namespace FlaxEditor.Windows.Assets /// /// Indication of if the prefab window selection is locked on specific objects. /// - public bool LockSelectedObjects + public bool LockSelection { get => _lockSelection; set diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index b38fa277a..756daf66a 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -413,7 +413,7 @@ namespace FlaxEditor.Windows.Assets var group = layout.Group("Functions"); var nodes = window.VisjectSurface.Nodes; - var grid = group.CustomContainer(); + var grid = layout.UniformGrid(); var gridControl = grid.CustomControl; gridControl.ClipChildren = false; gridControl.Height = Button.DefaultHeight; diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index f63db04bd..cf33d0b63 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -204,6 +204,7 @@ namespace FlaxEditor.Windows // Content structure tree _tree = new Tree(false) { + DrawRootTreeLine = false, Parent = _contentTreePanel, }; _tree.SelectedChanged += OnTreeSelectionChanged; diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs index 3600acbee..888dd4250 100644 --- a/Source/Editor/Windows/EditGameWindow.cs +++ b/Source/Editor/Windows/EditGameWindow.cs @@ -140,6 +140,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Editor"; + Icon = editor.Icons.Grid32; // Create viewport Viewport = new MainEditorGizmoViewport(editor) diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index f96ab0a78..f61f5cce6 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -2,6 +2,7 @@ using System; using FlaxEditor.Content; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; using DockWindow = FlaxEditor.GUI.Docking.DockWindow; @@ -49,6 +50,73 @@ namespace FlaxEditor.Windows } }); + // Set up editor window shortcuts + InputActions.Add(options => options.ContentWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.ContentWin.FocusOrShow(); + }); + InputActions.Add(options => options.SceneWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.SceneWin.FocusOrShow(); + }); + InputActions.Add(options => options.ToolboxWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.ToolboxWin.FocusOrShow(); + }); + InputActions.Add(options => options.PropertiesWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.PropertiesWin.FocusOrShow(); + }); + InputActions.Add(options => options.GameWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.GameWin.FocusOrShow(); + }); + InputActions.Add(options => options.EditorWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.EditWin.FocusOrShow(); + }); + InputActions.Add(options => options.DebugLogWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.DebugLogWin.FocusOrShow(); + }); + InputActions.Add(options => options.OutputLogWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.OutputLogWin.FocusOrShow(); + }); + InputActions.Add(options => options.GraphicsQualityWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.GraphicsQualityWin.FocusOrShow(); + }); + InputActions.Add(options => options.GameCookerWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.GameCookerWin.FocusOrShow(); + }); + InputActions.Add(options => options.ProfilerWindow, () => + { + if (InputOptions.ProfilerShortcutAvaliable) + Editor.Windows.ProfilerWin.FocusOrShow(); + }); + InputActions.Add(options => options.ContentFinder, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.ContentFinding.ShowSearch(); + }); + InputActions.Add(options => options.VisualScriptDebuggerWindow, () => + { + if (InputOptions.WindowShortcutsAvaliable) + Editor.Windows.VisualScriptDebuggerWin.FocusOrShow(); + }); + // Register Editor.Windows.OnWindowAdd(this); } diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index f738a7a8c..4db3bcf0f 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -305,6 +305,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Game"; + Icon = editor.Icons.Play64; AutoFocus = true; var task = MainRenderTask.Instance; diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index b087947dc..6fc0659d7 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -482,6 +482,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Output Log"; + Icon = editor.Icons.Info64; ClipChildren = false; FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); @@ -983,6 +984,10 @@ namespace FlaxEditor.Windows var cachedOutputTargetViewOffset = _output.TargetViewOffset; var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - (_scrollSize * 2) || wasEmpty; _output.Text = _textBuffer.ToString(); + if (_hScroll.Maximum <= 0.0) + cachedOutputTargetViewOffset.X = 0; + if (_vScroll.Maximum <= 0.0) + cachedOutputTargetViewOffset.Y = 0; _output.TargetViewOffset = cachedOutputTargetViewOffset; _textBufferCount = _entries.Count; if (!_vScroll.IsThumbClicked) diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index 87474fc3a..e90003038 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -45,7 +45,7 @@ namespace FlaxEditor.Windows /// /// Indication of if the properties window is locked on specific objects. /// - public bool LockObjects + public bool LockSelection { get => _lockObjects; set @@ -66,6 +66,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.Vertical) { Title = "Properties"; + Icon = editor.Icons.Build64; AutoFocus = true; Presenter = new CustomEditorPresenter(editor.Undo, null, this); @@ -86,9 +87,9 @@ namespace FlaxEditor.Windows if (Level.ScenesCount > 1) return; _actorScrollValues.Clear(); - if (LockObjects) + if (LockSelection) { - LockObjects = false; + LockSelection = false; Presenter.Deselect(); } } @@ -121,7 +122,7 @@ namespace FlaxEditor.Windows private void OnSelectionChanged() { - if (LockObjects) + if (LockSelection) return; // Update selected objects diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index e0c9e0068..705976e6e 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -48,6 +48,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Scene"; + Icon = editor.Icons.Globe32; // Scene searching query input box var headerPanel = new ContainerControl @@ -84,6 +85,7 @@ namespace FlaxEditor.Windows { Margin = new Margin(0.0f, 0.0f, -16.0f, _sceneTreePanel.ScrollBarsSize), // Hide root node IsScrollable = true, + DrawRootTreeLine = false, }; _tree.AddChild(root.TreeNode); _tree.SelectedChanged += Tree_OnSelectedChanged; diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 81f7b94d4..3c9c605a4 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -455,6 +455,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Toolbox"; + Icon = editor.Icons.Toolbox96; FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 3a6ed648e..fd4fae421 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -15,6 +15,8 @@ #if USE_EDITOR +#include "Engine/Engine/Globals.h" + ThreadLocal ContentDeprecatedFlags; void ContentDeprecated::Mark() @@ -593,7 +595,7 @@ bool Asset::onLoad(LoadAssetTask* task) #if USE_EDITOR // Auto-save deprecated assets to get rid of data in an old format - if (isDeprecated && isLoaded) + if (isDeprecated && isLoaded && !IsVirtual() && !GetPath().StartsWith(StringUtils::GetDirectoryName(Globals::TemporaryFolder))) { PROFILE_CPU_NAMED("Asset.Save"); LOG(Info, "Resaving asset '{}' that uses deprecated data format", ToString()); diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 10c471f8c..df95e58b7 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -326,7 +326,7 @@ bool Model::Init(const Span& meshesCountPerLod) lod.Link(this, lodIndex); lod.ScreenSize = 1.0f; const int32 meshesCount = meshesCountPerLod[lodIndex]; - if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES) + if (meshesCount < 0 || meshesCount > MODEL_MAX_MESHES) return true; lod.Meshes.Resize(meshesCount); @@ -365,7 +365,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion) // Meshes uint16 meshesCount; stream.ReadUint16(&meshesCount); - if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES) + if (meshesCount > MODEL_MAX_MESHES) return true; ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount); lod.Meshes.Resize(meshesCount, false); diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 5f2a89328..7d16639ab 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -669,6 +669,8 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l Array> vbElements; const bool useSeparatePositions = !isSkinned; const bool useSeparateColors = !isSkinned; + PixelFormat positionsFormat = modelData.PositionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float; + PixelFormat texCoordsFormat = modelData.TexCoordFormat == ModelData::TexCoordFormats::Float16 ? PixelFormat::R16G16_Float : PixelFormat::R8G8_UNorm; PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt; PixelFormat blendWeightsFormat = PixelFormat::R8G8B8A8_UNorm; for (const Int4& indices : mesh.BlendIndices) @@ -681,14 +683,13 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l } { byte vbIndex = 0; - // TODO: add option to quantize vertex positions (eg. 16-bit) // TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords) // Position if (useSeparatePositions) { auto& vb0 = vbElements.AddOne(); - vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float }); + vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, positionsFormat }); vbIndex++; } @@ -696,13 +697,13 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l { auto& vb = vbElements.AddOne(); if (!useSeparatePositions) - vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float }); + vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, positionsFormat }); for (int32 channelIdx = 0; channelIdx < mesh.UVs.Count(); channelIdx++) { auto& channel = mesh.UVs.Get()[channelIdx]; if (channel.HasItems()) { - vb.Add({ (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + channelIdx), vbIndex, 0, 0, PixelFormat::R16G16_Float }); + vb.Add({ (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + channelIdx), vbIndex, 0, 0, texCoordsFormat }); } } vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm }); @@ -737,7 +738,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l // Write vertex buffers for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++) { - if (useSeparatePositions && vbIndex == 0) + if (useSeparatePositions && vbIndex == 0 && positionsFormat == PixelFormat::R32G32B32_Float) { // Fast path for vertex positions of static models using the first buffer stream.WriteBytes(mesh.Positions.Get(), sizeof(Float3) * vertices); @@ -755,7 +756,15 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l case VertexElement::Types::Position: { const Float3 position = mesh.Positions.Get()[vertex]; - stream.Write(position); + if (positionsFormat == PixelFormat::R16G16B16A16_Float) + { + const Half4 positionEnc(Float4(position, 0.0f)); + stream.Write(positionEnc); + } + else //if (positionsFormat == PixelFormat::R32G32B32_Float) + { + stream.Write(position); + } break; } case VertexElement::Types::Color: @@ -821,8 +830,16 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l { const int32 channelIdx = (int32)element.Type - (int32)VertexElement::Types::TexCoord0; const Float2 uv = mesh.UVs.Get()[channelIdx].Get()[vertex]; - const Half2 uvEnc(uv); - stream.Write(uvEnc); + if (texCoordsFormat == PixelFormat::R8G8_UNorm) + { + stream.Write((uint8)Math::Clamp((int32)(uv.X * 255), 0, 255)); + stream.Write((uint8)Math::Clamp((int32)(uv.Y * 255), 0, 255)); + } + else //if (texCoordsFormat == PixelFormat::R16G16_Float) + { + const Half2 uvEnc(uv); + stream.Write(uvEnc); + } break; } default: diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 7c51f4332..9d10e0150 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -478,7 +478,7 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod) lod._lodIndex = lodIndex; lod.ScreenSize = 1.0f; const int32 meshesCount = meshesCountPerLod[lodIndex]; - if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES) + if (meshesCount < 0 || meshesCount > MODEL_MAX_MESHES) return true; lod.Meshes.Resize(meshesCount); diff --git a/Source/Engine/Content/Storage/JsonStorageProxy.cpp b/Source/Engine/Content/Storage/JsonStorageProxy.cpp index af19c67c0..3d837e61a 100644 --- a/Source/Engine/Content/Storage/JsonStorageProxy.cpp +++ b/Source/Engine/Content/Storage/JsonStorageProxy.cpp @@ -9,6 +9,10 @@ #include "Engine/Level/Types.h" #include "Engine/Debug/Exceptions/JsonParseException.h" #include "Engine/Profiler/ProfilerCPU.h" +#if USE_EDITOR +#include "Engine/Core/Collections/HashSet.h" +#include "Engine/Core/Collections/Dictionary.h" +#endif #include bool JsonStorageProxy::IsValidExtension(const StringView& extension) @@ -56,27 +60,31 @@ bool JsonStorageProxy::GetAssetInfo(const StringView& path, Guid& resultId, Stri #if USE_EDITOR -void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, const StringAnsi& srcId, const StringAnsi& dstId) +void FindObjectIds(const rapidjson_flax::Value& obj, const rapidjson_flax::Document& document, HashSet& ids, const char* parentName = nullptr) { if (obj.IsObject()) { - for (rapidjson_flax::Value::MemberIterator i = obj.MemberBegin(); i != obj.MemberEnd(); ++i) + for (rapidjson_flax::Value::ConstMemberIterator i = obj.MemberBegin(); i != obj.MemberEnd(); ++i) { - ChangeIds(i->value, document, srcId, dstId); + FindObjectIds(i->value, document, ids, i->name.GetString()); } } else if (obj.IsArray()) { for (rapidjson::SizeType i = 0; i < obj.Size(); i++) { - ChangeIds(obj[i], document, srcId, dstId); + FindObjectIds(obj[i], document, ids, parentName); } } - else if (obj.IsString()) + else if (obj.IsString() && obj.GetStringLength() == 32) { - if (StringUtils::Compare(srcId.Get(), obj.GetString()) == 0) + if (parentName && StringUtils::Compare(parentName, "ID") == 0) { - obj.SetString(dstId.Get(), document.GetAllocator()); + auto value = JsonTools::GetGuid(obj); + if (value.IsValid()) + { + ids.Add(value); + } } } } @@ -91,9 +99,7 @@ bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId) // Load file Array fileData; if (File::ReadAllBytes(path, fileData)) - { return false; - } // Parse data rapidjson_flax::Document document; @@ -107,33 +113,35 @@ bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId) return false; } - // Try get asset metadata + // Get all IDs inside the file + HashSet ids; + FindObjectIds(document, document, ids); + + // Remap into a unique IDs + Dictionary remap; + remap.EnsureCapacity(ids.Count()); + for (const auto& id : ids) + remap.Add(id.Item, Guid::New()); + + // Remap asset ID using the provided value auto idNode = document.FindMember("ID"); if (idNode == document.MemberEnd()) - { return true; - } + remap[JsonTools::GetGuid(idNode->value)] = newId; - // Change IDs - auto oldIdStr = idNode->value.GetString(); - auto newIdStr = newId.ToString(Guid::FormatType::N).ToStringAnsi(); - ChangeIds(document, document, oldIdStr, newIdStr); + // Change IDs of asset and objects inside asset + JsonTools::ChangeIds(document, remap); // Save to file rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writer(buffer); document.Accept(writer.GetWriter()); if (File::WriteAllBytes(path, (byte*)buffer.GetString(), (int32)buffer.GetSize())) - { return true; - } return false; - #else - LOG(Warning, "Editing cooked content is invalid."); return true; - #endif } diff --git a/Source/Engine/Core/Math/BoundingBox.cs b/Source/Engine/Core/Math/BoundingBox.cs index e0abe7e03..cd702d531 100644 --- a/Source/Engine/Core/Math/BoundingBox.cs +++ b/Source/Engine/Core/Math/BoundingBox.cs @@ -673,23 +673,23 @@ namespace FlaxEngine /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(ref BoundingBox value) + public bool Equals(ref BoundingBox other) { - return Minimum == value.Minimum && Maximum == value.Maximum; + return Minimum == other.Minimum && Maximum == other.Maximum; } /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(BoundingBox value) + public bool Equals(BoundingBox other) { - return Equals(ref value); + return Equals(ref other); } /// diff --git a/Source/Engine/Core/Math/BoundingSphere.cs b/Source/Engine/Core/Math/BoundingSphere.cs index 1a03967f4..b57177d77 100644 --- a/Source/Engine/Core/Math/BoundingSphere.cs +++ b/Source/Engine/Core/Math/BoundingSphere.cs @@ -487,23 +487,23 @@ namespace FlaxEngine /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(ref BoundingSphere value) + public bool Equals(ref BoundingSphere other) { - return (Center == value.Center) && (Radius == value.Radius); + return Center == other.Center && Radius == other.Radius; } /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(BoundingSphere value) + public bool Equals(BoundingSphere other) { - return Equals(ref value); + return Equals(ref other); } /// diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index ee7190b53..2d779dcfa 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -197,12 +197,9 @@ namespace FlaxEngine } /// - public override bool Equals(object other) + public override bool Equals(object value) { - if (!(other is Color)) - return false; - var color = (Color)other; - return Equals(ref color); + return value is Color other && Equals(ref other); } /// @@ -213,7 +210,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Color other) { - return Mathf.NearEqual(other.R, R) && Mathf.NearEqual(other.G, G) && Mathf.NearEqual(other.B, B) && Mathf.NearEqual(other.A, A); + return R == other.R && G == other.G && B == other.B && A == other.A; } /// @@ -661,23 +658,23 @@ namespace FlaxEngine /// /// Compares two colors. /// - /// The left. - /// The right. + /// The left. + /// The right. /// True if colors are equal, otherwise false. - public static bool operator ==(Color lhs, Color rhs) + public static bool operator ==(Color left, Color right) { - return lhs.Equals(ref rhs); + return left.Equals(ref right); } /// /// Compares two colors. /// - /// The left. - /// The right. + /// The left. + /// The right. /// True if colors are not equal, otherwise false. - public static bool operator !=(Color lhs, Color rhs) + public static bool operator !=(Color left, Color right) { - return !lhs.Equals(ref rhs); + return !left.Equals(ref right); } /// diff --git a/Source/Engine/Core/Math/Double2.cs b/Source/Engine/Core/Math/Double2.cs index b6ef33146..9594b22cb 100644 --- a/Source/Engine/Core/Math/Double2.cs +++ b/Source/Engine/Core/Math/Double2.cs @@ -1464,7 +1464,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Double2 left, Double2 right) { - return Mathd.NearEqual(left.X, right.X) && Mathd.NearEqual(left.Y, right.Y); + return left.Equals(ref right); } /// @@ -1476,7 +1476,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Double2 left, Double2 right) { - return !Mathd.NearEqual(left.X, right.X) || !Mathd.NearEqual(left.Y, right.Y); + return !left.Equals(ref right); } /// @@ -1582,7 +1582,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Double2 other) { - return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y); + return X == other.X && Y == other.Y; } /// @@ -1590,7 +1590,7 @@ namespace FlaxEngine /// public static bool Equals(ref Double2 a, ref Double2 b) { - return Mathd.NearEqual(a.X, b.X) && Mathd.NearEqual(a.Y, b.Y); + return a.Equals(ref b); } /// @@ -1601,7 +1601,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Double2 other) { - return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y); + return Equals(ref other); } /// @@ -1611,7 +1611,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Double2 other && Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y); + return value is Double2 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Double3.cs b/Source/Engine/Core/Math/Double3.cs index 0271612f4..cb26cf071 100644 --- a/Source/Engine/Core/Math/Double3.cs +++ b/Source/Engine/Core/Math/Double3.cs @@ -1759,7 +1759,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Double3 left, Double3 right) { - return Mathd.NearEqual(left.X, right.X) && Mathd.NearEqual(left.Y, right.Y) && Mathd.NearEqual(left.Z, right.Z); + return left.Equals(ref right); } /// @@ -1771,7 +1771,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Double3 left, Double3 right) { - return !Mathd.NearEqual(left.X, right.X) || !Mathd.NearEqual(left.Y, right.Y) || !Mathd.NearEqual(left.Z, right.Z); + return !left.Equals(ref right); } /// @@ -1880,7 +1880,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Double3 other) { - return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z); + return X == other.X && Y == other.Y && Z == other.Z; } /// @@ -1891,7 +1891,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Double3 other) { - return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z); + return Equals(ref other); } /// @@ -1901,7 +1901,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Double3 other && Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z); + return value is Double3 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Double4.cs b/Source/Engine/Core/Math/Double4.cs index f9f79069c..70d27cb28 100644 --- a/Source/Engine/Core/Math/Double4.cs +++ b/Source/Engine/Core/Math/Double4.cs @@ -1258,7 +1258,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Double4 left, Double4 right) { - return Mathd.NearEqual(left.X, right.X) && Mathd.NearEqual(left.Y, right.Y) && Mathd.NearEqual(left.Z, right.Z) && Mathd.NearEqual(left.W, right.W); + return left.Equals(ref right); } /// @@ -1379,7 +1379,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Double4 other) { - return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z) && Mathd.NearEqual(other.W, W); + return X == other.X && Y == other.Y && Z == other.Z && W == other.W; } /// @@ -1390,7 +1390,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Double4 other) { - return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z) && Mathd.NearEqual(other.W, W); + return Equals(ref other); } /// @@ -1400,7 +1400,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Double4 other && Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z) && Mathd.NearEqual(other.W, W); + return value is Double4 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Float2.cs b/Source/Engine/Core/Math/Float2.cs index 0da460889..1b70dd0a6 100644 --- a/Source/Engine/Core/Math/Float2.cs +++ b/Source/Engine/Core/Math/Float2.cs @@ -1540,7 +1540,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Float2 left, Float2 right) { - return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y); + return left.Equals(ref right); } /// @@ -1552,7 +1552,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Float2 left, Float2 right) { - return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y); + return !left.Equals(ref right); } /// @@ -1658,7 +1658,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Float2 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); + return X == other.X && Y == other.Y; } /// @@ -1666,7 +1666,7 @@ namespace FlaxEngine /// public static bool Equals(ref Float2 a, ref Float2 b) { - return Mathf.NearEqual(a.X, b.X) && Mathf.NearEqual(a.Y, b.Y); + return a.Equals(ref b); } /// @@ -1677,7 +1677,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Float2 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); + return Equals(ref other); } /// @@ -1687,7 +1687,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Float2 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y); + return value is Float2 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Float3.cs b/Source/Engine/Core/Math/Float3.cs index 659e6562a..5e8dceed6 100644 --- a/Source/Engine/Core/Math/Float3.cs +++ b/Source/Engine/Core/Math/Float3.cs @@ -1791,7 +1791,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Float3 left, Float3 right) { - return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z); + return left.Equals(ref right); } /// @@ -1803,7 +1803,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Float3 left, Float3 right) { - return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y) || !Mathf.NearEqual(left.Z, right.Z); + return !left.Equals(ref right); } /// @@ -1912,7 +1912,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Float3 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); + return X == other.X && Y == other.Y && Z == other.Z; } /// @@ -1923,7 +1923,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Float3 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); + return Equals(ref other); } /// @@ -1933,7 +1933,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Float3 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z); + return value is Float3 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Float4.cs b/Source/Engine/Core/Math/Float4.cs index 2e48c9d7f..b6eb6dd9e 100644 --- a/Source/Engine/Core/Math/Float4.cs +++ b/Source/Engine/Core/Math/Float4.cs @@ -1288,7 +1288,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Float4 left, Float4 right) { - return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z) && Mathf.NearEqual(left.W, right.W); + return left.Equals(ref right); } /// @@ -1419,7 +1419,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Float4 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return X == other.X && Y == other.Y && Z == other.Z && W == other.W; } /// @@ -1430,7 +1430,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Float4 other) { - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return Equals(ref other); } /// @@ -1440,7 +1440,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Float4 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return value is Float4 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Matrix.cpp b/Source/Engine/Core/Math/Matrix.cpp index dea1d77d2..4a73323d3 100644 --- a/Source/Engine/Core/Math/Matrix.cpp +++ b/Source/Engine/Core/Math/Matrix.cpp @@ -163,7 +163,7 @@ bool Matrix::operator==(const Matrix& other) const { for (int32 i = 0; i < 16; i++) { - if (Math::NotNearEqual(other.Raw[i], Raw[i])) + if (other.Raw[i] != Raw[i]) return false; } return true; diff --git a/Source/Engine/Core/Math/Matrix.cs b/Source/Engine/Core/Math/Matrix.cs index 9fe1bdc49..07ab2dea9 100644 --- a/Source/Engine/Core/Math/Matrix.cs +++ b/Source/Engine/Core/Math/Matrix.cs @@ -3236,22 +3236,22 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Matrix other) { - return Mathf.NearEqual(other.M11, M11) && - Mathf.NearEqual(other.M12, M12) && - Mathf.NearEqual(other.M13, M13) && - Mathf.NearEqual(other.M14, M14) && - Mathf.NearEqual(other.M21, M21) && - Mathf.NearEqual(other.M22, M22) && - Mathf.NearEqual(other.M23, M23) && - Mathf.NearEqual(other.M24, M24) && - Mathf.NearEqual(other.M31, M31) && - Mathf.NearEqual(other.M32, M32) && - Mathf.NearEqual(other.M33, M33) && - Mathf.NearEqual(other.M34, M34) && - Mathf.NearEqual(other.M41, M41) && - Mathf.NearEqual(other.M42, M42) && - Mathf.NearEqual(other.M43, M43) && - Mathf.NearEqual(other.M44, M44); + return other.M11 == M11 && + other.M12 == M12 && + other.M13 == M13 && + other.M14 == M14 && + other.M21 == M21 && + other.M22 == M22 && + other.M23 == M23 && + other.M24 == M24 && + other.M31 == M31 && + other.M32 == M32 && + other.M33 == M33 && + other.M34 == M34 && + other.M41 == M41 && + other.M42 == M42 && + other.M43 == M43 && + other.M44 == M44; } /// @@ -3272,10 +3272,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - if (!(value is Matrix)) - return false; - var v = (Matrix)value; - return Equals(ref v); + return value is Matrix other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Matrix2x2.cs b/Source/Engine/Core/Math/Matrix2x2.cs index 250789e80..7244d41bd 100644 --- a/Source/Engine/Core/Math/Matrix2x2.cs +++ b/Source/Engine/Core/Math/Matrix2x2.cs @@ -483,7 +483,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Matrix2x2 other) { - return Mathf.NearEqual(other.M11, M11) && Mathf.NearEqual(other.M12, M12) && Mathf.NearEqual(other.M21, M21) && Mathf.NearEqual(other.M22, M22); + return M11 == other.M11 && M12 == other.M12 && M21 == other.M21 && M22 == other.M22; } /// @@ -502,7 +502,7 @@ namespace FlaxEngine /// public static bool Equals(ref Matrix2x2 a, ref Matrix2x2 b) { - return Mathf.NearEqual(a.M11, b.M11) && Mathf.NearEqual(a.M12, b.M12) && Mathf.NearEqual(a.M21, b.M21) && Mathf.NearEqual(a.M22, b.M22); + return a.Equals(ref b); } /// diff --git a/Source/Engine/Core/Math/Matrix3x3.cpp b/Source/Engine/Core/Math/Matrix3x3.cpp index 8478b2ca1..218f2dad6 100644 --- a/Source/Engine/Core/Math/Matrix3x3.cpp +++ b/Source/Engine/Core/Math/Matrix3x3.cpp @@ -242,14 +242,13 @@ void Matrix3x3::Decompose(Float3& scale, Quaternion& rotation) const bool Matrix3x3::operator==(const Matrix3x3& other) const { - return - Math::NearEqual(M11, other.M11) && - Math::NearEqual(M12, other.M12) && - Math::NearEqual(M13, other.M13) && - Math::NearEqual(M21, other.M21) && - Math::NearEqual(M22, other.M22) && - Math::NearEqual(M23, other.M23) && - Math::NearEqual(M31, other.M31) && - Math::NearEqual(M32, other.M32) && - Math::NearEqual(M33, other.M33); + return M11 == other.M11 && + M12 == other.M12 && + M13 == other.M13 && + M21 == other.M21 && + M22 == other.M22 && + M23 == other.M23 && + M31 == other.M31 && + M32 == other.M32 && + M33 == other.M33; } diff --git a/Source/Engine/Core/Math/Matrix3x3.cs b/Source/Engine/Core/Math/Matrix3x3.cs index 9522c4087..76e10ed1a 100644 --- a/Source/Engine/Core/Math/Matrix3x3.cs +++ b/Source/Engine/Core/Math/Matrix3x3.cs @@ -2125,15 +2125,15 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Matrix3x3 other) { - return (Mathf.NearEqual(other.M11, M11) && - Mathf.NearEqual(other.M12, M12) && - Mathf.NearEqual(other.M13, M13) && - Mathf.NearEqual(other.M21, M21) && - Mathf.NearEqual(other.M22, M22) && - Mathf.NearEqual(other.M23, M23) && - Mathf.NearEqual(other.M31, M31) && - Mathf.NearEqual(other.M32, M32) && - Mathf.NearEqual(other.M33, M33)); + return M11 == other.M11 && + M12 == other.M12 && + M13 == other.M13 && + M21 == other.M21 && + M22 == other.M22 && + M23 == other.M23 && + M31 == other.M31 && + M32 == other.M32 && + M33 == other.M33; } /// @@ -2152,17 +2152,7 @@ namespace FlaxEngine /// public static bool Equals(ref Matrix3x3 a, ref Matrix3x3 b) { - return - Mathf.NearEqual(a.M11, b.M11) && - Mathf.NearEqual(a.M12, b.M12) && - Mathf.NearEqual(a.M13, b.M13) && - Mathf.NearEqual(a.M21, b.M21) && - Mathf.NearEqual(a.M22, b.M22) && - Mathf.NearEqual(a.M23, b.M23) && - Mathf.NearEqual(a.M31, b.M31) && - Mathf.NearEqual(a.M32, b.M32) && - Mathf.NearEqual(a.M33, b.M33) - ; + return a.Equals(ref b); } /// diff --git a/Source/Engine/Core/Math/OrientedBoundingBox.cs b/Source/Engine/Core/Math/OrientedBoundingBox.cs index 8171247e6..59ffd0009 100644 --- a/Source/Engine/Core/Math/OrientedBoundingBox.cs +++ b/Source/Engine/Core/Math/OrientedBoundingBox.cs @@ -397,7 +397,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref OrientedBoundingBox value) { - return (Extents == value.Extents) && (Transformation == value.Transformation); + return Extents == value.Extents && Transformation == value.Transformation; } /// diff --git a/Source/Engine/Core/Math/Plane.cs b/Source/Engine/Core/Math/Plane.cs index dc565b054..7156f4562 100644 --- a/Source/Engine/Core/Math/Plane.cs +++ b/Source/Engine/Core/Math/Plane.cs @@ -582,23 +582,23 @@ namespace FlaxEngine /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(ref Plane value) + public bool Equals(ref Plane other) { - return Normal == value.Normal && D == value.D; + return Normal == other.Normal && D == other.D; } /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Plane value) + public bool Equals(Plane other) { - return Equals(ref value); + return Equals(ref other); } /// @@ -608,10 +608,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - if (!(value is Plane)) - return false; - var strongValue = (Plane)value; - return Equals(ref strongValue); + return value is Plane other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index 935354354..d89b71488 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -1149,7 +1149,7 @@ namespace FlaxEngine } /// - /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized. + /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis. The input vectors don't need to be normalized. /// /// The source vector. /// The destination vector. @@ -1179,7 +1179,7 @@ namespace FlaxEngine } /// - /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized. + /// Gets the quaternion that will rotate the from into vector to, around their plan perpendicular axis. The input vectors don't need to be normalized. /// /// The source vector. /// The destination vector. @@ -1602,7 +1602,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) > Tolerance; + return left.Equals(ref right); } /// @@ -1614,7 +1614,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) <= Tolerance; + return !left.Equals(ref right); } /// @@ -1714,8 +1714,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Quaternion other) { - //return Dot(ref this, ref other) > Tolerance; - return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); + return X == other.X && Y == other.Y && Z == other.Z && W == other.W; } /// @@ -1736,10 +1735,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - if (!(value is Quaternion)) - return false; - var strongValue = (Quaternion)value; - return Equals(ref strongValue); + return value is Quaternion other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 2e300811a..965f92078 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -348,7 +348,7 @@ public: /// true if the specified is equal to this instance; otherwise, false. FORCE_INLINE bool operator==(const Quaternion& other) const { - return Dot(*this, other) > Tolerance; + return X == other.X && Y == other.Y && Z == other.Z && W == other.W; } /// @@ -358,7 +358,7 @@ public: /// true if the specified isn't equal to this instance; otherwise, false. FORCE_INLINE bool operator!=(const Quaternion& other) const { - return Dot(*this, other) < Tolerance; + return X != other.X || Y != other.Y || Z != other.Z || W != other.W; } public: diff --git a/Source/Engine/Core/Math/Ray.cs b/Source/Engine/Core/Math/Ray.cs index 3f5bde0e6..36203cbf7 100644 --- a/Source/Engine/Core/Math/Ray.cs +++ b/Source/Engine/Core/Math/Ray.cs @@ -428,23 +428,23 @@ namespace FlaxEngine /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(ref Ray value) + public bool Equals(ref Ray other) { - return (Position == value.Position) && (Direction == value.Direction); + return Position == other.Position && Direction == other.Direction; } /// /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Ray value) + public bool Equals(Ray other) { - return Equals(ref value); + return Equals(ref other); } /// diff --git a/Source/Engine/Core/Math/Rectangle.cs b/Source/Engine/Core/Math/Rectangle.cs index ee2a1703c..81c689d48 100644 --- a/Source/Engine/Core/Math/Rectangle.cs +++ b/Source/Engine/Core/Math/Rectangle.cs @@ -499,21 +499,19 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Rectangle other) { - return Location.Equals(ref other.Location) && Size.Equals(ref other.Size); + return Location == other.Location && Size == other.Size; } /// public bool Equals(Rectangle other) { - return Location.Equals(ref other.Location) && Size.Equals(ref other.Size); + return Equals(ref other); } /// public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) - return false; - return obj is Rectangle && Equals((Rectangle)obj); + return obj is Rectangle other && Equals(ref other); } /// diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index ae317897b..8e1599513 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -1654,7 +1654,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector2 left, Vector2 right) { - return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y); + return left.Equals(ref right); } /// @@ -1666,7 +1666,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Vector2 left, Vector2 right) { - return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y); + return !left.Equals(ref right); } /// @@ -1782,7 +1782,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Vector2 other) { - return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y); + return X == other.X && Y == other.Y; } /// @@ -1790,7 +1790,7 @@ namespace FlaxEngine /// public static bool Equals(ref Vector2 a, ref Vector2 b) { - return Mathr.NearEqual(a.X, b.X) && Mathr.NearEqual(a.Y, b.Y); + return a.Equals(ref b); } /// @@ -1801,7 +1801,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector2 other) { - return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y); + return Equals(ref other); } /// @@ -1811,7 +1811,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Vector2 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y); + return value is Vector2 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 845cf2f97..5e01a7a6c 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -2010,7 +2010,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector3 left, Vector3 right) { - return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z); + return left.Equals(ref right); } /// @@ -2022,7 +2022,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Vector3 left, Vector3 right) { - return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y) || !Mathr.NearEqual(left.Z, right.Z); + return !left.Equals(ref right); } /// @@ -2141,7 +2141,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Vector3 other) { - return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z); + return X == other.X && Y == other.Y && Z == other.Z; } /// @@ -2152,7 +2152,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector3 other) { - return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z); + return Equals(ref other); } /// @@ -2162,7 +2162,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Vector3 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z); + return value is Vector3 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs index 1564a9ee0..e50d03ca0 100644 --- a/Source/Engine/Core/Math/Vector4.cs +++ b/Source/Engine/Core/Math/Vector4.cs @@ -1362,7 +1362,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector4 left, Vector4 right) { - return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z) && Mathr.NearEqual(left.W, right.W); + return left.Equals(ref right); } /// @@ -1493,7 +1493,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Vector4 other) { - return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W); + return X == other.X && Y == other.Y && Z == other.Z && W == other.W; } /// @@ -1504,7 +1504,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Vector4 other) { - return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W); + return Equals(ref other); } /// @@ -1514,7 +1514,7 @@ namespace FlaxEngine /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { - return value is Vector4 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W); + return value is Vector4 other && Equals(ref other); } } } diff --git a/Source/Engine/Core/ScopeExit.h b/Source/Engine/Core/ScopeExit.h new file mode 100644 index 000000000..37068a2ff --- /dev/null +++ b/Source/Engine/Core/ScopeExit.h @@ -0,0 +1,36 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Core.h" + +template +struct ScopeExit +{ + explicit ScopeExit(FuncType&& func) + : _func((FuncType&&)func) + { + } + + ~ScopeExit() + { + _func(); + } + +private: + FuncType _func; +}; + +namespace THelpers +{ + struct ScopeExitInternal + { + template + ScopeExit operator*(FuncType&& func) + { + return ScopeExit((FuncType&&)func); + } + }; +} + +#define SCOPE_EXIT const auto CONCAT_MACROS(__scopeExit, __LINE__) = THelpers::ScopeExitInternal() * [&]() diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 873aba0eb..0ce2d8387 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -1162,9 +1162,9 @@ bool Variant::operator==(const Variant& other) const case VariantType::Enum: return AsEnum == other.AsEnum; case VariantType::Float: - return Math::NearEqual(AsFloat, other.AsFloat); + return AsFloat == other.AsFloat; case VariantType::Double: - return Math::Abs(AsDouble - other.AsDouble) < ZeroTolerance; + return AsDouble == other.AsDouble; case VariantType::Pointer: return AsPointer == other.AsPointer; case VariantType::String: diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index df2e41803..752e8bf24 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -99,6 +99,7 @@ struct DebugGeometryBuffer { GPUBuffer* Buffer; float TimeLeft; + bool Lines; Matrix Transform; }; @@ -234,6 +235,14 @@ void TeleportList(const Float3& delta, Array& list) } } +void TeleportList(const Float3& delta, Array& list) +{ + for (auto& v : list) + { + v.Transform.SetTranslation(v.Transform.GetTranslation() + delta); + } +} + struct DebugDrawData { Array GeometryBuffers; @@ -302,6 +311,7 @@ struct DebugDrawData void Teleport(const Float3& delta) { + TeleportList(delta, GeometryBuffers); TeleportList(delta, DefaultLines); TeleportList(delta, OneFrameLines); TeleportList(delta, DefaultTriangles); @@ -812,6 +822,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe defaultWireTriangles = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultWireTriangles, Context->DebugDrawDefault.OneFrameWireTriangles); { PROFILE_CPU_NAMED("Flush"); + ZoneValue(DebugDrawVB->Data.Count() / 1024); // Size in kB DebugDrawVB->Flush(context); } } @@ -871,8 +882,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe Matrix mvp; Matrix::Multiply(geometry.Transform, vp, mvp); Matrix::Transpose(mvp, tmp.ViewProjection); + auto state = data.EnableDepthTest ? (geometry.Lines ? &DebugDrawPsLinesDepthTest : &DebugDrawPsTrianglesDepthTest) : (geometry.Lines ? &DebugDrawPsLinesDefault : &DebugDrawPsTrianglesDefault); context->UpdateCB(cb, &tmp); - auto state = data.EnableDepthTest ? &DebugDrawPsLinesDepthTest : &DebugDrawPsLinesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&geometry.Buffer, 1)); context->Draw(0, geometry.Buffer->GetElementsCount()); @@ -920,8 +931,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe Matrix mvp; Matrix::Multiply(geometry.Transform, vp, mvp); Matrix::Transpose(mvp, tmp.ViewProjection); + auto state = geometry.Lines ? &DebugDrawPsLinesDefault : &DebugDrawPsTrianglesDefault; context->UpdateCB(cb, &tmp); - context->SetState(DebugDrawPsLinesDefault.Get(false, false)); + context->SetState(state->Get(false, false)); context->BindVB(ToSpan(&geometry.Buffer, 1)); context->Draw(0, geometry.Buffer->GetElementsCount()); } @@ -1166,6 +1178,7 @@ void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float durat auto& geometry = debugDrawData.GeometryBuffers.AddOne(); geometry.Buffer = lines; geometry.TimeLeft = duration; + geometry.Lines = true; geometry.Transform = transform * Matrix::Translation(-Context->Origin); } @@ -1522,6 +1535,23 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Matrix& transf } } +void DebugDraw::DrawTriangles(GPUBuffer* triangles, const Matrix& transform, float duration, bool depthTest) +{ + if (triangles == nullptr || triangles->GetSize() == 0) + return; + if (triangles->GetSize() % (sizeof(Vertex) * 3) != 0) + { + DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array"); + return; + } + auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault; + auto& geometry = debugDrawData.GeometryBuffers.AddOne(); + geometry.Buffer = triangles; + geometry.TimeLeft = duration; + geometry.Lines = false; + geometry.Transform = transform * Matrix::Translation(-Context->Origin); +} + void DebugDraw::DrawTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) { DrawTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index aa3836105..3b51c0e13 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -74,7 +74,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw API_FUNCTION() static bool CanClear(void* context = nullptr); #endif - // Gets the last view position when rendering the current context. Can be sued for custom culling or LODing when drawing more complex shapes. + // Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes. static Vector3 GetViewPos(); /// @@ -296,12 +296,21 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// Draws the triangles. /// /// The triangle vertices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles using the provided vertex buffer that contains groups of 3 Vertex elements per-triangle. + /// + /// The GPU buffer with vertices for triangles (must have multiple of 3 elements). + /// The custom matrix used to transform all triangle vertices. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawTriangles(GPUBuffer* triangles, const Matrix& transform, float duration = 0.0f, bool depthTest = true); + /// /// Draws the triangles. /// @@ -315,7 +324,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// Draws the triangles. /// /// The triangle vertices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. @@ -336,7 +345,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The triangle vertices list. /// The triangle indices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. @@ -357,7 +366,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The triangle vertices list. /// The triangle indices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. @@ -376,7 +385,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// Draws the triangles. /// /// The triangle vertices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. @@ -395,7 +404,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// Draws the triangles. /// /// The triangle vertices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. @@ -416,7 +425,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The triangle vertices list. /// The triangle indices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. @@ -437,7 +446,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The triangle vertices list. /// The triangle indices list (must have multiple of 3 elements). - /// The custom matrix used to transform all line vertices. + /// The custom matrix used to transform all triangle vertices. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index a20c1780b..d9a4173c8 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -459,6 +459,15 @@ bool Engine::IsEditor() #endif } +bool Engine::IsPlayMode() +{ +#if USE_EDITOR + return Editor::IsPlayMode; +#else + return true; +#endif +} + int32 Engine::GetFramesPerSecond() { return EngineImpl::Fps; diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index e8b6fef2c..1af16a930 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -178,6 +178,11 @@ public: /// API_PROPERTY() static bool IsEditor(); + /// + /// Returns whether the editor is in play mode or will always return true in a shipped applications. + /// + API_PROPERTY() static bool IsPlayMode(); + /// /// Gets the amount of frames rendered during last second known as Frames Per Second. User scripts updates or fixed updates for physics may run at a different frequency than scene rendering. Use this property to get an accurate amount of frames rendered during the last second. /// diff --git a/Source/Engine/Foliage/FoliageInstance.h b/Source/Engine/Foliage/FoliageInstance.h index 5022d12be..76037f0f4 100644 --- a/Source/Engine/Foliage/FoliageInstance.h +++ b/Source/Engine/Foliage/FoliageInstance.h @@ -52,7 +52,7 @@ API_STRUCT(NoPod) struct FLAXENGINE_API FoliageInstance public: bool operator==(const FoliageInstance& v) const { - return Type == v.Type && Math::NearEqual(Random, v.Random) && Transform == v.Transform; + return Type == v.Type && Random == v.Random && Transform == v.Transform; } /// diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 4566e764b..8e401b973 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -428,6 +428,21 @@ public: /// Array Animations; +public: + // See ModelTool::PositionFormat + enum class PositionFormats + { + Float32, + Float16, + } PositionFormat = PositionFormats::Float32; + + // See ModelTool::TexCoordFormats + enum class TexCoordFormats + { + Float16, + UNorm8, + } TexCoordFormat = TexCoordFormats::Float16; + public: /// /// Automatically calculates the screen size for every model LOD for a proper transitions. diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index dc5ecc236..e0fc0297f 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -1230,7 +1230,7 @@ void InputService::Update() { for (auto i = Axes.Begin(); i.IsNotEnd(); ++i) { - if (Math::NotNearEqual(i->Value.Value, i->Value.PrevValue)) + if (i->Value.Value != i->Value.PrevValue) { Input::AxisValueChanged(i->Key); } diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 12039da5e..02210d910 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -36,8 +36,6 @@ #define CHECK_EXECUTE_IN_EDITOR #endif -#define ACTOR_ORIENTATION_EPSILON 0.000000001f - // Start loop over actor children/scripts from the beginning to account for any newly added or removed actors. #define ACTOR_LOOP_START_MODIFIED_HIERARCHY() _isHierarchyDirty = false #define ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY() if (_isHierarchyDirty) { _isHierarchyDirty = false; i = -1; } @@ -660,7 +658,7 @@ void Actor::SetStaticFlags(StaticFlags value) void Actor::SetTransform(const Transform& value) { CHECK(!value.IsNanOrInfinity()); - if (!(Vector3::NearEqual(_transform.Translation, value.Translation) && Quaternion::NearEqual(_transform.Orientation, value.Orientation, ACTOR_ORIENTATION_EPSILON) && Float3::NearEqual(_transform.Scale, value.Scale))) + if (_transform.Translation != value.Translation || _transform.Orientation != value.Orientation || _transform.Scale != value.Scale) { if (_parent) _parent->_transform.WorldToLocal(value, _localTransform); @@ -673,7 +671,7 @@ void Actor::SetTransform(const Transform& value) void Actor::SetPosition(const Vector3& value) { CHECK(!value.IsNanOrInfinity()); - if (!Vector3::NearEqual(_transform.Translation, value)) + if (_transform.Translation != value) { if (_parent) _localTransform.Translation = _parent->_transform.WorldToLocal(value); @@ -686,7 +684,7 @@ void Actor::SetPosition(const Vector3& value) void Actor::SetOrientation(const Quaternion& value) { CHECK(!value.IsNanOrInfinity()); - if (!Quaternion::NearEqual(_transform.Orientation, value, ACTOR_ORIENTATION_EPSILON)) + if (_transform.Orientation != value) { if (_parent) _parent->_transform.WorldToLocal(value, _localTransform.Orientation); @@ -699,7 +697,7 @@ void Actor::SetOrientation(const Quaternion& value) void Actor::SetScale(const Float3& value) { CHECK(!value.IsNanOrInfinity()); - if (!Float3::NearEqual(_transform.Scale, value)) + if (_transform.Scale != value) { if (_parent) Float3::Divide(value, _parent->_transform.Scale, _localTransform.Scale); @@ -748,7 +746,7 @@ void Actor::ResetLocalTransform() void Actor::SetLocalTransform(const Transform& value) { CHECK(!value.IsNanOrInfinity()); - if (!(Vector3::NearEqual(_localTransform.Translation, value.Translation) && Quaternion::NearEqual(_localTransform.Orientation, value.Orientation, ACTOR_ORIENTATION_EPSILON) && Float3::NearEqual(_localTransform.Scale, value.Scale))) + if (_localTransform.Translation != value.Translation || _localTransform.Orientation != value.Orientation || _localTransform.Scale != value.Scale) { _localTransform = value; OnTransformChanged(); @@ -758,7 +756,7 @@ void Actor::SetLocalTransform(const Transform& value) void Actor::SetLocalPosition(const Vector3& value) { CHECK(!value.IsNanOrInfinity()); - if (!Vector3::NearEqual(_localTransform.Translation, value)) + if (_localTransform.Translation != value) { _localTransform.Translation = value; OnTransformChanged(); @@ -770,7 +768,7 @@ void Actor::SetLocalOrientation(const Quaternion& value) CHECK(!value.IsNanOrInfinity()); Quaternion v = value; v.Normalize(); - if (!Quaternion::NearEqual(_localTransform.Orientation, v, ACTOR_ORIENTATION_EPSILON)) + if (_localTransform.Orientation != value) { _localTransform.Orientation = v; OnTransformChanged(); @@ -780,7 +778,7 @@ void Actor::SetLocalOrientation(const Quaternion& value) void Actor::SetLocalScale(const Float3& value) { CHECK(!value.IsNanOrInfinity()); - if (!Float3::NearEqual(_localTransform.Scale, value)) + if (_localTransform.Scale != value) { _localTransform.Scale = value; OnTransformChanged(); diff --git a/Source/Engine/Level/Actors/BoxBrush.cpp b/Source/Engine/Level/Actors/BoxBrush.cpp index 64f4d208a..06bc047bc 100644 --- a/Source/Engine/Level/Actors/BoxBrush.cpp +++ b/Source/Engine/Level/Actors/BoxBrush.cpp @@ -65,7 +65,7 @@ void BoxBrush::SetMode(BrushMode value) void BoxBrush::SetCenter(const Vector3& value) { - if (Vector3::NearEqual(value, _center)) + if (value == _center) return; _center = value; @@ -77,7 +77,7 @@ void BoxBrush::SetCenter(const Vector3& value) void BoxBrush::SetSize(const Vector3& value) { - if (Vector3::NearEqual(value, _size)) + if (value == _size) return; _size = value; diff --git a/Source/Engine/Level/Actors/BoxVolume.cpp b/Source/Engine/Level/Actors/BoxVolume.cpp index bbce7db28..505f18dd5 100644 --- a/Source/Engine/Level/Actors/BoxVolume.cpp +++ b/Source/Engine/Level/Actors/BoxVolume.cpp @@ -12,7 +12,7 @@ BoxVolume::BoxVolume(const SpawnParams& params) void BoxVolume::SetSize(const Vector3& value) { - if (!Vector3::NearEqual(value, _size)) + if (value != _size) { const auto prevBounds = _box; _size = value; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index ef8bd812a..490f9d786 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -70,7 +70,7 @@ float Camera::GetFieldOfView() const void Camera::SetFieldOfView(float value) { value = Math::Clamp(value, 1.0f, 179.9f); - if (Math::NotNearEqual(_fov, value)) + if (_fov != value) { _fov = value; UpdateCache(); @@ -85,7 +85,7 @@ float Camera::GetCustomAspectRatio() const void Camera::SetCustomAspectRatio(float value) { value = Math::Clamp(value, 0.0f, 100.0f); - if (Math::NotNearEqual(_customAspectRatio, value)) + if (_customAspectRatio != value) { _customAspectRatio = value; UpdateCache(); @@ -100,7 +100,7 @@ float Camera::GetNearPlane() const void Camera::SetNearPlane(float value) { value = Math::Clamp(value, 0.001f, _far - 1.0f); - if (Math::NotNearEqual(_near, value)) + if (_near != value) { _near = value; UpdateCache(); @@ -115,7 +115,7 @@ float Camera::GetFarPlane() const void Camera::SetFarPlane(float value) { value = Math::Max(value, _near + 1.0f); - if (Math::NotNearEqual(_far, value)) + if (_far != value) { _far = value; UpdateCache(); @@ -130,7 +130,7 @@ float Camera::GetOrthographicSize() const void Camera::SetOrthographicSize(float value) { value = Math::Clamp(value, 0.0f, 1000000.0f); - if (Math::NotNearEqual(_orthoSize, value)) + if (_orthoSize != value) { _orthoSize = value; UpdateCache(); @@ -145,7 +145,7 @@ float Camera::GetOrthographicScale() const void Camera::SetOrthographicScale(float value) { value = Math::Clamp(value, 0.0001f, 1000000.0f); - if (Math::NotNearEqual(_orthoScale, value)) + if (_orthoScale != value) { _orthoScale = value; UpdateCache(); diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp index 9e6827ff7..3c49611d4 100644 --- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp +++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp @@ -41,7 +41,7 @@ float EnvironmentProbe::GetRadius() const void EnvironmentProbe::SetRadius(float value) { value = Math::Max(0.0f, value); - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; _radius = value; diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 94f1e77c6..607bf1bc4 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -49,7 +49,7 @@ float PointLight::GetScaledRadius() const void PointLight::SetRadius(float value) { value = Math::Max(0.0f, value); - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; _radius = value; diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 498d28663..4e1c0e58c 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -26,7 +26,7 @@ SkyLight::SkyLight(const SpawnParams& params) void SkyLight::SetRadius(float value) { value = Math::Max(0.0f, value); - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; _radius = value; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index e4fcff857..8680fb8d5 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -59,7 +59,7 @@ float SplineModel::GetQuality() const void SplineModel::SetQuality(float value) { value = Math::Clamp(value, 0.0f, 100.0f); - if (Math::NearEqual(value, _quality)) + if (value == _quality) return; _quality = value; OnSplineUpdated(); @@ -72,7 +72,7 @@ float SplineModel::GetBoundsScale() const void SplineModel::SetBoundsScale(float value) { - if (Math::NearEqual(_boundsScale, value)) + if (_boundsScale == value) return; _boundsScale = value; OnSplineUpdated(); diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index ca577c41f..85b77647a 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -57,7 +57,7 @@ float SpotLight::GetScaledRadius() const void SpotLight::SetRadius(float value) { value = Math::Max(0.0f, value); - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; _radius = value; @@ -70,7 +70,7 @@ void SpotLight::SetOuterConeAngle(float value) value = Math::Clamp(value, 0.0f, 89.0f); // Check if value will change - if (!Math::NearEqual(value, _outerConeAngle)) + if (value != _outerConeAngle) { // Change values _innerConeAngle = Math::Min(_innerConeAngle, value - ZeroTolerance); @@ -86,7 +86,7 @@ void SpotLight::SetInnerConeAngle(float value) value = Math::Clamp(value, 0.0f, 89.0f); // Check if value will change - if (!Math::NearEqual(value, _innerConeAngle)) + if (value != _innerConeAngle) { // Change values _innerConeAngle = value; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index f944da26b..38c1eed90 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -60,7 +60,7 @@ float StaticModel::GetBoundsScale() const void StaticModel::SetBoundsScale(float value) { - if (Math::NearEqual(_boundsScale, value)) + if (_boundsScale == value) return; _boundsScale = value; diff --git a/Source/Engine/Level/MeshReference.cs b/Source/Engine/Level/MeshReference.cs new file mode 100644 index 000000000..14ef0c72b --- /dev/null +++ b/Source/Engine/Level/MeshReference.cs @@ -0,0 +1,22 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + + +using FlaxEngine.Json; + +namespace FlaxEngine +{ + partial class ModelInstanceActor + { + partial struct MeshReference : ICustomValueEquals + { + /// + public bool ValueEquals(object other) + { + var o = (MeshReference)other; + return JsonSerializer.ValueEquals(Actor, o.Actor) && + LODIndex == o.LODIndex && + MeshIndex == o.MeshIndex; + } + } + } +} diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 0df0723a2..4409ae161 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -1090,7 +1090,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr root = dynamic_cast(sceneObjects.Value->At(targetActorIdx)); } - // Try using the first actor without a parent as a new ro0t + // Try using the first actor without a parent as a new root for (int32 i = 1; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects.Value->At(i); diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index f90d5efd1..01f6864c8 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -293,7 +293,7 @@ void NavMeshRuntime::SetTileSize(float tileSize) ScopeLock lock(Locker); // Skip if the same or invalid - if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1) + if (_tileSize == tileSize || tileSize < 1) return; // Dispose the existing mesh (its invalid) diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index 1d54550df..34983652f 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -109,7 +109,7 @@ Color NavMeshRuntime::NavAreasColors[64]; bool NavAgentProperties::operator==(const NavAgentProperties& other) const { - return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle) && Math::NearEqual(MaxSpeed, other.MaxSpeed) && Math::NearEqual(CrowdSeparationWeight, other.CrowdSeparationWeight); + return Radius == other.Radius && Height == other.Height && StepHeight == other.StepHeight && MaxSlopeAngle == other.MaxSlopeAngle && MaxSpeed == other.MaxSpeed && CrowdSeparationWeight == other.CrowdSeparationWeight; } bool NavAgentMask::IsAgentSupported(int32 agentIndex) const @@ -150,12 +150,12 @@ bool NavAgentMask::operator==(const NavAgentMask& other) const bool NavAreaProperties::operator==(const NavAreaProperties& other) const { - return Name == other.Name && Id == other.Id && Math::NearEqual(Cost, other.Cost); + return Name == other.Name && Id == other.Id && Cost == other.Cost; } bool NavMeshProperties::operator==(const NavMeshProperties& other) const { - return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent && Vector3::NearEqual(DefaultQueryExtent, other.DefaultQueryExtent); + return Name == other.Name && Rotation == other.Rotation && Agent == other.Agent && DefaultQueryExtent == other.DefaultQueryExtent; } class NavigationService : public EngineService diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index f198a3b8f..39f87d561 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -223,7 +223,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa #endif } #endif - ASSERT(!isnan(sphere.Radius) && !isinf(sphere.Radius) && !sphere.Center.IsNanOrInfinity()); + CHECK_RETURN(!isnan(sphere.Radius) && !isinf(sphere.Radius) && !sphere.Center.IsNanOrInfinity(), false); // Expand sphere based on the render modules rules (sprite or mesh size) for (int32 moduleIndex = 0; moduleIndex < emitter->Graph.RenderModules.Count(); moduleIndex++) @@ -244,7 +244,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa Vector2::Max(*((Vector2*)spriteSize), maxSpriteSize, maxSpriteSize); spriteSize += stride; } - ASSERT(!maxSpriteSize.IsNanOrInfinity()); + CHECK_RETURN(!maxSpriteSize.IsNanOrInfinity(), false); // Enlarge the emitter bounds sphere sphere.Radius += maxSpriteSize.MaxValue(); @@ -267,7 +267,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa if (radius > maxRadius) maxRadius = radius; } - ASSERT(!isnan(maxRadius) && !isinf(maxRadius)); + CHECK_RETURN(!isnan(maxRadius) && !isinf(maxRadius), false); // Enlarge the emitter bounds sphere sphere.Radius += maxRadius; @@ -315,7 +315,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa maxRibbonWidth = Math::Max(*((float*)ribbonWidth), maxRibbonWidth); ribbonWidth += stride; } - ASSERT(!isnan(maxRibbonWidth) && !isinf(maxRibbonWidth)); + CHECK_RETURN(!isnan(maxRibbonWidth) && !isinf(maxRibbonWidth), false); // Enlarge the emitter bounds sphere sphere.Radius += maxRibbonWidth * 0.5f; @@ -335,7 +335,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa maxRadius = Math::Max(*((float*)radius), maxRadius); radius += stride; } - ASSERT(!isnan(maxRadius) && !isinf(maxRadius)); + CHECK_RETURN(!isnan(maxRadius) && !isinf(maxRadius), false); } else { diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 037dc0000..da7823517 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -340,6 +340,7 @@ void Cloth::DrawPhysicsDebug(RenderView& view) #if WITH_CLOTH && COMPILE_WITH_DEBUG_DRAW if (_cloth) { + PROFILE_CPU(); const ModelInstanceActor::MeshReference mesh = GetMesh(); if (mesh.Actor == nullptr) return; diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 23b32598e..a58911dfb 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -44,7 +44,7 @@ void RigidBody::SetIsKinematic(const bool value) void RigidBody::SetLinearDamping(float value) { - if (Math::NearEqual(value, _linearDamping)) + if (value == _linearDamping) return; _linearDamping = value; if (_actor) @@ -53,7 +53,7 @@ void RigidBody::SetLinearDamping(float value) void RigidBody::SetAngularDamping(float value) { - if (Math::NearEqual(value, _angularDamping)) + if (value == _angularDamping) return; _angularDamping = value; if (_actor) @@ -108,7 +108,7 @@ void RigidBody::SetUpdateMassWhenScaleChanges(bool value) void RigidBody::SetMaxAngularVelocity(float value) { - if (Math::NearEqual(value, _maxAngularVelocity)) + if (value == _maxAngularVelocity) return; _maxAngularVelocity = value; if (_actor) @@ -135,7 +135,7 @@ float RigidBody::GetMass() const void RigidBody::SetMass(float value) { - if (Math::NearEqual(value, _mass)) + if (value == _mass) return; _mass = value; _overrideMass = true; @@ -149,7 +149,7 @@ float RigidBody::GetMassScale() const void RigidBody::SetMassScale(float value) { - if (Math::NearEqual(value, _massScale)) + if (value == _massScale) return; _massScale = value; UpdateMass(); @@ -157,7 +157,7 @@ void RigidBody::SetMassScale(float value) void RigidBody::SetCenterOfMassOffset(const Float3& value) { - if (Float3::NearEqual(value, _centerOfMassOffset)) + if (value == _centerOfMassOffset) return; _centerOfMassOffset = value; if (_actor) @@ -380,7 +380,7 @@ void RigidBody::UpdateBounds() void RigidBody::UpdateScale() { const Float3 scale = GetScale(); - if (Float3::NearEqual(_cachedScale, scale)) + if (_cachedScale == scale) return; _cachedScale = scale; diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index 78e9e4207..160120f87 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -12,7 +12,7 @@ BoxCollider::BoxCollider(const SpawnParams& params) void BoxCollider::SetSize(const Float3& value) { - if (Float3::NearEqual(value, _size)) + if (value == _size) return; _size = value; @@ -162,7 +162,7 @@ void BoxCollider::UpdateBounds() void BoxCollider::GetGeometry(CollisionShape& collision) { - Float3 size = _size * _cachedScale; + Float3 size = _size * _transform.Scale; const float minSize = 0.001f; size = Float3::Max(size.GetAbsolute() * 0.5f, Float3(minSize)); collision.SetBox(size.Raw); diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index e85c7075d..8a9e1d1cf 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -11,7 +11,7 @@ CapsuleCollider::CapsuleCollider(const SpawnParams& params) void CapsuleCollider::SetRadius(const float value) { - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; _radius = value; @@ -22,7 +22,7 @@ void CapsuleCollider::SetRadius(const float value) void CapsuleCollider::SetHeight(const float value) { - if (Math::NearEqual(value, _height)) + if (value == _height) return; _height = value; @@ -43,10 +43,9 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view) return; Quaternion rotation; Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize); + const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize); if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger()) DEBUG_DRAW_CAPSULE(_transform.LocalToWorld(_center), rotation, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true); else @@ -57,10 +56,9 @@ void CapsuleCollider::OnDebugDrawSelected() { Quaternion rotation; Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize); + const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize); const Vector3 position = _transform.LocalToWorld(_center); DEBUG_DRAW_WIRE_CAPSULE(position, rotation, radius, height, Color::GreenYellow, 0, false); @@ -92,9 +90,8 @@ void CapsuleCollider::UpdateBounds() void CapsuleCollider::GetGeometry(CollisionShape& collision) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize); + const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize); collision.SetCapsule(radius, height * 0.5f); } diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 6bfe1514c..74c324bad 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -21,6 +21,7 @@ CharacterController::CharacterController(const SpawnParams& params) , _upDirection(Vector3::Up) , _gravityDisplacement(Vector3::Zero) , _nonWalkableMode(NonWalkableModes::PreventClimbing) + , _originMode(OriginModes::CapsuleCenter) , _lastFlags(CollisionFlags::None) { _contactOffset = 10.0f; @@ -33,11 +34,9 @@ float CharacterController::GetRadius() const void CharacterController::SetRadius(const float value) { - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; - _radius = value; - UpdateSize(); UpdateBounds(); } @@ -49,11 +48,9 @@ float CharacterController::GetHeight() const void CharacterController::SetHeight(const float value) { - if (Math::NearEqual(value, _height)) + if (value == _height) return; - _height = value; - UpdateSize(); UpdateBounds(); } @@ -66,7 +63,7 @@ float CharacterController::GetSlopeLimit() const void CharacterController::SetSlopeLimit(float value) { value = Math::Clamp(value, 0.0f, 89.0f); - if (Math::NearEqual(value, _slopeLimit)) + if (value == _slopeLimit) return; _slopeLimit = value; if (_controller) @@ -87,6 +84,23 @@ void CharacterController::SetNonWalkableMode(NonWalkableModes value) PhysicsBackend::SetControllerNonWalkableMode(_controller, (int32)value); } +CharacterController::OriginModes CharacterController::GetOriginMode() const +{ + return _originMode; +} + +void CharacterController::SetOriginMode(OriginModes value) +{ + if (_originMode == value) + return; + _originMode = value; + if (_controller) + { + DeleteController(); + CreateController(); + } +} + float CharacterController::GetStepOffset() const { return _stepOffset; @@ -94,17 +108,13 @@ float CharacterController::GetStepOffset() const void CharacterController::SetStepOffset(float value) { - if (Math::NearEqual(value, _stepOffset)) + if (value == _stepOffset) return; - _stepOffset = value; - if (_controller) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float contactOffset = Math::Max(_contactOffset, ZeroTolerance); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); - const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, CC_MIN_SIZE); + float height, radius; + GetControllerSize(height, radius); PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - CC_MIN_SIZE)); } } @@ -169,17 +179,69 @@ CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector CharacterController::CollisionFlags CharacterController::Move(const Vector3& displacement) { CollisionFlags result = CollisionFlags::None; - if (_controller) + if (_controller && !_isUpdatingTransform) { + // Perform move const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); result = (CollisionFlags)PhysicsBackend::MoveController(_controller, _shape, displacement, _minMoveDistance, deltaTime); _lastFlags = result; - Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + + // Update position + Vector3 position; + if (_originMode == OriginModes::Base) + position = PhysicsBackend::GetControllerBasePosition(_controller); + else + position = PhysicsBackend::GetControllerPosition(_controller); + position -= _center; + _isUpdatingTransform = true; SetPosition(position); + _isUpdatingTransform = false; } return result; } +void CharacterController::Resize(float height, float radius) +{ + const float heightDiff = height - _height; + const float radiusDiff = radius - _radius; + if (Math::IsZero(heightDiff) && Math::IsZero(radiusDiff)) + return; + _height = height; + _radius = radius; + if (_controller) + { + float centerDiff = heightDiff * 0.5f + radiusDiff; + + // Change physics size + GetControllerSize(height, radius); + PhysicsBackend::SetControllerSize(_controller, radius, height); + Vector3 positionDelta = _upDirection * centerDiff; + + // Change physics position to maintain feet placement (base) + Vector3 position; + switch (_originMode) + { + case OriginModes::CapsuleCenter: + position = PhysicsBackend::GetControllerPosition(_controller); + position += positionDelta; + _center += positionDelta; + PhysicsBackend::SetControllerPosition(_controller, position); + break; + case OriginModes::Base: + position = PhysicsBackend::GetControllerBasePosition(_controller); + position += positionDelta; + PhysicsBackend::SetControllerBasePosition(_controller, position); + break; + } + + // Change actor position + _isUpdatingTransform = true; + SetPosition(position - _center); + _isUpdatingTransform = false; + } + UpdateBounds(); +} + #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" @@ -187,23 +249,47 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis void CharacterController::DrawPhysicsDebug(RenderView& view) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); - const Vector3 position = _transform.LocalToWorld(_center); + Quaternion rotation = Quaternion::Euler(90, 0, 0); + const Vector3 position = GetControllerPosition(); if (view.Mode == ViewMode::PhysicsColliders) - DEBUG_DRAW_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true); + DEBUG_DRAW_CAPSULE(position, rotation, _radius, _height, Color::LightYellow, 0, true); else - DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true); + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow * 0.8f, 0, true); } void CharacterController::OnDebugDrawSelected() { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); - const Vector3 position = _transform.LocalToWorld(_center); - DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false); + Quaternion rotation = Quaternion::Euler(90, 0, 0); + const Vector3 position = GetControllerPosition(); + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow, 0, false); + if (_contactOffset > 0) + DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius - _contactOffset, _height, Color::Blue.AlphaMultiplied(0.4f), 0, false); +#if 1 + // More technical visuals debugging + if (_controller) + { + float height, radius; + GetControllerSize(height, radius); + Vector3 base = PhysicsBackend::GetControllerBasePosition(_controller); + Vector3 pos = PhysicsBackend::GetControllerPosition(_controller); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(base, 5.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos, 4.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false); + DEBUG_DRAW_WIRE_CYLINDER(pos, Quaternion::Identity, radius, height, Color::Red.AlphaMultiplied(0.2f), 0, false); + } + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, 3.0f), Color::GreenYellow, 0, false); +#else + if (_controller) + { + // Physics backend capsule shape + float height, radius; + GetControllerSize(height, radius); + DEBUG_DRAW_WIRE_CAPSULE(PhysicsBackend::GetControllerPosition(_controller), rotation, radius, height, Color::Blue.AlphaMultiplied(0.2f), 0, false); + } +#endif // Base Collider::OnDebugDrawSelected(); @@ -215,10 +301,14 @@ void CharacterController::CreateController() { // Create controller ASSERT(_controller == nullptr && _shape == nullptr); - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const Vector3 position = _transform.LocalToWorld(_center); - _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); + _cachedScale = GetScale().GetAbsolute().MaxValue(); + float height, radius; + GetControllerSize(height, radius); + Vector3 position = _center; + if (_originMode == OriginModes::Base) + position += _upDirection * (_height * 0.5f + _radius); + position = _transform.LocalToWorld(position); + _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, radius, height, _stepOffset, _shape); // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); @@ -241,13 +331,35 @@ void CharacterController::UpdateSize() const { if (_controller) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + float height, radius; + GetControllerSize(height, radius); PhysicsBackend::SetControllerSize(_controller, radius, height); } } +Vector3 CharacterController::GetControllerPosition() const +{ + Vector3 position = _center; + if (_originMode == OriginModes::Base) + position += _upDirection * (_height * 0.5f + _radius); + position = _transform.LocalToWorld(position); + return position; +} + +void CharacterController::GetControllerSize(float& height, float& radius) const +{ + // Use absolute values including scale + height = Math::Abs(_height) * _cachedScale; + radius = Math::Abs(_radius) * _cachedScale; + + // Exclude contact offset around the capsule (otherwise character floats in the air) + radius = radius - Math::Max(_contactOffset, 0.0f); + + // Prevent too small controllers + height = Math::Max(height, CC_MIN_SIZE); + radius = Math::Max(radius, CC_MIN_SIZE); +} + void CharacterController::CreateShape() { // Not used @@ -255,10 +367,10 @@ void CharacterController::CreateShape() void CharacterController::UpdateBounds() { - const float scaling = GetScale().GetAbsolute().MaxValue(); - const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); - const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); - const Vector3 position = _transform.LocalToWorld(_center); + _cachedScale = GetScale().GetAbsolute().MaxValue(); + float height, radius; + GetControllerSize(height, radius); + const Vector3 position = GetControllerPosition(); const Vector3 extent(radius, height * 0.5f + radius, radius); _box = BoundingBox(position - extent, position + extent); BoundingSphere::FromBox(_box, _sphere); @@ -292,6 +404,21 @@ RigidBody* CharacterController::GetAttachedRigidBody() const return nullptr; } +void CharacterController::SetCenter(const Vector3& value) +{ + if (value == _center) + return; + Vector3 delta = value - _center; + _center = value; + if (_controller) + { + // Change physics position while maintaining actor placement + Vector3 position = PhysicsBackend::GetControllerPosition(_controller); + position += _upDirection * delta; + PhysicsBackend::SetControllerPosition(_controller, position); + } +} + void CharacterController::OnActiveTransformChanged() { if (!_shape) @@ -300,7 +427,12 @@ void CharacterController::OnActiveTransformChanged() // Change actor transform (but with locking) ASSERT(!_isUpdatingTransform); _isUpdatingTransform = true; - const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + Vector3 position; + if (_originMode == OriginModes::Base) + position = PhysicsBackend::GetControllerBasePosition(_controller); + else + position = PhysicsBackend::GetControllerPosition(_controller); + position -= _center; SetPosition(position); _isUpdatingTransform = false; @@ -319,7 +451,7 @@ void CharacterController::UpdateGeometry() return; // Setup shape geometry - _cachedScale = GetScale(); + _cachedScale = GetScale().GetAbsolute().MaxValue(); UpdateSize(); } @@ -382,9 +514,12 @@ void CharacterController::OnTransformChanged() const Vector3 position = _transform.LocalToWorld(_center); if (!_isUpdatingTransform && _controller) { - PhysicsBackend::SetControllerPosition(_controller, position); - const Float3 scale = GetScale(); - if (!Float3::NearEqual(_cachedScale, scale)) + if (_originMode == OriginModes::Base) + PhysicsBackend::SetControllerBasePosition(_controller, position); + else + PhysicsBackend::SetControllerPosition(_controller, position); + const float scale = GetScale().GetAbsolute().MaxValue(); + if (_cachedScale != scale) UpdateGeometry(); UpdateBounds(); } diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 736e83951..918f5beb9 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -41,6 +41,22 @@ public: Below = 1 << 2, }; + /// + /// Specifies how a character controller capsule placement. + /// + API_ENUM() enum class OriginModes + { + /// + /// Character origin starts at capsule center (including Center offset properly). + /// + CapsuleCenter, + + /// + /// Character origin starts at capsule base position aka character feet placement. + /// + Base, + }; + /// /// Specifies how a character controller interacts with non-walkable parts. /// @@ -69,6 +85,7 @@ private: Vector3 _upDirection; Vector3 _gravityDisplacement; NonWalkableModes _nonWalkableMode; + OriginModes _originMode; CollisionFlags _lastFlags; public: @@ -84,13 +101,13 @@ public: API_PROPERTY() void SetRadius(float value); /// - /// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. + /// Gets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale. /// API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") float GetHeight() const; /// - /// Sets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. + /// Sets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale. /// API_PROPERTY() void SetHeight(float value); @@ -116,6 +133,17 @@ public: /// API_PROPERTY() void SetNonWalkableMode(NonWalkableModes value); + /// + /// Gets the position origin placement mode. + /// + API_PROPERTY(Attributes="EditorOrder(216), DefaultValue(OriginModes.CapsuleCenter), EditorDisplay(\"Character Controller\")") + OriginModes GetOriginMode() const; + + /// + /// Sets the position origin placement mode. + /// + API_PROPERTY() void SetOriginMode(OriginModes value); + /// /// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error. /// @@ -194,6 +222,13 @@ public: /// The collision flags. It can be used to trigger various character animations. API_FUNCTION() CollisionFlags Move(const Vector3& displacement); + /// + /// Updates the character height and center position to ensure its feet position stays the same. This can be used to implement a 'crouch' functionality for example. Maintains the same actor position to stay in the middle of capsule by adjusting center of collider accordingly to height difference. + /// + /// The height of the capsule, measured in the object's local space. + /// The radius of the capsule, measured in the object's local space. + API_FUNCTION() void Resize(float height, float radius); + protected: /// /// Creates the physics actor. @@ -210,6 +245,10 @@ protected: /// void UpdateSize() const; +private: + Vector3 GetControllerPosition() const; + void GetControllerSize(float& height, float& radius) const; + public: // [Collider] #if USE_EDITOR @@ -220,6 +259,7 @@ public: void AddMovement(const Vector3& translation, const Quaternion& rotation) override; bool CanAttach(RigidBody* rigidBody) const override; RigidBody* GetAttachedRigidBody() const override; + void SetCenter(const Vector3& value) override; // [IPhysicsActor] void OnActiveTransformChanged() override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 39ef47686..0ff51e8e6 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -49,7 +49,7 @@ void Collider::SetIsTrigger(bool value) void Collider::SetCenter(const Vector3& value) { - if (Vector3::NearEqual(value, _center)) + if (value == _center) return; _center = value; if (_staticActor) @@ -62,7 +62,7 @@ void Collider::SetCenter(const Vector3& value) void Collider::SetContactOffset(float value) { value = Math::Clamp(value, 0.0f, 100.0f); - if (Math::NearEqual(value, _contactOffset)) + if (value == _contactOffset) return; _contactOffset = value; if (_shape) @@ -205,7 +205,7 @@ void Collider::CreateShape() ASSERT(_shape == nullptr); // Setup shape geometry - _cachedScale = GetScale(); + _cachedScale = GetScale().GetAbsolute().MaxValue(); CollisionShape shape; GetGeometry(shape); @@ -222,7 +222,7 @@ void Collider::UpdateGeometry() return; // Setup shape geometry - _cachedScale = GetScale(); + _cachedScale = GetScale().GetAbsolute().MaxValue(); CollisionShape shape; GetGeometry(shape); @@ -427,8 +427,8 @@ void Collider::OnTransformChanged() } } - const Float3 scale = GetScale(); - if (!Float3::NearEqual(_cachedScale, scale)) + const float scale = GetScale().GetAbsolute().MaxValue(); + if (_cachedScale != scale) UpdateGeometry(); UpdateBounds(); } diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 3156f0ce8..835d89a22 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -24,7 +24,7 @@ protected: bool _isTrigger; void* _shape; void* _staticActor; - Float3 _cachedScale; + float _cachedScale; float _contactOffset; Vector3 _cachedLocalPosePos; Quaternion _cachedLocalPoseRot; @@ -61,7 +61,7 @@ public: /// /// Sets the center of the collider, measured in the object's local space. /// - API_PROPERTY() void SetCenter(const Vector3& value); + API_PROPERTY() virtual void SetCenter(const Vector3& value); /// /// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated. diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 713fb6bca..0902f1106 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -131,7 +131,7 @@ void MeshCollider::UpdateBounds() void MeshCollider::GetGeometry(CollisionShape& collision) { // Prepare scale - Float3 scale = _cachedScale; + Float3 scale = _transform.Scale; const float minSize = 0.001f; Float3 scaleAbs = scale.GetAbsolute(); if (scaleAbs.X < minSize) diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp index 3c5e78350..f40d9e4af 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.cpp +++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp @@ -10,7 +10,7 @@ SphereCollider::SphereCollider(const SpawnParams& params) void SphereCollider::SetRadius(const float value) { - if (Math::NearEqual(value, _radius)) + if (value == _radius) return; _radius = value; @@ -67,8 +67,7 @@ void SphereCollider::UpdateBounds() void SphereCollider::GetGeometry(CollisionShape& collision) { - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Abs(_radius) * scaling; + const float radius = Math::Abs(_radius) * _cachedScale; const float minSize = 0.001f; collision.SetSphere(Math::Max(radius, minSize)); } diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index 909a62ef0..996100b20 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -259,8 +259,7 @@ void SplineCollider::GetGeometry(CollisionShape& collision) } // Prepare scale - Float3 scale = _cachedScale; - scale = Float3::Max(scale.GetAbsolute(), minSize); + Float3 scale = Float3::Max(_transform.Scale.GetAbsolute(), minSize); // TODO: add support for cooking collision for static splines in editor and reusing it in game diff --git a/Source/Engine/Physics/Joints/DistanceJoint.cpp b/Source/Engine/Physics/Joints/DistanceJoint.cpp index 8bb45ce5b..b17c6b135 100644 --- a/Source/Engine/Physics/Joints/DistanceJoint.cpp +++ b/Source/Engine/Physics/Joints/DistanceJoint.cpp @@ -25,7 +25,7 @@ void DistanceJoint::SetFlags(DistanceJointFlag value) void DistanceJoint::SetMinDistance(float value) { value = Math::Clamp(value, 0.0f, _maxDistance); - if (Math::NearEqual(value, _minDistance)) + if (value == _minDistance) return; _minDistance = value; if (_joint) @@ -35,7 +35,7 @@ void DistanceJoint::SetMinDistance(float value) void DistanceJoint::SetMaxDistance(float value) { value = Math::Max(_minDistance, value); - if (Math::NearEqual(value, _maxDistance)) + if (value == _maxDistance) return; _maxDistance = value; if (_joint) @@ -45,7 +45,7 @@ void DistanceJoint::SetMaxDistance(float value) void DistanceJoint::SetTolerance(float value) { value = Math::Max(0.1f, value); - if (Math::NearEqual(value, _tolerance)) + if (value == _tolerance) return; _tolerance = value; if (_joint) diff --git a/Source/Engine/Physics/Joints/HingeJoint.h b/Source/Engine/Physics/Joints/HingeJoint.h index 82a42edef..e3fdbe719 100644 --- a/Source/Engine/Physics/Joints/HingeJoint.h +++ b/Source/Engine/Physics/Joints/HingeJoint.h @@ -59,7 +59,7 @@ API_STRUCT() struct HingeJointDrive public: bool operator==(const HingeJointDrive& other) const { - return Math::NearEqual(Velocity, other.Velocity) && Math::NearEqual(ForceLimit, other.ForceLimit) && Math::NearEqual(GearRatio, other.GearRatio) && FreeSpin == other.FreeSpin; + return Velocity == other.Velocity && ForceLimit == other.ForceLimit && GearRatio == other.GearRatio && FreeSpin == other.FreeSpin; } }; diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index e4dceab86..950f2d146 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -24,7 +24,7 @@ Joint::Joint(const SpawnParams& params) void Joint::SetBreakForce(float value) { - if (Math::NearEqual(value, _breakForce)) + if (value == _breakForce) return; _breakForce = value; if (_joint) @@ -33,7 +33,7 @@ void Joint::SetBreakForce(float value) void Joint::SetBreakTorque(float value) { - if (Math::NearEqual(value, _breakTorque)) + if (value == _breakTorque) return; _breakTorque = value; if (_joint) @@ -61,7 +61,7 @@ void Joint::SetEnableAutoAnchor(bool value) void Joint::SetTargetAnchor(const Vector3& value) { - if (Vector3::NearEqual(value, _targetAnchor)) + if (value == _targetAnchor) return; _targetAnchor = value; if (_joint && !_enableAutoAnchor) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 5023b69c7..768788f90 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3160,10 +3160,9 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic desc.material = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); else desc.material = DefaultMaterial; - const float minSize = 0.001f; - desc.height = Math::Max(height, minSize); - desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize); - desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize); + desc.height = height; + desc.radius = radius; + desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - 0.001f); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); PxRigidActor* actorPhysX = controllerPhysX->getActor(); ASSERT(actorPhysX && actorPhysX->getNbShapes() == 1); @@ -3188,7 +3187,7 @@ void PhysicsBackend::SetControllerSize(void* controller, float radius, float hei { auto controllerPhysX = (PxCapsuleController*)controller; controllerPhysX->setRadius(radius); - controllerPhysX->resize(height); + controllerPhysX->setHeight(height); } void PhysicsBackend::SetControllerSlopeLimit(void* controller, float value) @@ -3209,6 +3208,20 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value) controllerPhysX->setStepOffset(value); } +Vector3 PhysicsBackend::GetControllerBasePosition(void* controller) +{ + auto controllerPhysX = (PxCapsuleController*)controller; + const Vector3 origin = SceneOrigins[controllerPhysX->getScene()]; + return P2C(controllerPhysX->getFootPosition()) + origin; +} + +void PhysicsBackend::SetControllerBasePosition(void* controller, const Vector3& value) +{ + auto controllerPhysX = (PxCapsuleController*)controller; + const Vector3 sceneOrigin = SceneOrigins[controllerPhysX->getScene()]; + controllerPhysX->setFootPosition(PxExtendedVec3(value.X - sceneOrigin.X, value.Y - sceneOrigin.Y, value.Z - sceneOrigin.Z)); +} + Vector3 PhysicsBackend::GetControllerUpDirection(void* controller) { auto controllerPhysX = (PxCapsuleController*)controller; diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index e6c5ffecc..dacd6c7fd 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -248,6 +248,8 @@ public: static void SetControllerSlopeLimit(void* controller, float value); static void SetControllerNonWalkableMode(void* controller, int32 value); static void SetControllerStepOffset(void* controller, float value); + static Vector3 GetControllerBasePosition(void* controller); + static void SetControllerBasePosition(void* controller, const Vector3& value); static Vector3 GetControllerUpDirection(void* controller); static void SetControllerUpDirection(void* controller, const Vector3& value); static Vector3 GetControllerPosition(void* controller); diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index 897d2d633..cfb1bc75e 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -59,7 +59,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class public: /// - /// The default gravity force value (in cm^2/s). + /// The default gravity value (in cm/(s^2)). /// API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Simulation\")") Vector3 DefaultGravity = Vector3(0, -981.0f, 0); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 222a20055..277927af3 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -42,17 +42,17 @@ void* WindowsPlatform::Instance = nullptr; extern "C" { static HANDLE dbgHelpLock; -void DbgHelpInit() +void FlaxDbgHelpInit() { dbgHelpLock = CreateMutexW(nullptr, FALSE, nullptr); } -void DbgHelpLock() +void FlaxDbgHelpLock() { WaitForSingleObject(dbgHelpLock, INFINITE); } -void DbgHelpUnlock() +void FlaxDbgHelpUnlock() { ReleaseMutex(dbgHelpLock); } @@ -544,7 +544,7 @@ void WindowsPlatform::PreInit(void* hInstance) #if CRASH_LOG_ENABLE TCHAR buffer[MAX_PATH] = { 0 }; - DbgHelpLock(); + FlaxDbgHelpLock(); if (::GetModuleFileNameW(::GetModuleHandleW(nullptr), buffer, MAX_PATH)) SymbolsPath.Add(StringUtils::GetDirectoryName(buffer)); if (::GetEnvironmentVariableW(TEXT("_NT_SYMBOL_PATH"), buffer, MAX_PATH)) @@ -553,7 +553,7 @@ void WindowsPlatform::PreInit(void* hInstance) options |= SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS; SymSetOptions(options); OnSymbolsPathModified(); - DbgHelpUnlock(); + FlaxDbgHelpUnlock(); #endif GetWindowsVersion(WindowsName, VersionMajor, VersionMinor, VersionBuild); @@ -767,7 +767,7 @@ void WindowsPlatform::BeforeExit() void WindowsPlatform::Exit() { #if CRASH_LOG_ENABLE - DbgHelpLock(); + FlaxDbgHelpLock(); #if !TRACY_ENABLE if (SymInitialized) { @@ -776,7 +776,7 @@ void WindowsPlatform::Exit() } #endif SymbolsPath.Resize(0); - DbgHelpUnlock(); + FlaxDbgHelpUnlock(); #endif // Unregister app class @@ -1278,14 +1278,14 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) #if CRASH_LOG_ENABLE // Refresh modules info during next stack trace collecting to have valid debug symbols information - DbgHelpLock(); + FlaxDbgHelpLock(); if (folder.HasChars() && !SymbolsPath.Contains(folder)) { SymbolsPath.Add(folder); SymbolsPath.Last().Replace('/', '\\'); OnSymbolsPathModified(); } - DbgHelpUnlock(); + FlaxDbgHelpUnlock(); #endif return handle; @@ -1296,7 +1296,7 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) Array WindowsPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context) { Array result; - DbgHelpLock(); + FlaxDbgHelpLock(); // Initialize HANDLE process = GetCurrentProcess(); @@ -1428,7 +1428,7 @@ Array WindowsPlatform::GetStackFrames(int32 skipCount, } } - DbgHelpUnlock(); + FlaxDbgHelpUnlock(); return result; } diff --git a/Source/Engine/Renderer/ForwardPass.cpp b/Source/Engine/Renderer/ForwardPass.cpp index 42796764f..caf624609 100644 --- a/Source/Engine/Renderer/ForwardPass.cpp +++ b/Source/Engine/Renderer/ForwardPass.cpp @@ -72,7 +72,7 @@ void ForwardPass::Dispose() _shader = nullptr; } -void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output) +void ForwardPass::Render(RenderContext& renderContext, GPUTexture*& input, GPUTexture*& output) { PROFILE_GPU_CPU("Forward"); auto context = GPUDevice::Instance->GetMainContext(); @@ -91,6 +91,16 @@ void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTex // Check if there is no objects to render or no resources ready auto& forwardList = mainCache->DrawCallsLists[(int32)DrawCallsListType::Forward]; auto& distortionList = mainCache->DrawCallsLists[(int32)DrawCallsListType::Distortion]; + if ((forwardList.IsEmpty() && distortionList.IsEmpty()) +#if USE_EDITOR + || renderContext.View.Mode == ViewMode::PhysicsColliders +#endif + ) + { + // Skip rendering + Swap(input, output); + return; + } if (distortionList.IsEmpty() || checkIfSkipPass()) { // Copy frame diff --git a/Source/Engine/Renderer/ForwardPass.h b/Source/Engine/Renderer/ForwardPass.h index 552052eb1..be3126e0e 100644 --- a/Source/Engine/Renderer/ForwardPass.h +++ b/Source/Engine/Renderer/ForwardPass.h @@ -31,7 +31,7 @@ public: /// The rendering context. /// Target with renderer frame ready for further processing. /// The output frame. - void Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output); + void Render(RenderContext& renderContext, GPUTexture*& input, GPUTexture*& output); private: diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 56c78600b..87d1d94f1 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -240,6 +240,12 @@ void Renderer::Render(SceneRenderTask* task) | ViewFlags::ContactShadows | ViewFlags::DepthOfField); } + + // Force Debug Draw usage in some specific views that depend on it + if (renderContext.View.Mode == ViewMode::PhysicsColliders) + { + renderContext.View.Flags |= ViewFlags::DebugDraw; + } #endif // Perform the actual rendering diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index de1556577..4321ffa36 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -27,6 +27,11 @@ namespace FlaxEngine.Json } } + internal interface ICustomValueEquals + { + bool ValueEquals(object other); + } + partial class JsonSerializer { internal class SerializerCache @@ -262,7 +267,7 @@ namespace FlaxEngine.Json return true; if (objA == null || objB == null) return false; - + // Special case when saving reference to prefab object and the objects are different but the point to the same prefab object // In that case, skip saving reference as it's defined in prefab (will be populated via IdsMapping during deserialization) if (objA is SceneObject sceneA && objB is SceneObject sceneB && sceneA && sceneB && sceneA.HasPrefabLink && sceneB.HasPrefabLink) @@ -311,6 +316,8 @@ namespace FlaxEngine.Json return !bEnumerator.MoveNext(); } + if (objA is ICustomValueEquals customValueEquals && objA.GetType() == objB.GetType()) + return customValueEquals.ValueEquals(objB); return objA.Equals(objB); #endif } diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index 25ce8ff5b..32d229a08 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -28,7 +28,7 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c else if (obj.IsString() && obj.GetStringLength() == 32) { auto value = JsonTools::GetGuid(obj); - if (mapping.TryGet(value, value)) + if (value.IsValid() && mapping.TryGet(value, value)) { // Unoptimized version: //obj.SetString(value.ToString(Guid::FormatType::N).ToSTD().c_str(), 32, document.GetAllocator()); @@ -255,9 +255,8 @@ BoundingBox JsonTools::GetBoundingBox(const Value& value) Guid JsonTools::GetGuid(const Value& value) { - if (!value.IsString()) + if (!value.IsString() || value.GetStringLength() != 32) return Guid::Empty; - CHECK_RETURN(value.GetStringLength() == 32, Guid::Empty); // Split const char* a = value.GetString(); @@ -267,10 +266,12 @@ Guid JsonTools::GetGuid(const Value& value) // Parse Guid result; - StringUtils::ParseHex(a, 8, &result.A); - StringUtils::ParseHex(b, 8, &result.B); - StringUtils::ParseHex(c, 8, &result.C); - StringUtils::ParseHex(d, 8, &result.D); + bool failed = StringUtils::ParseHex(a, 8, &result.A); + failed |= StringUtils::ParseHex(b, 8, &result.B); + failed |= StringUtils::ParseHex(c, 8, &result.C); + failed |= StringUtils::ParseHex(d, 8, &result.D); + if (failed) + return Guid::Empty; return result; } diff --git a/Source/Engine/Serialization/JsonTools.h b/Source/Engine/Serialization/JsonTools.h index 0e1e3850f..0e807c784 100644 --- a/Source/Engine/Serialization/JsonTools.h +++ b/Source/Engine/Serialization/JsonTools.h @@ -214,7 +214,7 @@ public: const auto member = node.FindMember(name); if (member != node.MemberEnd() && member->value.IsInt()) { - result = member->value.GetInt(); + result = (byte)member->value.GetInt(); } } @@ -232,7 +232,7 @@ public: const auto member = node.FindMember(name); if (member != node.MemberEnd() && member->value.IsInt()) { - result = member->value.GetInt(); + result = (uint32)member->value.GetInt(); } } @@ -241,7 +241,7 @@ public: const auto member = node.FindMember(name); if (member != node.MemberEnd() && member->value.IsInt()) { - result = member->value.GetInt(); + result = (int16)member->value.GetInt(); } } @@ -250,7 +250,7 @@ public: const auto member = node.FindMember(name); if (member != node.MemberEnd() && member->value.IsInt()) { - result = member->value.GetInt(); + result = (uint16)member->value.GetInt(); } } diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index b39b7ef2f..ebbfd70e6 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -240,6 +240,7 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo void Terrain::DrawPhysicsDebug(RenderView& view) { + PROFILE_CPU(); for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) { _patches[pathIndex]->DrawPhysicsDebug(view); @@ -260,7 +261,7 @@ void Terrain::SetScaleInLightmap(float value) void Terrain::SetBoundsExtent(const Vector3& value) { - if (Vector3::NearEqual(_boundsExtent, value)) + if (_boundsExtent == value) return; _boundsExtent = value; @@ -891,7 +892,7 @@ void Terrain::OnTransformChanged() auto patch = _patches[i]; patch->UpdateTransform(); } - if (!Float3::NearEqual(_cachedScale, _transform.Scale)) + if (_cachedScale != _transform.Scale) { _cachedScale = _transform.Scale; for (int32 i = 0; i < _patches.Count(); i++) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index b2f668a9d..1c754d843 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -104,6 +104,8 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) #endif #if USE_EDITOR _collisionTriangles.Resize(0); + SAFE_DELETE_GPU_RESOURCE(_collisionTrianglesBuffer); + _collisionTrianglesBufferDirty = true; #endif _collisionVertices.Resize(0); } @@ -120,6 +122,9 @@ TerrainPatch::~TerrainPatch() #if TERRAIN_USE_PHYSICS_DEBUG SAFE_DELETE_GPU_RESOURCE(_debugLines); #endif +#if USE_EDITOR + SAFE_DELETE_GPU_RESOURCE(_collisionTrianglesBuffer); +#endif } RawDataAsset* TerrainPatch::GetHeightfield() const @@ -2225,6 +2230,8 @@ void TerrainPatch::DestroyCollision() #endif #if USE_EDITOR _collisionTriangles.Resize(0); + SAFE_DELETE(_collisionTrianglesBuffer); + _collisionTrianglesBufferDirty = true; #endif _collisionVertices.Resize(0); } @@ -2317,7 +2324,32 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view) return; if (view.Mode == ViewMode::PhysicsColliders) { - DEBUG_DRAW_TRIANGLES(GetCollisionTriangles(), Color::DarkOliveGreen, 0, true); + const auto& triangles = GetCollisionTriangles(); + typedef DebugDraw::Vertex Vertex; + if (!_collisionTrianglesBuffer) + _collisionTrianglesBuffer = GPUDevice::Instance->CreateBuffer(TEXT("Terrain.CollisionTriangles")); + const uint32 count = triangles.Count(); + if (_collisionTrianglesBuffer->GetElementsCount() != count) + { + if (_collisionTrianglesBuffer->Init(GPUBufferDescription::Vertex(Vertex::GetLayout(), sizeof(Vertex), count))) + return; + _collisionTrianglesBufferDirty = true; + } + if (_collisionTrianglesBufferDirty) + { + const Color32 color(Color::DarkOliveGreen); + Array vertices; + vertices.Resize((int32)count); + const Vector3* src = triangles.Get(); + Vertex* dst = vertices.Get(); + for (uint32 i = 0; i < count; i++) + { + dst[i] = { (Float3)src[i], color }; + } + _collisionTrianglesBuffer->SetData(vertices.Get(), _collisionTrianglesBuffer->GetSize()); + _collisionTrianglesBufferDirty = false; + } + DebugDraw::DrawTriangles(_collisionTrianglesBuffer, Matrix::Identity, 0, true); } else { @@ -2351,6 +2383,7 @@ const Array& TerrainPatch::GetCollisionTriangles() PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols); _collisionTriangles.Resize((rows - 1) * (cols - 1) * 6); + _collisionTrianglesBufferDirty = true; Vector3* data = _collisionTriangles.Get(); #define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 8e4277068..7d85c5b1c 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -49,6 +49,8 @@ private: #endif #if USE_EDITOR Array _collisionTriangles; // TODO: large-worlds + class GPUBuffer* _collisionTrianglesBuffer = nullptr; + bool _collisionTrianglesBufferDirty = true; #endif Array _collisionVertices; // TODO: large-worlds diff --git a/Source/Engine/Tests/TestFloatR10G10B10A2.cs b/Source/Engine/Tests/TestFloatR10G10B10A2.cs index e67272b41..1435f305e 100644 --- a/Source/Engine/Tests/TestFloatR10G10B10A2.cs +++ b/Source/Engine/Tests/TestFloatR10G10B10A2.cs @@ -11,13 +11,13 @@ namespace FlaxEngine.Tests [Test] public void TestConversion() { - Assert.AreEqual(Float4.Zero, new FloatR10G10B10A2(Float4.Zero).ToFloat4()); - Assert.AreEqual(Float4.One, new FloatR10G10B10A2(Float4.One).ToFloat4()); - Assert.AreEqual(new Float4(0.5004888f, 0.5004888f, 0.5004888f, 0.666667f), new FloatR10G10B10A2(new Float4(0.5f)).ToFloat4()); - Assert.AreEqual(new Float4(1, 0, 0, 0), new FloatR10G10B10A2(new Float4(1, 0, 0, 0)).ToFloat4()); - Assert.AreEqual(new Float4(0, 1, 0, 0), new FloatR10G10B10A2(new Float4(0, 1, 0, 0)).ToFloat4()); - Assert.AreEqual(new Float4(0, 0, 1, 0), new FloatR10G10B10A2(new Float4(0, 0, 1, 0)).ToFloat4()); - Assert.AreEqual(new Float4(0, 0, 0, 1), new FloatR10G10B10A2(new Float4(0, 0, 0, 1)).ToFloat4()); + Assert.IsTrue(Float4.NearEqual(Float4.Zero, new FloatR10G10B10A2(Float4.Zero).ToFloat4())); + Assert.IsTrue(Float4.NearEqual(Float4.One, new FloatR10G10B10A2(Float4.One).ToFloat4())); + Assert.IsTrue(Float4.NearEqual(new Float4(0.5004888f, 0.5004888f, 0.5004888f, 0.666667f), new FloatR10G10B10A2(new Float4(0.5f)).ToFloat4())); + Assert.IsTrue(Float4.NearEqual(new Float4(1, 0, 0, 0), new FloatR10G10B10A2(new Float4(1, 0, 0, 0)).ToFloat4())); + Assert.IsTrue(Float4.NearEqual(new Float4(0, 1, 0, 0), new FloatR10G10B10A2(new Float4(0, 1, 0, 0)).ToFloat4())); + Assert.IsTrue(Float4.NearEqual(new Float4(0, 0, 1, 0), new FloatR10G10B10A2(new Float4(0, 0, 1, 0)).ToFloat4())); + Assert.IsTrue(Float4.NearEqual(new Float4(0, 0, 0, 1), new FloatR10G10B10A2(new Float4(0, 0, 0, 1)).ToFloat4())); } } } diff --git a/Source/Engine/Tests/TestFloatR11G11B10.cs b/Source/Engine/Tests/TestFloatR11G11B10.cs index 593989710..2fd080594 100644 --- a/Source/Engine/Tests/TestFloatR11G11B10.cs +++ b/Source/Engine/Tests/TestFloatR11G11B10.cs @@ -11,13 +11,13 @@ namespace FlaxEngine.Tests [Test] public void TestConversion() { - Assert.AreEqual(Float3.Zero, new FloatR11G11B10(Float3.Zero).ToFloat3()); - Assert.AreEqual(Float3.One, new FloatR11G11B10(Float3.One).ToFloat3()); - Assert.AreEqual(new Float3(0.5f, 0.5f, 0.5f), new FloatR11G11B10(new Float3(0.5f)).ToFloat3()); - Assert.AreEqual(new Float3(1, 0, 0), new FloatR11G11B10(new Float3(1, 0, 0)).ToFloat3()); - Assert.AreEqual(new Float3(0, 1, 0), new FloatR11G11B10(new Float3(0, 1, 0)).ToFloat3()); - Assert.AreEqual(new Float3(0, 0, 1), new FloatR11G11B10(new Float3(0, 0, 1)).ToFloat3()); - Assert.AreEqual(new Float3(10, 11, 12), new FloatR11G11B10(new Float3(10, 11, 12)).ToFloat3()); + Assert.IsTrue(Float3.NearEqual(Float3.Zero, new FloatR11G11B10(Float3.Zero).ToFloat3())); + Assert.IsTrue(Float3.NearEqual(Float3.One, new FloatR11G11B10(Float3.One).ToFloat3())); + Assert.IsTrue(Float3.NearEqual(new Float3(0.5f, 0.5f, 0.5f), new FloatR11G11B10(new Float3(0.5f)).ToFloat3())); + Assert.IsTrue(Float3.NearEqual(new Float3(1, 0, 0), new FloatR11G11B10(new Float3(1, 0, 0)).ToFloat3())); + Assert.IsTrue(Float3.NearEqual(new Float3(0, 1, 0), new FloatR11G11B10(new Float3(0, 1, 0)).ToFloat3())); + Assert.IsTrue(Float3.NearEqual(new Float3(0, 0, 1), new FloatR11G11B10(new Float3(0, 0, 1)).ToFloat3())); + Assert.IsTrue(Float3.NearEqual(new Float3(10, 11, 12), new FloatR11G11B10(new Float3(10, 11, 12)).ToFloat3())); } } } diff --git a/Source/Engine/Tests/TestMain.cpp b/Source/Engine/Tests/TestMain.cpp index a490f6d1c..88db6144c 100644 --- a/Source/Engine/Tests/TestMain.cpp +++ b/Source/Engine/Tests/TestMain.cpp @@ -44,9 +44,9 @@ void TestsRunnerService::Update() LOG(Info, "Running Flax Tests..."); const int result = Catch::Session().run(); if (result == 0) - LOG(Info, "Result: {0}", result); + LOG(Info, "Flax Tests result: {0}", result); else - LOG(Error, "Result: {0}", result); + LOG(Error, "Flax Tests result: {0}", result); Log::Logger::WriteFloor(); Engine::RequestExit(result); } diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index a265a9d93..5e19a5fa0 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -3,6 +3,7 @@ #include "Engine/Content/Content.h" #include "Engine/Content/AssetReference.h" #include "Engine/Core/Log.h" +#include "Engine/Core/ScopeExit.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Actors/EmptyActor.h" #include "Engine/Level/Actors/DirectionalLight.h" @@ -27,6 +28,7 @@ TEST_CASE("Prefabs") // Create Prefab B with two children attached to the root AssetReference prefabB = Content::CreateVirtualAsset(); REQUIRE(prefabB); + SCOPE_EXIT{ Content::DeleteAsset(prefabB); }; Guid id; Guid::Parse("665bb01c49a3370f14a023b5395de261", id); prefabB->ChangeID(id); @@ -55,6 +57,7 @@ TEST_CASE("Prefabs") // Create Prefab A with nested Prefab B attached to the root AssetReference prefabA = Content::CreateVirtualAsset(); REQUIRE(prefabA); + SCOPE_EXIT{ Content::DeleteAsset(prefabA); }; Guid::Parse("02524a044184af56b6c664a0f98bd761", id); prefabA->ChangeID(id); auto prefabAInit = prefabA->Init(Prefab::TypeName, @@ -123,8 +126,6 @@ TEST_CASE("Prefabs") // Cleanup instanceA->DeleteObject(); instanceB->DeleteObject(); - Content::DeleteAsset(prefabA); - Content::DeleteAsset(prefabB); } SECTION("Test Adding Object in Nested Prefab") { @@ -133,6 +134,7 @@ TEST_CASE("Prefabs") // Create Prefab B with just root object AssetReference prefabB = Content::CreateVirtualAsset(); REQUIRE(prefabB); + SCOPE_EXIT{ Content::DeleteAsset(prefabB); }; Guid id; Guid::Parse("25dbe4b0416be0777a6ce59e8788b10f", id); prefabB->ChangeID(id); @@ -149,6 +151,7 @@ TEST_CASE("Prefabs") // Create Prefab A with two nested Prefab B attached to the root AssetReference prefabA = Content::CreateVirtualAsset(); REQUIRE(prefabA); + SCOPE_EXIT{ Content::DeleteAsset(prefabA); }; Guid::Parse("4cb744714f746e31855f41815612d14b", id); prefabA->ChangeID(id); auto prefabAInit = prefabA->Init(Prefab::TypeName, @@ -243,8 +246,6 @@ TEST_CASE("Prefabs") // Cleanup instanceA->DeleteObject(); instanceB->DeleteObject(); - Content::DeleteAsset(prefabA); - Content::DeleteAsset(prefabB); } SECTION("Test Syncing Changes In Nested Prefab Instance") { @@ -253,6 +254,7 @@ TEST_CASE("Prefabs") // Create TestActor prefab with just root object AssetReference testActorPrefab = Content::CreateVirtualAsset(); REQUIRE(testActorPrefab); + SCOPE_EXIT{ Content::DeleteAsset(testActorPrefab); }; Guid id; Guid::Parse("7691e981482f2a486e10cfae149e07d3", id); testActorPrefab->ChangeID(id); @@ -269,6 +271,7 @@ TEST_CASE("Prefabs") // Create NestedActor prefab that inherits from TestActor prefab AssetReference nestedActorPrefab = Content::CreateVirtualAsset(); REQUIRE(nestedActorPrefab); + SCOPE_EXIT{ Content::DeleteAsset(nestedActorPrefab); }; Guid::Parse("1d521df4465ad849e274748c6d14b703", id); nestedActorPrefab->ChangeID(id); auto nestedActorPrefabInit = nestedActorPrefab->Init(Prefab::TypeName, @@ -328,8 +331,6 @@ TEST_CASE("Prefabs") // Cleanup nestedActor->DeleteObject(); testActor->DeleteObject(); - Content::DeleteAsset(nestedActorPrefab); - Content::DeleteAsset(testActorPrefab); } SECTION("Test Loading Nested Prefab After Changing Root") { @@ -338,6 +339,7 @@ TEST_CASE("Prefabs") // Create base prefab with 3 objects AssetReference prefabBase = Content::CreateVirtualAsset(); REQUIRE(prefabBase); + SCOPE_EXIT{ Content::DeleteAsset(prefabBase); }; Guid id; Guid::Parse("2b3334524c696dcfa93cabacd2a4f404", id); prefabBase->ChangeID(id); @@ -366,6 +368,7 @@ TEST_CASE("Prefabs") // Create nested prefab but with 'old' state where root object is different AssetReference prefabNested = Content::CreateVirtualAsset(); REQUIRE(prefabNested); + SCOPE_EXIT{ Content::DeleteAsset(prefabNested); }; Guid::Parse("a71447e947cbd2deea018a8377636ce6", id); prefabNested->ChangeID(id); auto prefabNestedInit = prefabNested->Init(Prefab::TypeName, @@ -411,8 +414,6 @@ TEST_CASE("Prefabs") // Cleanup instanceNested->DeleteObject(); instanceBase->DeleteObject(); - Content::DeleteAsset(prefabNested); - Content::DeleteAsset(prefabBase); } SECTION("Test Loading Nested Prefab After Changing and Deleting Root") { @@ -421,6 +422,7 @@ TEST_CASE("Prefabs") // Create base prefab with 1 object AssetReference prefabBase = Content::CreateVirtualAsset(); REQUIRE(prefabBase); + SCOPE_EXIT{ Content::DeleteAsset(prefabBase); }; Guid id; Guid::Parse("3b3334524c696dcfa93cabacd2a4f404", id); prefabBase->ChangeID(id); @@ -455,6 +457,7 @@ TEST_CASE("Prefabs") // Create nested prefab but with 'old' state where root object is different AssetReference prefabNested1 = Content::CreateVirtualAsset(); REQUIRE(prefabNested1); + SCOPE_EXIT{ Content::DeleteAsset(prefabNested1); }; Guid::Parse("671447e947cbd2deea018a8377636ce6", id); prefabNested1->ChangeID(id); auto prefabNestedInit1 = prefabNested1->Init(Prefab::TypeName, @@ -491,6 +494,7 @@ TEST_CASE("Prefabs") // Create nested prefab but with 'old' state where root object is different and doesn't exist anymore AssetReference prefabNested2 = Content::CreateVirtualAsset(); REQUIRE(prefabNested2); + SCOPE_EXIT{ Content::DeleteAsset(prefabNested2); }; Guid::Parse("b71447e947cbd2deea018a8377636ce6", id); prefabNested2->ChangeID(id); auto prefabNestedInit2 = prefabNested2->Init(Prefab::TypeName, @@ -555,9 +559,6 @@ TEST_CASE("Prefabs") instanceNested2->DeleteObject(); instanceNested1->DeleteObject(); instanceBase->DeleteObject(); - Content::DeleteAsset(prefabNested2); - Content::DeleteAsset(prefabNested1); - Content::DeleteAsset(prefabBase); } SECTION("Test Applying Prefab Change To Object References") { @@ -566,6 +567,7 @@ TEST_CASE("Prefabs") // Create Prefab AssetReference prefab = Content::CreateVirtualAsset(); REQUIRE(prefab); + SCOPE_EXIT{ Content::DeleteAsset(prefab); }; Guid id; Guid::Parse("690e55514cd6fdc2a269429a2bf84133", id); prefab->ChangeID(id); @@ -612,7 +614,6 @@ TEST_CASE("Prefabs") // Cleanup instanceA->DeleteObject(); instanceB->DeleteObject(); - Content::DeleteAsset(prefab); } SECTION("Test Applying Prefab With Missing Nested Prefab") { @@ -637,6 +638,7 @@ TEST_CASE("Prefabs") // Create Prefab A with nested Prefab B attached to the root AssetReference prefabA = Content::CreateVirtualAsset(); REQUIRE(prefabA); + SCOPE_EXIT{ Content::DeleteAsset(prefabA); }; Guid::Parse("4cb744714f746e31855f41815612d14b", id); prefabA->ChangeID(id); auto prefabAInit = prefabA->Init(Prefab::TypeName, @@ -685,7 +687,6 @@ TEST_CASE("Prefabs") instanceA->DeleteObject(); instanceB->DeleteObject(); instanceC->DeleteObject(); - Content::DeleteAsset(prefabA); } } diff --git a/Source/Engine/Tests/TestQuaternion.cs b/Source/Engine/Tests/TestQuaternion.cs index bafa1b3e5..749f7ee81 100644 --- a/Source/Engine/Tests/TestQuaternion.cs +++ b/Source/Engine/Tests/TestQuaternion.cs @@ -17,10 +17,10 @@ namespace FlaxEngine.Tests [Test] public void TestEuler() { - Assert.AreEqual(Quaternion.Euler(90, 0, 0), new Quaternion(0.7071068f, 0, 0, 0.7071068f)); - Assert.AreEqual(Quaternion.Euler(25, 0, 10), new Quaternion(0.215616f, -0.018864f, 0.0850898f, 0.9725809f)); - Assert.AreEqual(new Float3(25, 0, 10), Quaternion.Euler(25, 0, 10).EulerAngles); - Assert.AreEqual(new Float3(25, -5, 10), Quaternion.Euler(25, -5, 10).EulerAngles); + Assert.IsTrue(Quaternion.NearEqual(Quaternion.Euler(90, 0, 0), new Quaternion(0.7071068f, 0, 0, 0.7071068f))); + Assert.IsTrue(Quaternion.NearEqual(Quaternion.Euler(25, 0, 10), new Quaternion(0.215616f, -0.018864f, 0.0850898f, 0.9725809f))); + Assert.IsTrue(Float3.NearEqual(new Float3(25, 0, 10), Quaternion.Euler(25, 0, 10).EulerAngles, 0.00001f)); + Assert.IsTrue(Float3.NearEqual(new Float3(25, -5, 10), Quaternion.Euler(25, -5, 10).EulerAngles, 0.00001f)); } /// @@ -33,7 +33,7 @@ namespace FlaxEngine.Tests var delta = Quaternion.Euler(0, 10, 0); for (int i = 0; i < 9; i++) q *= delta; - Assert.AreEqual(Quaternion.Euler(0, 90, 0), q); + Assert.IsTrue(Quaternion.NearEqual(Quaternion.Euler(0, 90, 0), q)); } } } diff --git a/Source/Engine/Tests/TestTransform.cs b/Source/Engine/Tests/TestTransform.cs index 24edcb989..a3f3b2f03 100644 --- a/Source/Engine/Tests/TestTransform.cs +++ b/Source/Engine/Tests/TestTransform.cs @@ -68,9 +68,9 @@ namespace FlaxEngine.Tests t1.LocalToWorld(new Vector3[1] { t2.Translation }, a4T); Vector3 a4 = a4T[0]; - Assert.AreEqual(a1.Translation, a2); - Assert.AreEqual(a2, a3); - Assert.AreEqual(a2, a4); + Assert.IsTrue(Vector3.NearEqual(a1.Translation, a2)); + Assert.IsTrue(Vector3.NearEqual(a2, a3)); + Assert.IsTrue(Vector3.NearEqual(a2, a4)); } /// @@ -100,9 +100,9 @@ namespace FlaxEngine.Tests t1.WorldToLocal(new Vector3[1] { t2.Translation }, a4T); Float3 a4 = a4T[0]; - Assert.AreEqual((Float3)a1.Translation, a2); - Assert.AreEqual(a2, a3); - Assert.AreEqual(a2, a4); + Assert.IsTrue(Float3.NearEqual((Float3)a1.Translation, a2)); + Assert.IsTrue(Float3.NearEqual(a2, a3, 0.0001f)); + Assert.IsTrue(Float3.NearEqual(a2, a4)); } /// @@ -113,28 +113,28 @@ namespace FlaxEngine.Tests { Transform trans = new Transform(new Vector3(1, 2, 3)); - Assert.AreEqual(new Float3(1, 2, 3), (Float3)trans.LocalToWorld(new Float3(0, 0, 0))); - Assert.AreEqual(new Float3(4, 4, 4), (Float3)trans.LocalToWorld(new Float3(3, 2, 1))); - Assert.AreEqual(new Float3(-1, -2, -3), (Float3)trans.WorldToLocal(new Float3(0, 0, 0))); - Assert.AreEqual(new Float3(0, 0, 0), (Float3)trans.WorldToLocal(new Float3(1, 2, 3))); + Assert.IsTrue(Float3.NearEqual(new Float3(1, 2, 3), (Float3)trans.LocalToWorld(new Float3(0, 0, 0)))); + Assert.IsTrue(Float3.NearEqual(new Float3(4, 4, 4), (Float3)trans.LocalToWorld(new Float3(3, 2, 1)))); + Assert.IsTrue(Float3.NearEqual(new Float3(-1, -2, -3), (Float3)trans.WorldToLocal(new Float3(0, 0, 0)))); + Assert.IsTrue(Float3.NearEqual(new Float3(0, 0, 0), (Float3)trans.WorldToLocal(new Float3(1, 2, 3)))); trans = new Transform(Vector3.Zero, Quaternion.Euler(0, 90, 0)); - Assert.AreEqual(new Float3(0, 2, -1), (Float3)trans.LocalToWorld(new Float3(1, 2, 0))); + Assert.IsTrue(Float3.NearEqual(new Float3(0, 2, -1), (Float3)trans.LocalToWorld(new Float3(1, 2, 0)))); trans.Translation = new Vector3(1, 0, 0); trans.Orientation = Quaternion.RotationX((float)Math.PI * 0.5f); trans.Scale = new Vector3(2, 2, 2); - Assert.AreEqual(new Float3(1, 0, 2), (Float3)trans.LocalToWorld(new Float3(0, 1, 0))); + Assert.IsTrue(Float3.NearEqual(new Float3(1, 0, 2), (Float3)trans.LocalToWorld(new Float3(0, 1, 0)))); Transform t1 = trans.LocalToWorld(Transform.Identity); - Assert.AreEqual(new Float3(1.0f, 0, 0), (Float3)t1.Translation); - Assert.AreEqual(Quaternion.RotationX((float)Math.PI * 0.5f), t1.Orientation); - Assert.AreEqual(new Float3(2.0f, 2.0f, 2.0f), t1.Scale); + Assert.IsTrue(Float3.NearEqual(new Float3(1.0f, 0, 0), (Float3)t1.Translation)); + Assert.IsTrue(Quaternion.NearEqual(Quaternion.RotationX((float)Math.PI * 0.5f), t1.Orientation)); + Assert.IsTrue(Float3.NearEqual(new Float3(2.0f, 2.0f, 2.0f), t1.Scale)); Transform t2 = trans.WorldToLocal(Transform.Identity); - Assert.AreEqual(new Float3(-0.5f, 0, 0), (Float3)t2.Translation); - Assert.AreEqual(Quaternion.RotationX((float)Math.PI * -0.5f), t2.Orientation); - Assert.AreEqual(new Float3(0.5f, 0.5f, 0.5f), t2.Scale); + Assert.IsTrue(Float3.NearEqual(new Float3(-0.5f, 0, 0), (Float3)t2.Translation)); + Assert.IsTrue(Quaternion.NearEqual(Quaternion.RotationX((float)Math.PI * -0.5f), t2.Orientation)); + Assert.IsTrue(Float3.NearEqual(new Float3(0.5f, 0.5f, 0.5f), t2.Scale)); var rand = new Random(10); for (int i = 0; i < 10; i++) diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp index cd36619c9..601079e85 100644 --- a/Source/Engine/Threading/Task.cpp +++ b/Source/Engine/Threading/Task.cpp @@ -40,7 +40,7 @@ void Task::Cancel() bool Task::Wait(double timeoutMilliseconds) const { PROFILE_CPU(); - double startTime = Platform::GetTimeSeconds() * 0.001; + const double startTime = Platform::GetTimeSeconds(); // TODO: no active waiting! use a semaphore! @@ -54,7 +54,7 @@ bool Task::Wait(double timeoutMilliseconds) const // Wait for child if has if (_continueWith) { - auto spendTime = Platform::GetTimeSeconds() * 0.001 - startTime; + const auto spendTime = (Platform::GetTimeSeconds() - startTime) * 1000.0; return _continueWith->Wait(timeoutMilliseconds - spendTime); } @@ -66,7 +66,7 @@ bool Task::Wait(double timeoutMilliseconds) const return true; Platform::Sleep(1); - } while (timeoutMilliseconds <= 0.0 || Platform::GetTimeSeconds() * 0.001 - startTime < timeoutMilliseconds); + } while (timeoutMilliseconds <= 0.0 || (Platform::GetTimeSeconds() - startTime) * 1000.0 < timeoutMilliseconds); // Timeout reached! LOG(Warning, "\'{0}\' has timed out. Wait time: {1} ms", ToString(), timeoutMilliseconds); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 51584504d..fe268c350 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -676,6 +676,9 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(CalculateBoneOffsetMatrices); SERIALIZE(LightmapUVsSource); SERIALIZE(CollisionMeshesPrefix); + SERIALIZE(CollisionType); + SERIALIZE(PositionFormat); + SERIALIZE(TexCoordFormat); SERIALIZE(Scale); SERIALIZE(Rotation); SERIALIZE(Translation); @@ -727,6 +730,9 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(CalculateBoneOffsetMatrices); DESERIALIZE(LightmapUVsSource); DESERIALIZE(CollisionMeshesPrefix); + DESERIALIZE(CollisionType); + DESERIALIZE(PositionFormat); + DESERIALIZE(TexCoordFormat); DESERIALIZE(Scale); DESERIALIZE(Rotation); DESERIALIZE(Translation); @@ -1137,6 +1143,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option if (ImportData(path, data, options, errorMsg)) return true; + // Copy over data format options + data.PositionFormat = (ModelData::PositionFormats)options.PositionFormat; + data.TexCoordFormat = (ModelData::TexCoordFormats)options.TexCoordFormat; + // Validate result data if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry)) { @@ -1921,9 +1931,43 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option auto mesh = lod.Meshes[i]; if (mesh->Name.StartsWith(options.CollisionMeshesPrefix, StringSearchCase::IgnoreCase)) { + // Remove material slot used by this mesh (if no other mesh else uses it) + int32 materialSlotUsageCount = 0; + for (const auto& e : data.LODs) + { + for (const MeshData* q : e.Meshes) + { + if (q->MaterialSlotIndex == mesh->MaterialSlotIndex) + materialSlotUsageCount++; + } + } + if (materialSlotUsageCount == 1) + { + data.Materials.RemoveAt(mesh->MaterialSlotIndex); + + // Fixup linkage of other meshes to materials + for (auto& e : data.LODs) + { + for (MeshData* q : e.Meshes) + { + if (q->MaterialSlotIndex > mesh->MaterialSlotIndex) + { + q->MaterialSlotIndex--; + } + } + } + } + + // Remove data linkage + mesh->NodeIndex = 0; + mesh->MaterialSlotIndex = 0; + + // Add mesh to collision if (collisionModel.LODs.Count() == 0) collisionModel.LODs.AddOne(); collisionModel.LODs[0].Meshes.Add(mesh); + + // Remove mesh from model lod.Meshes.RemoveAtKeepOrder(i); if (lod.Meshes.IsEmpty()) break; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 7e0587c3d..b09583471 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -142,6 +142,28 @@ public: ExtractCenterOfMass = 2, }; + /// + /// Declares the imported vertex positions data formats. + /// + API_ENUM(Attributes="HideInEditor") enum class PositionFormat + { + // XYZ channels with 32-bit precision (12 bytes per vertex). + Float32, + // XYZ(W) channels with 12-bit precision (8 bytes per vertex). + Float16, + }; + + /// + /// Declares the imported vertex texture coordinates data formats. + /// + API_ENUM(Attributes="HideInEditor") enum class TexCoordFormats + { + // XY channels with 16-bit precision (4 bytes per vertex). + Float16, + // XY channels with 8-bit precision (2 bytes per vertex). Valid only for normalized UVs within range [0; 1], scaled or negative UVs won't work. + UNorm8, + }; + /// /// Model import options. /// @@ -201,6 +223,14 @@ public: API_FIELD(Attributes="EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") CollisionDataType CollisionType = CollisionDataType::ConvexMesh; + public: + // The imported vertex positions data format to use by meshes. Changing this affects memory usage of the mesh data, performance and overall quality. + API_FIELD(Attributes = "EditorOrder(200), EditorDisplay(\"Data Format\"), VisibleIf(nameof(ShowGeometry))") + PositionFormat PositionFormat = PositionFormat::Float32; + // The imported vertex texture coordinates data format to use by meshes. Changing this affects memory usage of the mesh data, performance and overall quality. + API_FIELD(Attributes = "EditorOrder(205), EditorDisplay(\"Data Format\"), VisibleIf(nameof(ShowGeometry))") + TexCoordFormats TexCoordFormat = TexCoordFormats::Float16; + public: // Transform // Custom uniform import scale. diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 76727cc07..1dfd9facc 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -298,9 +298,13 @@ namespace FlaxEngine.GUI color *= 0.85f; Render2D.DrawText(font, text, color, ref _layout, TextMaterial); } - else if (!string.IsNullOrEmpty(_watermarkText)) + else { - Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + text = _watermarkText; + if (text?.Length > 0) + { + Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); + } } // Caret diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index ff9d3df2d..0bfa799c2 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -51,6 +51,11 @@ namespace FlaxEngine.GUI /// protected float _cachedHeight = 16.0f; + /// + /// The items spacing. + /// + protected float _itemsSpacing = 2.0f; + /// /// The items margin. /// @@ -168,9 +173,9 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the item slots margin (the space between items). + /// Gets or sets the item slots margin (the space around items). /// - [EditorOrder(10), Tooltip("The item slots margin (the space between items).")] + [EditorOrder(10)] public Margin ItemsMargin { get => _itemsMargin; @@ -184,6 +189,23 @@ namespace FlaxEngine.GUI } } + /// + /// Gets or sets the item slots spacing (the margin between items). + /// + [EditorOrder(11)] + public float ItemsSpacing + { + get => _itemsSpacing; + set + { + if (!Mathf.NearEqual(_itemsSpacing, value)) + { + _itemsSpacing = value; + PerformLayout(); + } + } + } + /// /// Gets or sets the panel close/open animation duration (in seconds). /// @@ -563,25 +585,27 @@ namespace FlaxEngine.GUI var slotsLeft = clientArea.Left + slotsMargin.Left; var slotsWidth = clientArea.Width - slotsMargin.Width; float minHeight = HeaderHeight; - float y = clientArea.Top; - float height = clientArea.Top + dropOffset; + float y = clientArea.Top + slotsMargin.Top; + bool anyAdded = false; for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; if (c.IsScrollable && c.Visible) { var h = c.Height; - y += slotsMargin.Top; - c.Bounds = new Rectangle(slotsLeft, y, slotsWidth, h); - - h += slotsMargin.Bottom; + h += _itemsSpacing; y += h; - height += h + slotsMargin.Top; + anyAdded = true; } } // Update panel height + if (anyAdded) + y -= _itemsSpacing; + if (anyAdded) + y += slotsMargin.Bottom; + float height = dropOffset + y; _cachedHeight = height; if (_animationProgress >= 1.0f && _isClosed) y = minHeight; diff --git a/Source/Engine/UI/GUI/Panels/Panel.cs b/Source/Engine/UI/GUI/Panels/Panel.cs index 01c3bb61d..ff5483d0f 100644 --- a/Source/Engine/UI/GUI/Panels/Panel.cs +++ b/Source/Engine/UI/GUI/Panels/Panel.cs @@ -20,6 +20,7 @@ namespace FlaxEngine.GUI private Color _scrollbarTrackColor; private Color _scrollbarThumbColor; private Color _scrollbarThumbSelectedColor; + private Rectangle _controlsBoundsBeforeLayout; /// /// The cached scroll area bounds. Used to scroll contents of the panel control. Cached during performing layout. @@ -530,8 +531,25 @@ namespace FlaxEngine.GUI { // Arrange controls and get scroll bounds ArrangeAndGetBounds(); + UpdateScrollBars(); + _controlsBoundsBeforeLayout = _controlsBounds; + } - // Update scroll bars + /// + protected override void PerformLayoutAfterChildren() + { + // If controls area changed during layout then update scroll bars again + ArrangeAndGetBounds(); + if (_controlsBoundsBeforeLayout != _controlsBounds) + { + UpdateScrollBars(); + } + + base.PerformLayoutAfterChildren(); + } + + private void UpdateScrollBars() + { var controlsBounds = _controlsBounds; var scrollBounds = controlsBounds; _scrollMargin.ExpandRectangle(ref scrollBounds); diff --git a/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs b/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs index 6292a3e81..b8d59c4d7 100644 --- a/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs +++ b/Source/Engine/UI/GUI/Panels/UniformGridPanel.cs @@ -72,8 +72,11 @@ namespace FlaxEngine.GUI get => _slotSpacing; set { - _slotSpacing = value; - PerformLayout(); + if (!Float2.NearEqual(ref _slotSpacing, ref value)) + { + _slotSpacing = value; + PerformLayout(); + } } } @@ -89,11 +92,11 @@ namespace FlaxEngine.GUI /// Initializes a new instance of the class. /// /// The slot padding. - public UniformGridPanel(float slotPadding = 0) + public UniformGridPanel(float slotPadding) { AutoFocus = false; - SlotPadding = new Margin(slotPadding); - SlotSpacing = new Float2(2); + _slotPadding = new Margin(slotPadding); + _slotSpacing = new Float2(2); _slotsH = _slotsV = 5; } @@ -105,25 +108,32 @@ namespace FlaxEngine.GUI int slotsV = _slotsV; int slotsH = _slotsH; Float2 slotSize; + Float2 size = Size; + bool applySpacing = true; + APPLY_SPACING: if (_slotsV + _slotsH == 0) { slotSize = HasChildren ? Children[0].Size : new Float2(32); - slotsH = Mathf.CeilToInt(Width / slotSize.X); - slotsV = Mathf.CeilToInt(Height / slotSize.Y); + slotsH = Mathf.CeilToInt(size.X / slotSize.X); + slotsV = Mathf.CeilToInt(size.Y / slotSize.Y); } else if (slotsH == 0) { - float size = Height / slotsV; - slotSize = new Float2(size); + slotSize = new Float2(size.Y / slotsV); } else if (slotsV == 0) { - float size = Width / slotsH; - slotSize = new Float2(size); + slotSize = new Float2(size.X / slotsH); } else { - slotSize = new Float2(Width / slotsH, Height / slotsV); + slotSize = new Float2(size.X / slotsH, size.Y / slotsV); + } + if (applySpacing && _slotSpacing != Float2.Zero) + { + applySpacing = false; + size -= _slotSpacing * new Float2(slotsH > 1 ? slotsH - 1 : 0, slotsV > 1 ? slotsV - 1 : 0); + goto APPLY_SPACING; } int i = 0; @@ -135,45 +145,9 @@ namespace FlaxEngine.GUI for (int x = 0; x < end; x++) { - var slotBounds = new Rectangle(slotSize.X * x, slotSize.Y * y, slotSize.X, slotSize.Y); + var slotBounds = new Rectangle((slotSize + _slotSpacing) * new Float2(x, y), slotSize); _slotPadding.ShrinkRectangle(ref slotBounds); - if (slotsV > 1) - { - if (y == 0) - { - slotBounds.Height -= _slotSpacing.Y * 0.5f; - } - else if (y == slotsV - 1) - { - slotBounds.Height -= _slotSpacing.Y * 0.5f; - slotBounds.Y += _slotSpacing.Y * 0.5f; - } - else - { - slotBounds.Height -= _slotSpacing.Y; - slotBounds.Y += _slotSpacing.Y * 0.5f; - } - } - - if (slotsH > 1) - { - if (x == 0) - { - slotBounds.Width -= _slotSpacing.X * 0.5f; - } - else if (x == slotsH - 1) - { - slotBounds.Width -= _slotSpacing.X * 0.5f; - slotBounds.X += _slotSpacing.X * 0.5f; - } - else - { - slotBounds.Width -= _slotSpacing.X; - slotBounds.X += _slotSpacing.X * 0.5f; - } - } - var c = _children[i++]; c.Bounds = slotBounds; } diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index 3ce197a18..ef4f53cf9 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -372,8 +372,8 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) // Atan2 case 41: { - Value v1 = tryGetValue(node->GetBox(0), Value::Zero); - Value v2 = tryGetValue(node->GetBox(1), Value::Zero); + Value v1 = tryGetValue(node->GetBox(0), 0, Value::Zero); + Value v2 = tryGetValue(node->GetBox(1), 1, Value::Zero); value = writeFunction2(node, v1, v2, TEXT("atan2")); break; } diff --git a/Source/ThirdParty/tracy/TracyClient.cpp b/Source/ThirdParty/tracy/TracyClient.cpp index 1fbd78f96..ff4cce68f 100644 --- a/Source/ThirdParty/tracy/TracyClient.cpp +++ b/Source/ThirdParty/tracy/TracyClient.cpp @@ -29,21 +29,24 @@ #include "client/tracy_rpmalloc.cpp" #include "client/TracyAlloc.cpp" #include "client/TracyOverride.cpp" +#include "client/TracyKCore.cpp" -#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 -# include "libbacktrace/alloc.cpp" -# include "libbacktrace/dwarf.cpp" -# include "libbacktrace/fileline.cpp" -# include "libbacktrace/mmapio.cpp" -# include "libbacktrace/posix.cpp" -# include "libbacktrace/sort.cpp" -# include "libbacktrace/state.cpp" -# if TRACY_HAS_CALLSTACK == 4 -# include "libbacktrace/macho.cpp" -# else -# include "libbacktrace/elf.cpp" +#if defined(TRACY_HAS_CALLSTACK) +# if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 +# include "libbacktrace/alloc.cpp" +# include "libbacktrace/dwarf.cpp" +# include "libbacktrace/fileline.cpp" +# include "libbacktrace/mmapio.cpp" +# include "libbacktrace/posix.cpp" +# include "libbacktrace/sort.cpp" +# include "libbacktrace/state.cpp" +# if TRACY_HAS_CALLSTACK == 4 +# include "libbacktrace/macho.cpp" +# else +# include "libbacktrace/elf.cpp" +# endif +# include "common/TracyStackFrames.cpp" # endif -# include "common/TracyStackFrames.cpp" #endif #ifdef _MSC_VER diff --git a/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp b/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp index 2b4459764..2b47c3a60 100644 --- a/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp +++ b/Source/ThirdParty/tracy/client/TracyArmCpuTable.hpp @@ -305,6 +305,14 @@ static const char* DecodeIosDevice( const char* id ) "iPhone14,4", "iPhone 13 Mini", "iPhone14,5", "iPhone 13", "iPhone14,6", "iPhone SE 3rd Gen", + "iPhone14,7", "iPhone 14", + "iPhone14,8", "iPhone 14 Plus", + "iPhone15,2", "iPhone 14 Pro", + "iPhone15,3", "iPhone 14 Pro Max", + "iPhone15,4", "iPhone 15", + "iPhone15,5", "iPhone 15 Plus", + "iPhone16,1", "iPhone 15 Pro", + "iPhone16,2", "iPhone 15 Pro Max", "iPad1,1", "iPad (A1219/A1337)", "iPad2,1", "iPad 2 (A1395)", "iPad2,2", "iPad 2 (A1396)", @@ -365,6 +373,8 @@ static const char* DecodeIosDevice( const char* id ) "iPad11,4", "iPad Air 3rd gen (A2123/A2153/A2154)", "iPad11,6", "iPad 8th gen (WiFi)", "iPad11,7", "iPad 8th gen (WiFi+Cellular)", + "iPad12,1", "iPad 9th Gen (WiFi)", + "iPad12,2", "iPad 9th Gen (WiFi+Cellular)", "iPad13,1", "iPad Air 4th gen (WiFi)", "iPad13,2", "iPad Air 4th gen (WiFi+Cellular)", "iPad13,4", "iPad Pro 11\" 3rd gen", @@ -377,6 +387,14 @@ static const char* DecodeIosDevice( const char* id ) "iPad13,11", "iPad Pro 12.9\" 5th gen", "iPad13,16", "iPad Air 5th Gen (WiFi)", "iPad13,17", "iPad Air 5th Gen (WiFi+Cellular)", + "iPad13,18", "iPad 10th Gen", + "iPad13,19", "iPad 10th Gen", + "iPad14,1", "iPad mini 6th Gen (WiFi)", + "iPad14,2", "iPad mini 6th Gen (WiFi+Cellular)", + "iPad14,3", "iPad Pro 11\" 4th Gen", + "iPad14,4", "iPad Pro 11\" 4th Gen", + "iPad14,5", "iPad Pro 12.9\" 6th Gen", + "iPad14,6", "iPad Pro 12.9\" 6th Gen", "iPod1,1", "iPod Touch", "iPod2,1", "iPod Touch 2nd gen", "iPod3,1", "iPod Touch 3rd gen", diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.cpp b/Source/ThirdParty/tracy/client/TracyCallstack.cpp index 0de7c9d2e..bd3290604 100644 --- a/Source/ThirdParty/tracy/client/TracyCallstack.cpp +++ b/Source/ThirdParty/tracy/client/TracyCallstack.cpp @@ -3,10 +3,12 @@ #include #include #include "TracyCallstack.hpp" +#include "TracyDebug.hpp" #include "TracyFastVector.hpp" #include "TracyStringHelpers.hpp" #include "../common/TracyAlloc.hpp" -#include "TracyDebug.hpp" +#include "../common/TracySystem.hpp" + #ifdef TRACY_HAS_CALLSTACK @@ -31,7 +33,6 @@ # include # include # include -# include "TracyFastVector.hpp" #elif TRACY_HAS_CALLSTACK == 5 # include # include @@ -66,7 +67,7 @@ extern "C" extern "C" const char* ___tracy_demangle( const char* mangled ); #ifndef TRACY_DEMANGLE -constexpr size_t ___tracy_demangle_buffer_len = 1024*1024; +constexpr size_t ___tracy_demangle_buffer_len = 1024*1024; char* ___tracy_demangle_buffer; void ___tracy_init_demangle_buffer() @@ -90,9 +91,177 @@ extern "C" const char* ___tracy_demangle( const char* mangled ) #endif #endif +#if TRACY_HAS_CALLSTACK == 3 +# define TRACY_USE_IMAGE_CACHE +# include +#endif + namespace tracy { +#ifdef TRACY_USE_IMAGE_CACHE +// when we have access to dl_iterate_phdr(), we can build a cache of address ranges to image paths +// so we can quickly determine which image an address falls into. +// We refresh this cache only when we hit an address that doesn't fall into any known range. +class ImageCache +{ +public: + struct ImageEntry + { + void* m_startAddress = nullptr; + void* m_endAddress = nullptr; + char* m_name = nullptr; + }; + + ImageCache() + : m_images( 512 ) + { + Refresh(); + } + + ~ImageCache() + { + Clear(); + } + + const ImageEntry* GetImageForAddress( void* address ) + { + const ImageEntry* entry = GetImageForAddressImpl( address ); + if( !entry ) + { + Refresh(); + return GetImageForAddressImpl( address ); + } + return entry; + } + +private: + tracy::FastVector m_images; + bool m_updated = false; + bool m_haveMainImageName = false; + + static int Callback( struct dl_phdr_info* info, size_t size, void* data ) + { + ImageCache* cache = reinterpret_cast( data ); + + const auto startAddress = reinterpret_cast( info->dlpi_addr ); + if( cache->Contains( startAddress ) ) return 0; + + const uint32_t headerCount = info->dlpi_phnum; + assert( headerCount > 0); + const auto endAddress = reinterpret_cast( info->dlpi_addr + + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); + + ImageEntry* image = cache->m_images.push_next(); + image->m_startAddress = startAddress; + image->m_endAddress = endAddress; + + // the base executable name isn't provided when iterating with dl_iterate_phdr, + // we will have to patch the executable image name outside this callback + if( info->dlpi_name && info->dlpi_name[0] != '\0' ) + { + size_t sz = strlen( info->dlpi_name ) + 1; + image->m_name = (char*)tracy_malloc( sz ); + memcpy( image->m_name, info->dlpi_name, sz ); + } + else + { + image->m_name = nullptr; + } + + cache->m_updated = true; + + return 0; + } + + bool Contains( void* startAddress ) const + { + return std::any_of( m_images.begin(), m_images.end(), [startAddress]( const ImageEntry& entry ) { return startAddress == entry.m_startAddress; } ); + } + + void Refresh() + { + m_updated = false; + dl_iterate_phdr( Callback, this ); + + if( m_updated ) + { + std::sort( m_images.begin(), m_images.end(), + []( const ImageEntry& lhs, const ImageEntry& rhs ) { return lhs.m_startAddress > rhs.m_startAddress; } ); + + // patch the main executable image name here, as calling dl_* functions inside the dl_iterate_phdr callback might cause deadlocks + UpdateMainImageName(); + } + } + + void UpdateMainImageName() + { + if( m_haveMainImageName ) + { + return; + } + + for( ImageEntry& entry : m_images ) + { + if( entry.m_name == nullptr ) + { + Dl_info dlInfo; + if( dladdr( (void *)entry.m_startAddress, &dlInfo ) ) + { + if( dlInfo.dli_fname ) + { + size_t sz = strlen( dlInfo.dli_fname ) + 1; + entry.m_name = (char*)tracy_malloc( sz ); + memcpy( entry.m_name, dlInfo.dli_fname, sz ); + } + } + + // we only expect one entry to be null for the main executable entry + break; + } + } + + m_haveMainImageName = true; + } + + const ImageEntry* GetImageForAddressImpl( void* address ) const + { + auto it = std::lower_bound( m_images.begin(), m_images.end(), address, + []( const ImageEntry& lhs, const void* rhs ) { return lhs.m_startAddress > rhs; } ); + + if( it != m_images.end() && address < it->m_endAddress ) + { + return it; + } + return nullptr; + } + + void Clear() + { + for( ImageEntry& entry : m_images ) + { + tracy_free( entry.m_name ); + } + + m_images.clear(); + m_haveMainImageName = false; + } +}; +#endif //#ifdef TRACY_USE_IMAGE_CACHE + +// when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime, +// simply resolve the offset and image name (which will be enough the resolving to be done offline) +#ifdef TRACY_SYMBOL_OFFLINE_RESOLVE +constexpr bool s_shouldResolveSymbolsOffline = true; +#else +static bool s_shouldResolveSymbolsOffline = false; +bool ShouldResolveSymbolsOffline() +{ + const char* symbolOfflineResolve = GetEnvVar( "TRACY_SYMBOL_OFFLINE_RESOLVE" ); + return (symbolOfflineResolve && symbolOfflineResolve[0] == '1'); +} +#endif // #ifdef TRACY_SYMBOL_OFFLINE_RESOLVE + #if TRACY_HAS_CALLSTACK == 1 enum { MaxCbTrace = 64 }; @@ -108,13 +277,18 @@ extern "C" typedef BOOL (__stdcall *t_SymFromInlineContext)( HANDLE hProcess, DWORD64 Address, ULONG InlineContext, PDWORD64 Displacement, PSYMBOL_INFO Symbol ); typedef BOOL (__stdcall *t_SymGetLineFromInlineContext)( HANDLE hProcess, DWORD64 qwAddr, ULONG InlineContext, DWORD64 qwModuleBaseAddress, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64 ); - TRACY_API ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChain = 0; t_SymAddrIncludeInlineTrace _SymAddrIncludeInlineTrace = 0; t_SymQueryInlineTrace _SymQueryInlineTrace = 0; t_SymFromInlineContext _SymFromInlineContext = 0; t_SymGetLineFromInlineContext _SymGetLineFromInlineContext = 0; -} + typedef unsigned long (__stdcall *___tracy_t_RtlWalkFrameChain)( void**, unsigned long, unsigned long ); + ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChainPtr = nullptr; + TRACY_API unsigned long ___tracy_RtlWalkFrameChain( void** callers, unsigned long count, unsigned long flags) + { + return ___tracy_RtlWalkFrameChainPtr(callers, count, flags); + } +} struct ModuleCache { @@ -136,18 +310,19 @@ struct KernelDriver KernelDriver* s_krnlCache = nullptr; size_t s_krnlCacheCnt; - void InitCallstackCritical() { - ___tracy_RtlWalkFrameChain = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); + ___tracy_RtlWalkFrameChainPtr = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); } -void InitCallstack() +void DbgHelpInit() { - _SymAddrIncludeInlineTrace = (t_SymAddrIncludeInlineTrace)GetProcAddress( GetModuleHandleA( "dbghelp.dll" ), "SymAddrIncludeInlineTrace" ); - _SymQueryInlineTrace = (t_SymQueryInlineTrace)GetProcAddress( GetModuleHandleA( "dbghelp.dll" ), "SymQueryInlineTrace" ); - _SymFromInlineContext = (t_SymFromInlineContext)GetProcAddress( GetModuleHandleA( "dbghelp.dll" ), "SymFromInlineContext" ); - _SymGetLineFromInlineContext = (t_SymGetLineFromInlineContext)GetProcAddress( GetModuleHandleA( "dbghelp.dll" ), "SymGetLineFromInlineContext" ); + if( s_shouldResolveSymbolsOffline ) return; + + _SymAddrIncludeInlineTrace = (t_SymAddrIncludeInlineTrace)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymAddrIncludeInlineTrace"); + _SymQueryInlineTrace = (t_SymQueryInlineTrace)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymQueryInlineTrace"); + _SymFromInlineContext = (t_SymFromInlineContext)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymFromInlineContext"); + _SymGetLineFromInlineContext = (t_SymGetLineFromInlineContext)GetProcAddress(GetModuleHandleA("dbghelp.dll"), "SymGetLineFromInlineContext"); #ifdef TRACY_DBGHELP_LOCK DBGHELP_INIT; @@ -157,9 +332,78 @@ void InitCallstack() SymInitialize( GetCurrentProcess(), nullptr, true ); SymSetOptions( SYMOPT_LOAD_LINES ); +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif +} + +DWORD64 DbgHelpLoadSymbolsForModule( const char* imageName, uint64_t baseOfDll, uint32_t bllSize ) +{ + if( s_shouldResolveSymbolsOffline ) return 0; + return SymLoadModuleEx( GetCurrentProcess(), nullptr, imageName, nullptr, baseOfDll, bllSize, nullptr, 0 ); +} + +ModuleCache* LoadSymbolsForModuleAndCache( const char* imageName, uint32_t imageNameLength, uint64_t baseOfDll, uint32_t dllSize ) +{ + DbgHelpLoadSymbolsForModule( imageName, baseOfDll, dllSize ); + + ModuleCache* cachedModule = s_modCache->push_next(); + cachedModule->start = baseOfDll; + cachedModule->end = baseOfDll + dllSize; + + // when doing offline symbol resolution, we must store the full path of the dll for the resolving to work + if( s_shouldResolveSymbolsOffline ) + { + cachedModule->name = (char*)tracy_malloc_fast(imageNameLength + 1); + memcpy(cachedModule->name, imageName, imageNameLength); + cachedModule->name[imageNameLength] = '\0'; + } + else + { + auto ptr = imageName + imageNameLength; + while (ptr > imageName && *ptr != '\\' && *ptr != '/') ptr--; + if (ptr > imageName) ptr++; + const auto namelen = imageName + imageNameLength - ptr; + cachedModule->name = (char*)tracy_malloc_fast(namelen + 3); + cachedModule->name[0] = '['; + memcpy(cachedModule->name + 1, ptr, namelen); + cachedModule->name[namelen + 1] = ']'; + cachedModule->name[namelen + 2] = '\0'; + } + + return cachedModule; +} + +void InitCallstack() +{ +#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE + s_shouldResolveSymbolsOffline = ShouldResolveSymbolsOffline(); +#endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE + if( s_shouldResolveSymbolsOffline ) + { + TracyDebug("TRACY: enabling offline symbol resolving!\n"); + } + + DbgHelpInit(); + +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_LOCK; +#endif + + // use TRACY_NO_DBGHELP_INIT_LOAD=1 to disable preloading of driver + // and process module symbol loading at startup time - they will be loaded on demand later + // Sometimes this process can take a very long time and prevent resolving callstack frames + // symbols during that time. + const char* noInitLoadEnv = GetEnvVar( "TRACY_NO_DBGHELP_INIT_LOAD" ); + const bool initTimeModuleLoad = !( noInitLoadEnv && noInitLoadEnv[0] == '1' ); + if ( !initTimeModuleLoad ) + { + TracyDebug("TRACY: skipping init time dbghelper module load\n"); + } + DWORD needed; LPVOID dev[4096]; - if( EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 ) + if( initTimeModuleLoad && EnumDeviceDrivers( dev, sizeof(dev), &needed ) != 0 ) { char windir[MAX_PATH]; if( !GetWindowsDirectoryA( windir, sizeof( windir ) ) ) memcpy( windir, "c:\\windows", 11 ); @@ -193,7 +437,7 @@ void InitCallstack() path = full; } - SymLoadModuleEx( GetCurrentProcess(), nullptr, path, nullptr, (DWORD64)dev[i], 0, nullptr, 0 ); + DbgHelpLoadSymbolsForModule( path, (DWORD64)dev[i], 0 ); const auto psz = strlen( path ); auto pptr = (char*)tracy_malloc_fast( psz+1 ); @@ -214,7 +458,7 @@ void InitCallstack() HANDLE proc = GetCurrentProcess(); HMODULE mod[1024]; - if( EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) + if( initTimeModuleLoad && EnumProcessModules( proc, mod, sizeof( mod ), &needed ) != 0 ) { const auto sz = needed / sizeof( HMODULE ); for( size_t i=0; i 0 ) + const auto nameLength = GetModuleFileNameA( mod[i], name, 1021 ); + if( nameLength > 0 ) { // This may be a new module loaded since our call to SymInitialize. // Just in case, force DbgHelp to load its pdb ! - SymLoadModuleEx(proc, NULL, name, NULL, (DWORD64)info.lpBaseOfDll, info.SizeOfImage, NULL, 0); - - auto ptr = name + res; - while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--; - if( ptr > name ) ptr++; - const auto namelen = name + res - ptr; - auto cache = s_modCache->push_next(); - cache->start = base; - cache->end = base + info.SizeOfImage; - cache->name = (char*)tracy_malloc_fast( namelen+3 ); - cache->name[0] = '['; - memcpy( cache->name+1, ptr, namelen ); - cache->name[namelen+1] = ']'; - cache->name[namelen+2] = '\0'; + LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); } } } @@ -259,6 +489,8 @@ void EndCallstack() const char* DecodeCallstackPtrFast( uint64_t ptr ) { + if( s_shouldResolveSymbolsOffline ) return "[unresolved]"; + static char ret[MaxNameSize]; const auto proc = GetCurrentProcess(); @@ -294,7 +526,13 @@ const char* GetKernelModulePath( uint64_t addr ) return it->path; } -static const char* GetModuleNameAndPrepareSymbols( uint64_t addr ) +struct ModuleNameAndBaseAddress +{ + const char* name; + uint64_t baseAddr; +}; + +ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr ) { if( ( addr >> 63 ) != 0 ) { @@ -303,17 +541,17 @@ static const char* GetModuleNameAndPrepareSymbols( uint64_t addr ) auto it = std::lower_bound( s_krnlCache, s_krnlCache + s_krnlCacheCnt, addr, []( const KernelDriver& lhs, const uint64_t& rhs ) { return lhs.addr > rhs; } ); if( it != s_krnlCache + s_krnlCacheCnt ) { - return it->mod; + return ModuleNameAndBaseAddress{ it->mod, it->addr }; } } - return ""; + return ModuleNameAndBaseAddress{ "", addr }; } for( auto& v : *s_modCache ) { if( addr >= v.start && addr < v.end ) { - return v.name; + return ModuleNameAndBaseAddress{ v.name, v.start }; } } @@ -334,35 +572,33 @@ static const char* GetModuleNameAndPrepareSymbols( uint64_t addr ) if( addr >= base && addr < base + info.SizeOfImage ) { char name[1024]; - const auto res = GetModuleFileNameA( mod[i], name, 1021 ); - if( res > 0 ) + const auto nameLength = GetModuleFileNameA( mod[i], name, 1021 ); + if( nameLength > 0 ) { // since this is the first time we encounter this module, load its symbols (needed for modules loaded after SymInitialize) - SymLoadModuleEx(proc, NULL, name, NULL, (DWORD64)info.lpBaseOfDll, info.SizeOfImage, NULL, 0); - auto ptr = name + res; - while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--; - if( ptr > name ) ptr++; - const auto namelen = name + res - ptr; - auto cache = s_modCache->push_next(); - cache->start = base; - cache->end = base + info.SizeOfImage; - cache->name = (char*)tracy_malloc_fast( namelen+3 ); - cache->name[0] = '['; - memcpy( cache->name+1, ptr, namelen ); - cache->name[namelen+1] = ']'; - cache->name[namelen+2] = '\0'; - return cache->name; + ModuleCache* cachedModule = LoadSymbolsForModuleAndCache( name, nameLength, (DWORD64)info.lpBaseOfDll, info.SizeOfImage ); + return ModuleNameAndBaseAddress{ cachedModule->name, cachedModule->start }; } } } } } - return "[unknown]"; + + return ModuleNameAndBaseAddress{ "[unknown]", 0x0 }; } CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) { CallstackSymbolData sym; + + if( s_shouldResolveSymbolsOffline ) + { + sym.file = "[unknown]"; + sym.line = 0; + sym.needFree = false; + return sym; + } + IMAGEHLP_LINE64 line; DWORD displacement = 0; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); @@ -390,15 +626,32 @@ CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { - int write; - const auto proc = GetCurrentProcess(); - InitRpmalloc(); - #ifdef TRACY_DBGHELP_LOCK DBGHELP_LOCK; #endif - const auto moduleName = GetModuleNameAndPrepareSymbols(ptr); + InitRpmalloc(); + + const ModuleNameAndBaseAddress moduleNameAndAddress = GetModuleNameAndPrepareSymbols( ptr ); + + if( s_shouldResolveSymbolsOffline ) + { +#ifdef TRACY_DBGHELP_LOCK + DBGHELP_UNLOCK; +#endif + + cb_data[0].symAddr = ptr - moduleNameAndAddress.baseAddr; + cb_data[0].symLen = 0; + + cb_data[0].name = CopyStringFast("[unresolved]"); + cb_data[0].file = CopyStringFast("[unknown]"); + cb_data[0].line = 0; + + return { cb_data, 1, moduleNameAndAddress.name }; + } + + int write; + const auto proc = GetCurrentProcess(); #if !defined TRACY_NO_CALLSTACK_INLINES BOOL doInline = FALSE; @@ -448,7 +701,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) cb_data[write].line = line.LineNumber; } - cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleName ); + cb_data[write].name = symValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name ); cb_data[write].file = CopyStringFast( filename ); if( symValid ) { @@ -481,7 +734,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) cb.line = line.LineNumber; } - cb.name = symInlineValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleName ); + cb.name = symInlineValid ? CopyStringFast( si->Name, si->NameLen ) : CopyStringFast( moduleNameAndAddress.name ); cb.file = CopyStringFast( filename ); if( symInlineValid ) { @@ -502,17 +755,21 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) DBGHELP_UNLOCK; #endif - return { cb_data, uint8_t( cb_num ), moduleName }; + return { cb_data, uint8_t( cb_num ), moduleNameAndAddress.name }; } #elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 enum { MaxCbTrace = 64 }; -struct backtrace_state* cb_bts; +struct backtrace_state* cb_bts = nullptr; + int cb_num; CallstackEntry cb_data[MaxCbTrace]; int cb_fixup; +#ifdef TRACY_USE_IMAGE_CACHE +static ImageCache* s_imageCache = nullptr; +#endif //#ifdef TRACY_USE_IMAGE_CACHE #ifdef TRACY_DEBUGINFOD debuginfod_client* s_debuginfod; @@ -525,13 +782,14 @@ struct DebugInfo int fd; }; -FastVector s_di_known( 16 ); +static FastVector* s_di_known; #endif #ifdef __linux struct KernelSymbol { uint64_t addr; + uint32_t size; const char* name; const char* mod; }; @@ -543,10 +801,11 @@ static void InitKernelSymbols() { FILE* f = fopen( "/proc/kallsyms", "rb" ); if( !f ) return; - tracy::FastVector tmpSym( 1024 ); + tracy::FastVector tmpSym( 512 * 1024 ); size_t linelen = 16 * 1024; // linelen must be big enough to prevent reallocs in getline() auto linebuf = (char*)tracy_malloc( linelen ); ssize_t sz; + size_t validCnt = 0; while( ( sz = getline( &linebuf, &linelen, f ) ) != -1 ) { auto ptr = linebuf; @@ -579,7 +838,7 @@ static void InitKernelSymbols() } if( addr == 0 ) continue; ptr++; - if( *ptr != 'T' && *ptr != 't' ) continue; + const bool valid = *ptr == 'T' || *ptr == 't'; ptr += 2; const auto namestart = ptr; while( *ptr != '\t' && *ptr != '\n' ) ptr++; @@ -594,20 +853,28 @@ static void InitKernelSymbols() modend = ptr; } - auto strname = (char*)tracy_malloc_fast( nameend - namestart + 1 ); - memcpy( strname, namestart, nameend - namestart ); - strname[nameend-namestart] = '\0'; - + char* strname = nullptr; char* strmod = nullptr; - if( modstart ) + + if( valid ) { - strmod = (char*)tracy_malloc_fast( modend - modstart + 1 ); - memcpy( strmod, modstart, modend - modstart ); - strmod[modend-modstart] = '\0'; + validCnt++; + + strname = (char*)tracy_malloc_fast( nameend - namestart + 1 ); + memcpy( strname, namestart, nameend - namestart ); + strname[nameend-namestart] = '\0'; + + if( modstart ) + { + strmod = (char*)tracy_malloc_fast( modend - modstart + 1 ); + memcpy( strmod, modstart, modend - modstart ); + strmod[modend-modstart] = '\0'; + } } auto sym = tmpSym.push_next(); sym->addr = addr; + sym->size = 0; sym->name = strname; sym->mod = strmod; } @@ -615,11 +882,22 @@ static void InitKernelSymbols() fclose( f ); if( tmpSym.empty() ) return; - std::sort( tmpSym.begin(), tmpSym.end(), []( const KernelSymbol& lhs, const KernelSymbol& rhs ) { return lhs.addr > rhs.addr; } ); - s_kernelSymCnt = tmpSym.size(); - s_kernelSym = (KernelSymbol*)tracy_malloc_fast( sizeof( KernelSymbol ) * s_kernelSymCnt ); - memcpy( s_kernelSym, tmpSym.data(), sizeof( KernelSymbol ) * s_kernelSymCnt ); - TracyDebug( "Loaded %zu kernel symbols\n", s_kernelSymCnt ); + std::sort( tmpSym.begin(), tmpSym.end(), []( const KernelSymbol& lhs, const KernelSymbol& rhs ) { return lhs.addr < rhs.addr; } ); + for( size_t i=0; i*)tracy_malloc( sizeof( FastVector ) ); + new (s_di_known) FastVector( 16 ); #endif } @@ -725,11 +1023,11 @@ DebugInfo* FindDebugInfo( FastVector& vec, const uint8_t* buildid_dat int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const char* filename ) { auto buildid = (uint8_t*)buildid_data; - auto it = FindDebugInfo( s_di_known, buildid, buildid_size ); + auto it = FindDebugInfo( *s_di_known, buildid, buildid_size ); if( it ) return it->fd >= 0 ? dup( it->fd ) : -1; int fd = debuginfod_find_debuginfo( s_debuginfod, buildid, buildid_size, nullptr ); - it = s_di_known.push_next(); + it = s_di_known->push_next(); it->buildid_size = buildid_size; it->buildid = (uint8_t*)tracy_malloc( buildid_size ); memcpy( it->buildid, buildid, buildid_size ); @@ -744,7 +1042,7 @@ int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const const uint8_t* GetBuildIdForImage( const char* image, size_t& size ) { assert( image ); - for( auto& v : s_di_known ) + for( auto& v : *s_di_known ) { if( strcmp( image, v.filename ) == 0 ) { @@ -763,11 +1061,21 @@ debuginfod_client* GetDebuginfodClient() void EndCallstack() { +#ifdef TRACY_USE_IMAGE_CACHE + if( s_imageCache ) + { + s_imageCache->~ImageCache(); + tracy_free( s_imageCache ); + } +#endif //#ifdef TRACY_USE_IMAGE_CACHE #ifndef TRACY_DEMANGLE ___tracy_free_demangle_buffer(); #endif #ifdef TRACY_DEBUGINFOD - ClearDebugInfoVector( s_di_known ); + ClearDebugInfoVector( *s_di_known ); + s_di_known->~FastVector(); + tracy_free( s_di_known ); + debuginfod_end( s_debuginfod ); #endif } @@ -824,7 +1132,15 @@ static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum* CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) { CallstackSymbolData sym; - backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); + if( cb_bts ) + { + backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); + } + else + { + SymbolAddressErrorCb(&sym, nullptr, 0); + } + return sym; } @@ -927,33 +1243,67 @@ void SymInfoError( void* /*data*/, const char* /*msg*/, int /*errnum*/ ) cb_data[cb_num-1].symAddr = 0; } +void GetSymbolForOfflineResolve(void* address, uint64_t imageBaseAddress, CallstackEntry& cbEntry) +{ + // tagged with a string that we can identify as an unresolved symbol + cbEntry.name = CopyStringFast( "[unresolved]" ); + // set .so relative offset so it can be resolved offline + cbEntry.symAddr = (uint64_t)address - imageBaseAddress; + cbEntry.symLen = 0x0; + cbEntry.file = CopyStringFast( "[unknown]" ); + cbEntry.line = 0; +} + CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { InitRpmalloc(); if( ptr >> 63 == 0 ) { - cb_num = 0; - backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr ); - assert( cb_num > 0 ); + const char* imageName = nullptr; + uint64_t imageBaseAddress = 0x0; - backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); - - const char* symloc = nullptr; +#ifdef TRACY_USE_IMAGE_CACHE + const auto* image = s_imageCache->GetImageForAddress((void*)ptr); + if( image ) + { + imageName = image->m_name; + imageBaseAddress = uint64_t(image->m_startAddress); + } +#else Dl_info dlinfo; - if( dladdr( (void*)ptr, &dlinfo ) ) symloc = dlinfo.dli_fname; + if( dladdr( (void*)ptr, &dlinfo ) ) + { + imageName = dlinfo.dli_fname; + imageBaseAddress = uint64_t( dlinfo.dli_fbase ); + } +#endif - return { cb_data, uint8_t( cb_num ), symloc ? symloc : "[unknown]" }; + if( s_shouldResolveSymbolsOffline ) + { + cb_num = 1; + GetSymbolForOfflineResolve( (void*)ptr, imageBaseAddress, cb_data[0] ); + } + else + { + cb_num = 0; + backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr ); + assert( cb_num > 0 ); + + backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); + } + + return { cb_data, uint8_t( cb_num ), imageName ? imageName : "[unknown]" }; } #ifdef __linux else if( s_kernelSym ) { - auto it = std::lower_bound( s_kernelSym, s_kernelSym + s_kernelSymCnt, ptr, []( const KernelSymbol& lhs, const uint64_t& rhs ) { return lhs.addr > rhs; } ); + auto it = std::lower_bound( s_kernelSym, s_kernelSym + s_kernelSymCnt, ptr, []( const KernelSymbol& lhs, const uint64_t& rhs ) { return lhs.addr + lhs.size < rhs; } ); if( it != s_kernelSym + s_kernelSymCnt ) { cb_data[0].name = CopyStringFast( it->name ); cb_data[0].file = CopyStringFast( "" ); cb_data[0].line = 0; - cb_data[0].symLen = 0; + cb_data[0].symLen = it->size; cb_data[0].symAddr = it->addr; return { cb_data, 1, it->mod ? it->mod : "" }; } diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.hpp b/Source/ThirdParty/tracy/client/TracyCallstack.hpp index 96bee3f51..7991c3087 100644 --- a/Source/ThirdParty/tracy/client/TracyCallstack.hpp +++ b/Source/ThirdParty/tracy/client/TracyCallstack.hpp @@ -3,22 +3,28 @@ #include "TracyCallstack.h" -#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 5 -# include -#elif TRACY_HAS_CALLSTACK >= 3 -# include -#endif - - #ifndef TRACY_HAS_CALLSTACK namespace tracy { -static tracy_force_inline void* Callstack( int depth ) { return nullptr; } +static constexpr bool has_callstack() { return false; } +static tracy_force_inline void* Callstack( int32_t /*depth*/ ) { return nullptr; } } #else +#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 5 +# include +#elif TRACY_HAS_CALLSTACK >= 3 +# ifdef TRACY_LIBUNWIND_BACKTRACE + // libunwind is, in general, significantly faster than execinfo based backtraces +# define UNW_LOCAL_ONLY +# include +# else +# include +# endif +#endif + #ifdef TRACY_DEBUGINFOD # include #endif @@ -31,6 +37,8 @@ static tracy_force_inline void* Callstack( int depth ) { return nullptr; } namespace tracy { +static constexpr bool has_callstack() { return true; } + struct CallstackSymbolData { const char* file; @@ -72,11 +80,10 @@ debuginfod_client* GetDebuginfodClient(); extern "C" { - typedef unsigned long (__stdcall *___tracy_t_RtlWalkFrameChain)( void**, unsigned long, unsigned long ); - TRACY_API extern ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChain; + TRACY_API unsigned long ___tracy_RtlWalkFrameChain( void**, unsigned long, unsigned long ); } -static tracy_force_inline void* Callstack( int depth ) +static tracy_force_inline void* Callstack( int32_t depth ) { assert( depth >= 1 && depth < 63 ); auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) ); @@ -105,7 +112,7 @@ static _Unwind_Reason_Code tracy_unwind_callback( struct _Unwind_Context* ctx, v return _URC_NO_REASON; } -static tracy_force_inline void* Callstack( int depth ) +static tracy_force_inline void* Callstack( int32_t depth ) { assert( depth >= 1 && depth < 63 ); @@ -120,12 +127,18 @@ static tracy_force_inline void* Callstack( int depth ) #elif TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 -static tracy_force_inline void* Callstack( int depth ) +static tracy_force_inline void* Callstack( int32_t depth ) { assert( depth >= 1 ); auto trace = (uintptr_t*)tracy_malloc( ( 1 + (size_t)depth ) * sizeof( uintptr_t ) ); + +#ifdef TRACY_LIBUNWIND_BACKTRACE + size_t num = unw_backtrace( (void**)(trace+1), depth ); +#else const auto num = (size_t)backtrace( (void**)(trace+1), depth ); +#endif + *trace = num; return trace; diff --git a/Source/ThirdParty/tracy/client/TracyKCore.cpp b/Source/ThirdParty/tracy/client/TracyKCore.cpp new file mode 100644 index 000000000..09d51d117 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyKCore.cpp @@ -0,0 +1,121 @@ +#ifdef __linux__ + +#include +#include +#include +#include +#include + +#include "TracyDebug.hpp" +#include "TracyKCore.hpp" +#include "../common/TracyAlloc.hpp" + +#if !defined(__GLIBC__) && !defined(__WORDSIZE) +// include __WORDSIZE headers for musl +# include +#endif + +namespace tracy +{ + +using elf_half = uint16_t; +using elf_word = uint32_t; +using elf_sword = int32_t; + +#if __WORDSIZE == 32 + using elf_addr = uint32_t; + using elf_off = uint32_t; + using elf_xword = uint32_t; +#else + using elf_addr = uint64_t; + using elf_off = uint64_t; + using elf_xword = uint64_t; +#endif + +struct elf_ehdr +{ + unsigned char e_ident[16]; + elf_half e_type; + elf_half e_machine; + elf_word e_version; + elf_addr e_entry; + elf_off e_phoff; + elf_off e_shoff; + elf_word e_flags; + elf_half e_ehsize; + elf_half e_phentsize; + elf_half e_phnum; + elf_half e_shentsize; + elf_half e_shnum; + elf_half e_shstrndx; +}; + +struct elf_phdr +{ + elf_word p_type; + elf_word p_flags; + elf_off p_offset; + elf_addr p_vaddr; + elf_addr p_paddr; + elf_xword p_filesz; + elf_xword p_memsz; + uint64_t p_align; // include 32-bit-only flags field for 32-bit compatibility +}; + +KCore::KCore() + : m_offsets( 16 ) +{ + m_fd = open( "/proc/kcore", O_RDONLY ); + if( m_fd == -1 ) return; + + elf_ehdr ehdr; + if( read( m_fd, &ehdr, sizeof( ehdr ) ) != sizeof( ehdr ) ) goto err; + + assert( ehdr.e_phentsize == sizeof( elf_phdr ) ); + + for( elf_half i=0; istart = phdr.p_vaddr; + ptr->size = phdr.p_memsz; + ptr->offset = phdr.p_offset; + } + + std::sort( m_offsets.begin(), m_offsets.end(), []( const Offset& lhs, const Offset& rhs ) { return lhs.start < rhs.start; } ); + TracyDebug( "KCore: %zu segments found\n", m_offsets.size() ); + return; + +err: + close( m_fd ); + m_fd = -1; +} + +KCore::~KCore() +{ + if( m_fd != -1 ) close( m_fd ); +} + +void* KCore::Retrieve( uint64_t addr, uint64_t size ) const +{ + if( m_fd == -1 ) return nullptr; + auto it = std::lower_bound( m_offsets.begin(), m_offsets.end(), addr, []( const Offset& lhs, uint64_t rhs ) { return lhs.start + lhs.size < rhs; } ); + if( it == m_offsets.end() ) return nullptr; + if( addr + size > it->start + it->size ) return nullptr; + if( lseek( m_fd, it->offset + addr - it->start, SEEK_SET ) == -1 ) return nullptr; + auto ptr = tracy_malloc( size ); + if( read( m_fd, ptr, size ) != ssize_t( size ) ) + { + tracy_free( ptr ); + return nullptr; + } + return ptr; +} + +} + +#endif \ No newline at end of file diff --git a/Source/ThirdParty/tracy/client/TracyKCore.hpp b/Source/ThirdParty/tracy/client/TracyKCore.hpp new file mode 100644 index 000000000..437e172c2 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyKCore.hpp @@ -0,0 +1,37 @@ +#ifndef __TRACYKCORE_HPP__ +#define __TRACYKCORE_HPP__ + +#ifdef __linux__ + +#include + +#include "TracyFastVector.hpp" + +namespace tracy +{ + +class KCore +{ + struct Offset + { + uint64_t start; + uint64_t size; + uint64_t offset; + }; + +public: + KCore(); + ~KCore(); + + void* Retrieve( uint64_t addr, uint64_t size ) const; + +private: + int m_fd; + FastVector m_offsets; +}; + +} + +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.cpp b/Source/ThirdParty/tracy/client/TracyProfiler.cpp index 6d67eabf2..c96fc5beb 100644 --- a/Source/ThirdParty/tracy/client/TracyProfiler.cpp +++ b/Source/ThirdParty/tracy/client/TracyProfiler.cpp @@ -45,6 +45,14 @@ # include #endif +#ifdef __QNX__ +# include +# include +# include +# include +# include +#endif + #include #include #include @@ -70,6 +78,9 @@ #include "TracyArmCpuTable.hpp" #include "TracySysTrace.hpp" +#if defined TRACY_MANUAL_LIFETIME && !defined(TRACY_DELAYED_INIT) +# error "TRACY_MANUAL_LIFETIME requires enabled TRACY_DELAYED_INIT" +#endif #ifdef TRACY_PORT # ifndef TRACY_DATA_PORT @@ -96,9 +107,12 @@ # include extern "C" typedef LONG (WINAPI *t_RtlGetVersion)( PRTL_OSVERSIONINFOW ); extern "C" typedef BOOL (WINAPI *t_GetLogicalProcessorInformationEx)( LOGICAL_PROCESSOR_RELATIONSHIP, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD ); +extern "C" typedef char* (WINAPI *t_WineGetVersion)(); +extern "C" typedef char* (WINAPI *t_WineGetBuildId)(); #else # include # include +# include #endif #if defined __linux__ # include @@ -118,6 +132,10 @@ extern "C" typedef BOOL (WINAPI *t_GetLogicalProcessorInformationEx)( LOGICAL_PR #include "Engine/Platform/MemoryStats.h" #endif +#ifdef __QNX__ +extern char* __progname; +#endif + namespace tracy { @@ -160,7 +178,11 @@ static std::vector ParseMappings() { uintptr_t start_addr; uintptr_t end_addr; +#if defined(__LP64__) if( sscanf( line, "%lx-%lx", &start_addr, &end_addr ) != 2 ) continue; +#else + if (sscanf( line, "%dx-%dx", &start_addr, &end_addr ) != 2 ) continue; +#endif char* first_space = strchr( line, ' ' ); if( !first_space ) continue; char* perm = first_space + 1; @@ -258,8 +280,19 @@ static bool EnsureReadable( uintptr_t address ) MappingInfo* mapping = LookUpMapping(address); return mapping && EnsureReadable( *mapping ); } - -#endif // defined __ANDROID__ +#elif defined WIN32 +static bool EnsureReadable( uintptr_t address ) +{ + MEMORY_BASIC_INFORMATION memInfo; + VirtualQuery( reinterpret_cast( address ), &memInfo, sizeof( memInfo ) ); + return memInfo.Protect != PAGE_NOACCESS; +} +#else +static bool EnsureReadable( uintptr_t address ) +{ + return true; +} +#endif #ifndef TRACY_DELAYED_INIT @@ -284,7 +317,7 @@ struct ThreadHandleWrapper static inline void CpuId( uint32_t* regs, uint32_t leaf ) { memset(regs, 0, sizeof(uint32_t) * 4); -#if defined _WIN32 +#if defined _MSC_VER __cpuidex( (int*)regs, leaf, 0 ); #else __get_cpuid( leaf, regs, regs+1, regs+2, regs+3 ); @@ -407,6 +440,8 @@ static const char* GetProcessName() #elif defined __APPLE__ || defined BSD auto buf = getprogname(); if( buf ) processName = buf; +#elif defined __QNX__ + processName = __progname; #endif return processName; } @@ -444,6 +479,10 @@ static const char* GetProcessExecutablePath() static char buf[1024]; readlink( "/proc/curproc/exe", buf, 1024 ); return buf; +#elif defined __QNX__ + static char buf[_PC_PATH_MAX + 1]; + _cmdname(buf); + return buf; #else return nullptr; #endif @@ -495,7 +534,16 @@ static const char* GetHostInfo() # ifdef __MINGW32__ ptr += sprintf( ptr, "OS: Windows %i.%i.%i (MingW)\n", (int)ver.dwMajorVersion, (int)ver.dwMinorVersion, (int)ver.dwBuildNumber ); # else - ptr += sprintf( ptr, "OS: Windows %i.%i.%i\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber ); + auto WineGetVersion = (t_WineGetVersion)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "wine_get_version" ); + auto WineGetBuildId = (t_WineGetBuildId)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "wine_get_build_id" ); + if( WineGetVersion && WineGetBuildId ) + { + ptr += sprintf( ptr, "OS: Windows %lu.%lu.%lu (Wine %s [%s])\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, WineGetVersion(), WineGetBuildId() ); + } + else + { + ptr += sprintf( ptr, "OS: Windows %lu.%lu.%lu\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber ); + } # endif } #elif defined __linux__ @@ -522,6 +570,8 @@ static const char* GetHostInfo() ptr += sprintf( ptr, "OS: BSD (NetBSD)\n" ); #elif defined __OpenBSD__ ptr += sprintf( ptr, "OS: BSD (OpenBSD)\n" ); +#elif defined __QNX__ + ptr += sprintf( ptr, "OS: QNX\n" ); #else String computerName = Platform::GetComputerName(); char computerNameBuf[60]; @@ -697,6 +747,21 @@ static const char* GetHostInfo() size_t sz = sizeof( memSize ); sysctlbyname( "hw.physmem", &memSize, &sz, nullptr, 0 ); ptr += sprintf( ptr, "RAM: %zu MB\n", memSize / 1024 / 1024 ); +#elif defined __QNX__ + struct asinfo_entry *entries = SYSPAGE_ENTRY(asinfo); + size_t count = SYSPAGE_ENTRY_SIZE(asinfo) / sizeof(struct asinfo_entry); + char *strings = SYSPAGE_ENTRY(strings)->data; + + uint64_t memSize = 0; + size_t i; + for (i = 0; i < count; i++) { + struct asinfo_entry *entry = &entries[i]; + if (strcmp(strings + entry->name, "ram") == 0) { + memSize += entry->end - entry->start + 1; + } + } + memSize = memSize / 1024 / 1024; + ptr += sprintf( ptr, "RAM: %llu MB\n", memSize); #else ptr += sprintf( ptr, "RAM: %zu MB\n", (size_t)Platform::GetMemoryStats().TotalPhysicalMemory / 1024 / 1024 ); #endif @@ -1152,12 +1217,14 @@ thread_local bool RpThreadShutdown = false; # ifdef TRACY_MANUAL_LIFETIME ProfilerData* s_profilerData = nullptr; static ProfilerThreadData& GetProfilerThreadData(); +static std::atomic s_isProfilerStarted { false }; TRACY_API void StartupProfiler() { s_profilerData = (ProfilerData*)tracy_malloc( sizeof( ProfilerData ) ); new (s_profilerData) ProfilerData(); s_profilerData->profiler.SpawnWorkerThreads(); GetProfilerThreadData().token = ProducerWrapper( *s_profilerData ); + s_isProfilerStarted.store( true, std::memory_order_seq_cst ); } static ProfilerData& GetProfilerData() { @@ -1166,6 +1233,7 @@ static ProfilerData& GetProfilerData() } TRACY_API void ShutdownProfiler() { + s_isProfilerStarted.store( false, std::memory_order_seq_cst ); s_profilerData->~ProfilerData(); tracy_free( s_profilerData ); s_profilerData = nullptr; @@ -1173,6 +1241,10 @@ TRACY_API void ShutdownProfiler() RpThreadInitDone = false; RpInitDone.store( 0, std::memory_order_release ); } +TRACY_API bool IsProfilerStarted() +{ + return s_isProfilerStarted.load( std::memory_order_seq_cst ); +} # else static std::atomic profilerDataLock { 0 }; static std::atomic profilerData { nullptr }; @@ -1331,6 +1403,8 @@ TRACY_API LuaZoneState& GetLuaZoneState() { return s_luaZoneState; } TRACY_API bool ProfilerAvailable() { return s_instance != nullptr; } TRACY_API bool ProfilerAllocatorAvailable() { return !RpThreadShutdown; } +constexpr static size_t SafeSendBufferSize = 65536; + Profiler::Profiler() : m_timeBegin( 0 ) , m_mainThread( detail::GetThreadHandleImpl() ) @@ -1385,6 +1459,11 @@ Profiler::Profiler() CalibrateDelay(); ReportTopology(); +#ifdef __linux__ + m_kcore = (KCore*)tracy_malloc( sizeof( KCore ) ); + new(m_kcore) KCore(); +#endif + #ifndef TRACY_NO_EXIT const char* noExitEnv = GetEnvVar( "TRACY_NO_EXIT" ); if( noExitEnv && noExitEnv[0] == '1' ) @@ -1399,15 +1478,99 @@ Profiler::Profiler() m_userPort = atoi( userPort ); } + m_safeSendBuffer = (char*)tracy_malloc( SafeSendBufferSize ); + +#ifndef _WIN32 + pipe(m_pipe); +# if defined __APPLE__ || defined BSD + // FreeBSD/XNU don't have F_SETPIPE_SZ, so use the default + m_pipeBufSize = 16384; +# else + m_pipeBufSize = (int)(ptrdiff_t)SafeSendBufferSize; + while( fcntl( m_pipe[0], F_SETPIPE_SZ, m_pipeBufSize ) < 0 && errno == EPERM ) m_pipeBufSize /= 2; // too big; reduce + m_pipeBufSize = fcntl( m_pipe[0], F_GETPIPE_SZ ); +# endif + fcntl( m_pipe[1], F_SETFL, O_NONBLOCK ); +#endif + #if !defined(TRACY_DELAYED_INIT) || !defined(TRACY_MANUAL_LIFETIME) SpawnWorkerThreads(); #endif } +void Profiler::InstallCrashHandler() +{ + +#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER + struct sigaction threadFreezer = {}; + threadFreezer.sa_handler = ThreadFreezer; + sigaction( TRACY_CRASH_SIGNAL, &threadFreezer, &m_prevSignal.pwr ); + + struct sigaction crashHandler = {}; + crashHandler.sa_sigaction = CrashHandler; + crashHandler.sa_flags = SA_SIGINFO; + sigaction( SIGILL, &crashHandler, &m_prevSignal.ill ); + sigaction( SIGFPE, &crashHandler, &m_prevSignal.fpe ); + sigaction( SIGSEGV, &crashHandler, &m_prevSignal.segv ); + sigaction( SIGPIPE, &crashHandler, &m_prevSignal.pipe ); + sigaction( SIGBUS, &crashHandler, &m_prevSignal.bus ); + sigaction( SIGABRT, &crashHandler, &m_prevSignal.abrt ); +#endif + +#if defined _WIN32 && !defined TRACY_UWP && !defined TRACY_NO_CRASH_HANDLER + // We cannot use Vectored Exception handling because it catches application-wide frame-based SEH blocks. We only + // want to catch unhandled exceptions. + m_prevHandler = SetUnhandledExceptionFilter( CrashFilter ); +#endif + +#ifndef TRACY_NO_CRASH_HANDLER + m_crashHandlerInstalled = true; +#endif + +} + +void Profiler::RemoveCrashHandler() +{ +#if defined _WIN32 && !defined TRACY_UWP && !defined TRACY_NO_CRASH_HANDLER + if( m_crashHandlerInstalled ) + { + auto prev = SetUnhandledExceptionFilter( (LPTOP_LEVEL_EXCEPTION_FILTER)m_prevHandler ); + if( prev != CrashFilter ) SetUnhandledExceptionFilter( prev ); // A different exception filter was installed over ours => put it back + } +#endif + +#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER + if( m_crashHandlerInstalled ) + { + auto restore = []( int signum, struct sigaction* prev ) { + struct sigaction old; + sigaction( signum, prev, &old ); + if( old.sa_sigaction != CrashHandler ) sigaction( signum, &old, nullptr ); // A different signal handler was installed over ours => put it back + }; + restore( TRACY_CRASH_SIGNAL, &m_prevSignal.pwr ); + restore( SIGILL, &m_prevSignal.ill ); + restore( SIGFPE, &m_prevSignal.fpe ); + restore( SIGSEGV, &m_prevSignal.segv ); + restore( SIGPIPE, &m_prevSignal.pipe ); + restore( SIGBUS, &m_prevSignal.bus ); + restore( SIGABRT, &m_prevSignal.abrt ); + } +#endif + m_crashHandlerInstalled = false; +} + void Profiler::SpawnWorkerThreads() { #ifdef TRACY_HAS_SYSTEM_TRACING - if( SysTraceStart( m_samplingPeriod ) ) + // use TRACY_NO_SYS_TRACE=1 to force disabling sys tracing (even if available in the underlying system) + // as it can have significant impact on the size of the traces + const char* noSysTrace = GetEnvVar( "TRACY_NO_SYS_TRACE" ); + const bool disableSystrace = (noSysTrace && noSysTrace[0] == '1'); + if( disableSystrace ) + { + TracyDebug("TRACY: Sys Trace was disabled by 'TRACY_NO_SYS_TRACE=1'\n"); + } + else if( SysTraceStart( m_samplingPeriod ) ) { s_sysTraceThread = (Thread*)tracy_malloc( sizeof( Thread ) ); new(s_sysTraceThread) Thread( SysTraceWorker, nullptr ); @@ -1433,27 +1596,6 @@ void Profiler::SpawnWorkerThreads() # ifdef TRACY_HAS_CALLSTACK s_symbolThreadId = GetThreadId( s_symbolThread->Handle() ); # endif - m_exceptionHandler = AddVectoredExceptionHandler( 1, CrashFilter ); -#endif - -#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER - struct sigaction threadFreezer = {}; - threadFreezer.sa_handler = ThreadFreezer; - sigaction( TRACY_CRASH_SIGNAL, &threadFreezer, &m_prevSignal.pwr ); - - struct sigaction crashHandler = {}; - crashHandler.sa_sigaction = CrashHandler; - crashHandler.sa_flags = SA_SIGINFO; - sigaction( SIGILL, &crashHandler, &m_prevSignal.ill ); - sigaction( SIGFPE, &crashHandler, &m_prevSignal.fpe ); - sigaction( SIGSEGV, &crashHandler, &m_prevSignal.segv ); - sigaction( SIGPIPE, &crashHandler, &m_prevSignal.pipe ); - sigaction( SIGBUS, &crashHandler, &m_prevSignal.bus ); - sigaction( SIGABRT, &crashHandler, &m_prevSignal.abrt ); -#endif - -#ifndef TRACY_NO_CRASH_HANDLER - m_crashHandlerInstalled = true; #endif #ifdef TRACY_HAS_CALLSTACK @@ -1467,22 +1609,7 @@ Profiler::~Profiler() { m_shutdown.store( true, std::memory_order_relaxed ); -#if defined _WIN32 && !defined TRACY_UWP - if( m_crashHandlerInstalled ) RemoveVectoredExceptionHandler( m_exceptionHandler ); -#endif - -#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER - if( m_crashHandlerInstalled ) - { - sigaction( TRACY_CRASH_SIGNAL, &m_prevSignal.pwr, nullptr ); - sigaction( SIGILL, &m_prevSignal.ill, nullptr ); - sigaction( SIGFPE, &m_prevSignal.fpe, nullptr ); - sigaction( SIGSEGV, &m_prevSignal.segv, nullptr ); - sigaction( SIGPIPE, &m_prevSignal.pipe, nullptr ); - sigaction( SIGBUS, &m_prevSignal.bus, nullptr ); - sigaction( SIGABRT, &m_prevSignal.abrt, nullptr ); - } -#endif + RemoveCrashHandler(); #ifdef TRACY_HAS_SYSTEM_TRACING if( s_sysTraceThread ) @@ -1510,6 +1637,17 @@ Profiler::~Profiler() EndCallstack(); #endif +#ifdef __linux__ + m_kcore->~KCore(); + tracy_free( m_kcore ); +#endif + +#ifndef _WIN32 + close( m_pipe[0] ); + close( m_pipe[1] ); +#endif + tracy_free( m_safeSendBuffer ); + tracy_free( m_lz4Buf ); tracy_free( m_buffer ); LZ4_freeStream( (LZ4_stream_t*)m_stream ); @@ -1687,6 +1825,12 @@ void Profiler::Worker() new(m_broadcast) UdpBroadcast(); # ifdef TRACY_ONLY_LOCALHOST const char* addr = "127.255.255.255"; +# elif defined TRACY_CLIENT_ADDRESS + const char* addr = TRACY_CLIENT_ADDRESS; +# elif defined __QNX__ + // global broadcast address of 255.255.255.255 is not well-supported by QNX, + // use the interface broadcast address instead, e.g. "const char* addr = 192.168.1.255;" +# error Need to specify TRACY_CLIENT_ADDRESS for a QNX target. # else const char* addr = "255.255.255.255"; # endif @@ -1799,6 +1943,7 @@ void Profiler::Worker() m_connectionId.fetch_add( 1, std::memory_order_release ); #endif m_isConnected.store( true, std::memory_order_release ); + InstallCrashHandler(); HandshakeStatus handshake = HandshakeWelcome; m_sock->Send( &handshake, sizeof( handshake ) ); @@ -1901,6 +2046,8 @@ void Profiler::Worker() if( ShouldExit() ) break; m_isConnected.store( false, std::memory_order_release ); + RemoveCrashHandler(); + #ifdef TRACY_ON_DEMAND m_bufferOffset = 0; m_bufferStart = 0; @@ -2728,6 +2875,15 @@ Profiler::DequeueStatus Profiler::DequeueSerial() MemWrite( &item->memFree.time, dt ); break; } + case QueueType::MemDiscard: + case QueueType::MemDiscardCallstack: + { + int64_t t = MemRead( &item->memDiscard.time ); + int64_t dt = t - refSerial; + refSerial = t; + MemWrite( &item->memDiscard.time, dt ); + break; + } case QueueType::GpuZoneBeginSerial: case QueueType::GpuZoneBeginCallstackSerial: { @@ -2964,6 +3120,62 @@ bool Profiler::CommitData() return ret; } +char* Profiler::SafeCopyProlog( const char* data, size_t size ) +{ + bool success = true; + char* buf = m_safeSendBuffer; +#ifndef NDEBUG + assert( !m_inUse.exchange(true) ); +#endif + + if( size > SafeSendBufferSize ) buf = (char*)tracy_malloc( size ); + +#ifdef _WIN32 + __try + { + memcpy( buf, data, size ); + } + __except( 1 /*EXCEPTION_EXECUTE_HANDLER*/ ) + { + success = false; + } +#else + // Send through the pipe to ensure safe reads + for( size_t offset = 0; offset != size; /*in loop*/ ) + { + size_t sendsize = size - offset; + ssize_t result1, result2; + while( ( result1 = write( m_pipe[1], data + offset, sendsize ) ) < 0 && errno == EINTR ) { /* retry */ } + if( result1 < 0 ) + { + success = false; + break; + } + while( ( result2 = read( m_pipe[0], buf + offset, result1 ) ) < 0 && errno == EINTR ) { /* retry */ } + if( result2 != result1 ) + { + success = false; + break; + } + offset += result1; + } +#endif + + if( success ) return buf; + + SafeCopyEpilog( buf ); + return nullptr; +} + +void Profiler::SafeCopyEpilog( char* buf ) +{ + if( buf != m_safeSendBuffer ) tracy_free( buf ); + +#ifndef NDEBUG + m_inUse.store( false ); +#endif +} + bool Profiler::SendData( const char* data, size_t len ) { const lz4sz_t lz4sz = LZ4_compress_fast_continue( (LZ4_stream_t*)m_stream, data, m_lz4Buf + sizeof( lz4sz_t ), (int)len, LZ4Size, 1 ); @@ -3290,6 +3502,17 @@ void Profiler::HandleSymbolQueueItem( const SymbolQueueItem& si ) } } } +#elif defined __linux__ + void* data = m_kcore->Retrieve( si.ptr, si.extra ); + if( data ) + { + TracyLfqPrepare( QueueType::SymbolCodeMetadata ); + MemWrite( &item->symbolCodeMetadata.symbol, si.ptr ); + MemWrite( &item->symbolCodeMetadata.ptr, (uint64_t)data ); + MemWrite( &item->symbolCodeMetadata.size, (uint32_t)si.extra ); + TracyLfqCommit; + break; + } #endif TracyLfqPrepare( QueueType::AckSymbolCodeNotAvailable ); TracyLfqCommit; @@ -3375,7 +3598,22 @@ bool Profiler::HandleServerQuery() } else { - SendString( ptr, GetThreadName( ptr ), QueueType::ThreadName ); + auto t = GetThreadNameData( (uint32_t)ptr ); + if( t ) + { + SendString( ptr, t->name, QueueType::ThreadName ); + if( t->groupHint != 0 ) + { + TracyLfqPrepare( QueueType::ThreadGroupHint ); + MemWrite( &item->threadGroupHint.thread, (uint32_t)ptr ); + MemWrite( &item->threadGroupHint.groupHint, t->groupHint ); + TracyLfqCommit; + } + } + else + { + SendString( ptr, GetThreadName( (uint32_t)ptr ), QueueType::ThreadName ); + } } break; case ServerQuerySourceLocation: @@ -3613,6 +3851,7 @@ void Profiler::ReportTopology() struct CpuData { uint32_t package; + uint32_t die; uint32_t core; uint32_t thread; }; @@ -3625,23 +3864,55 @@ void Profiler::ReportTopology() # endif if( !_GetLogicalProcessorInformationEx ) return; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* packageInfo = nullptr; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* dieInfo = nullptr; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* coreInfo = nullptr; + DWORD psz = 0; _GetLogicalProcessorInformationEx( RelationProcessorPackage, nullptr, &psz ); - auto packageInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( psz ); - auto res = _GetLogicalProcessorInformationEx( RelationProcessorPackage, packageInfo, &psz ); - assert( res ); + if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) + { + packageInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( psz ); + auto res = _GetLogicalProcessorInformationEx( RelationProcessorPackage, packageInfo, &psz ); + assert( res ); + } + else + { + psz = 0; + } + + DWORD dsz = 0; + _GetLogicalProcessorInformationEx( RelationProcessorDie, nullptr, &dsz ); + if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) + { + dieInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( dsz ); + auto res = _GetLogicalProcessorInformationEx( RelationProcessorDie, dieInfo, &dsz ); + assert( res ); + } + else + { + dsz = 0; + } DWORD csz = 0; _GetLogicalProcessorInformationEx( RelationProcessorCore, nullptr, &csz ); - auto coreInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( csz ); - res = _GetLogicalProcessorInformationEx( RelationProcessorCore, coreInfo, &csz ); - assert( res ); + if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) + { + coreInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( csz ); + auto res = _GetLogicalProcessorInformationEx( RelationProcessorCore, coreInfo, &csz ); + assert( res ); + } + else + { + csz = 0; + } SYSTEM_INFO sysinfo; GetSystemInfo( &sysinfo ); const uint32_t numcpus = sysinfo.dwNumberOfProcessors; auto cpuData = (CpuData*)tracy_malloc( sizeof( CpuData ) * numcpus ); + memset( cpuData, 0, sizeof( CpuData ) * numcpus ); for( uint32_t i=0; iRelationship == RelationProcessorDie ); + // FIXME account for GroupCount + auto mask = ptr->Processor.GroupMask[0].Mask; + int core = 0; + while( mask != 0 ) + { + if( mask & 1 ) cpuData[core].die = idx; + core++; + mask >>= 1; + } + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + idx++; + } + idx = 0; ptr = coreInfo; while( (char*)ptr < ((char*)coreInfo) + csz ) @@ -3686,6 +3975,7 @@ void Profiler::ReportTopology() TracyLfqPrepare( QueueType::CpuTopology ); MemWrite( &item->cpuTopology.package, data.package ); + MemWrite( &item->cpuTopology.die, data.die ); MemWrite( &item->cpuTopology.core, data.core ); MemWrite( &item->cpuTopology.thread, data.thread ); @@ -3721,12 +4011,26 @@ void Profiler::ReportTopology() fclose( f ); cpuData[i].package = uint32_t( atoi( buf ) ); cpuData[i].thread = i; + sprintf( path, "%s%i/topology/core_id", basePath, i ); f = fopen( path, "rb" ); - read = fread( buf, 1, 1024, f ); - buf[read] = '\0'; - fclose( f ); - cpuData[i].core = uint32_t( atoi( buf ) ); + if( f ) + { + read = fread( buf, 1, 1024, f ); + buf[read] = '\0'; + fclose( f ); + cpuData[i].core = uint32_t( atoi( buf ) ); + } + + sprintf( path, "%s%i/topology/die_id", basePath, i ); + f = fopen( path, "rb" ); + if( f ) + { + read = fread( buf, 1, 1024, f ); + buf[read] = '\0'; + fclose( f ); + cpuData[i].die = uint32_t( atoi( buf ) ); + } } for( int i=0; icpuTopology.package, data.package ); + MemWrite( &item->cpuTopology.die, data.die ); MemWrite( &item->cpuTopology.core, data.core ); MemWrite( &item->cpuTopology.thread, data.thread ); @@ -3858,58 +4163,58 @@ void Profiler::ConfigurePlot( const char* name, PlotFormatType type, bool step, TracyLfqCommit; } -void Profiler::Message( const char* txt, size_t size, int callstack ) +void Profiler::Message( const char* txt, size_t size, int32_t callstack_depth ) { assert( size < (std::numeric_limits::max)() ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif - if( callstack != 0 ) + if( callstack_depth != 0 && has_callstack() ) { - tracy::GetProfiler().SendCallstack( callstack ); + tracy::GetProfiler().SendCallstack( callstack_depth ); } auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); - TracyQueuePrepare( callstack == 0 ? QueueType::Message : QueueType::MessageCallstack ); + TracyQueuePrepare( callstack_depth == 0 ? QueueType::Message : QueueType::MessageCallstack ); MemWrite( &item->messageFat.time, GetTime() ); MemWrite( &item->messageFat.text, (uint64_t)ptr ); MemWrite( &item->messageFat.size, (uint16_t)size ); TracyQueueCommit( messageFatThread ); } -void Profiler::Message( const char* txt, int callstack ) +void Profiler::Message( const char* txt, int32_t callstack_depth ) { #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif - if( callstack != 0 ) + if( callstack_depth != 0 && has_callstack() ) { - tracy::GetProfiler().SendCallstack( callstack ); + tracy::GetProfiler().SendCallstack( callstack_depth ); } - TracyQueuePrepare( callstack == 0 ? QueueType::MessageLiteral : QueueType::MessageLiteralCallstack ); + TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageLiteral : QueueType::MessageLiteralCallstack ); MemWrite( &item->messageLiteral.time, GetTime() ); MemWrite( &item->messageLiteral.text, (uint64_t)txt ); TracyQueueCommit( messageLiteralThread ); } -void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int callstack ) +void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int32_t callstack_depth ) { assert( size < (std::numeric_limits::max)() ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif - if( callstack != 0 ) + if( callstack_depth != 0 && has_callstack() ) { - tracy::GetProfiler().SendCallstack( callstack ); + tracy::GetProfiler().SendCallstack( callstack_depth ); } auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); - TracyQueuePrepare( callstack == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack ); + TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack ); MemWrite( &item->messageColorFat.time, GetTime() ); MemWrite( &item->messageColorFat.text, (uint64_t)ptr ); MemWrite( &item->messageColorFat.b, uint8_t( ( color ) & 0xFF ) ); @@ -3919,17 +4224,17 @@ void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int c TracyQueueCommit( messageColorFatThread ); } -void Profiler::MessageColor( const char* txt, uint32_t color, int callstack ) +void Profiler::MessageColor( const char* txt, uint32_t color, int32_t callstack_depth ) { #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif - if( callstack != 0 ) + if( callstack_depth != 0 && has_callstack() ) { - tracy::GetProfiler().SendCallstack( callstack ); + tracy::GetProfiler().SendCallstack( callstack_depth ); } - TracyQueuePrepare( callstack == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack ); + TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack ); MemWrite( &item->messageColorLiteral.time, GetTime() ); MemWrite( &item->messageColorLiteral.text, (uint64_t)txt ); MemWrite( &item->messageColorLiteral.b, uint8_t( ( color ) & 0xFF ) ); @@ -3984,23 +4289,25 @@ void Profiler::MemFree( const void* ptr, bool secure ) void Profiler::MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure ) { if( secure && !ProfilerAvailable() ) return; -#ifdef TRACY_HAS_CALLSTACK - auto& profiler = GetProfiler(); + if( depth > 0 && has_callstack() ) + { + auto& profiler = GetProfiler(); # ifdef TRACY_ON_DEMAND - if( !profiler.IsConnected() ) return; + if( !profiler.IsConnected() ) return; # endif - const auto thread = GetThreadHandle(); + const auto thread = GetThreadHandle(); - auto callstack = Callstack( depth ); + auto callstack = Callstack( depth ); - profiler.m_serialLock.lock(); - SendCallstackSerial( callstack ); - SendMemAlloc( QueueType::MemAllocCallstack, thread, ptr, size ); - profiler.m_serialLock.unlock(); -#else - static_cast(depth); // unused - MemAlloc( ptr, size, secure ); -#endif + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemAlloc( QueueType::MemAllocCallstack, thread, ptr, size ); + profiler.m_serialLock.unlock(); + } + else + { + MemAlloc( ptr, size, secure ); + } } void Profiler::MemFreeCallstack( const void* ptr, int depth, bool secure ) @@ -4011,23 +4318,25 @@ void Profiler::MemFreeCallstack( const void* ptr, int depth, bool secure ) MemFree( ptr, secure ); return; } -#ifdef TRACY_HAS_CALLSTACK - auto& profiler = GetProfiler(); + if( depth > 0 && has_callstack() ) + { + auto& profiler = GetProfiler(); # ifdef TRACY_ON_DEMAND - if( !profiler.IsConnected() ) return; + if( !profiler.IsConnected() ) return; # endif - const auto thread = GetThreadHandle(); + const auto thread = GetThreadHandle(); - auto callstack = Callstack( depth ); + auto callstack = Callstack( depth ); - profiler.m_serialLock.lock(); - SendCallstackSerial( callstack ); - SendMemFree( QueueType::MemFreeCallstack, thread, ptr ); - profiler.m_serialLock.unlock(); -#else - static_cast(depth); // unused - MemFree( ptr, secure ); -#endif + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemFree( QueueType::MemFreeCallstack, thread, ptr ); + profiler.m_serialLock.unlock(); + } + else + { + MemFree( ptr, secure ); + } } void Profiler::MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ) @@ -4061,61 +4370,98 @@ void Profiler::MemFreeNamed( const void* ptr, bool secure, const char* name ) void Profiler::MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name ) { if( secure && !ProfilerAvailable() ) return; -#ifdef TRACY_HAS_CALLSTACK - auto& profiler = GetProfiler(); + if( depth > 0 && has_callstack() ) + { + auto& profiler = GetProfiler(); # ifdef TRACY_ON_DEMAND - if( !profiler.IsConnected() ) return; + if( !profiler.IsConnected() ) return; # endif - const auto thread = GetThreadHandle(); + const auto thread = GetThreadHandle(); - auto callstack = Callstack( depth ); + auto callstack = Callstack( depth ); - profiler.m_serialLock.lock(); - SendCallstackSerial( callstack ); - SendMemName( name ); - SendMemAlloc( QueueType::MemAllocCallstackNamed, thread, ptr, size ); - profiler.m_serialLock.unlock(); -#else - static_cast(depth); // unused - static_cast(name); // unused - MemAlloc( ptr, size, secure ); -#endif + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemName( name ); + SendMemAlloc( QueueType::MemAllocCallstackNamed, thread, ptr, size ); + profiler.m_serialLock.unlock(); + } + else + { + MemAllocNamed( ptr, size, secure, name ); + } } void Profiler::MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name ) { if( secure && !ProfilerAvailable() ) return; -#ifdef TRACY_HAS_CALLSTACK - auto& profiler = GetProfiler(); + if( depth > 0 && has_callstack() ) + { + auto& profiler = GetProfiler(); # ifdef TRACY_ON_DEMAND - if( !profiler.IsConnected() ) return; + if( !profiler.IsConnected() ) return; # endif + const auto thread = GetThreadHandle(); + + auto callstack = Callstack( depth ); + + profiler.m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemName( name ); + SendMemFree( QueueType::MemFreeCallstackNamed, thread, ptr ); + profiler.m_serialLock.unlock(); + } + else + { + MemFreeNamed( ptr, secure, name ); + } +} + +void Profiler::MemDiscard( const char* name, bool secure ) +{ + if( secure && !ProfilerAvailable() ) return; +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif const auto thread = GetThreadHandle(); - auto callstack = Callstack( depth ); + GetProfiler().m_serialLock.lock(); + SendMemDiscard( QueueType::MemDiscard, thread, name ); + GetProfiler().m_serialLock.unlock(); +} - profiler.m_serialLock.lock(); - SendCallstackSerial( callstack ); - SendMemName( name ); - SendMemFree( QueueType::MemFreeCallstackNamed, thread, ptr ); - profiler.m_serialLock.unlock(); -#else - static_cast(depth); // unused - static_cast(name); // unused - MemFree( ptr, secure ); -#endif +void Profiler::MemDiscardCallstack( const char* name, bool secure, int32_t depth ) +{ + if( secure && !ProfilerAvailable() ) return; + if( depth > 0 && has_callstack() ) + { +# ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +# endif + const auto thread = GetThreadHandle(); + + auto callstack = Callstack( depth ); + + GetProfiler().m_serialLock.lock(); + SendCallstackSerial( callstack ); + SendMemDiscard( QueueType::MemDiscard, thread, name ); + GetProfiler().m_serialLock.unlock(); + } + else + { + MemDiscard( name, secure ); + } } void Profiler::SendCallstack( int depth ) { -#ifdef TRACY_HAS_CALLSTACK - auto ptr = Callstack( depth ); - TracyQueuePrepare( QueueType::Callstack ); - MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); - TracyQueueCommit( callstackFatThread ); -#else - static_cast(depth); // unused -#endif + if( depth > 0 && has_callstack() ) + { + auto ptr = Callstack( depth ); + TracyQueuePrepare( QueueType::Callstack ); + MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); + TracyQueueCommit( callstackFatThread ); + } } void Profiler::ParameterRegister( ParameterCallback cb, void* data ) @@ -4140,7 +4486,7 @@ void Profiler::ParameterSetup( uint32_t idx, const char* name, bool isBool, int3 TracyLfqCommit; } -void Profiler::SendCallstack( int depth, const char* skipBefore ) +void Profiler::SendCallstack( int32_t depth, const char* skipBefore ) { #ifdef TRACY_HAS_CALLSTACK auto ptr = Callstack( depth ); @@ -4215,44 +4561,41 @@ void Profiler::HandleSymbolCodeQuery( uint64_t symbol, uint32_t size ) } else { -#ifdef __ANDROID__ - // On Android it's common for code to be in mappings that are only executable - // but not readable. - if( !EnsureReadable( symbol ) ) - { - AckSymbolCodeNotAvailable(); - return; - } -#endif - SendLongString( symbol, (const char*)symbol, size, QueueType::SymbolCode ); + auto&& lambda = [ this, symbol ]( const char* buf, size_t size ) { + SendLongString( symbol, buf, size, QueueType::SymbolCode ); + }; + + // 'symbol' may have come from a module that has since unloaded, perform a safe copy before sending + if( !WithSafeCopy( (const char*)symbol, size, lambda ) ) AckSymbolCodeNotAvailable(); } } void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) { bool ok = false; - struct stat st; - if( stat( data, &st ) == 0 && (uint64_t)st.st_mtime < m_exectime ) + FILE* f = fopen( data, "rb" ); + if( f ) { - if( st.st_size < ( TargetFrameSize - 16 ) ) + struct stat st; + if( fstat( fileno( f ), &st ) == 0 && (uint64_t)st.st_mtime < m_exectime && st.st_size < ( TargetFrameSize - 16 ) ) { - FILE* f = fopen( data, "rb" ); - if( f ) + auto ptr = (char*)tracy_malloc_fast( st.st_size ); + auto rd = fread( ptr, 1, st.st_size, f ); + if( rd == (size_t)st.st_size ) { - auto ptr = (char*)tracy_malloc_fast( st.st_size ); - auto rd = fread( ptr, 1, st.st_size, f ); - fclose( f ); - if( rd == (size_t)st.st_size ) - { - TracyLfqPrepare( QueueType::SourceCodeMetadata ); - MemWrite( &item->sourceCodeMetadata.ptr, (uint64_t)ptr ); - MemWrite( &item->sourceCodeMetadata.size, (uint32_t)rd ); - MemWrite( &item->sourceCodeMetadata.id, id ); - TracyLfqCommit; - ok = true; - } + TracyLfqPrepare( QueueType::SourceCodeMetadata ); + MemWrite( &item->sourceCodeMetadata.ptr, (uint64_t)ptr ); + MemWrite( &item->sourceCodeMetadata.size, (uint32_t)rd ); + MemWrite( &item->sourceCodeMetadata.id, id ); + TracyLfqCommit; + ok = true; + } + else + { + tracy_free_fast( ptr ); } } + fclose( f ); } #ifdef TRACY_DEBUGINFOD @@ -4282,6 +4625,10 @@ void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) TracyLfqCommit; ok = true; } + else + { + tracy_free_fast( ptr ); + } } close( d ); } @@ -4308,6 +4655,10 @@ void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) TracyLfqCommit; ok = true; } + else + { + tracy_free_fast( ptr ); + } } } @@ -4333,4 +4684,754 @@ int64_t Profiler::GetTimeQpc() } +#if 0 + +#ifdef __cplusplus +extern "C" { +#endif + +TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin( const struct ___tracy_source_location_data* srcloc, int32_t active ) +{ + ___tracy_c_zone_context ctx; +#ifdef TRACY_ON_DEMAND + ctx.active = active && tracy::GetProfiler().IsConnected(); +#else + ctx.active = active; +#endif + if( !ctx.active ) return ctx; + const auto id = tracy::GetProfiler().GetNextZoneId(); + ctx.id = id; + +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneBegin ); + tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyQueueCommitC( zoneBeginThread ); + } + return ctx; +} + +TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_callstack( const struct ___tracy_source_location_data* srcloc, int32_t depth, int32_t active ) +{ + ___tracy_c_zone_context ctx; +#ifdef TRACY_ON_DEMAND + ctx.active = active && tracy::GetProfiler().IsConnected(); +#else + ctx.active = active; +#endif + if( !ctx.active ) return ctx; + const auto id = tracy::GetProfiler().GetNextZoneId(); + ctx.id = id; + +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + auto zoneQueue = tracy::QueueType::ZoneBegin; + if( depth > 0 && tracy::has_callstack() ) + { + tracy::GetProfiler().SendCallstack( depth ); + zoneQueue = tracy::QueueType::ZoneBeginCallstack; + } + TracyQueuePrepareC( zoneQueue ); + tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyQueueCommitC( zoneBeginThread ); + + return ctx; +} + +TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc( uint64_t srcloc, int32_t active ) +{ + ___tracy_c_zone_context ctx; +#ifdef TRACY_ON_DEMAND + ctx.active = active && tracy::GetProfiler().IsConnected(); +#else + ctx.active = active; +#endif + if( !ctx.active ) + { + tracy::tracy_free( (void*)srcloc ); + return ctx; + } + const auto id = tracy::GetProfiler().GetNextZoneId(); + ctx.id = id; + +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneBeginAllocSrcLoc ); + tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->zoneBegin.srcloc, srcloc ); + TracyQueueCommitC( zoneBeginThread ); + } + return ctx; +} + +TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc_callstack( uint64_t srcloc, int32_t depth, int32_t active ) +{ + ___tracy_c_zone_context ctx; +#ifdef TRACY_ON_DEMAND + ctx.active = active && tracy::GetProfiler().IsConnected(); +#else + ctx.active = active; +#endif + if( !ctx.active ) + { + tracy::tracy_free( (void*)srcloc ); + return ctx; + } + const auto id = tracy::GetProfiler().GetNextZoneId(); + ctx.id = id; + +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + auto zoneQueue = tracy::QueueType::ZoneBeginAllocSrcLoc; + if( depth > 0 && tracy::has_callstack() ) + { + tracy::GetProfiler().SendCallstack( depth ); + zoneQueue = tracy::QueueType::ZoneBeginAllocSrcLocCallstack; + } + TracyQueuePrepareC( zoneQueue ); + tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->zoneBegin.srcloc, srcloc ); + TracyQueueCommitC( zoneBeginThread ); + + return ctx; +} + +TRACY_API void ___tracy_emit_zone_end( TracyCZoneCtx ctx ) +{ + if( !ctx.active ) return; +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneEnd ); + tracy::MemWrite( &item->zoneEnd.time, tracy::Profiler::GetTime() ); + TracyQueueCommitC( zoneEndThread ); + } +} + +TRACY_API void ___tracy_emit_zone_text( TracyCZoneCtx ctx, const char* txt, size_t size ) +{ + assert( size < std::numeric_limits::max() ); + if( !ctx.active ) return; + auto ptr = (char*)tracy::tracy_malloc( size ); + memcpy( ptr, txt, size ); +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneText ); + tracy::MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + tracy::MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyQueueCommitC( zoneTextFatThread ); + } +} + +TRACY_API void ___tracy_emit_zone_name( TracyCZoneCtx ctx, const char* txt, size_t size ) +{ + assert( size < std::numeric_limits::max() ); + if( !ctx.active ) return; + auto ptr = (char*)tracy::tracy_malloc( size ); + memcpy( ptr, txt, size ); +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneName ); + tracy::MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + tracy::MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyQueueCommitC( zoneTextFatThread ); + } +} + +TRACY_API void ___tracy_emit_zone_color( TracyCZoneCtx ctx, uint32_t color ) { + if( !ctx.active ) return; +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneColor ); + tracy::MemWrite( &item->zoneColor.b, uint8_t( ( color ) & 0xFF ) ); + tracy::MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) ); + tracy::MemWrite( &item->zoneColor.r, uint8_t( ( color >> 16 ) & 0xFF ) ); + TracyQueueCommitC( zoneColorThread ); + } +} + +TRACY_API void ___tracy_emit_zone_value( TracyCZoneCtx ctx, uint64_t value ) +{ + if( !ctx.active ) return; +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + { + TracyQueuePrepareC( tracy::QueueType::ZoneValue ); + tracy::MemWrite( &item->zoneValue.value, value ); + TracyQueueCommitC( zoneValueThread ); + } +} + +TRACY_API void ___tracy_emit_memory_alloc( const void* ptr, size_t size, int32_t secure ) { tracy::Profiler::MemAlloc( ptr, size, secure != 0 ); } +TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int32_t depth, int32_t secure ) +{ + if( depth > 0 && tracy::has_callstack() ) + { + tracy::Profiler::MemAllocCallstack( ptr, size, depth, secure != 0 ); + } + else + { + tracy::Profiler::MemAlloc( ptr, size, secure != 0 ); + } +} +TRACY_API void ___tracy_emit_memory_free( const void* ptr, int32_t secure ) { tracy::Profiler::MemFree( ptr, secure != 0 ); } +TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int32_t depth, int32_t secure ) +{ + if( depth > 0 && tracy::has_callstack() ) + { + tracy::Profiler::MemFreeCallstack( ptr, depth, secure != 0 ); + } + else + { + tracy::Profiler::MemFree( ptr, secure != 0 ); + } +} +TRACY_API void ___tracy_emit_memory_discard( const char* name, int32_t secure ) { tracy::Profiler::MemDiscard( name, secure != 0 ); } +TRACY_API void ___tracy_emit_memory_discard_callstack( const char* name, int32_t secure, int32_t depth ) +{ + if( depth > 0 && tracy::has_callstack() ) + { + tracy::Profiler::MemDiscardCallstack( name, secure != 0, depth ); + } + else + { + tracy::Profiler::MemDiscard( name, secure != 0 ); + } +} +TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, int32_t secure, const char* name ) { tracy::Profiler::MemAllocNamed( ptr, size, secure != 0, name ); } +TRACY_API void ___tracy_emit_memory_alloc_callstack_named( const void* ptr, size_t size, int32_t depth, int32_t secure, const char* name ) +{ + if( depth > 0 && tracy::has_callstack() ) + { + tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, secure != 0, name ); + } + else + { + tracy::Profiler::MemAllocNamed( ptr, size, secure != 0, name ); + } +} +TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, int32_t secure, const char* name ) { tracy::Profiler::MemFreeNamed( ptr, secure != 0, name ); } +TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int32_t depth, int32_t secure, const char* name ) +{ + if( depth > 0 && tracy::has_callstack() ) + { + tracy::Profiler::MemFreeCallstackNamed( ptr, depth, secure != 0, name ); + } + else + { + tracy::Profiler::MemFreeNamed( ptr, secure != 0, name ); + } +} +TRACY_API void ___tracy_emit_frame_mark( const char* name ) { tracy::Profiler::SendFrameMark( name ); } +TRACY_API void ___tracy_emit_frame_mark_start( const char* name ) { tracy::Profiler::SendFrameMark( name, tracy::QueueType::FrameMarkMsgStart ); } +TRACY_API void ___tracy_emit_frame_mark_end( const char* name ) { tracy::Profiler::SendFrameMark( name, tracy::QueueType::FrameMarkMsgEnd ); } +TRACY_API void ___tracy_emit_frame_image( const void* image, uint16_t w, uint16_t h, uint8_t offset, int32_t flip ) { tracy::Profiler::SendFrameImage( image, w, h, offset, flip != 0 ); } +TRACY_API void ___tracy_emit_plot( const char* name, double val ) { tracy::Profiler::PlotData( name, val ); } +TRACY_API void ___tracy_emit_plot_float( const char* name, float val ) { tracy::Profiler::PlotData( name, val ); } +TRACY_API void ___tracy_emit_plot_int( const char* name, int64_t val ) { tracy::Profiler::PlotData( name, val ); } +TRACY_API void ___tracy_emit_plot_config( const char* name, int32_t type, int32_t step, int32_t fill, uint32_t color ) { tracy::Profiler::ConfigurePlot( name, tracy::PlotFormatType(type), step != 0, fill != 0, color ); } +TRACY_API void ___tracy_emit_message( const char* txt, size_t size, int32_t callstack_depth ) { tracy::Profiler::Message( txt, size, callstack_depth ); } +TRACY_API void ___tracy_emit_messageL( const char* txt, int32_t callstack_depth ) { tracy::Profiler::Message( txt, callstack_depth ); } +TRACY_API void ___tracy_emit_messageC( const char* txt, size_t size, uint32_t color, int32_t callstack_depth ) { tracy::Profiler::MessageColor( txt, size, color, callstack_depth ); } +TRACY_API void ___tracy_emit_messageLC( const char* txt, uint32_t color, int32_t callstack_depth ) { tracy::Profiler::MessageColor( txt, color, callstack_depth ); } +TRACY_API void ___tracy_emit_message_appinfo( const char* txt, size_t size ) { tracy::Profiler::MessageAppInfo( txt, size ); } + +TRACY_API uint64_t ___tracy_alloc_srcloc( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, uint32_t color ) { + return tracy::Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, color ); +} + +TRACY_API uint64_t ___tracy_alloc_srcloc_name( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, uint32_t color ) { + return tracy::Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz, color ); +} + +TRACY_API void ___tracy_emit_gpu_zone_begin( const struct ___tracy_gpu_zone_begin_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuZoneBegin ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_callstack( const struct ___tracy_gpu_zone_begin_callstack_data data ) +{ + tracy::GetProfiler().SendCallstack( data.depth ); + TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginCallstack ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_alloc( const struct ___tracy_gpu_zone_begin_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginAllocSrcLoc ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack( const struct ___tracy_gpu_zone_begin_callstack_data data ) +{ + tracy::GetProfiler().SendCallstack( data.depth ); + TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginAllocSrcLocCallstack ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_time( const struct ___tracy_gpu_time_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuTime ); + tracy::MemWrite( &item->gpuTime.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuTime.queryId, data.queryId ); + tracy::MemWrite( &item->gpuTime.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_zone_end( const struct ___tracy_gpu_zone_end_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuZoneEnd ); + tracy::MemWrite( &item->gpuZoneEnd.cpuTime, tracy::Profiler::GetTime() ); + memset( &item->gpuZoneEnd.thread, 0, sizeof( item->gpuZoneEnd.thread ) ); + tracy::MemWrite( &item->gpuZoneEnd.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneEnd.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_new_context( ___tracy_gpu_new_context_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuNewContext ); + tracy::MemWrite( &item->gpuNewContext.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuNewContext.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuNewContext.period, data.period ); + tracy::MemWrite( &item->gpuNewContext.context, data.context ); + tracy::MemWrite( &item->gpuNewContext.flags, data.flags ); + tracy::MemWrite( &item->gpuNewContext.type, data.type ); + +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_context_name( const struct ___tracy_gpu_context_name_data data ) +{ + auto ptr = (char*)tracy::tracy_malloc( data.len ); + memcpy( ptr, data.name, data.len ); + + TracyLfqPrepareC( tracy::QueueType::GpuContextName ); + tracy::MemWrite( &item->gpuContextNameFat.context, data.context ); + tracy::MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); + tracy::MemWrite( &item->gpuContextNameFat.size, data.len ); + +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_calibration( const struct ___tracy_gpu_calibration_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuCalibration ); + tracy::MemWrite( &item->gpuCalibration.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuCalibration.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuCalibration.cpuDelta, data.cpuDelta ); + tracy::MemWrite( &item->gpuCalibration.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_time_sync( const struct ___tracy_gpu_time_sync_data data ) +{ + TracyLfqPrepareC( tracy::QueueType::GpuTimeSync ); + tracy::MemWrite( &item->gpuTimeSync.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuTimeSync.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuTimeSync.context, data.context ); + TracyLfqCommitC; +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_serial( const struct ___tracy_gpu_zone_begin_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginSerial ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_callstack_serial( const struct ___tracy_gpu_zone_begin_callstack_data data ) +{ + auto item = tracy::Profiler::QueueSerialCallstack( tracy::Callstack( data.depth ) ); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginCallstackSerial ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_serial( const struct ___tracy_gpu_zone_begin_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocSerial ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack_serial( const struct ___tracy_gpu_zone_begin_callstack_data data ) +{ + auto item = tracy::Profiler::QueueSerialCallstack( tracy::Callstack( data.depth ) ); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocCallstackSerial ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc ); + tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneBegin.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_time_serial( const struct ___tracy_gpu_time_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTime ); + tracy::MemWrite( &item->gpuTime.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuTime.queryId, data.queryId ); + tracy::MemWrite( &item->gpuTime.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_zone_end_serial( const struct ___tracy_gpu_zone_end_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneEndSerial ); + tracy::MemWrite( &item->gpuZoneEnd.cpuTime, tracy::Profiler::GetTime() ); + memset( &item->gpuZoneEnd.thread, 0, sizeof( item->gpuZoneEnd.thread ) ); + tracy::MemWrite( &item->gpuZoneEnd.queryId, data.queryId ); + tracy::MemWrite( &item->gpuZoneEnd.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_new_context_serial( ___tracy_gpu_new_context_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuNewContext ); + tracy::MemWrite( &item->gpuNewContext.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->gpuNewContext.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuNewContext.period, data.period ); + tracy::MemWrite( &item->gpuNewContext.context, data.context ); + tracy::MemWrite( &item->gpuNewContext.flags, data.flags ); + tracy::MemWrite( &item->gpuNewContext.type, data.type ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_context_name_serial( const struct ___tracy_gpu_context_name_data data ) +{ + auto ptr = (char*)tracy::tracy_malloc( data.len ); + memcpy( ptr, data.name, data.len ); + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuContextName ); + tracy::MemWrite( &item->gpuContextNameFat.context, data.context ); + tracy::MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); + tracy::MemWrite( &item->gpuContextNameFat.size, data.len ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_calibration_serial( const struct ___tracy_gpu_calibration_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuCalibration ); + tracy::MemWrite( &item->gpuCalibration.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuCalibration.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuCalibration.cpuDelta, data.cpuDelta ); + tracy::MemWrite( &item->gpuCalibration.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_emit_gpu_time_sync_serial( const struct ___tracy_gpu_time_sync_data data ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTimeSync ); + tracy::MemWrite( &item->gpuTimeSync.cpuTime, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->gpuTimeSync.gpuTime, data.gpuTime ); + tracy::MemWrite( &item->gpuTimeSync.context, data.context ); + tracy::Profiler::QueueSerialFinish(); +} + +struct __tracy_lockable_context_data +{ + uint32_t m_id; +#ifdef TRACY_ON_DEMAND + std::atomic m_lockCount; + std::atomic m_active; +#endif +}; + +TRACY_API struct __tracy_lockable_context_data* ___tracy_announce_lockable_ctx( const struct ___tracy_source_location_data* srcloc ) +{ + struct __tracy_lockable_context_data *lockdata = (__tracy_lockable_context_data*)tracy::tracy_malloc( sizeof( __tracy_lockable_context_data ) ); + lockdata->m_id =tracy:: GetLockCounter().fetch_add( 1, std::memory_order_relaxed ); +#ifdef TRACY_ON_DEMAND + new(&lockdata->m_lockCount) std::atomic( 0 ); + new(&lockdata->m_active) std::atomic( false ); +#endif + assert( lockdata->m_id != (std::numeric_limits::max)() ); + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockAnnounce ); + tracy::MemWrite( &item->lockAnnounce.id, lockdata->m_id ); + tracy::MemWrite( &item->lockAnnounce.time, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->lockAnnounce.lckloc, (uint64_t)srcloc ); + tracy::MemWrite( &item->lockAnnounce.type, tracy::LockType::Lockable ); +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + tracy::Profiler::QueueSerialFinish(); + + return lockdata; +} + +TRACY_API void ___tracy_terminate_lockable_ctx( struct __tracy_lockable_context_data* lockdata ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockTerminate ); + tracy::MemWrite( &item->lockTerminate.id, lockdata->m_id ); + tracy::MemWrite( &item->lockTerminate.time, tracy::Profiler::GetTime() ); +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + tracy::Profiler::QueueSerialFinish(); + +#ifdef TRACY_ON_DEMAND + lockdata->m_lockCount.~atomic(); + lockdata->m_active.~atomic(); +#endif + tracy::tracy_free((void*)lockdata); +} + +TRACY_API int32_t ___tracy_before_lock_lockable_ctx( struct __tracy_lockable_context_data* lockdata ) +{ +#ifdef TRACY_ON_DEMAND + bool queue = false; + const auto locks = lockdata->m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = lockdata->m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = tracy::GetProfiler().IsConnected(); + if( active != connected ) lockdata->m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return static_cast(false); +#endif + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockWait ); + tracy::MemWrite( &item->lockWait.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockWait.id, lockdata->m_id ); + tracy::MemWrite( &item->lockWait.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); + return static_cast(true); +} + +TRACY_API void ___tracy_after_lock_lockable_ctx( struct __tracy_lockable_context_data* lockdata ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockObtain ); + tracy::MemWrite( &item->lockObtain.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockObtain.id, lockdata->m_id ); + tracy::MemWrite( &item->lockObtain.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_after_unlock_lockable_ctx( struct __tracy_lockable_context_data* lockdata ) +{ +#ifdef TRACY_ON_DEMAND + lockdata->m_lockCount.fetch_sub( 1, std::memory_order_relaxed ); + if( !lockdata->m_active.load( std::memory_order_relaxed ) ) return; + if( !tracy::GetProfiler().IsConnected() ) + { + lockdata->m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockRelease ); + tracy::MemWrite( &item->lockRelease.id, lockdata->m_id ); + tracy::MemWrite( &item->lockRelease.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_after_try_lock_lockable_ctx( struct __tracy_lockable_context_data* lockdata, int32_t acquired ) +{ +#ifdef TRACY_ON_DEMAND + if( !acquired ) return; + + bool queue = false; + const auto locks = lockdata->m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = lockdata->m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = tracy::GetProfiler().IsConnected(); + if( active != connected ) lockdata->m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return; +#endif + + if( acquired ) + { + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockObtain ); + tracy::MemWrite( &item->lockObtain.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockObtain.id, lockdata->m_id ); + tracy::MemWrite( &item->lockObtain.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); + } +} + +TRACY_API void ___tracy_mark_lockable_ctx( struct __tracy_lockable_context_data* lockdata, const struct ___tracy_source_location_data* srcloc ) +{ +#ifdef TRACY_ON_DEMAND + const auto active = lockdata->m_active.load( std::memory_order_relaxed ); + if( !active ) return; + const auto connected = tracy::GetProfiler().IsConnected(); + if( !connected ) + { + if( active ) lockdata->m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockMark ); + tracy::MemWrite( &item->lockMark.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockMark.id, lockdata->m_id ); + tracy::MemWrite( &item->lockMark.srcloc, (uint64_t)srcloc ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_custom_name_lockable_ctx( struct __tracy_lockable_context_data* lockdata, const char* name, size_t nameSz ) +{ + assert( nameSz < (std::numeric_limits::max)() ); + auto ptr = (char*)tracy::tracy_malloc( nameSz ); + memcpy( ptr, name, nameSz ); + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockName ); + tracy::MemWrite( &item->lockNameFat.id, lockdata->m_id ); + tracy::MemWrite( &item->lockNameFat.name, (uint64_t)ptr ); + tracy::MemWrite( &item->lockNameFat.size, (uint16_t)nameSz ); +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API int32_t ___tracy_connected( void ) +{ + return static_cast( tracy::GetProfiler().IsConnected() ); +} + +#ifdef TRACY_FIBERS +TRACY_API void ___tracy_fiber_enter( const char* fiber ){ tracy::Profiler::EnterFiber( fiber, 0 ); } +TRACY_API void ___tracy_fiber_leave( void ){ tracy::Profiler::LeaveFiber(); } +#endif + +# if defined TRACY_MANUAL_LIFETIME && defined TRACY_DELAYED_INIT +TRACY_API void ___tracy_startup_profiler( void ) +{ + tracy::StartupProfiler(); +} + +TRACY_API void ___tracy_shutdown_profiler( void ) +{ + tracy::ShutdownProfiler(); +} + +TRACY_API int32_t ___tracy_profiler_started( void ) +{ + return static_cast( tracy::s_isProfilerStarted.load( std::memory_order_seq_cst ) ); +} +# endif + +#ifdef __cplusplus +} +#endif + +#endif + #endif diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.hpp b/Source/ThirdParty/tracy/client/TracyProfiler.hpp index b303a4503..0bfa7b246 100644 --- a/Source/ThirdParty/tracy/client/TracyProfiler.hpp +++ b/Source/ThirdParty/tracy/client/TracyProfiler.hpp @@ -10,6 +10,7 @@ #include "tracy_concurrentqueue.h" #include "tracy_SPSCQueue.h" #include "TracyCallstack.hpp" +#include "TracyKCore.hpp" #include "TracySysPower.hpp" #include "TracySysTime.hpp" #include "TracyFastVector.hpp" @@ -27,7 +28,7 @@ # include #endif -#if ( defined _WIN32 || ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) || ( defined TARGET_OS_IOS && TARGET_OS_IOS == 1 ) ) +#if ( (defined _WIN32 && !(defined _M_ARM64 || defined _M_ARM)) || ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) || ( defined TARGET_OS_IOS && TARGET_OS_IOS == 1 ) ) # define TRACY_HW_TIMER #endif @@ -44,6 +45,10 @@ namespace tracy #if defined(TRACY_DELAYED_INIT) && defined(TRACY_MANUAL_LIFETIME) TRACY_API void StartupProfiler(); TRACY_API void ShutdownProfiler(); +TRACY_API bool IsProfilerStarted(); +# define TracyIsStarted tracy::IsProfilerStarted() +#else +# define TracyIsStarted true #endif class GpuCtx; @@ -92,11 +97,11 @@ struct LuaZoneState #define TracyLfqPrepare( _type ) \ - moodycamel::ConcurrentQueueDefaultTraits::index_t __magic; \ - auto __token = GetToken(); \ + tracy::moodycamel::ConcurrentQueueDefaultTraits::index_t __magic; \ + auto __token = tracy::GetToken(); \ auto& __tail = __token->get_tail_index(); \ auto item = __token->enqueue_begin( __magic ); \ - MemWrite( &item->hdr.type, _type ); + tracy::MemWrite( &item->hdr.type, _type ); #define TracyLfqCommit \ __tail.store( __magic + 1, std::memory_order_release ); @@ -114,11 +119,11 @@ struct LuaZoneState #ifdef TRACY_FIBERS # define TracyQueuePrepare( _type ) \ - auto item = Profiler::QueueSerial(); \ - MemWrite( &item->hdr.type, _type ); + auto item = tracy::Profiler::QueueSerial(); \ + tracy::MemWrite( &item->hdr.type, _type ); # define TracyQueueCommit( _name ) \ - MemWrite( &item->_name.thread, GetThreadHandle() ); \ - Profiler::QueueSerialFinish(); + tracy::MemWrite( &item->_name.thread, tracy::GetThreadHandle() ); \ + tracy::Profiler::QueueSerialFinish(); # define TracyQueuePrepareC( _type ) \ auto item = tracy::Profiler::QueueSerial(); \ tracy::MemWrite( &item->hdr.type, _type ); @@ -278,6 +283,8 @@ public: static void MemFreeNamed( const void* ptr, bool secure, const char* name ); static void MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name ); static void MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name ); + static void MemDiscard( const char* name, bool secure ); + static void MemDiscardCallstack( const char* name, bool secure, int32_t depth ); static void SendCallstack( int depth ); static void ParameterRegister( ParameterCallback cb, void* data ); static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ); @@ -290,11 +297,12 @@ public: } #ifdef TRACY_FIBERS - static tracy_force_inline void EnterFiber( const char* fiber ) + static tracy_force_inline void EnterFiber( const char* fiber, int32_t groupHint ) { TracyQueuePrepare( QueueType::FiberEnter ); MemWrite( &item->fiberEnter.time, GetTime() ); MemWrite( &item->fiberEnter.fiber, (uint64_t)fiber ); + MemWrite( &item->fiberEnter.groupHint, groupHint ); TracyQueueCommit( fiberEnter ); } @@ -306,7 +314,7 @@ public: } #endif - void SendCallstack( int depth, const char* skipBefore ); + void SendCallstack( int32_t depth, const char* skipBefore ); static void CutCallstack( void* callstack, const char* skipBefore ); static bool ShouldExit(); @@ -359,29 +367,29 @@ public: // 1b null terminator // nsz zone name (optional) - static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, const char* function ) + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, const char* function, uint32_t color = 0 ) { - return AllocSourceLocation( line, source, function, nullptr, 0 ); + return AllocSourceLocation( line, source, function, nullptr, 0, color ); } - static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, const char* function, const char* name, size_t nameSz ) + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, const char* function, const char* name, size_t nameSz, uint32_t color = 0 ) { - return AllocSourceLocation( line, source, strlen(source), function, strlen(function), name, nameSz ); + return AllocSourceLocation( line, source, strlen(source), function, strlen(function), name, nameSz, color ); } - static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz ) + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, uint32_t color = 0 ) { - return AllocSourceLocation( line, source, sourceSz, function, functionSz, nullptr, 0 ); + return AllocSourceLocation( line, source, sourceSz, function, functionSz, nullptr, 0, color ); } - static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz ) + static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, uint32_t color = 0 ) { const auto sz32 = uint32_t( 2 + 4 + 4 + functionSz + 1 + sourceSz + 1 + nameSz ); assert( sz32 <= (std::numeric_limits::max)() ); const auto sz = uint16_t( sz32 ); auto ptr = (char*)tracy_malloc( sz ); memcpy( ptr, &sz, 2 ); - memset( ptr + 2, 0, 4 ); + memcpy( ptr + 2, &color, 4 ); memcpy( ptr + 6, &line, 4 ); memcpy( ptr + 10, function, functionSz ); ptr[10 + functionSz] = '\0'; @@ -412,6 +420,9 @@ private: void HandleSymbolQueueItem( const SymbolQueueItem& si ); #endif + void InstallCrashHandler(); + void RemoveCrashHandler(); + void ClearQueues( tracy::moodycamel::ConsumerToken& token ); void ClearSerial(); DequeueStatus Dequeue( tracy::moodycamel::ConsumerToken& token ); @@ -444,6 +455,21 @@ private: m_bufferOffset += int( len ); } + char* SafeCopyProlog( const char* p, size_t size ); + void SafeCopyEpilog( char* buf ); + + template // must be void( const char* buf, size_t size ) + bool WithSafeCopy( const char* p, size_t size, Callable&& callable ) + { + if( char* buf = SafeCopyProlog( p, size ) ) + { + callable( buf, size ); + SafeCopyEpilog( buf ); + return true; + } + return false; + } + bool SendData( const char* data, size_t len ); void SendLongString( uint64_t ptr, const char* str, size_t len, QueueType type ); void SendSourceLocation( uint64_t ptr ); @@ -473,14 +499,13 @@ private: static tracy_force_inline void SendCallstackSerial( void* ptr ) { -#ifdef TRACY_HAS_CALLSTACK - auto item = GetProfiler().m_serialQueue.prepare_next(); - MemWrite( &item->hdr.type, QueueType::CallstackSerial ); - MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); - GetProfiler().m_serialQueue.commit_next(); -#else - static_cast(ptr); // unused -#endif + if( has_callstack() ) + { + auto item = GetProfiler().m_serialQueue.prepare_next(); + MemWrite( &item->hdr.type, QueueType::CallstackSerial ); + MemWrite( &item->callstackFat.ptr, (uint64_t)ptr ); + GetProfiler().m_serialQueue.commit_next(); + } } static tracy_force_inline void SendMemAlloc( QueueType type, const uint32_t thread, const void* ptr, size_t size ) @@ -518,6 +543,18 @@ private: GetProfiler().m_serialQueue.commit_next(); } + static tracy_force_inline void SendMemDiscard( QueueType type, const uint32_t thread, const char* name ) + { + assert( type == QueueType::MemDiscard || type == QueueType::MemDiscardCallstack ); + + auto item = GetProfiler().m_serialQueue.prepare_next(); + MemWrite( &item->hdr.type, type ); + MemWrite( &item->memDiscard.time, GetTime() ); + MemWrite( &item->memDiscard.thread, thread ); + MemWrite( &item->memDiscard.name, (uint64_t)name ); + GetProfiler().m_serialQueue.commit_next(); + } + static tracy_force_inline void SendMemName( const char* name ) { assert( name ); @@ -601,13 +638,24 @@ private: char* m_queryData; char* m_queryDataPtr; -#if defined _WIN32 - void* m_exceptionHandler; +#ifndef NDEBUG + // m_safeSendBuffer and m_pipe should only be used by the Tracy Profiler thread; this ensures that in debug builds. + std::atomic_bool m_inUse{ false }; #endif + char* m_safeSendBuffer; + +#if defined _WIN32 + void* m_prevHandler; +#else + int m_pipe[2]; + int m_pipeBufSize; +#endif + #ifdef __linux__ struct { struct sigaction pwr, ill, fpe, segv, pipe, bus, abrt; } m_prevSignal; + KCore* m_kcore; #endif bool m_crashHandlerInstalled; diff --git a/Source/ThirdParty/tracy/client/TracyScoped.hpp b/Source/ThirdParty/tracy/client/TracyScoped.hpp index 2182bf65b..aa3cf5aad 100644 --- a/Source/ThirdParty/tracy/client/TracyScoped.hpp +++ b/Source/ThirdParty/tracy/client/TracyScoped.hpp @@ -2,6 +2,7 @@ #define __TRACYSCOPED_HPP__ #include +#include #include #include @@ -9,6 +10,8 @@ #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" #include "../client/TracyLock.hpp" +#include "TracyProfiler.hpp" +#include "TracyCallstack.hpp" namespace tracy { @@ -34,7 +37,7 @@ void ScopedZone::End() TracyQueueCommit( zoneEndThread ); } -ScopedZone::ScopedZone( const SourceLocationData* srcloc, bool is_active ) +ScopedZone::ScopedZone( const SourceLocationData* srcloc, int32_t depth, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) #else @@ -45,13 +48,19 @@ ScopedZone::ScopedZone( const SourceLocationData* srcloc, bool is_active ) #ifdef TRACY_ON_DEMAND m_connectionId = GetProfiler().ConnectionId(); #endif - TracyQueuePrepare( QueueType::ZoneBegin ); - MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); - MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); - TracyQueueCommit( zoneBeginThread ); -} + auto zoneQueue = QueueType::ZoneBegin; + if( depth > 0 && has_callstack() ) + { + GetProfiler().SendCallstack( depth ); + zoneQueue = QueueType::ZoneBeginCallstack; + } + TracyQueuePrepare( zoneQueue ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyQueueCommit( zoneBeginThread ); + } -ScopedZone::ScopedZone( const SourceLocationData* srcloc, int depth, bool is_active ) +ScopedZone::ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, uint32_t color, int32_t depth, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) #else @@ -62,51 +71,21 @@ ScopedZone::ScopedZone( const SourceLocationData* srcloc, int depth, bool is_act #ifdef TRACY_ON_DEMAND m_connectionId = GetProfiler().ConnectionId(); #endif - GetProfiler().SendCallstack( depth ); + auto zoneQueue = QueueType::ZoneBeginAllocSrcLoc; + if( depth > 0 && has_callstack() ) + { + GetProfiler().SendCallstack( depth ); + zoneQueue = QueueType::ZoneBeginAllocSrcLocCallstack; + } + TracyQueuePrepare( zoneQueue ); + const auto srcloc = + Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz, color ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, srcloc ); + TracyQueueCommit( zoneBeginThread ); + } - TracyQueuePrepare( QueueType::ZoneBeginCallstack ); - MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); - MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); - TracyQueueCommit( zoneBeginThread ); -} - -ScopedZone::ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, bool is_active ) -#ifdef TRACY_ON_DEMAND - : m_active( is_active && GetProfiler().IsConnected() ) -#else - : m_active( is_active ) -#endif -{ - if( !m_active ) return; -#ifdef TRACY_ON_DEMAND - m_connectionId = GetProfiler().ConnectionId(); -#endif - TracyQueuePrepare( QueueType::ZoneBeginAllocSrcLoc ); - const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); - MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); - MemWrite( &item->zoneBegin.srcloc, srcloc ); - TracyQueueCommit( zoneBeginThread ); -} - -ScopedZone::ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int depth, bool is_active ) -#ifdef TRACY_ON_DEMAND - : m_active( is_active && GetProfiler().IsConnected() ) -#else - : m_active( is_active ) -#endif -{ - if( !m_active ) return; -#ifdef TRACY_ON_DEMAND - m_connectionId = GetProfiler().ConnectionId(); -#endif - GetProfiler().SendCallstack( depth ); - - TracyQueuePrepare( QueueType::ZoneBeginAllocSrcLocCallstack ); - const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); - MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); - MemWrite( &item->zoneBegin.srcloc, srcloc ); - TracyQueueCommit( zoneBeginThread ); -} +ScopedZone::ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int32_t depth, bool is_active ) : ScopedZone( line, source, sourceSz, function, functionSz, name, nameSz, 0, depth, is_active ) {} ScopedZone::~ScopedZone() { @@ -150,6 +129,30 @@ void ScopedZone::Text( const Char* txt, size_t size ) TracyQueueCommit( zoneTextFatThread ); } +void ScopedZone::TextFmt( const char* fmt, ... ) +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + va_list args; + va_start( args, fmt ); + auto size = vsnprintf( nullptr, 0, fmt, args ); + va_end( args ); + if( size < 0 ) return; + assert( size < (std::numeric_limits::max)() ); + + char* ptr = (char*)tracy_malloc( size_t( size ) + 1 ); + va_start( args, fmt ); + vsnprintf( ptr, size_t( size ) + 1, fmt, args ); + va_end( args ); + + TracyQueuePrepare( QueueType::ZoneText ); + MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyQueueCommit( zoneTextFatThread ); +} + void ScopedZone::Name( const char* txt, size_t size ) { assert( size < (std::numeric_limits::max)() ); @@ -181,6 +184,30 @@ void ScopedZone::Name( const Char* txt, size_t size ) TracyQueueCommit( zoneTextFatThread ); } +void ScopedZone::NameFmt( const char* fmt, ... ) +{ + if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif + va_list args; + va_start( args, fmt ); + auto size = vsnprintf( nullptr, 0, fmt, args ); + va_end( args ); + if( size < 0 ) return; + assert( size < (std::numeric_limits::max)() ); + + char* ptr = (char*)tracy_malloc( size_t( size ) + 1 ); + va_start( args, fmt ); + vsnprintf( ptr, size_t( size ) + 1, fmt, args ); + va_end( args ); + + TracyQueuePrepare( QueueType::ZoneName ); + MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyQueueCommit( zoneTextFatThread ); +} + void ScopedZone::Color( uint32_t color ) { if( !m_active ) return; diff --git a/Source/ThirdParty/tracy/client/TracySysPower.cpp b/Source/ThirdParty/tracy/client/TracySysPower.cpp index bd5939da2..6ad1d6478 100644 --- a/Source/ThirdParty/tracy/client/TracySysPower.cpp +++ b/Source/ThirdParty/tracy/client/TracySysPower.cpp @@ -85,7 +85,7 @@ void SysPower::ScanDirectory( const char* path, int parent ) FILE* f = fopen( tmp, "r" ); if( f ) { - fscanf( f, "%" PRIu64, &maxRange ); + (void)fscanf( f, "%" PRIu64, &maxRange ); fclose( f ); } } diff --git a/Source/ThirdParty/tracy/client/TracySysTrace.cpp b/Source/ThirdParty/tracy/client/TracySysTrace.cpp index af0641fef..8e7f6139b 100644 --- a/Source/ThirdParty/tracy/client/TracySysTrace.cpp +++ b/Source/ThirdParty/tracy/client/TracySysTrace.cpp @@ -16,16 +16,25 @@ namespace tracy { -static constexpr int GetSamplingFrequency() +static int GetSamplingFrequency() { + int samplingHz = TRACY_SAMPLING_HZ; + + auto env = GetEnvVar( "TRACY_SAMPLING_HZ" ); + if( env ) + { + int val = atoi( env ); + if( val > 0 ) samplingHz = val; + } + #if defined _WIN32 - return TRACY_SAMPLING_HZ > 8000 ? 8000 : ( TRACY_SAMPLING_HZ < 1 ? 1 : TRACY_SAMPLING_HZ ); + return samplingHz > 8000 ? 8000 : ( samplingHz < 1 ? 1 : samplingHz ); #else - return TRACY_SAMPLING_HZ > 1000000 ? 1000000 : ( TRACY_SAMPLING_HZ < 1 ? 1 : TRACY_SAMPLING_HZ ); + return samplingHz > 1000000 ? 1000000 : ( samplingHz < 1 ? 1 : samplingHz ); #endif } -static constexpr int GetSamplingPeriod() +static int GetSamplingPeriod() { return 1000000000 / GetSamplingFrequency(); } @@ -164,8 +173,11 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) MemWrite( &item->contextSwitch.oldThread, cswitch->oldThreadId ); MemWrite( &item->contextSwitch.newThread, cswitch->newThreadId ); MemWrite( &item->contextSwitch.cpu, record->BufferContext.ProcessorNumber ); - MemWrite( &item->contextSwitch.reason, cswitch->oldThreadWaitReason ); - MemWrite( &item->contextSwitch.state, cswitch->oldThreadState ); + MemWrite( &item->contextSwitch.oldThreadWaitReason, cswitch->oldThreadWaitReason ); + MemWrite( &item->contextSwitch.oldThreadState, cswitch->oldThreadState ); + MemWrite( &item->contextSwitch.newThreadPriority, cswitch->newThreadPriority ); + MemWrite( &item->contextSwitch.oldThreadPriority, cswitch->oldThreadPriority ); + MemWrite( &item->contextSwitch.previousCState, cswitch->previousCState ); TracyLfqCommit; } else if( hdr.EventDescriptor.Opcode == 50 ) @@ -174,7 +186,10 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) TracyLfqPrepare( QueueType::ThreadWakeup ); MemWrite( &item->threadWakeup.time, hdr.TimeStamp.QuadPart ); + MemWrite( &item->threadWakeup.cpu, record->BufferContext.ProcessorNumber ); MemWrite( &item->threadWakeup.thread, rt->threadId ); + MemWrite( &item->threadWakeup.adjustReason, rt->adjustReason ); + MemWrite( &item->threadWakeup.adjustIncrement, rt->adjustIncrement ); TracyLfqCommit; } else if( hdr.EventDescriptor.Opcode == 1 || hdr.EventDescriptor.Opcode == 3 ) @@ -321,7 +336,7 @@ static void SetupVsync() #endif } -static constexpr int GetSamplingInterval() +static int GetSamplingInterval() { return GetSamplingPeriod() / 100; } @@ -489,11 +504,11 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch if( _GetThreadDescription ) { PWSTR tmp; - _GetThreadDescription( hnd, &tmp ); - char buf[256]; - if( tmp ) + if ( SUCCEEDED( _GetThreadDescription( hnd, &tmp ) ) ) { + char buf[256]; auto ret = wcstombs( buf, tmp, 256 ); + LocalFree(tmp); if( ret != 0 ) { threadName = CopyString( buf, ret ); @@ -669,7 +684,7 @@ enum TraceEventId EventBranchMiss, EventVsync, EventContextSwitch, - EventWakeup, + EventWaking, }; static void ProbePreciseIp( perf_event_attr& pe, unsigned long long config0, unsigned long long config1, pid_t pid ) @@ -758,16 +773,16 @@ bool SysTraceStart( int64_t& samplingPeriod ) TracyDebug( "perf_event_paranoid: %i\n", paranoidLevel ); #endif - int switchId = -1, wakeupId = -1, vsyncId = -1; + int switchId = -1, wakingId = -1, vsyncId = -1; const auto switchIdStr = ReadFile( "/sys/kernel/debug/tracing/events/sched/sched_switch/id" ); if( switchIdStr ) switchId = atoi( switchIdStr ); - const auto wakeupIdStr = ReadFile( "/sys/kernel/debug/tracing/events/sched/sched_wakeup/id" ); - if( wakeupIdStr ) wakeupId = atoi( wakeupIdStr ); + const auto wakingIdStr = ReadFile( "/sys/kernel/debug/tracing/events/sched/sched_waking/id" ); + if( wakingIdStr ) wakingId = atoi( wakingIdStr ); const auto vsyncIdStr = ReadFile( "/sys/kernel/debug/tracing/events/drm/drm_vblank_event/id" ); if( vsyncIdStr ) vsyncId = atoi( vsyncIdStr ); TracyDebug( "sched_switch id: %i\n", switchId ); - TracyDebug( "sched_wakeup id: %i\n", wakeupId ); + TracyDebug( "sched_waking id: %i\n", wakingId ); TracyDebug( "drm_vblank_event id: %i\n", vsyncId ); #ifdef TRACY_NO_SAMPLING @@ -822,7 +837,7 @@ bool SysTraceStart( int64_t& samplingPeriod ) 2 + // CPU cycles + instructions retired 2 + // cache reference + miss 2 + // branch retired + miss - 2 + // context switches + wakeups + 2 + // context switches + waking ups 1 // vsync ); s_ring = (RingBuffer*)tracy_malloc( sizeof( RingBuffer ) * maxNumBuffers ); @@ -1067,18 +1082,31 @@ bool SysTraceStart( int64_t& samplingPeriod ) } } - if( wakeupId != -1 ) + if( wakingId != -1 ) { - pe.config = wakeupId; - pe.config &= ~PERF_SAMPLE_CALLCHAIN; + pe = {}; + pe.type = PERF_TYPE_TRACEPOINT; + pe.size = sizeof( perf_event_attr ); + pe.sample_period = 1; + pe.sample_type = PERF_SAMPLE_TIME | PERF_SAMPLE_RAW; + // Coult ask for callstack here + //pe.sample_type |= PERF_SAMPLE_CALLCHAIN; + pe.disabled = 1; + pe.inherit = 1; + pe.config = wakingId; + pe.read_format = 0; +#if !defined TRACY_HW_TIMER || !( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) + pe.use_clockid = 1; + pe.clockid = CLOCK_MONOTONIC_RAW; +#endif - TracyDebug( "Setup wakeup capture\n" ); + TracyDebug( "Setup waking up capture\n" ); for( int i=0; i 0 ) { + // Find the earliest event from the active buffers int sel = -1; int selPos; int64_t t0 = std::numeric_limits::max(); @@ -1360,6 +1389,7 @@ void SysTraceWorker( void* ptr ) } } } + // Found any event if( sel >= 0 ) { auto& ring = ringArray[ctxBufferIdx + sel]; @@ -1375,10 +1405,10 @@ void SysTraceWorker( void* ptr ) const auto rid = ring.GetId(); if( rid == EventContextSwitch ) { - // Layout: - // u64 time - // u64 cnt - // u64 ip[cnt] + // Layout: See /sys/kernel/debug/tracing/events/sched/sched_switch/format + // u64 time // PERF_SAMPLE_TIME + // u64 cnt // PERF_SAMPLE_CALLCHAIN + // u64 ip[cnt] // PERF_SAMPLE_CALLCHAIN // u32 size // u8 data[size] // Data (not ABI stable, but has not changed since it was added, in 2009): @@ -1399,35 +1429,43 @@ void SysTraceWorker( void* ptr ) const auto traceOffset = offset; offset += sizeof( uint64_t ) * cnt + sizeof( uint32_t ) + 8 + 16; - uint32_t prev_pid, next_pid; + uint32_t prev_pid, prev_prio; + uint32_t next_pid, next_prio; long prev_state; ring.Read( &prev_pid, offset, sizeof( uint32_t ) ); - offset += sizeof( uint32_t ) + sizeof( uint32_t ); + offset += sizeof( uint32_t ); + ring.Read( &prev_prio, offset, sizeof( uint32_t ) ); + offset += sizeof( uint32_t ); ring.Read( &prev_state, offset, sizeof( long ) ); offset += sizeof( long ) + 16; ring.Read( &next_pid, offset, sizeof( uint32_t ) ); + offset += sizeof( uint32_t ); + ring.Read( &next_prio, offset, sizeof( uint32_t ) ); - uint8_t reason = 100; - uint8_t state; + uint8_t oldThreadWaitReason = 100; + uint8_t oldThreadState; - if( prev_state & 0x0001 ) state = 104; - else if( prev_state & 0x0002 ) state = 101; - else if( prev_state & 0x0004 ) state = 105; - else if( prev_state & 0x0008 ) state = 106; - else if( prev_state & 0x0010 ) state = 108; - else if( prev_state & 0x0020 ) state = 109; - else if( prev_state & 0x0040 ) state = 110; - else if( prev_state & 0x0080 ) state = 102; - else state = 103; + if( prev_state & 0x0001 ) oldThreadState = 104; + else if( prev_state & 0x0002 ) oldThreadState = 101; + else if( prev_state & 0x0004 ) oldThreadState = 105; + else if( prev_state & 0x0008 ) oldThreadState = 106; + else if( prev_state & 0x0010 ) oldThreadState = 108; + else if( prev_state & 0x0020 ) oldThreadState = 109; + else if( prev_state & 0x0040 ) oldThreadState = 110; + else if( prev_state & 0x0080 ) oldThreadState = 102; + else oldThreadState = 103; TracyLfqPrepare( QueueType::ContextSwitch ); MemWrite( &item->contextSwitch.time, t0 ); MemWrite( &item->contextSwitch.oldThread, prev_pid ); MemWrite( &item->contextSwitch.newThread, next_pid ); MemWrite( &item->contextSwitch.cpu, uint8_t( ring.GetCpu() ) ); - MemWrite( &item->contextSwitch.reason, reason ); - MemWrite( &item->contextSwitch.state, state ); + MemWrite( &item->contextSwitch.oldThreadWaitReason, oldThreadWaitReason ); + MemWrite( &item->contextSwitch.oldThreadState, oldThreadState ); + MemWrite( &item->contextSwitch.previousCState, uint8_t( 0 ) ); + MemWrite( &item->contextSwitch.newThreadPriority, int8_t( next_prio ) ); + MemWrite( &item->contextSwitch.oldThreadPriority, int8_t( prev_prio ) ); TracyLfqCommit; if( cnt > 0 && prev_pid != 0 && CurrentProcOwnsThread( prev_pid ) ) @@ -1441,27 +1479,33 @@ void SysTraceWorker( void* ptr ) TracyLfqCommit; } } - else if( rid == EventWakeup ) + else if( rid == EventWaking) { + // See /sys/kernel/debug/tracing/events/sched/sched_waking/format // Layout: - // u64 time + // u64 time // PERF_SAMPLE_TIME // u32 size // u8 data[size] // Data: // u8 hdr[8] // u8 comm[16] // u32 pid - // u32 prio - // u64 target_cpu - - offset += sizeof( perf_event_header ) + sizeof( uint64_t ) + sizeof( uint32_t ) + 8 + 16; - + // i32 prio + // i32 target_cpu + const uint32_t dataOffset = sizeof( perf_event_header ) + sizeof( uint64_t ) + sizeof( uint32_t ); + offset += dataOffset + 8 + 16; uint32_t pid; ring.Read( &pid, offset, sizeof( uint32_t ) ); - + TracyLfqPrepare( QueueType::ThreadWakeup ); MemWrite( &item->threadWakeup.time, t0 ); MemWrite( &item->threadWakeup.thread, pid ); + MemWrite( &item->threadWakeup.cpu, (uint8_t)ring.GetCpu() ); + + int8_t adjustReason = -1; // Does not exist on Linux + int8_t adjustIncrement = 0; // Should perhaps store the new prio? + MemWrite( &item->threadWakeup.adjustReason, adjustReason ); + MemWrite( &item->threadWakeup.adjustIncrement, adjustIncrement ); TracyLfqCommit; } else diff --git a/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp b/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp index e94957552..d5102488e 100644 --- a/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp +++ b/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp @@ -690,7 +690,9 @@ static pthread_key_t _memory_thread_heap; # define _Thread_local __declspec(thread) # define TLS_MODEL # else -# ifndef __HAIKU__ +# if defined(__ANDROID__) && __ANDROID_API__ >= 29 && defined(__NDK_MAJOR__) && __NDK_MAJOR__ >= 26 +# define TLS_MODEL __attribute__((tls_model("local-dynamic"))) +# elif !defined(__HAIKU__) # define TLS_MODEL __attribute__((tls_model("initial-exec"))) # else # define TLS_MODEL @@ -795,8 +797,7 @@ _rpmalloc_spin(void) { #elif defined(__sparc__) __asm__ volatile("rd %ccr, %g0 \n\trd %ccr, %g0 \n\trd %ccr, %g0"); #else - struct timespec ts = {0}; - nanosleep(&ts, 0); + std::this_thread::yield(); #endif } diff --git a/Source/ThirdParty/tracy/common/TracyProtocol.hpp b/Source/ThirdParty/tracy/common/TracyProtocol.hpp index 5eb1639db..40cf5e673 100644 --- a/Source/ThirdParty/tracy/common/TracyProtocol.hpp +++ b/Source/ThirdParty/tracy/common/TracyProtocol.hpp @@ -9,7 +9,7 @@ namespace tracy constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; } -enum : uint32_t { ProtocolVersion = 64 }; +enum : uint32_t { ProtocolVersion = 74 }; enum : uint16_t { BroadcastVersion = 3 }; using lz4sz_t = uint32_t; @@ -47,10 +47,10 @@ enum ServerQuery : uint8_t ServerQueryFrameName, ServerQueryParameter, ServerQueryFiberName, + ServerQueryExternalName, // Items above are high priority. Split order must be preserved. See IsQueryPrio(). ServerQueryDisconnect, ServerQueryCallstackFrame, - ServerQueryExternalName, ServerQuerySymbol, ServerQuerySymbolCode, ServerQuerySourceCode, diff --git a/Source/ThirdParty/tracy/common/TracyQueue.hpp b/Source/ThirdParty/tracy/common/TracyQueue.hpp index 051d412ab..daef3ec1b 100644 --- a/Source/ThirdParty/tracy/common/TracyQueue.hpp +++ b/Source/ThirdParty/tracy/common/TracyQueue.hpp @@ -42,6 +42,8 @@ enum class QueueType : uint8_t MemAllocCallstackNamed, MemFreeCallstack, MemFreeCallstackNamed, + MemDiscard, + MemDiscardCallstack, GpuZoneBegin, GpuZoneBeginCallstack, GpuZoneBeginAllocSrcLoc, @@ -70,6 +72,7 @@ enum class QueueType : uint8_t KeepAlive, ThreadContext, GpuCalibration, + GpuTimeSync, Crash, CrashReport, ZoneValidation, @@ -107,6 +110,7 @@ enum class QueueType : uint8_t SingleStringData, SecondStringData, MemNamePayload, + ThreadGroupHint, StringData, ThreadName, PlotName, @@ -258,6 +262,7 @@ struct QueueFiberEnter int64_t time; uint64_t fiber; // ptr uint32_t thread; + int32_t groupHint; }; struct QueueFiberLeave @@ -398,7 +403,10 @@ enum class GpuContextType : uint8_t Vulkan, OpenCL, Direct3D12, - Direct3D11 + Direct3D11, + Metal, + Custom, + CUDA }; enum GpuContextFlags : uint8_t @@ -453,6 +461,13 @@ struct QueueGpuCalibration uint8_t context; }; +struct QueueGpuTimeSync +{ + int64_t gpuTime; + int64_t cpuTime; + uint8_t context; +}; + struct QueueGpuContextName { uint8_t context; @@ -469,6 +484,12 @@ struct QueueMemNamePayload uint64_t name; }; +struct QueueThreadGroupHint +{ + uint32_t thread; + int32_t groupHint; +}; + struct QueueMemAlloc { int64_t time; @@ -484,6 +505,13 @@ struct QueueMemFree uint64_t ptr; }; +struct QueueMemDiscard +{ + int64_t time; + uint32_t thread; + uint64_t name; +}; + struct QueueCallstackFat { uint64_t ptr; @@ -577,14 +605,20 @@ struct QueueContextSwitch uint32_t oldThread; uint32_t newThread; uint8_t cpu; - uint8_t reason; - uint8_t state; + uint8_t oldThreadWaitReason; + uint8_t oldThreadState; + uint8_t previousCState; + int8_t newThreadPriority; + int8_t oldThreadPriority; }; struct QueueThreadWakeup { int64_t time; uint32_t thread; + uint8_t cpu; + int8_t adjustReason; + int8_t adjustIncrement; }; struct QueueTidToPid @@ -631,6 +665,7 @@ struct QueueSourceCodeNotAvailable struct QueueCpuTopology { uint32_t package; + uint32_t die; uint32_t core; uint32_t thread; }; @@ -718,11 +753,14 @@ struct QueueItem QueueGpuZoneEnd gpuZoneEnd; QueueGpuTime gpuTime; QueueGpuCalibration gpuCalibration; + QueueGpuTimeSync gpuTimeSync; QueueGpuContextName gpuContextName; QueueGpuContextNameFat gpuContextNameFat; QueueMemAlloc memAlloc; QueueMemFree memFree; + QueueMemDiscard memDiscard; QueueMemNamePayload memName; + QueueThreadGroupHint threadGroupHint; QueueCallstackFat callstackFat; QueueCallstackFatThread callstackFatThread; QueueCallstackAllocFat callstackAllocFat; @@ -792,6 +830,8 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ) + sizeof( QueueMemAlloc ), // callstack, named sizeof( QueueHeader ) + sizeof( QueueMemFree ), // callstack sizeof( QueueHeader ) + sizeof( QueueMemFree ), // callstack, named + sizeof( QueueHeader ) + sizeof( QueueMemDiscard ), + sizeof( QueueHeader ) + sizeof( QueueMemDiscard ), // callstack sizeof( QueueHeader ) + sizeof( QueueGpuZoneBegin ), sizeof( QueueHeader ) + sizeof( QueueGpuZoneBegin ), // callstack sizeof( QueueHeader ) + sizeof( QueueGpuZoneBeginLean ),// allocated source location @@ -821,6 +861,7 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ), // keep alive sizeof( QueueHeader ) + sizeof( QueueThreadContext ), sizeof( QueueHeader ) + sizeof( QueueGpuCalibration ), + sizeof( QueueHeader ) + sizeof( QueueGpuTimeSync ), sizeof( QueueHeader ), // crash sizeof( QueueHeader ) + sizeof( QueueCrashReport ), sizeof( QueueHeader ) + sizeof( QueueZoneValidation ), @@ -858,6 +899,7 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ), // single string data sizeof( QueueHeader ), // second string data sizeof( QueueHeader ) + sizeof( QueueMemNamePayload ), + sizeof( QueueHeader ) + sizeof( QueueThreadGroupHint ), // keep all QueueStringTransfer below sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // string data sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // thread name diff --git a/Source/ThirdParty/tracy/common/TracySocket.cpp b/Source/ThirdParty/tracy/common/TracySocket.cpp index 42e815232..2e3babc06 100644 --- a/Source/ThirdParty/tracy/common/TracySocket.cpp +++ b/Source/ThirdParty/tracy/common/TracySocket.cpp @@ -21,6 +21,9 @@ # pragma warning(disable:4267) # endif # define poll WSAPoll +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# endif #else # include # include diff --git a/Source/ThirdParty/tracy/common/TracySystem.cpp b/Source/ThirdParty/tracy/common/TracySystem.cpp index 4de80185d..eb831fe20 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.cpp +++ b/Source/ThirdParty/tracy/common/TracySystem.cpp @@ -26,8 +26,13 @@ # include #elif defined __FreeBSD__ # include -#elif defined __NetBSD__ || defined __DragonFly__ +#elif defined __NetBSD__ +# include +#elif defined __DragonFly__ # include +#elif defined __QNX__ +# include +# include #endif #ifdef __MINGW32__ @@ -82,6 +87,8 @@ TRACY_API uint32_t GetThreadHandleImpl() return lwp_gettid(); #elif defined __OpenBSD__ return getthrid(); +#elif defined __QNX__ + return (uint32_t) gettid(); #elif defined __EMSCRIPTEN__ // Not supported, but let it compile. return 0; @@ -100,16 +107,10 @@ TRACY_API uint32_t GetThreadHandleImpl() } #ifdef TRACY_ENABLE -struct ThreadNameData -{ - uint32_t id; - const char* name; - ThreadNameData* next; -}; std::atomic& GetThreadNameData(); #endif -#ifdef _MSC_VER +#if defined _MSC_VER && !defined __clang__ # pragma pack( push, 8 ) struct THREADNAME_INFO { @@ -133,6 +134,11 @@ void ThreadNameMsvcMagic( const THREADNAME_INFO& info ) #endif TRACY_API void SetThreadName( const char* name ) +{ + SetThreadNameWithHint( name, 0 ); +} + +TRACY_API void SetThreadNameWithHint( const char* name, int32_t groupHint ) { #if defined _WIN32 # ifdef TRACY_UWP @@ -148,7 +154,7 @@ TRACY_API void SetThreadName( const char* name ) } else { -# if defined _MSC_VER +# if defined _MSC_VER && !defined __clang__ THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name; @@ -180,6 +186,21 @@ TRACY_API void SetThreadName( const char* name ) #endif } } +#elif defined __QNX__ + { + const auto sz = strlen( name ); + if( sz <= _NTO_THREAD_NAME_MAX ) + { + pthread_setname_np( pthread_self(), name ); + } + else + { + char buf[_NTO_THREAD_NAME_MAX + 1]; + memcpy( buf, name, _NTO_THREAD_NAME_MAX ); + buf[_NTO_THREAD_NAME_MAX] = '\0'; + pthread_setname_np( pthread_self(), buf ); + } + }; #endif #ifdef TRACY_ENABLE { @@ -189,6 +210,7 @@ TRACY_API void SetThreadName( const char* name ) buf[sz] = '\0'; auto data = (ThreadNameData*)tracy_malloc_fast( sizeof( ThreadNameData ) ); data->id = detail::GetThreadHandleImpl(); + data->groupHint = groupHint; data->name = buf; data->next = GetThreadNameData().load( std::memory_order_relaxed ); while( !GetThreadNameData().compare_exchange_weak( data->next, data, std::memory_order_release, std::memory_order_relaxed ) ) {} @@ -196,6 +218,22 @@ TRACY_API void SetThreadName( const char* name ) #endif } +#ifdef TRACY_ENABLE +ThreadNameData* GetThreadNameData( uint32_t id ) +{ + auto ptr = GetThreadNameData().load( std::memory_order_relaxed ); + while( ptr ) + { + if( ptr->id == id ) + { + return ptr; + } + ptr = ptr->next; + } + return nullptr; +} +#endif + TRACY_API const char* GetThreadName( uint32_t id ) { static char buf[256]; @@ -259,6 +297,11 @@ TRACY_API const char* GetThreadName( uint32_t id ) pthread_setcancelstate( cs, 0 ); # endif return buf; +#elif defined __QNX__ + static char qnxNameBuf[_NTO_THREAD_NAME_MAX + 1] = {0}; + if (pthread_getname_np(static_cast(id), qnxNameBuf, _NTO_THREAD_NAME_MAX) == 0) { + return qnxNameBuf; + }; #endif sprintf( buf, "%" PRIu32, id ); diff --git a/Source/ThirdParty/tracy/common/TracySystem.hpp b/Source/ThirdParty/tracy/common/TracySystem.hpp index 497d047e5..ea29f0c0e 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.hpp +++ b/Source/ThirdParty/tracy/common/TracySystem.hpp @@ -47,17 +47,18 @@ public: ScopedZone& operator=( const ScopedZone& ) = delete; ScopedZone& operator=( ScopedZone&& ) = delete; - ScopedZone( const SourceLocationData* srcloc, bool is_active = true ); - ScopedZone( const SourceLocationData* srcloc, int depth, bool is_active = true ); - ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, bool is_active = true ); - ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int depth, bool is_active = true ); + ScopedZone( const SourceLocationData* srcloc, int32_t depth = -1, bool is_active = true ); + ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, uint32_t color, int32_t depth = -1, bool is_active = true ); + ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int32_t depth, bool is_active = true ); ~ScopedZone(); void Text( const char* txt, size_t size ); void Text( const Char* txt, size_t size ); + void TextFmt( const char* fmt, ... ); void Name( const char* txt, size_t size ); void Name( const Char* txt, size_t size ); + void NameFmt( const char* fmt, ... ); void Color( uint32_t color ); void Value( uint64_t value ); @@ -75,6 +76,16 @@ TRACY_API uint32_t GetThreadHandleImpl(); } #ifdef TRACY_ENABLE +struct ThreadNameData +{ + uint32_t id; + int32_t groupHint; + const char* name; + ThreadNameData* next; +}; + +ThreadNameData* GetThreadNameData( uint32_t id ); + TRACY_API uint32_t GetThreadHandle(); #else static inline uint32_t GetThreadHandle() @@ -84,9 +95,10 @@ static inline uint32_t GetThreadHandle() #endif TRACY_API void SetThreadName( const char* name ); +TRACY_API void SetThreadNameWithHint( const char* name, int32_t groupHint ); TRACY_API const char* GetThreadName( uint32_t id ); -TRACY_API const char* GetEnvVar(const char* name); +TRACY_API const char* GetEnvVar( const char* name ); } diff --git a/Source/ThirdParty/tracy/common/TracyVersion.hpp b/Source/ThirdParty/tracy/common/TracyVersion.hpp index 2355279f7..f1e3c0b2c 100644 --- a/Source/ThirdParty/tracy/common/TracyVersion.hpp +++ b/Source/ThirdParty/tracy/common/TracyVersion.hpp @@ -6,7 +6,7 @@ namespace tracy namespace Version { enum { Major = 0 }; -enum { Minor = 10 }; +enum { Minor = 12 }; enum { Patch = 0 }; } } diff --git a/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp b/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp index f3899cbce..52fa8a8d2 100644 --- a/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp +++ b/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp @@ -725,8 +725,8 @@ struct dwarf_data struct dwarf_data *next; /* The data for .gnu_debugaltlink. */ struct dwarf_data *altlink; - /* The base address for this file. */ - uintptr_t base_address; +/* The base address mapping for this file. */ + struct libbacktrace_base_address base_address; /* A sorted list of address ranges. */ struct unit_addrs *addrs; /* Number of address ranges in list. */ @@ -1947,8 +1947,9 @@ update_pcrange (const struct attr* attr, const struct attr_val* val, static int add_low_high_range (struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, - uintptr_t base_address, int is_bigendian, - struct unit *u, const struct pcrange *pcrange, + struct libbacktrace_base_address base_address, + int is_bigendian, struct unit *u, + const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, uintptr_t lowpc, uintptr_t highpc, @@ -1983,8 +1984,8 @@ add_low_high_range (struct backtrace_state *state, /* Add in the base address of the module when recording PC values, so that we can look up the PC directly. */ - lowpc += base_address; - highpc += base_address; + lowpc = libbacktrace_add_base (lowpc, base_address); + highpc = libbacktrace_add_base (highpc, base_address); return add_range (state, rdata, lowpc, highpc, error_callback, data, vec); } @@ -1996,7 +1997,7 @@ static int add_ranges_from_ranges ( struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, - uintptr_t base_address, int is_bigendian, + struct libbacktrace_base_address base_address, int is_bigendian, struct unit *u, uintptr_t base, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, @@ -2042,10 +2043,11 @@ add_ranges_from_ranges ( base = (uintptr_t) high; else { - if (!add_range (state, rdata, - (uintptr_t) low + base + base_address, - (uintptr_t) high + base + base_address, - error_callback, data, vec)) + uintptr_t rl, rh; + + rl = libbacktrace_add_base ((uintptr_t) low + base, base_address); + rh = libbacktrace_add_base ((uintptr_t) high + base, base_address); + if (!add_range (state, rdata, rl, rh, error_callback, data, vec)) return 0; } } @@ -2063,7 +2065,7 @@ static int add_ranges_from_rnglists ( struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, - uintptr_t base_address, int is_bigendian, + struct libbacktrace_base_address base_address, int is_bigendian, struct unit *u, uintptr_t base, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, @@ -2146,9 +2148,10 @@ add_ranges_from_rnglists ( u->addrsize, is_bigendian, index, error_callback, data, &high)) return 0; - if (!add_range (state, rdata, low + base_address, - high + base_address, error_callback, data, - vec)) + if (!add_range (state, rdata, + libbacktrace_add_base (low, base_address), + libbacktrace_add_base (high, base_address), + error_callback, data, vec)) return 0; } break; @@ -2165,7 +2168,7 @@ add_ranges_from_rnglists ( error_callback, data, &low)) return 0; length = read_uleb128 (&rnglists_buf); - low += base_address; + low = libbacktrace_add_base (low, base_address); if (!add_range (state, rdata, low, low + length, error_callback, data, vec)) return 0; @@ -2179,8 +2182,9 @@ add_ranges_from_rnglists ( low = read_uleb128 (&rnglists_buf); high = read_uleb128 (&rnglists_buf); - if (!add_range (state, rdata, low + base + base_address, - high + base + base_address, + if (!add_range (state, rdata, + libbacktrace_add_base (low + base, base_address), + libbacktrace_add_base (high + base, base_address), error_callback, data, vec)) return 0; } @@ -2197,9 +2201,10 @@ add_ranges_from_rnglists ( low = (uintptr_t) read_address (&rnglists_buf, u->addrsize); high = (uintptr_t) read_address (&rnglists_buf, u->addrsize); - if (!add_range (state, rdata, low + base_address, - high + base_address, error_callback, data, - vec)) + if (!add_range (state, rdata, + libbacktrace_add_base (low, base_address), + libbacktrace_add_base (high, base_address), + error_callback, data, vec)) return 0; } break; @@ -2211,7 +2216,7 @@ add_ranges_from_rnglists ( low = (uintptr_t) read_address (&rnglists_buf, u->addrsize); length = (uintptr_t) read_uleb128 (&rnglists_buf); - low += base_address; + low = libbacktrace_add_base (low, base_address); if (!add_range (state, rdata, low, low + length, error_callback, data, vec)) return 0; @@ -2239,7 +2244,7 @@ add_ranges_from_rnglists ( static int add_ranges (struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, - uintptr_t base_address, int is_bigendian, + struct libbacktrace_base_address base_address, int is_bigendian, struct unit *u, uintptr_t base, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, uintptr_t lowpc, uintptr_t highpc, @@ -2275,7 +2280,8 @@ add_ranges (struct backtrace_state *state, read, 0 if there is some error. */ static int -find_address_ranges (struct backtrace_state *state, uintptr_t base_address, +find_address_ranges (struct backtrace_state *state, + struct libbacktrace_base_address base_address, struct dwarf_buf *unit_buf, const struct dwarf_sections *dwarf_sections, int is_bigendian, struct dwarf_data *altlink, @@ -2430,7 +2436,8 @@ find_address_ranges (struct backtrace_state *state, uintptr_t base_address, on success, 0 on failure. */ static int -build_address_map (struct backtrace_state *state, uintptr_t base_address, +build_address_map (struct backtrace_state *state, + struct libbacktrace_base_address base_address, const struct dwarf_sections *dwarf_sections, int is_bigendian, struct dwarf_data *altlink, backtrace_error_callback error_callback, void *data, @@ -2649,7 +2656,7 @@ add_line (struct backtrace_state *state, struct dwarf_data *ddata, /* Add in the base address here, so that we can look up the PC directly. */ - ln->pc = pc + ddata->base_address; + ln->pc = libbacktrace_add_base (pc, ddata->base_address); ln->filename = filename; ln->lineno = lineno; @@ -4251,6 +4258,19 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, } } +bool dwarf_fileline_dwarf_lookup_pc_in_all_entries(struct backtrace_state *state, uintptr_t pc, + backtrace_full_callback callback, backtrace_error_callback error_callback, void *data, + int& found, int ret) +{ + for (struct dwarf_data* ddata = (struct dwarf_data *)state->fileline_data; + ddata != NULL; + ddata = ddata->next) + { + ret = dwarf_lookup_pc(state, ddata, pc, callback, error_callback, data, &found); + if (ret != 0 || found) return true; + } + return false; +} /* Return the file/line information for a PC using the DWARF mapping we built earlier. */ @@ -4262,20 +4282,30 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, { struct dwarf_data *ddata; int found; - int ret; + int ret = 0; if (!state->threaded) + { + if (dwarf_fileline_dwarf_lookup_pc_in_all_entries(state, pc, callback, error_callback, data, found, ret)) { - for (ddata = (struct dwarf_data *) state->fileline_data; - ddata != NULL; - ddata = ddata->next) - { - ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback, - data, &found); - if (ret != 0 || found) - return ret; - } + return ret; } + + // if we failed to obtain an entry in range, it can mean that the address map has been changed and new entries + // have been loaded in the meantime. Request a refresh and try again. + if (state->request_known_address_ranges_refresh_fn) + { + int new_range_count = state->request_known_address_ranges_refresh_fn(state, pc); + if (new_range_count > 0) + { + if (dwarf_fileline_dwarf_lookup_pc_in_all_entries(state, pc, callback, error_callback, data, found, ret)) + { + return ret; + } + } + } + + } else { struct dwarf_data **pp; @@ -4306,7 +4336,7 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, static struct dwarf_data * build_dwarf_data (struct backtrace_state *state, - uintptr_t base_address, + struct libbacktrace_base_address base_address, const struct dwarf_sections *dwarf_sections, int is_bigendian, struct dwarf_data *altlink, @@ -4364,7 +4394,7 @@ build_dwarf_data (struct backtrace_state *state, int backtrace_dwarf_add (struct backtrace_state *state, - uintptr_t base_address, + struct libbacktrace_base_address base_address, const struct dwarf_sections *dwarf_sections, int is_bigendian, struct dwarf_data *fileline_altlink, diff --git a/Source/ThirdParty/tracy/libbacktrace/elf.cpp b/Source/ThirdParty/tracy/libbacktrace/elf.cpp index c65bc4e76..ffe8d7024 100644 --- a/Source/ThirdParty/tracy/libbacktrace/elf.cpp +++ b/Source/ThirdParty/tracy/libbacktrace/elf.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. */ #include #include #include +#include #ifdef HAVE_DL_ITERATE_PHDR #include @@ -642,7 +643,7 @@ elf_symbol_search (const void *vkey, const void *ventry) static int elf_initialize_syminfo (struct backtrace_state *state, - uintptr_t base_address, + struct libbacktrace_base_address base_address, const unsigned char *symtab_data, size_t symtab_size, const unsigned char *strtab, size_t strtab_size, backtrace_error_callback error_callback, @@ -708,7 +709,8 @@ elf_initialize_syminfo (struct backtrace_state *state, = *(const b_elf_addr *) (opd->data + (sym->st_value - opd->addr)); else elf_symbols[j].address = sym->st_value; - elf_symbols[j].address += base_address; + elf_symbols[j].address = + libbacktrace_add_base (elf_symbols[j].address, base_address); elf_symbols[j].size = sym->st_size; ++j; } @@ -1199,14 +1201,7 @@ elf_fetch_bits_backward (const unsigned char **ppin, val = *pval; if (unlikely (pin <= pinend)) - { - if (bits == 0) - { - elf_uncompress_failed (); - return 0; - } - return 1; - } + return 1; pin -= 4; @@ -5093,7 +5088,7 @@ elf_uncompress_chdr (struct backtrace_state *state, backtrace_error_callback error_callback, void *data, unsigned char **uncompressed, size_t *uncompressed_size) { - const b_elf_chdr *chdr; + b_elf_chdr chdr; char *alc; size_t alc_len; unsigned char *po; @@ -5105,27 +5100,30 @@ elf_uncompress_chdr (struct backtrace_state *state, if (compressed_size < sizeof (b_elf_chdr)) return 1; - chdr = (const b_elf_chdr *) compressed; + /* The lld linker can misalign a compressed section, so we can't safely read + the fields directly as we can for other ELF sections. See + https://github.com/ianlancetaylor/libbacktrace/pull/120. */ + memcpy (&chdr, compressed, sizeof (b_elf_chdr)); alc = NULL; alc_len = 0; - if (*uncompressed != NULL && *uncompressed_size >= chdr->ch_size) + if (*uncompressed != NULL && *uncompressed_size >= chdr.ch_size) po = *uncompressed; else { - alc_len = chdr->ch_size; + alc_len = chdr.ch_size; alc = (char*)backtrace_alloc (state, alc_len, error_callback, data); if (alc == NULL) return 0; po = (unsigned char *) alc; } - switch (chdr->ch_type) + switch (chdr.ch_type) { case ELFCOMPRESS_ZLIB: if (!elf_zlib_inflate_and_verify (compressed + sizeof (b_elf_chdr), compressed_size - sizeof (b_elf_chdr), - zdebug_table, po, chdr->ch_size)) + zdebug_table, po, chdr.ch_size)) goto skip; break; @@ -5133,7 +5131,7 @@ elf_uncompress_chdr (struct backtrace_state *state, if (!elf_zstd_decompress (compressed + sizeof (b_elf_chdr), compressed_size - sizeof (b_elf_chdr), (unsigned char *)zdebug_table, po, - chdr->ch_size)) + chdr.ch_size)) goto skip; break; @@ -5143,7 +5141,7 @@ elf_uncompress_chdr (struct backtrace_state *state, } *uncompressed = po; - *uncompressed_size = chdr->ch_size; + *uncompressed_size = chdr.ch_size; return 1; @@ -5585,6 +5583,7 @@ elf_uncompress_lzma_block (const unsigned char *compressed, uint64_t header_compressed_size; uint64_t header_uncompressed_size; unsigned char lzma2_properties; + size_t crc_offset; uint32_t computed_crc; uint32_t stream_crc; size_t uncompressed_offset; @@ -5688,28 +5687,29 @@ elf_uncompress_lzma_block (const unsigned char *compressed, /* The properties describe the dictionary size, but we don't care what that is. */ - /* Block header padding. */ - if (unlikely (off + 4 > compressed_size)) + /* Skip to just before CRC, verifying zero bytes in between. */ + crc_offset = block_header_offset + block_header_size - 4; + if (unlikely (crc_offset + 4 > compressed_size)) { elf_uncompress_failed (); return 0; } - - off = (off + 3) &~ (size_t) 3; - - if (unlikely (off + 4 > compressed_size)) + for (; off < crc_offset; off++) { - elf_uncompress_failed (); - return 0; + if (compressed[off] != 0) + { + elf_uncompress_failed (); + return 0; + } } /* Block header CRC. */ computed_crc = elf_crc32 (0, compressed + block_header_offset, block_header_size - 4); - stream_crc = (compressed[off] - | (compressed[off + 1] << 8) - | (compressed[off + 2] << 16) - | (compressed[off + 3] << 24)); + stream_crc = ((uint32_t)compressed[off] + | ((uint32_t)compressed[off + 1] << 8) + | ((uint32_t)compressed[off + 2] << 16) + | ((uint32_t)compressed[off + 3] << 24)); if (unlikely (computed_crc != stream_crc)) { elf_uncompress_failed (); @@ -6216,10 +6216,10 @@ elf_uncompress_lzma_block (const unsigned char *compressed, return 0; } computed_crc = elf_crc32 (0, uncompressed, uncompressed_offset); - stream_crc = (compressed[off] - | (compressed[off + 1] << 8) - | (compressed[off + 2] << 16) - | (compressed[off + 3] << 24)); + stream_crc = ((uint32_t)compressed[off] + | ((uint32_t)compressed[off + 1] << 8) + | ((uint32_t)compressed[off + 2] << 16) + | ((uint32_t)compressed[off + 3] << 24)); if (computed_crc != stream_crc) { elf_uncompress_failed (); @@ -6319,10 +6319,10 @@ elf_uncompress_lzma (struct backtrace_state *state, /* Next comes a CRC of the stream flags. */ computed_crc = elf_crc32 (0, compressed + 6, 2); - stream_crc = (compressed[8] - | (compressed[9] << 8) - | (compressed[10] << 16) - | (compressed[11] << 24)); + stream_crc = ((uint32_t)compressed[8] + | ((uint32_t)compressed[9] << 8) + | ((uint32_t)compressed[10] << 16) + | ((uint32_t)compressed[11] << 24)); if (unlikely (computed_crc != stream_crc)) { elf_uncompress_failed (); @@ -6363,10 +6363,10 @@ elf_uncompress_lzma (struct backtrace_state *state, /* Before that is a footer CRC. */ computed_crc = elf_crc32 (0, compressed + offset, 6); - stream_crc = (compressed[offset - 4] - | (compressed[offset - 3] << 8) - | (compressed[offset - 2] << 16) - | (compressed[offset - 1] << 24)); + stream_crc = ((uint32_t)compressed[offset - 4] + | ((uint32_t)compressed[offset - 3] << 8) + | ((uint32_t)compressed[offset - 2] << 16) + | ((uint32_t)compressed[offset - 1] << 24)); if (unlikely (computed_crc != stream_crc)) { elf_uncompress_failed (); @@ -6422,10 +6422,10 @@ elf_uncompress_lzma (struct backtrace_state *state, /* Next is a CRC of the index. */ computed_crc = elf_crc32 (0, compressed + index_offset, offset - index_offset); - stream_crc = (compressed[offset] - | (compressed[offset + 1] << 8) - | (compressed[offset + 2] << 16) - | (compressed[offset + 3] << 24)); + stream_crc = ((uint32_t)compressed[offset] + | ((uint32_t)compressed[offset + 1] << 8) + | ((uint32_t)compressed[offset + 2] << 16) + | ((uint32_t)compressed[offset + 3] << 24)); if (unlikely (computed_crc != stream_crc)) { elf_uncompress_failed (); @@ -6518,8 +6518,10 @@ backtrace_uncompress_lzma (struct backtrace_state *state, static int elf_add (struct backtrace_state *state, const char *filename, int descriptor, const unsigned char *memory, size_t memory_size, - uintptr_t base_address, backtrace_error_callback error_callback, - void *data, fileline *fileline_fn, int *found_sym, int *found_dwarf, + struct libbacktrace_base_address base_address, + struct elf_ppc64_opd_data *caller_opd, + backtrace_error_callback error_callback, void *data, + fileline *fileline_fn, int *found_sym, int *found_dwarf, struct dwarf_data **fileline_entry, int exe, int debuginfo, const char *with_buildid_data, uint32_t with_buildid_size) { @@ -6574,6 +6576,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, struct elf_view split_debug_view[DEBUG_MAX]; unsigned char split_debug_view_valid[DEBUG_MAX]; struct elf_ppc64_opd_data opd_data, *opd; + int opd_view_valid; struct dwarf_sections dwarf_sections; struct dwarf_data *fileline_altlink = NULL; @@ -6602,6 +6605,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, debug_view_valid = 0; memset (&split_debug_view_valid[0], 0, sizeof split_debug_view_valid); opd = NULL; + opd_view_valid = 0; if (!elf_get_view (state, descriptor, memory, memory_size, 0, sizeof ehdr, error_callback, data, &ehdr_view)) @@ -6858,7 +6862,8 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, } } - if (!gnu_debugdata_view_valid + if (!debuginfo + && !gnu_debugdata_view_valid && strcmp (name, ".gnu_debugdata") == 0) { if (!elf_get_view (state, descriptor, memory, memory_size, @@ -6885,12 +6890,18 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, opd->addr = shdr->sh_addr; opd->data = (const char *) opd_data.view.view.data; opd->size = shdr->sh_size; + opd_view_valid = 1; } } + /* A debuginfo file may not have a useful .opd section, but we can use the + one from the original executable. */ + if (opd == NULL) + opd = caller_opd; + if (symtab_shndx == 0) symtab_shndx = dynsym_shndx; - if (symtab_shndx != 0 && !debuginfo) + if (symtab_shndx != 0) { const b_elf_shdr *symtab_shdr; unsigned int strtab_shndx; @@ -6966,9 +6977,9 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, elf_release_view (state, &debuglink_view, error_callback, data); if (debugaltlink_view_valid) elf_release_view (state, &debugaltlink_view, error_callback, data); - ret = elf_add (state, "", d, NULL, 0, base_address, error_callback, - data, fileline_fn, found_sym, found_dwarf, NULL, 0, - 1, NULL, 0); + ret = elf_add (state, "", d, NULL, 0, base_address, opd, + error_callback, data, fileline_fn, found_sym, + found_dwarf, NULL, 0, 1, NULL, 0); if (ret < 0) backtrace_close (d, error_callback, data); else if (descriptor >= 0) @@ -6983,12 +6994,6 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, buildid_view_valid = 0; } - if (opd) - { - elf_release_view (state, &opd->view, error_callback, data); - opd = NULL; - } - if (debuglink_name != NULL) { int d; @@ -7003,9 +7008,9 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, elf_release_view (state, &debuglink_view, error_callback, data); if (debugaltlink_view_valid) elf_release_view (state, &debugaltlink_view, error_callback, data); - ret = elf_add (state, "", d, NULL, 0, base_address, error_callback, - data, fileline_fn, found_sym, found_dwarf, NULL, 0, - 1, NULL, 0); + ret = elf_add (state, "", d, NULL, 0, base_address, opd, + error_callback, data, fileline_fn, found_sym, + found_dwarf, NULL, 0, 1, NULL, 0); if (ret < 0) backtrace_close (d, error_callback, data); else if (descriptor >= 0) @@ -7030,7 +7035,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, { int ret; - ret = elf_add (state, filename, d, NULL, 0, base_address, + ret = elf_add (state, filename, d, NULL, 0, base_address, opd, error_callback, data, fileline_fn, found_sym, found_dwarf, &fileline_altlink, 0, 1, debugaltlink_buildid_data, debugaltlink_buildid_size); @@ -7067,7 +7072,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, if (ret) { ret = elf_add (state, filename, -1, gnu_debugdata_uncompressed, - gnu_debugdata_uncompressed_size, base_address, + gnu_debugdata_uncompressed_size, base_address, opd, error_callback, data, fileline_fn, found_sym, found_dwarf, NULL, 0, 0, NULL, 0); if (ret >= 0 && descriptor >= 0) @@ -7076,6 +7081,13 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, } } + if (opd_view_valid) + { + elf_release_view (state, &opd->view, error_callback, data); + opd_view_valid = 0; + opd = NULL; + } + /* Read all the debug sections in a single view, since they are probably adjacent in the file. If any of sections are uncompressed, we never release this view. */ @@ -7322,7 +7334,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, if (split_debug_view_valid[i]) elf_release_view (state, &split_debug_view[i], error_callback, data); } - if (opd) + if (opd_view_valid) elf_release_view (state, &opd->view, error_callback, data); if (descriptor >= 0) backtrace_close (descriptor, error_callback, data); @@ -7350,13 +7362,37 @@ struct PhdrIterate { char* dlpi_name; ElfW(Addr) dlpi_addr; + ElfW(Addr) dlpi_end_addr; }; FastVector s_phdrData(16); +struct ElfAddrRange +{ + ElfW(Addr) dlpi_addr; + ElfW(Addr) dlpi_end_addr; +}; +FastVector s_sortedKnownElfRanges(16); + +static int address_in_known_elf_ranges(uintptr_t pc) +{ + auto it = std::lower_bound( s_sortedKnownElfRanges.begin(), s_sortedKnownElfRanges.end(), pc, + []( const ElfAddrRange& lhs, const uintptr_t rhs ) { return uintptr_t(lhs.dlpi_addr) > rhs; } ); + if( it != s_sortedKnownElfRanges.end() && pc <= it->dlpi_end_addr ) + { + return true; + } + return false; +} + static int phdr_callback_mock (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, void *pdata) { + if( address_in_known_elf_ranges(info->dlpi_addr) ) + { + return 0; + } + auto ptr = s_phdrData.push_next(); if (info->dlpi_name) { @@ -7366,6 +7402,12 @@ phdr_callback_mock (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, } else ptr->dlpi_name = nullptr; ptr->dlpi_addr = info->dlpi_addr; + + // calculate the end address as well, so we can quickly determine if a PC is within the range of this image + ptr->dlpi_end_addr = uintptr_t(info->dlpi_addr) + (info->dlpi_phnum ? uintptr_t( + info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz) : 0); + return 0; } @@ -7379,6 +7421,7 @@ phdr_callback (struct PhdrIterate *info, void *pdata) const char *filename; int descriptor; int does_not_exist; + struct libbacktrace_base_address base_address; fileline elf_fileline_fn; int found_dwarf; @@ -7408,7 +7451,8 @@ phdr_callback (struct PhdrIterate *info, void *pdata) return 0; } - if (elf_add (pd->state, filename, descriptor, NULL, 0, info->dlpi_addr, + base_address.m = info->dlpi_addr; + if (elf_add (pd->state, filename, descriptor, NULL, 0, base_address, NULL, pd->error_callback, pd->data, &elf_fileline_fn, pd->found_sym, &found_dwarf, NULL, 0, 0, NULL, 0)) { @@ -7422,6 +7466,66 @@ phdr_callback (struct PhdrIterate *info, void *pdata) return 0; } +static int elf_iterate_phdr_and_add_new_files(phdr_data *pd) +{ + assert(s_phdrData.empty()); + // dl_iterate_phdr, will only add entries for elf files loaded in a previously unseen range + dl_iterate_phdr(phdr_callback_mock, nullptr); + + if(s_phdrData.size() == 0) + { + return 0; + } + + uint32_t headersAdded = 0; + for (auto &v : s_phdrData) + { + phdr_callback(&v, (void *)pd); + + auto newEntry = s_sortedKnownElfRanges.push_next(); + newEntry->dlpi_addr = v.dlpi_addr; + newEntry->dlpi_end_addr = v.dlpi_end_addr; + + tracy_free(v.dlpi_name); + + headersAdded++; + } + + s_phdrData.clear(); + + std::sort( s_sortedKnownElfRanges.begin(), s_sortedKnownElfRanges.end(), + []( const ElfAddrRange& lhs, const ElfAddrRange& rhs ) { return lhs.dlpi_addr > rhs.dlpi_addr; } ); + + return headersAdded; +} + +#ifdef TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT +/* Request an elf entry update if the pc passed in is not in any of the known elf ranges. +This could mean that new images were dlopened and we need to add those new elf entries */ +static int elf_refresh_address_ranges_if_needed(struct backtrace_state *state, uintptr_t pc) +{ + if ( address_in_known_elf_ranges(pc) ) + { + return 0; + } + + struct phdr_data pd; + int found_sym = 0; + int found_dwarf = 0; + fileline fileline_fn = nullptr; + pd.state = state; + pd.error_callback = nullptr; + pd.data = nullptr; + pd.fileline_fn = &fileline_fn; + pd.found_sym = &found_sym; + pd.found_dwarf = &found_dwarf; + pd.exe_filename = nullptr; + pd.exe_descriptor = -1; + + return elf_iterate_phdr_and_add_new_files(&pd); +} +#endif //#ifdef TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT + /* Initialize the backtrace data we need from an ELF executable. At the ELF level, all we need to do is find the debug info sections. */ @@ -7437,11 +7541,21 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, fileline elf_fileline_fn = elf_nodebug; struct phdr_data pd; - ret = elf_add (state, filename, descriptor, NULL, 0, 0, error_callback, data, - &elf_fileline_fn, &found_sym, &found_dwarf, NULL, 1, 0, NULL, - 0); - if (!ret) - return 0; + + /* When using fdpic we must use dl_iterate_phdr for all modules, including + the main executable, so that we can get the right base address + mapping. */ + if (!libbacktrace_using_fdpic ()) + { + struct libbacktrace_base_address zero_base_address; + + memset (&zero_base_address, 0, sizeof zero_base_address); + ret = elf_add (state, filename, descriptor, NULL, 0, zero_base_address, + NULL, error_callback, data, &elf_fileline_fn, &found_sym, + &found_dwarf, NULL, 1, 0, NULL, 0); + if (!ret) + return 0; + } pd.state = state; pd.error_callback = error_callback; @@ -7452,14 +7566,7 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, pd.exe_filename = filename; pd.exe_descriptor = ret < 0 ? descriptor : -1; - assert (s_phdrData.empty()); - dl_iterate_phdr (phdr_callback_mock, nullptr); - for (auto& v : s_phdrData) - { - phdr_callback (&v, (void *) &pd); - tracy_free (v.dlpi_name); - } - s_phdrData.clear(); + elf_iterate_phdr_and_add_new_files(&pd); if (!state->threaded) { @@ -7485,6 +7592,13 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, if (*fileline_fn == NULL || *fileline_fn == elf_nodebug) *fileline_fn = elf_fileline_fn; + // install an address range refresh callback so we can cope with dynamically loaded elf files +#ifdef TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT + state->request_known_address_ranges_refresh_fn = elf_refresh_address_ranges_if_needed; +#else + state->request_known_address_ranges_refresh_fn = NULL; +#endif + return 1; } diff --git a/Source/ThirdParty/tracy/libbacktrace/fileline.cpp b/Source/ThirdParty/tracy/libbacktrace/fileline.cpp index 8645d754a..5a37ff0c7 100644 --- a/Source/ThirdParty/tracy/libbacktrace/fileline.cpp +++ b/Source/ThirdParty/tracy/libbacktrace/fileline.cpp @@ -47,6 +47,18 @@ POSSIBILITY OF SUCH DAMAGE. */ #include #endif +#ifdef HAVE_WINDOWS_H +#ifndef WIN32_MEAN_AND_LEAN +#define WIN32_MEAN_AND_LEAN +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#endif + #include "backtrace.hpp" #include "internal.hpp" @@ -158,6 +170,47 @@ macho_get_executable_path (struct backtrace_state *state, #endif /* !defined (HAVE_MACH_O_DYLD_H) */ +#if HAVE_DECL__PGMPTR + +#define windows_executable_filename() _pgmptr + +#else /* !HAVE_DECL__PGMPTR */ + +#define windows_executable_filename() NULL + +#endif /* !HAVE_DECL__PGMPTR */ + +#ifdef HAVE_WINDOWS_H + +#define FILENAME_BUF_SIZE (MAX_PATH) + +static char * +windows_get_executable_path (char *buf, backtrace_error_callback error_callback, + void *data) +{ + size_t got; + int error; + + got = GetModuleFileNameA (NULL, buf, FILENAME_BUF_SIZE - 1); + error = GetLastError (); + if (got == 0 + || (got == FILENAME_BUF_SIZE - 1 && error == ERROR_INSUFFICIENT_BUFFER)) + { + error_callback (data, + "could not get the filename of the current executable", + error); + return NULL; + } + return buf; +} + +#else /* !defined (HAVE_WINDOWS_H) */ + +#define windows_get_executable_path(buf, error_callback, data) NULL +#define FILENAME_BUF_SIZE 64 + +#endif /* !defined (HAVE_WINDOWS_H) */ + /* Initialize the fileline information from the executable. Returns 1 on success, 0 on failure. */ @@ -171,7 +224,7 @@ fileline_initialize (struct backtrace_state *state, int called_error_callback; int descriptor; const char *filename; - char buf[64]; + char buf[FILENAME_BUF_SIZE]; if (!state->threaded) failed = state->fileline_initialization_failed; @@ -195,7 +248,7 @@ fileline_initialize (struct backtrace_state *state, descriptor = -1; called_error_callback = 0; - for (pass = 0; pass < 8; ++pass) + for (pass = 0; pass < 10; ++pass) { int does_not_exist; @@ -208,25 +261,33 @@ fileline_initialize (struct backtrace_state *state, filename = getexecname (); break; case 2: - filename = "/proc/self/exe"; + /* Test this before /proc/self/exe, as the latter exists but points + to the wine binary (and thus doesn't work). */ + filename = windows_executable_filename (); break; case 3: - filename = "/proc/curproc/file"; + filename = "/proc/self/exe"; break; case 4: + filename = "/proc/curproc/file"; + break; + case 5: snprintf (buf, sizeof (buf), "/proc/%ld/object/a.out", (long) getpid ()); filename = buf; break; - case 5: + case 6: filename = sysctl_exec_name1 (state, error_callback, data); break; - case 6: + case 7: filename = sysctl_exec_name2 (state, error_callback, data); break; - case 7: + case 8: filename = macho_get_executable_path (state, error_callback, data); break; + case 9: + filename = windows_get_executable_path (buf, error_callback, data); + break; default: abort (); } diff --git a/Source/ThirdParty/tracy/libbacktrace/internal.hpp b/Source/ThirdParty/tracy/libbacktrace/internal.hpp index f871844b6..213959759 100644 --- a/Source/ThirdParty/tracy/libbacktrace/internal.hpp +++ b/Source/ThirdParty/tracy/libbacktrace/internal.hpp @@ -133,6 +133,11 @@ typedef void (*syminfo) (struct backtrace_state *state, uintptr_t pc, backtrace_syminfo_callback callback, backtrace_error_callback error_callback, void *data); +/* The type of the function that will trigger an known address range refresh + (if pc passed in is for an address whichs lies ourtisde of known ranges) */ +typedef int (*request_known_address_ranges_refresh)(struct backtrace_state *state, + uintptr_t pc); + /* What the backtrace state pointer points to. */ struct backtrace_state @@ -159,6 +164,8 @@ struct backtrace_state int lock_alloc; /* The freelist when using mmap. */ struct backtrace_freelist_struct *freelist; + /* Trigger an known address range refresh */ + request_known_address_ranges_refresh request_known_address_ranges_refresh_fn; }; /* Open a file for reading. Returns -1 on error. If DOES_NOT_EXIST @@ -326,10 +333,44 @@ struct dwarf_sections struct dwarf_data; +/* The load address mapping. */ + +#if defined(__FDPIC__) && defined(HAVE_DL_ITERATE_PHDR) && (defined(HAVE_LINK_H) || defined(HAVE_SYS_LINK_H)) + +#ifdef HAVE_LINK_H + #include +#endif +#ifdef HAVE_SYS_LINK_H + #include +#endif + +#define libbacktrace_using_fdpic() (1) + +struct libbacktrace_base_address +{ + struct elf32_fdpic_loadaddr m; +}; + +#define libbacktrace_add_base(pc, base) \ + ((uintptr_t) (__RELOC_POINTER ((pc), (base).m))) + +#else /* not _FDPIC__ */ + +#define libbacktrace_using_fdpic() (0) + +struct libbacktrace_base_address +{ + uintptr_t m; +}; + +#define libbacktrace_add_base(pc, base) ((pc) + (base).m) + +#endif /* not _FDPIC__ */ + /* Add file/line information for a DWARF module. */ extern int backtrace_dwarf_add (struct backtrace_state *state, - uintptr_t base_address, + struct libbacktrace_base_address base_address, const struct dwarf_sections *dwarf_sections, int is_bigendian, struct dwarf_data *fileline_altlink, diff --git a/Source/ThirdParty/tracy/libbacktrace/macho.cpp b/Source/ThirdParty/tracy/libbacktrace/macho.cpp index 6cccdabaa..b9f084565 100644 --- a/Source/ThirdParty/tracy/libbacktrace/macho.cpp +++ b/Source/ThirdParty/tracy/libbacktrace/macho.cpp @@ -274,12 +274,14 @@ struct macho_nlist_64 /* Value found in nlist n_type field. */ -#define MACH_O_N_EXT 0x01 /* Extern symbol */ -#define MACH_O_N_ABS 0x02 /* Absolute symbol */ -#define MACH_O_N_SECT 0x0e /* Defined in section */ - -#define MACH_O_N_TYPE 0x0e /* Mask for type bits */ #define MACH_O_N_STAB 0xe0 /* Stabs debugging symbol */ +#define MACH_O_N_TYPE 0x0e /* Mask for type bits */ + +/* Values found after masking with MACH_O_N_TYPE. */ +#define MACH_O_N_UNDF 0x00 /* Undefined symbol */ +#define MACH_O_N_ABS 0x02 /* Absolute symbol */ +#define MACH_O_N_SECT 0x0e /* Defined in section from n_sect field */ + /* Information we keep for a Mach-O symbol. */ @@ -316,8 +318,9 @@ static const char * const dwarf_section_names[DEBUG_MAX] = /* Forward declaration. */ static int macho_add (struct backtrace_state *, const char *, int, off_t, - const unsigned char *, uintptr_t, int, - backtrace_error_callback, void *, fileline *, int *); + const unsigned char *, struct libbacktrace_base_address, + int, backtrace_error_callback, void *, fileline *, + int *); /* A dummy callback function used when we can't find any debug info. */ @@ -495,10 +498,10 @@ macho_defined_symbol (uint8_t type) { if ((type & MACH_O_N_STAB) != 0) return 0; - if ((type & MACH_O_N_EXT) != 0) - return 0; switch (type & MACH_O_N_TYPE) { + case MACH_O_N_UNDF: + return 0; case MACH_O_N_ABS: return 1; case MACH_O_N_SECT: @@ -512,7 +515,7 @@ macho_defined_symbol (uint8_t type) static int macho_add_symtab (struct backtrace_state *state, int descriptor, - uintptr_t base_address, int is_64, + struct libbacktrace_base_address base_address, int is_64, off_t symoff, unsigned int nsyms, off_t stroff, unsigned int strsize, backtrace_error_callback error_callback, void *data) @@ -627,7 +630,7 @@ macho_add_symtab (struct backtrace_state *state, int descriptor, if (name[0] == '_') ++name; macho_symbols[j].name = name; - macho_symbols[j].address = value + base_address; + macho_symbols[j].address = libbacktrace_add_base (value, base_address); ++j; } @@ -760,7 +763,8 @@ macho_syminfo (struct backtrace_state *state, uintptr_t addr, static int macho_add_fat (struct backtrace_state *state, const char *filename, int descriptor, int swapped, off_t offset, - const unsigned char *match_uuid, uintptr_t base_address, + const unsigned char *match_uuid, + struct libbacktrace_base_address base_address, int skip_symtab, uint32_t nfat_arch, int is_64, backtrace_error_callback error_callback, void *data, fileline *fileline_fn, int *found_sym) @@ -862,7 +866,8 @@ macho_add_fat (struct backtrace_state *state, const char *filename, static int macho_add_dsym (struct backtrace_state *state, const char *filename, - uintptr_t base_address, const unsigned char *uuid, + struct libbacktrace_base_address base_address, + const unsigned char *uuid, backtrace_error_callback error_callback, void *data, fileline* fileline_fn) { @@ -980,7 +985,7 @@ macho_add_dsym (struct backtrace_state *state, const char *filename, static int macho_add (struct backtrace_state *state, const char *filename, int descriptor, off_t offset, const unsigned char *match_uuid, - uintptr_t base_address, int skip_symtab, + struct libbacktrace_base_address base_address, int skip_symtab, backtrace_error_callback error_callback, void *data, fileline *fileline_fn, int *found_sym) { @@ -1242,7 +1247,7 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, c = _dyld_image_count (); for (i = 0; i < c; ++i) { - uintptr_t base_address; + struct libbacktrace_base_address base_address; const char *name; int d; fileline mff; @@ -1266,7 +1271,7 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, continue; } - base_address = _dyld_get_image_vmaddr_slide (i); + base_address.m = _dyld_get_image_vmaddr_slide (i); mff = macho_nodebug; if (!macho_add (state, name, d, 0, NULL, base_address, 0, @@ -1321,10 +1326,12 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, void *data, fileline *fileline_fn) { fileline macho_fileline_fn; + struct libbacktrace_base_address zero_base_address; int found_sym; macho_fileline_fn = macho_nodebug; - if (!macho_add (state, filename, descriptor, 0, NULL, 0, 0, + memset (&zero_base_address, 0, sizeof zero_base_address); + if (!macho_add (state, filename, descriptor, 0, NULL, zero_base_address, 0, error_callback, data, &macho_fileline_fn, &found_sym)) return 0; diff --git a/Source/ThirdParty/tracy/tracy.Build.cs b/Source/ThirdParty/tracy/tracy.Build.cs index e23a7c10f..9d54ca688 100644 --- a/Source/ThirdParty/tracy/tracy.Build.cs +++ b/Source/ThirdParty/tracy/tracy.Build.cs @@ -47,7 +47,7 @@ public class tracy : ThirdPartyModule switch (options.Platform.Target) { case TargetPlatform.Windows: - options.PrivateDefinitions.Add("TRACY_DBGHELP_LOCK=DbgHelp"); + options.PrivateDefinitions.Add("TRACY_DBGHELP_LOCK=FlaxDbgHelp"); break; case TargetPlatform.Switch: options.PrivateDefinitions.Add("TRACY_USE_MALLOC"); diff --git a/Source/ThirdParty/tracy/tracy/Tracy.hpp b/Source/ThirdParty/tracy/tracy/Tracy.hpp index e9c943d2f..fbc0746eb 100644 --- a/Source/ThirdParty/tracy/tracy/Tracy.hpp +++ b/Source/ThirdParty/tracy/tracy/Tracy.hpp @@ -10,11 +10,13 @@ #endif #ifndef TracyLine -# define TracyLine __LINE__ +# define TracyLine TracyConcat(__LINE__,U) // MSVC Edit and continue __LINE__ is non-constant. See https://developercommunity.visualstudio.com/t/-line-cannot-be-used-as-an-argument-for-constexpr/195665 #endif #ifndef TRACY_ENABLE +#define TracyNoop + #define ZoneNamed(x,y) #define ZoneNamedN(x,y,z) #define ZoneNamedC(x,y,z) @@ -30,8 +32,12 @@ #define ZoneText(x,y) #define ZoneTextV(x,y,z) +#define ZoneTextF(x,...) +#define ZoneTextVF(x,y,...) #define ZoneName(x,y) #define ZoneNameV(x,y,z) +#define ZoneNameF(x,...) +#define ZoneNameVF(x,y,...) #define ZoneColor(x) #define ZoneColorV(x,y) #define ZoneValue(x) @@ -53,8 +59,10 @@ #define TracyAlloc(x,y) #define TracyFree(x) +#define TracyMemoryDiscard(x) #define TracySecureAlloc(x,y) #define TracySecureFree(x) +#define TracySecureMemoryDiscard(x) #define TracyAllocN(x,y,z) #define TracyFreeN(x,y) @@ -76,8 +84,10 @@ #define TracyAllocS(x,y,z) #define TracyFreeS(x,y) +#define TracyMemoryDiscardS(x,y) #define TracySecureAllocS(x,y,z) #define TracySecureFreeS(x,y) +#define TracySecureMemoryDiscardS(x,y) #define TracyAllocNS(x,y,z,w) #define TracyFreeNS(x,y,z) @@ -93,9 +103,11 @@ #define TracyParameterRegister(x,y) #define TracyParameterSetup(x,y,z,w) #define TracyIsConnected false +#define TracyIsStarted false #define TracySetProgramName(x) #define TracyFiberEnter(x) +#define TracyFiberEnterHint(x,y) #define TracyFiberLeave #else @@ -137,24 +149,21 @@ public: }; } -#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK -# define ZoneNamed( varname, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) -# define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) -# define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) -# define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) - -# define ZoneTransient( varname, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), nullptr, 0, TRACY_CALLSTACK, active ) -# define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), name, strlen( name ), TRACY_CALLSTACK, active ) -#else -# define ZoneNamed( varname, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), active ) -# define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), active ) -# define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), active ) -# define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), active ) - -# define ZoneTransient( varname, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), nullptr, 0, active ) -# define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), name, strlen( name ), active ) +#ifndef TRACY_CALLSTACK +#define TRACY_CALLSTACK 0 #endif +#define TracyNoop tracy::ProfilerAvailable() + +#define ZoneNamed( varname, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) +#define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) +#define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) +#define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), TRACY_CALLSTACK, active ) + +#define ZoneTransient( varname, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), nullptr, 0, TRACY_CALLSTACK, active ) +#define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), name, strlen( name ), TRACY_CALLSTACK, active ) +#define ZoneTransientNC( varname, name, color, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), name, strlen( name ), color, TRACY_CALLSTACK, active ) + #define ZoneScoped ZoneNamed( ___tracy_scoped_zone, true ) #define ZoneScopedN( name ) ZoneNamedN( ___tracy_scoped_zone, name, true ) #define ZoneScopedC( color ) ZoneNamedC( ___tracy_scoped_zone, color, true ) @@ -162,8 +171,12 @@ public: #define ZoneText( txt, size ) ___tracy_scoped_zone.Text( txt, size ) #define ZoneTextV( varname, txt, size ) varname.Text( txt, size ) +#define ZoneTextF( fmt, ... ) ___tracy_scoped_zone.TextFmt( fmt, ##__VA_ARGS__ ) +#define ZoneTextVF( varname, fmt, ... ) varname.TextFmt( fmt, ##__VA_ARGS__ ) #define ZoneName( txt, size ) ___tracy_scoped_zone.Name( txt, size ) #define ZoneNameV( varname, txt, size ) varname.Name( txt, size ) +#define ZoneNameF( fmt, ... ) ___tracy_scoped_zone.NameFmt( fmt, ##__VA_ARGS__ ) +#define ZoneNameVF( varname, fmt, ... ) varname.NameFmt( fmt, ##__VA_ARGS__ ) #define ZoneColor( color ) ___tracy_scoped_zone.Color( color ) #define ZoneColorV( varname, color ) varname.Color( color ) #define ZoneValue( value ) ___tracy_scoped_zone.Value( value ) @@ -184,7 +197,7 @@ public: #define TracySharedLockableN( type, varname, desc ) tracy::SharedLockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, desc, TracyFile, TracyLine, 0 }; return &srcloc; }() } #define LockableBase( type ) tracy::Lockable #define SharedLockableBase( type ) tracy::SharedLockable -#define LockMark( varname ) static constexpr tracy::SourceLocationData __tracy_lock_location_##varname { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; varname.Mark( &__tracy_lock_location_##varname ) +#define LockMark( varname ) static constexpr tracy::SourceLocationData __tracy_lock_location_##__LINE__ { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; varname.Mark( &__tracy_lock_location_##__LINE__ ) #define LockableName( varname, txt, size ) varname.CustomName( txt, size ) #define TracyPlot( name, val ) tracy::Profiler::PlotData( name, val ) @@ -192,95 +205,52 @@ public: #define TracyAppInfo( txt, size ) tracy::Profiler::MessageAppInfo( txt, size ) -#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK -# define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size, TRACY_CALLSTACK ) -# define TracyMessageL( txt ) tracy::Profiler::Message( txt, TRACY_CALLSTACK ) -# define TracyMessageC( txt, size, color ) tracy::Profiler::MessageColor( txt, size, color, TRACY_CALLSTACK ) -# define TracyMessageLC( txt, color ) tracy::Profiler::MessageColor( txt, color, TRACY_CALLSTACK ) +#define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size, TRACY_CALLSTACK ) +#define TracyMessageL( txt ) tracy::Profiler::Message( txt, TRACY_CALLSTACK ) +#define TracyMessageC( txt, size, color ) tracy::Profiler::MessageColor( txt, size, color, TRACY_CALLSTACK ) +#define TracyMessageLC( txt, color ) tracy::Profiler::MessageColor( txt, color, TRACY_CALLSTACK ) -# define TracyAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, false ) -# define TracyFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, false ) -# define TracySecureAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, true ) -# define TracySecureFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, true ) +#define TracyAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, false ) +#define TracyFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, false ) +#define TracySecureAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, true ) +#define TracySecureFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, true ) -# define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, false, name ) -# define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, false, name ) -# define TracySecureAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, true, name ) -# define TracySecureFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, true, name ) -#else -# define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size, 0 ) -# define TracyMessageL( txt ) tracy::Profiler::Message( txt, 0 ) -# define TracyMessageC( txt, size, color ) tracy::Profiler::MessageColor( txt, size, color, 0 ) -# define TracyMessageLC( txt, color ) tracy::Profiler::MessageColor( txt, color, 0 ) +#define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, false, name ) +#define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, false, name ) +#define TracyMemoryDiscard( name ) tracy::Profiler::MemDiscardCallstack( name, false, TRACY_CALLSTACK ) +#define TracySecureAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, true, name ) +#define TracySecureFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, true, name ) +#define TracySecureMemoryDiscard( name ) tracy::Profiler::MemDiscardCallstack( name, true, TRACY_CALLSTACK ) -# define TracyAlloc( ptr, size ) tracy::Profiler::MemAlloc( ptr, size, false ) -# define TracyFree( ptr ) tracy::Profiler::MemFree( ptr, false ) -# define TracySecureAlloc( ptr, size ) tracy::Profiler::MemAlloc( ptr, size, true ) -# define TracySecureFree( ptr ) tracy::Profiler::MemFree( ptr, true ) +#define ZoneNamedS( varname, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) +#define ZoneNamedNS( varname, name, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) +#define ZoneNamedCS( varname, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) +#define ZoneNamedNCS( varname, name, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) -# define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocNamed( ptr, size, false, name ) -# define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeNamed( ptr, false, name ) -# define TracySecureAllocN( ptr, size, name ) tracy::Profiler::MemAllocNamed( ptr, size, true, name ) -# define TracySecureFreeN( ptr, name ) tracy::Profiler::MemFreeNamed( ptr, true, name ) -#endif +#define ZoneTransientS( varname, depth, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), nullptr, 0, depth, active ) +#define ZoneTransientNS( varname, name, depth, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), name, strlen( name ), depth, active ) -#ifdef TRACY_HAS_CALLSTACK -# define ZoneNamedS( varname, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) -# define ZoneNamedNS( varname, name, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) -# define ZoneNamedCS( varname, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) -# define ZoneNamedNCS( varname, name, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) +#define ZoneScopedS( depth ) ZoneNamedS( ___tracy_scoped_zone, depth, true ) +#define ZoneScopedNS( name, depth ) ZoneNamedNS( ___tracy_scoped_zone, name, depth, true ) +#define ZoneScopedCS( color, depth ) ZoneNamedCS( ___tracy_scoped_zone, color, depth, true ) +#define ZoneScopedNCS( name, color, depth ) ZoneNamedNCS( ___tracy_scoped_zone, name, color, depth, true ) -# define ZoneTransientS( varname, depth, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), nullptr, 0, depth, active ) -# define ZoneTransientNS( varname, name, depth, active ) tracy::ScopedZone varname( TracyLine, TracyFile, strlen( TracyFile ), TracyFunction, strlen( TracyFunction ), name, strlen( name ), depth, active ) +#define TracyAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, false ) +#define TracyFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, false ) +#define TracySecureAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, true ) +#define TracySecureFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, true ) -# define ZoneScopedS( depth ) ZoneNamedS( ___tracy_scoped_zone, depth, true ) -# define ZoneScopedNS( name, depth ) ZoneNamedNS( ___tracy_scoped_zone, name, depth, true ) -# define ZoneScopedCS( color, depth ) ZoneNamedCS( ___tracy_scoped_zone, color, depth, true ) -# define ZoneScopedNCS( name, color, depth ) ZoneNamedNCS( ___tracy_scoped_zone, name, color, depth, true ) +#define TracyAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, false, name ) +#define TracyFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, false, name ) +#define TracyMemoryDiscardS( name, depth ) tracy::Profiler::MemDiscardCallstack( name, false, depth ) +#define TracySecureAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, true, name ) +#define TracySecureFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, true, name ) +#define TracySecureMemoryDiscardS( name, depth ) tracy::Profiler::MemDiscardCallstack( name, true, depth ) -# define TracyAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, false ) -# define TracyFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, false ) -# define TracySecureAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, true ) -# define TracySecureFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, true ) - -# define TracyAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, false, name ) -# define TracyFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, false, name ) -# define TracySecureAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, true, name ) -# define TracySecureFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, true, name ) - -# define TracyMessageS( txt, size, depth ) tracy::Profiler::Message( txt, size, depth ) -# define TracyMessageLS( txt, depth ) tracy::Profiler::Message( txt, depth ) -# define TracyMessageCS( txt, size, color, depth ) tracy::Profiler::MessageColor( txt, size, color, depth ) -# define TracyMessageLCS( txt, color, depth ) tracy::Profiler::MessageColor( txt, color, depth ) -#else -# define ZoneNamedS( varname, depth, active ) ZoneNamed( varname, active ) -# define ZoneNamedNS( varname, name, depth, active ) ZoneNamedN( varname, name, active ) -# define ZoneNamedCS( varname, color, depth, active ) ZoneNamedC( varname, color, active ) -# define ZoneNamedNCS( varname, name, color, depth, active ) ZoneNamedNC( varname, name, color, active ) - -# define ZoneTransientS( varname, depth, active ) ZoneTransient( varname, active ) -# define ZoneTransientNS( varname, name, depth, active ) ZoneTransientN( varname, name, active ) - -# define ZoneScopedS( depth ) ZoneScoped -# define ZoneScopedNS( name, depth ) ZoneScopedN( name ) -# define ZoneScopedCS( color, depth ) ZoneScopedC( color ) -# define ZoneScopedNCS( name, color, depth ) ZoneScopedNC( name, color ) - -# define TracyAllocS( ptr, size, depth ) TracyAlloc( ptr, size ) -# define TracyFreeS( ptr, depth ) TracyFree( ptr ) -# define TracySecureAllocS( ptr, size, depth ) TracySecureAlloc( ptr, size ) -# define TracySecureFreeS( ptr, depth ) TracySecureFree( ptr ) - -# define TracyAllocNS( ptr, size, depth, name ) TracyAllocN( ptr, size, name ) -# define TracyFreeNS( ptr, depth, name ) TracyFreeN( ptr, name ) -# define TracySecureAllocNS( ptr, size, depth, name ) TracySecureAllocN( ptr, size, name ) -# define TracySecureFreeNS( ptr, depth, name ) TracySecureFreeN( ptr, name ) - -# define TracyMessageS( txt, size, depth ) TracyMessage( txt, size ) -# define TracyMessageLS( txt, depth ) TracyMessageL( txt ) -# define TracyMessageCS( txt, size, color, depth ) TracyMessageC( txt, size, color ) -# define TracyMessageLCS( txt, color, depth ) TracyMessageLC( txt, color ) -#endif +#define TracyMessageS( txt, size, depth ) tracy::Profiler::Message( txt, size, depth ) +#define TracyMessageLS( txt, depth ) tracy::Profiler::Message( txt, depth ) +#define TracyMessageCS( txt, size, color, depth ) tracy::Profiler::MessageColor( txt, size, color, depth ) +#define TracyMessageLCS( txt, color, depth ) tracy::Profiler::MessageColor( txt, color, depth ) #define TracySourceCallbackRegister( cb, data ) tracy::Profiler::SourceCallbackRegister( cb, data ) #define TracyParameterRegister( cb, data ) tracy::Profiler::ParameterRegister( cb, data ) @@ -289,7 +259,8 @@ public: #define TracySetProgramName( name ) tracy::GetProfiler().SetProgramName( name ); #ifdef TRACY_FIBERS -# define TracyFiberEnter( fiber ) tracy::Profiler::EnterFiber( fiber ) +# define TracyFiberEnter( fiber ) tracy::Profiler::EnterFiber( fiber, 0 ) +# define TracyFiberEnterHint( fiber, groupHint ) tracy::Profiler::EnterFiber( fiber, groupHint ) # define TracyFiberLeave tracy::Profiler::LeaveFiber() #endif diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index aff928ad2..bf3c61f7e 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -283,8 +283,10 @@ namespace Flax.Build Log.Verbose($"Found the following .NET SDK versions: {string.Join(", ", dotnetSdkVersions)}"); Log.Verbose($"Found the following .NET runtime versions: {string.Join(", ", dotnetRuntimeVersions)}"); + string configuredDotnetVersion = Configuration.Dotnet; var dotnetSdkVersion = GetVersion(dotnetSdkVersions); var dotnetRuntimeVersion = GetVersion(dotnetRuntimeVersions); + if (!string.IsNullOrEmpty(dotnetSdkVersion) && !string.IsNullOrEmpty(dotnetRuntimeVersion) && ParseVersion(dotnetRuntimeVersion).Major > ParseVersion(dotnetSdkVersion).Major) { // Make sure the reference assemblies are not newer than the SDK itself @@ -296,21 +298,24 @@ namespace Flax.Build } while (!string.IsNullOrEmpty(dotnetRuntimeVersion) && ParseVersion(dotnetRuntimeVersion).Major > ParseVersion(dotnetSdkVersion).Major); } - var minVer = string.IsNullOrEmpty(Configuration.Dotnet) ? MinimumVersion.ToString() : Configuration.Dotnet; if (string.IsNullOrEmpty(dotnetSdkVersion)) { - if (dotnetSdkVersions.Any()) - Log.Warning($"Unsupported .NET SDK versions found: {string.Join(", ", dotnetSdkVersions)}. Minimum version required is .NET {minVer}."); - else - Log.Warning($"Missing .NET SDK. Minimum version required is .NET {minVer}."); + string installedVersionsText = dotnetSdkVersions.Any() + ? $"{string.Join(", ", dotnetSdkVersions)}" + : "None"; + Log.Warning(!string.IsNullOrEmpty(configuredDotnetVersion) + ? $"Configured .NET SDK '{configuredDotnetVersion}' not found. Installed versions: {installedVersionsText}." + : $"No compatible .NET SDK found within the supported range: .NET {MinimumVersion.ToString()} - {MaximumVersion.ToString()}. Installed versions: {installedVersionsText}."); return; } if (string.IsNullOrEmpty(dotnetRuntimeVersion)) { - if (dotnetRuntimeVersions.Any()) - Log.Warning($"Unsupported .NET runtime versions found: {string.Join(", ", dotnetRuntimeVersions)}. Minimum version required is .NET {minVer}."); - else - Log.Warning($"Missing .NET runtime. Minimum version required is .NET {minVer}."); + string installedRuntimeVersionsText = dotnetRuntimeVersions.Any() + ? $"{string.Join(", ", dotnetRuntimeVersions)}" + : "None"; + Log.Warning(!string.IsNullOrEmpty(configuredDotnetVersion) + ? $"Configured .NET runtime version '{configuredDotnetVersion}' not found. Installed versions: {installedRuntimeVersionsText}." + : $"No compatible .NET runtime found within the supported range: .NET {MinimumVersion.ToString()} - {MaximumVersion.ToString()}. Installed versions: {installedRuntimeVersionsText}."); return; } RootPath = dotnetPath; diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs index 0f2fc517c..a1018368b 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs @@ -87,6 +87,13 @@ namespace Flax.Build.Platforms if (lines.Length > 1) { var ver = lines[1].Substring(lines[1].IndexOf(" = ", StringComparison.Ordinal) + 2); + if (ver.Contains('-')) + { + // Ignore any beta tags (eg. '29.0.13113456-beta1') + var parts = ver.Split('-'); + if (parts.Length > 1) + ver = parts[0]; + } if (Version.TryParse(ver, out var v)) { Version = v;