diff --git a/Source/Editor/Content/AssetPickerValidator.cs b/Source/Editor/Content/AssetPickerValidator.cs new file mode 100644 index 000000000..f43ab6a29 --- /dev/null +++ b/Source/Editor/Content/AssetPickerValidator.cs @@ -0,0 +1,292 @@ +using System; +using System.IO; +using FlaxEditor.Scripting; +using FlaxEngine; +using FlaxEngine.Utilities; + +namespace FlaxEditor.Content; + +/// +/// Manages and converts the selected content item to the appropriate types. Useful for drag operations. +/// +public class AssetPickerValidator : IContentItemOwner +{ + private Asset _selected; + private ContentItem _selectedItem; + private ScriptType _type; + private string _fileExtension; + + /// + /// Gets or sets the selected item. + /// + public ContentItem SelectedItem + { + get => _selectedItem; + set + { + if (_selectedItem == value) + return; + if (value == null) + { + if (_selected == null && _selectedItem is SceneItem) + { + // Deselect scene reference + _selectedItem.RemoveReference(this); + _selectedItem = null; + _selected = null; + OnSelectedItemChanged(); + return; + } + + // Deselect + _selectedItem?.RemoveReference(this); + _selectedItem = null; + _selected = null; + OnSelectedItemChanged(); + } + else if (value is SceneItem item) + { + if (_selectedItem == item) + return; + if (!IsValid(item)) + item = null; + + // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue) + _selectedItem?.RemoveReference(this); + _selectedItem = item; + _selected = null; + _selectedItem?.AddReference(this); + OnSelectedItemChanged(); + } + else if (value is AssetItem assetItem) + { + SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID); + } + else + { + // Change value + _selectedItem?.RemoveReference(this); + _selectedItem = value; + _selected = null; + OnSelectedItemChanged(); + } + } + } + + /// + /// Gets or sets the selected asset identifier. + /// + public Guid SelectedID + { + get + { + if (_selected != null) + return _selected.ID; + if (_selectedItem is AssetItem assetItem) + return assetItem.ID; + return Guid.Empty; + } + set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value); + } + + /// + /// Gets or sets the selected content item path. + /// + public string SelectedPath + { + get + { + string path = _selectedItem?.Path ?? _selected?.Path; + if (path != null) + { + // Convert into path relative to the project (cross-platform) + var projectFolder = Globals.ProjectFolder; + if (path.StartsWith(projectFolder)) + path = path.Substring(projectFolder.Length + 1); + } + return path; + } + set + { + if (string.IsNullOrEmpty(value)) + { + SelectedItem = null; + } + else + { + var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value; + SelectedItem = Editor.Instance.ContentDatabase.Find(path); + } + } + } + + /// + /// Gets or sets the selected asset object. + /// + public Asset SelectedAsset + { + get => _selected; + set + { + // Check if value won't change + if (value == _selected) + return; + + // Find item from content database and check it + var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null; + if (item != null && !IsValid(item)) + item = null; + + // Change value + _selectedItem?.RemoveReference(this); + _selectedItem = item; + _selected = value; + _selectedItem?.AddReference(this); + OnSelectedItemChanged(); + } + } + + /// + /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker. + /// + public ScriptType AssetType + { + get => _type; + set + { + if (_type != value) + { + _type = value; + + // Auto deselect if the current value is invalid + if (_selectedItem != null && !IsValid(_selectedItem)) + SelectedItem = null; + } + } + } + + /// + /// Gets or sets the content items extensions filter. Null if unused. + /// + public string FileExtension + { + get => _fileExtension; + set + { + if (_fileExtension != value) + { + _fileExtension = value; + + // Auto deselect if the current value is invalid + if (_selectedItem != null && !IsValid(_selectedItem)) + SelectedItem = null; + } + } + } + + /// + /// Occurs when selected item gets changed. + /// + public event Action SelectedItemChanged; + + /// + /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick. + /// + public Func CheckValid; + + /// + /// Returns whether item is valid. + /// + /// + /// + public bool IsValid(ContentItem item) + { + if (_fileExtension != null && !item.Path.EndsWith(_fileExtension)) + return false; + if (CheckValid != null && !CheckValid(item)) + return false; + if (_type == ScriptType.Null) + return true; + + if (item is AssetItem assetItem) + { + // Faster path for binary items (in-built) + if (assetItem is BinaryAssetItem binaryItem) + return _type.IsAssignableFrom(new ScriptType(binaryItem.Type)); + + // Type filter + var type = TypeUtils.GetType(assetItem.TypeName); + if (_type.IsAssignableFrom(type)) + return true; + + // Json assets can contain any type of the object defined by the C# type (data oriented design) + if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset))) + return true; + + // Special case for scene asset references + if (_type.Type == typeof(SceneReference) && assetItem is SceneItem) + return true; + } + + return false; + } + + /// + /// Initializes a new instance of the class. + /// + public AssetPickerValidator() + : this(new ScriptType(typeof(Asset))) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The assets types that this picker accepts. + public AssetPickerValidator(ScriptType assetType) + { + _type = assetType; + } + + /// + /// Called when selected item gets changed. + /// + protected virtual void OnSelectedItemChanged() + { + SelectedItemChanged?.Invoke(); + } + + /// + public void OnItemDeleted(ContentItem item) + { + // Deselect item + SelectedItem = null; + } + + /// + public void OnItemRenamed(ContentItem item) + { + } + + /// + public void OnItemReimported(ContentItem item) + { + } + + /// + public void OnItemDispose(ContentItem item) + { + // Deselect item + SelectedItem = null; + } + + /// + /// Call to remove reference from the selected item. + /// + public void OnDestroy() + { + _selectedItem?.RemoveReference(this); + _selectedItem = null; + _selected = null; + } +} diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 03b21e12b..32111e51c 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -157,6 +157,12 @@ namespace FlaxEditor.CustomEditors var values = _values; var presenter = _presenter; var layout = _layout; + if (layout.Editors.Count != 1) + { + // There are more editors using the same layout so rebuild parent editor to prevent removing others editors + _parent?.RebuildLayout(); + return; + } var control = layout.ContainerControl; var parent = _parent; var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1; diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs index bf087feda..4829bd91a 100644 --- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs @@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors value = 0; // If selected is single actor that has children, ask if apply layer to the sub objects as well - if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren) + if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren && !Editor.IsPlayMode) { var valueText = comboBox.SelectedItem; diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index cfba940c2..1f3359fd5 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Generic file picker assetType = ScriptType.Null; - Picker.FileExtension = assetReference.TypeName; + Picker.Validator.FileExtension = assetReference.TypeName; } else { @@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors } } - Picker.AssetType = assetType; + Picker.Validator.AssetType = assetType; Picker.Height = height; Picker.SelectedItemChanged += OnSelectedItemChanged; } @@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors if (_isRefreshing) return; if (typeof(AssetItem).IsAssignableFrom(_valueType.Type)) - SetValue(Picker.SelectedItem); + SetValue(Picker.Validator.SelectedItem); else if (_valueType.Type == typeof(Guid)) - SetValue(Picker.SelectedID); + SetValue(Picker.Validator.SelectedID); else if (_valueType.Type == typeof(SceneReference)) - SetValue(new SceneReference(Picker.SelectedID)); + SetValue(new SceneReference(Picker.Validator.SelectedID)); else if (_valueType.Type == typeof(string)) - SetValue(Picker.SelectedPath); + SetValue(Picker.Validator.SelectedPath); else - SetValue(Picker.SelectedAsset); + SetValue(Picker.Validator.SelectedAsset); } /// @@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors { _isRefreshing = true; if (Values[0] is AssetItem assetItem) - Picker.SelectedItem = assetItem; + Picker.Validator.SelectedItem = assetItem; else if (Values[0] is Guid guid) - Picker.SelectedID = guid; + Picker.Validator.SelectedID = guid; else if (Values[0] is SceneReference sceneAsset) - Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); + Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); else if (Values[0] is string path) - Picker.SelectedPath = path; + Picker.Validator.SelectedPath = path; else - Picker.SelectedAsset = Values[0] as Asset; + Picker.Validator.SelectedAsset = Values[0] as Asset; _isRefreshing = false; } } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 8922e2d25..7d92c71c0 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -3,9 +3,12 @@ using System; using System.Collections; using System.Linq; +using FlaxEditor.Content; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Drag; +using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -135,14 +138,43 @@ namespace FlaxEditor.CustomEditors.Editors spacing = collection.Spacing; } + var dragArea = layout.CustomContainer(); + dragArea.CustomControl.Editor = this; + dragArea.CustomControl.ElementType = ElementType; + + // Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter + // which scripts can be dragged over and dropped on this collection editor. + var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute); + if (assetReference != null) + { + if (string.IsNullOrEmpty(assetReference.TypeName)) + { + } + else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.') + { + dragArea.CustomControl.ElementType = ScriptType.Null; + dragArea.CustomControl.FileExtension = assetReference.TypeName; + } + else + { + var customType = TypeUtils.GetType(assetReference.TypeName); + if (customType != ScriptType.Null) + dragArea.CustomControl.ElementType = customType; + else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName)) + Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName)); + else + dragArea.CustomControl.ElementType = ScriptType.Void; + } + } + // Size if (_readOnly || (NotNullItems && size == 0)) { - layout.Label("Size", size.ToString()); + dragArea.Label("Size", size.ToString()); } else { - _size = layout.IntegerValue("Size"); + _size = dragArea.IntegerValue("Size"); _size.IntValue.MinValue = 0; _size.IntValue.MaxValue = ushort.MaxValue; _size.IntValue.Value = size; @@ -152,7 +184,7 @@ namespace FlaxEditor.CustomEditors.Editors // Elements if (size > 0) { - var panel = layout.VerticalPanel(); + var panel = dragArea.VerticalPanel(); panel.Panel.BackgroundColor = _background; var elementType = ElementType; @@ -212,37 +244,33 @@ namespace FlaxEditor.CustomEditors.Editors // Add/Remove buttons if (!_readOnly) { - var area = layout.Space(20); - var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16) - { - Text = "+", - TooltipText = "Add new item", - AnchorPreset = AnchorPresets.TopRight, - Parent = area.ContainerControl, - Enabled = !NotNullItems || size > 0, - }; - addButton.Clicked += () => - { - if (IsSetBlocked) - return; + var panel = dragArea.HorizontalPanel(); + panel.Panel.Size = new Float2(0, 20); + panel.Panel.Margin = new Margin(2); - Resize(Count + 1); - }; - var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16) - { - Text = "-", - TooltipText = "Remove last item", - AnchorPreset = AnchorPresets.TopRight, - Parent = area.ContainerControl, - Enabled = size > 0, - }; - removeButton.Clicked += () => + var removeButton = panel.Button("-", "Remove last item"); + removeButton.Button.Size = new Float2(16, 16); + removeButton.Button.Enabled = size > 0; + removeButton.Button.AnchorPreset = AnchorPresets.TopRight; + removeButton.Button.Clicked += () => { if (IsSetBlocked) return; Resize(Count - 1); }; + + var addButton = panel.Button("+", "Add new item"); + addButton.Button.Size = new Float2(16, 16); + addButton.Button.Enabled = !NotNullItems || size > 0; + addButton.Button.AnchorPreset = AnchorPresets.TopRight; + addButton.Button.Clicked += () => + { + if (IsSetBlocked) + return; + + Resize(Count + 1); + }; } } @@ -369,5 +397,232 @@ namespace FlaxEditor.CustomEditors.Editors } return base.OnDirty(editor, value, token); } + + private class DragAreaControl : VerticalPanel + { + private DragItems _dragItems; + private DragActors _dragActors; + private DragHandlers _dragHandlers; + private AssetPickerValidator _pickerValidator; + + public ScriptType ElementType + { + get => _pickerValidator?.AssetType ?? ScriptType.Null; + set => _pickerValidator = new AssetPickerValidator(value); + } + + public CollectionEditor Editor { get; set; } + + public string FileExtension + { + set => _pickerValidator.FileExtension = value; + } + + /// + public override void Draw() + { + if (_dragHandlers is { HasValidDrag: true }) + { + var area = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(area, Color.Orange * 0.5f); + Render2D.DrawRectangle(area, Color.Black); + } + + base.Draw(); + } + + public override void OnDestroy() + { + _pickerValidator.OnDestroy(); + } + + private bool ValidateActors(ActorNode node) + { + return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor)); + } + + /// + public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) + { + var result = base.OnDragEnter(ref location, data); + if (result != DragDropEffect.None) + return result; + + if (_dragHandlers == null) + { + _dragItems = new DragItems(_pickerValidator.IsValid); + _dragActors = new DragActors(ValidateActors); + _dragHandlers = new DragHandlers + { + _dragActors, + _dragItems + }; + } + return _dragHandlers.OnDragEnter(data); + } + + /// + public override DragDropEffect OnDragMove(ref Float2 location, DragData data) + { + var result = base.OnDragMove(ref location, data); + if (result != DragDropEffect.None) + return result; + + return _dragHandlers.Effect; + } + + /// + public override void OnDragLeave() + { + _dragHandlers.OnDragLeave(); + + base.OnDragLeave(); + } + + /// + public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) + { + var result = base.OnDragDrop(ref location, data); + if (result != DragDropEffect.None) + { + _dragHandlers.OnDragDrop(null); + return result; + } + + if (_dragHandlers.HasValidDrag) + { + if (_dragItems.HasValidDrag) + { + var list = Editor.CloneValues(); + if (list == null) + { + if (Editor.Values.Type.IsArray) + { + list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0); + } + else + { + list = Editor.Values.Type.CreateInstance() as IList; + } + } + if (list.IsFixedSize) + { + var oldSize = list.Count; + var newSize = list.Count + _dragItems.Objects.Count; + var type = Editor.Values.Type.GetElementType(); + var array = TypeUtils.CreateArrayInstance(type, newSize); + list.CopyTo(array, 0); + + for (var i = oldSize; i < newSize; i++) + { + var validator = new AssetPickerValidator + { + FileExtension = _pickerValidator.FileExtension, + AssetType = _pickerValidator.AssetType, + SelectedItem = _dragItems.Objects[i - oldSize], + }; + + if (typeof(AssetItem).IsAssignableFrom(ElementType.Type)) + array.SetValue(validator.SelectedItem, i); + else if (ElementType.Type == typeof(Guid)) + array.SetValue(validator.SelectedID, i); + else if (ElementType.Type == typeof(SceneReference)) + array.SetValue(new SceneReference(validator.SelectedID), i); + else if (ElementType.Type == typeof(string)) + array.SetValue(validator.SelectedPath, i); + else + array.SetValue(validator.SelectedAsset, i); + + validator.OnDestroy(); + } + Editor.SetValue(array); + } + else + { + foreach (var item in _dragItems.Objects) + { + var validator = new AssetPickerValidator + { + FileExtension = _pickerValidator.FileExtension, + AssetType = _pickerValidator.AssetType, + SelectedItem = item, + }; + + if (typeof(AssetItem).IsAssignableFrom(ElementType.Type)) + list.Add(validator.SelectedItem); + else if (ElementType.Type == typeof(Guid)) + list.Add(validator.SelectedID); + else if (ElementType.Type == typeof(SceneReference)) + list.Add(new SceneReference(validator.SelectedID)); + else if (ElementType.Type == typeof(string)) + list.Add(validator.SelectedPath); + else + list.Add(validator.SelectedAsset); + + validator.OnDestroy(); + } + Editor.SetValue(list); + } + } + else if (_dragActors.HasValidDrag) + { + var list = Editor.CloneValues(); + if (list == null) + { + if (Editor.Values.Type.IsArray) + { + list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0); + } + else + { + list = Editor.Values.Type.CreateInstance() as IList; + } + } + + if (list.IsFixedSize) + { + var oldSize = list.Count; + var newSize = list.Count + _dragActors.Objects.Count; + var type = Editor.Values.Type.GetElementType(); + var array = TypeUtils.CreateArrayInstance(type, newSize); + list.CopyTo(array, 0); + + for (var i = oldSize; i < newSize; i++) + { + var actor = _dragActors.Objects[i - oldSize].Actor; + if (ElementType.Type.IsAssignableTo(typeof(Actor))) + { + array.SetValue(actor, i); + } + else + { + array.SetValue(actor.GetScript(ElementType.Type), i); + } + } + Editor.SetValue(array); + } + else + { + foreach (var actorNode in _dragActors.Objects) + { + if (ElementType.Type.IsAssignableTo(typeof(Actor))) + { + list.Add(actorNode.Actor); + } + else + { + list.Add(actorNode.Actor.GetScript(ElementType.Type)); + } + } + Editor.SetValue(list); + } + } + + _dragHandlers.OnDragDrop(null); + } + + return result; + } + } } } diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs index 9607680f2..c215a5ab7 100644 --- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs @@ -72,14 +72,14 @@ namespace FlaxEditor.CustomEditors.Editors return; _isRefreshing = true; var slots = _modelInstance.MaterialSlots; - var material = _materialEditor.Picker.SelectedAsset as MaterialBase; + var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase; var defaultMaterial = GPUDevice.Instance.DefaultMaterial; var value = (ModelInstanceEntry)Values[0]; var prevMaterial = value.Material; if (!material) { // Fallback to default material - _materialEditor.Picker.SelectedAsset = defaultMaterial; + _materialEditor.Picker.Validator.SelectedAsset = defaultMaterial; value.Material = defaultMaterial; } else if (material == slots[_entryIndex].Material) diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 8d6b0f9e2..1476832c4 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -5,6 +5,7 @@ using System.IO; using FlaxEditor.Content; using FlaxEditor.GUI.Drag; using FlaxEditor.Scripting; +using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -17,189 +18,21 @@ namespace FlaxEditor.GUI /// /// [HideInEditor] - public class AssetPicker : Control, IContentItemOwner + public class AssetPicker : Control { private const float DefaultIconSize = 64; private const float ButtonsOffset = 2; private const float ButtonsSize = 12; - private Asset _selected; - private ContentItem _selectedItem; - private ScriptType _type; - private string _fileExtension; - private bool _isMouseDown; private Float2 _mouseDownPos; private Float2 _mousePos; private DragItems _dragOverElement; /// - /// Gets or sets the selected item. + /// The asset validator. Used to ensure only appropriate items can be picked. /// - public ContentItem SelectedItem - { - get => _selectedItem; - set - { - if (_selectedItem == value) - return; - if (value == null) - { - if (_selected == null && _selectedItem is SceneItem) - { - // Deselect scene reference - _selectedItem.RemoveReference(this); - _selectedItem = null; - _selected = null; - OnSelectedItemChanged(); - return; - } - - // Deselect - _selectedItem?.RemoveReference(this); - _selectedItem = null; - _selected = null; - OnSelectedItemChanged(); - } - else if (value is SceneItem item) - { - if (_selectedItem == item) - return; - if (!IsValid(item)) - item = null; - - // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue) - _selectedItem?.RemoveReference(this); - _selectedItem = item; - _selected = null; - _selectedItem?.AddReference(this); - OnSelectedItemChanged(); - } - else if (value is AssetItem assetItem) - { - SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID); - } - else - { - // Change value - _selectedItem?.RemoveReference(this); - _selectedItem = value; - _selected = null; - OnSelectedItemChanged(); - } - } - } - - /// - /// Gets or sets the selected asset identifier. - /// - public Guid SelectedID - { - get - { - if (_selected != null) - return _selected.ID; - if (_selectedItem is AssetItem assetItem) - return assetItem.ID; - return Guid.Empty; - } - set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value); - } - - /// - /// Gets or sets the selected content item path. - /// - public string SelectedPath - { - get - { - string path = _selectedItem?.Path ?? _selected?.Path; - if (path != null) - { - // Convert into path relative to the project (cross-platform) - var projectFolder = Globals.ProjectFolder; - if (path.StartsWith(projectFolder)) - path = path.Substring(projectFolder.Length + 1); - } - return path; - } - set - { - if (string.IsNullOrEmpty(value)) - { - SelectedItem = null; - } - else - { - var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value; - SelectedItem = Editor.Instance.ContentDatabase.Find(path); - } - } - } - - /// - /// Gets or sets the selected asset object. - /// - public Asset SelectedAsset - { - get => _selected; - set - { - // Check if value won't change - if (value == _selected) - return; - - // Find item from content database and check it - var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null; - if (item != null && !IsValid(item)) - item = null; - - // Change value - _selectedItem?.RemoveReference(this); - _selectedItem = item; - _selected = value; - _selectedItem?.AddReference(this); - OnSelectedItemChanged(); - } - } - - /// - /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker. - /// - public ScriptType AssetType - { - get => _type; - set - { - if (_type != value) - { - _type = value; - - // Auto deselect if the current value is invalid - if (_selectedItem != null && !IsValid(_selectedItem)) - SelectedItem = null; - } - } - } - - /// - /// Gets or sets the content items extensions filter. Null if unused. - /// - public string FileExtension - { - get => _fileExtension; - set - { - if (_fileExtension != value) - { - _fileExtension = value; - - // Auto deselect if the current value is invalid - if (_selectedItem != null && !IsValid(_selectedItem)) - SelectedItem = null; - } - } - } + public AssetPickerValidator Validator { get; } /// /// Occurs when selected item gets changed. @@ -216,38 +49,6 @@ namespace FlaxEditor.GUI /// public bool CanEdit = true; - private bool IsValid(ContentItem item) - { - if (_fileExtension != null && !item.Path.EndsWith(_fileExtension)) - return false; - if (CheckValid != null && !CheckValid(item)) - return false; - if (_type == ScriptType.Null) - return true; - - if (item is AssetItem assetItem) - { - // Faster path for binary items (in-built) - if (assetItem is BinaryAssetItem binaryItem) - return _type.IsAssignableFrom(new ScriptType(binaryItem.Type)); - - // Type filter - var type = TypeUtils.GetType(assetItem.TypeName); - if (_type.IsAssignableFrom(type)) - return true; - - // Json assets can contain any type of the object defined by the C# type (data oriented design) - if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset))) - return true; - - // Special case for scene asset references - if (_type.Type == typeof(SceneReference) && assetItem is SceneItem) - return true; - } - - return false; - } - /// /// Initializes a new instance of the class. /// @@ -264,7 +65,8 @@ namespace FlaxEditor.GUI public AssetPicker(ScriptType assetType, Float2 location) : base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize)) { - _type = assetType; + Validator = new AssetPickerValidator(assetType); + Validator.SelectedItemChanged += OnSelectedItemChanged; _mousePos = Float2.Minimum; } @@ -275,10 +77,10 @@ namespace FlaxEditor.GUI { // Update tooltip string tooltip; - if (_selectedItem is AssetItem assetItem) + if (Validator.SelectedItem is AssetItem assetItem) tooltip = assetItem.NamePath; else - tooltip = SelectedPath; + tooltip = Validator.SelectedPath; TooltipText = tooltip; SelectedItemChanged?.Invoke(); @@ -289,37 +91,13 @@ namespace FlaxEditor.GUI // Do the drag drop operation if has selected element if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos)) { - if (_selected != null) - DoDragDrop(DragAssets.GetDragData(_selected)); - else if (_selectedItem != null) - DoDragDrop(DragItems.GetDragData(_selectedItem)); + if (Validator.SelectedAsset != null) + DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset)); + else if (Validator.SelectedItem != null) + DoDragDrop(DragItems.GetDragData(Validator.SelectedItem)); } } - /// - public void OnItemDeleted(ContentItem item) - { - // Deselect item - SelectedItem = null; - } - - /// - public void OnItemRenamed(ContentItem item) - { - } - - /// - public void OnItemReimported(ContentItem item) - { - } - - /// - public void OnItemDispose(ContentItem item) - { - // Deselect item - SelectedItem = null; - } - private Rectangle IconRect => new Rectangle(0, 0, Height, Height); private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize); @@ -341,10 +119,10 @@ namespace FlaxEditor.GUI if (CanEdit) Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); - if (_selectedItem != null) + if (Validator.SelectedItem != null) { // Draw item preview - _selectedItem.DrawThumbnail(ref iconRect); + Validator.SelectedItem.DrawThumbnail(ref iconRect); // Draw buttons if (CanEdit) @@ -363,7 +141,7 @@ namespace FlaxEditor.GUI { Render2D.DrawText( style.FontSmall, - _selectedItem.ShortName, + Validator.SelectedItem.ShortName, new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), style.Foreground, TextAlignment.Near, @@ -371,7 +149,7 @@ namespace FlaxEditor.GUI } } // Check if has no item but has an asset (eg. virtual asset) - else if (_selected) + else if (Validator.SelectedAsset) { // Draw remove button Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); @@ -380,8 +158,8 @@ namespace FlaxEditor.GUI float sizeForTextLeft = Width - button1Rect.Right; if (sizeForTextLeft > 30) { - var name = _selected.GetType().Name; - if (_selected.IsVirtual) + var name = Validator.SelectedAsset.GetType().Name; + if (Validator.SelectedAsset.IsVirtual) name += " (virtual)"; Render2D.DrawText( style.FontSmall, @@ -407,9 +185,7 @@ namespace FlaxEditor.GUI /// public override void OnDestroy() { - _selectedItem?.RemoveReference(this); - _selectedItem = null; - _selected = null; + Validator.OnDestroy(); base.OnDestroy(); } @@ -463,57 +239,57 @@ namespace FlaxEditor.GUI // Buttons logic if (!CanEdit) { - if (Button1Rect.Contains(location) && _selectedItem != null) + if (Button1Rect.Contains(location) && Validator.SelectedItem != null) { // Select asset - Editor.Instance.Windows.ContentWin.Select(_selectedItem); + Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem); } } else if (Button1Rect.Contains(location)) { Focus(); - if (_type != ScriptType.Null) + if (Validator.AssetType != ScriptType.Null) { // Show asset picker popup - var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item => + var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item => { - SelectedItem = item; + Validator.SelectedItem = item; RootWindow.Focus(); Focus(); }); - if (_selected != null) + if (Validator.SelectedAsset != null) { - var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path); + var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path); popup.ScrollToAndHighlightItemByName(selectedAssetName); } } else { // Show content item picker popup - var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item => + var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item => { - SelectedItem = item; + Validator.SelectedItem = item; RootWindow.Focus(); Focus(); }); - if (_selectedItem != null) + if (Validator.SelectedItem != null) { - popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName); + popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName); } } } - else if (_selected != null || _selectedItem != null) + else if (Validator.SelectedAsset != null || Validator.SelectedItem != null) { - if (Button2Rect.Contains(location) && _selectedItem != null) + if (Button2Rect.Contains(location) && Validator.SelectedItem != null) { // Select asset - Editor.Instance.Windows.ContentWin.Select(_selectedItem); + Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem); } else if (Button3Rect.Contains(location)) { // Deselect asset Focus(); - SelectedItem = null; + Validator.SelectedItem = null; } } } @@ -540,10 +316,10 @@ namespace FlaxEditor.GUI { Focus(); - if (_selectedItem != null && IconRect.Contains(location)) + if (Validator.SelectedItem != null && IconRect.Contains(location)) { // Open it - Editor.Instance.ContentEditing.Open(_selectedItem); + Editor.Instance.ContentEditing.Open(Validator.SelectedItem); } // Handled @@ -557,7 +333,7 @@ namespace FlaxEditor.GUI // Check if drop asset if (_dragOverElement == null) - _dragOverElement = new DragItems(IsValid); + _dragOverElement = new DragItems(Validator.IsValid); if (CanEdit && _dragOverElement.OnDragEnter(data)) { } @@ -590,7 +366,7 @@ namespace FlaxEditor.GUI if (CanEdit && _dragOverElement.HasValidDrag) { // Select element - SelectedItem = _dragOverElement.Objects[0]; + Validator.SelectedItem = _dragOverElement.Objects[0]; } // Clear cache diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index 5054aee98..86e24e3b3 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs /// public DialogResult Result => _result; + /// + /// Returns the size of the dialog. + /// + public Float2 DialogSize => _dialogSize; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index e544285a5..b04aad08c 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking { if (Parent.Parent is SplitPanel splitter) { - // Check if has any child panels - var childPanel = new List(_childPanels); - for (int i = 0; i < childPanel.Count; i++) + // Check if there is another nested dock panel inside this dock panel and extract it here + var childPanels = _childPanels.ToArray(); + if (childPanels.Length != 0) { - // Undock all tabs - var panel = childPanel[i]; - int count = panel.TabsCount; - while (count-- > 0) + // Move tabs from child panels into this one + DockWindow selectedTab = null; + foreach (var childPanel in childPanels) { - panel.GetTab(0).Close(); + var childPanelTabs = childPanel.Tabs.ToArray(); + for (var i = 0; i < childPanelTabs.Length; i++) + { + var childPanelTab = childPanelTabs[i]; + if (selectedTab == null && childPanelTab.IsSelected) + selectedTab = childPanelTab; + childPanel.UndockWindow(childPanelTab); + AddTab(childPanelTab, false); + } } + if (selectedTab != null) + SelectTab(selectedTab); } - - // Unlink splitter - var splitterParent = splitter.Parent; - Assert.IsNotNull(splitterParent); - splitter.Parent = null; - - // Move controls from second split panel to the split panel parent - var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2; - var srcPanelChildrenCount = scrPanel.ChildrenCount; - for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--) + else { - scrPanel.GetChild(i).Parent = splitterParent; - } - Assert.IsTrue(scrPanel.ChildrenCount == 0); - Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount); + // Unlink splitter + var splitterParent = splitter.Parent; + Assert.IsNotNull(splitterParent); + splitter.Parent = null; - // Delete - splitter.Dispose(); + // Move controls from second split panel to the split panel parent + var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2; + var srcPanelChildrenCount = scrPanel.ChildrenCount; + for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--) + { + scrPanel.GetChild(i).Parent = splitterParent; + } + Assert.IsTrue(scrPanel.ChildrenCount == 0); + Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount); + + // Delete + splitter.Dispose(); + } } else if (!IsMaster) { @@ -582,19 +593,17 @@ namespace FlaxEditor.GUI.Docking /// Adds the tab. /// /// The window to insert as a tab. - protected virtual void AddTab(DockWindow window) + /// True if auto-select newly added tab. + protected virtual void AddTab(DockWindow window, bool autoSelect = true) { - // Dock _tabs.Add(window); window.ParentDockPanel = this; - - // Select tab - SelectTab(window); + if (autoSelect) + SelectTab(window); } private void CreateTabsProxy() { - // Check if has no tabs proxy created if (_tabsProxy == null) { // Create proxy and make set simple full dock diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs index f8ffe62ad..7bb85751a 100644 --- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs +++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.Docking settings.Size = size; settings.Position = location; settings.MinimumSize = new Float2(1); - settings.MaximumSize = new Float2(4096); + settings.MaximumSize = Float2.Zero; // Unlimited size settings.Fullscreen = false; settings.HasBorder = true; settings.SupportsTransparency = false; diff --git a/Source/Editor/GUI/Tabs/Tab.cs b/Source/Editor/GUI/Tabs/Tab.cs index 3968cc17d..85f805af0 100644 --- a/Source/Editor/GUI/Tabs/Tab.cs +++ b/Source/Editor/GUI/Tabs/Tab.cs @@ -13,6 +13,8 @@ namespace FlaxEditor.GUI.Tabs [HideInEditor] public class Tab : ContainerControl { + internal Tabs _selectedInTabs; + /// /// Gets or sets the text. /// @@ -86,5 +88,25 @@ namespace FlaxEditor.GUI.Tabs { return new Tabs.TabHeader((Tabs)Parent, this); } + + /// + protected override void OnParentChangedInternal() + { + if (_selectedInTabs != null) + _selectedInTabs.SelectedTab = null; + + base.OnParentChangedInternal(); + } + + /// + public override void OnDestroy() + { + if (IsDisposing) + return; + if (_selectedInTabs != null) + _selectedInTabs.SelectedTab = null; + + base.OnDestroy(); + } } } diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index d830b9e0f..b5e2cfe39 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -263,7 +263,12 @@ namespace FlaxEditor.GUI.Tabs // Check if index will change if (_selectedIndex != index) { - SelectedTab?.OnDeselected(); + var prev = SelectedTab; + if (prev != null) + { + prev._selectedInTabs = null; + prev.OnDeselected(); + } _selectedIndex = index; PerformLayout(); OnSelectedTabChanged(); @@ -342,8 +347,13 @@ namespace FlaxEditor.GUI.Tabs /// protected virtual void OnSelectedTabChanged() { + var selectedTab = SelectedTab; SelectedTabChanged?.Invoke(this); - SelectedTab?.OnSelected(); + if (selectedTab != null) + { + selectedTab._selectedInTabs = this; + selectedTab.OnSelected(); + } } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs index b7c87cb01..3bbca15ef 100644 --- a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (AssetID == value?.ID) return; AssetID = value?.ID ?? Guid.Empty; - _picker.SelectedAsset = value; + _picker.Validator.SelectedAsset = value; OnAssetChanged(); Timeline?.MarkAsEdited(); } @@ -63,10 +63,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks private void OnPickerSelectedItemChanged() { - if (Asset == (TAsset)_picker.SelectedAsset) + if (Asset == (TAsset)_picker.Validator.SelectedAsset) return; using (new TrackUndoBlock(this)) - Asset = (TAsset)_picker.SelectedAsset; + Asset = (TAsset)_picker.Validator.SelectedAsset; } /// diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 7789eb7c4..4fb28ac76 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -266,6 +266,19 @@ namespace FlaxEditor.Modules Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive); } + /// + /// Reload all loaded scenes. + /// + public void ReloadScenes() + { + foreach (var scene in Level.Scenes) + { + var sceneId = scene.ID; + if (!Level.UnloadScene(scene)) + Level.LoadScene(sceneId); + } + } + /// /// Closes scene (async). /// diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs index 225bad082..082439402 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs @@ -147,6 +147,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing } if (key != null) xml.TryGetValue(key, out text); + + // Customize tooltips for properties to be more human-readable in UI + if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal)) + { + text = text.Substring(13); + unsafe + { + fixed (char* e = text) + e[0] = char.ToUpper(e[0]); + } + } } } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 1369ecd1e..3386d411c 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -39,6 +39,7 @@ namespace FlaxEditor.Modules ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup(); private ContextMenuButton _menuFileSaveScenes; + private ContextMenuButton _menuFileReloadScenes; private ContextMenuButton _menuFileCloseScenes; private ContextMenuButton _menuFileOpenScriptsProject; private ContextMenuButton _menuFileGenerateScriptsProjectFiles; @@ -470,13 +471,13 @@ namespace FlaxEditor.Modules // Place dialog nearby the target control var targetControlDesktopCenter = targetControl.PointToScreen(targetControl.Size * 0.5f); var desktopSize = Platform.GetMonitorBounds(targetControlDesktopCenter); - var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.Height * 0.5f); - var dialogEnd = pos + dialog.Size; + var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.DialogSize.Y * 0.5f); + var dialogEnd = pos + dialog.DialogSize; var desktopEnd = desktopSize.BottomRight - new Float2(10.0f); if (dialogEnd.X >= desktopEnd.X || dialogEnd.Y >= desktopEnd.Y) - pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.Width, dialog.Height); + pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.DialogSize.X, dialog.DialogSize.Y); var desktopBounds = Platform.VirtualDesktopBounds; - pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.Size); + pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.DialogSize); dialog.RootWindow.Window.Position = pos; // Register for context menu (prevent auto-closing context menu when selecting color) @@ -527,6 +528,7 @@ namespace FlaxEditor.Modules _menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll); _menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes); _menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes); + _menuFileReloadScenes = cm.AddButton("Reload scenes", Editor.Scene.ReloadScenes); cm.AddSeparator(); _menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution); _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); @@ -830,6 +832,7 @@ namespace FlaxEditor.Modules _menuFileSaveScenes.Enabled = hasOpenedScene; _menuFileCloseScenes.Enabled = hasOpenedScene; + _menuFileReloadScenes.Enabled = hasOpenedScene; _menuFileGenerateScriptsProjectFiles.Enabled = !Editor.ProgressReporting.GenerateScriptsProjectFiles.IsActive; c.PerformLayout(); diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 05d72598f..cda83b56e 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -171,9 +171,13 @@ namespace FlaxEditor.Modules var mainWindow = MainWindow; if (mainWindow) { - var projectPath = Globals.ProjectFolder.Replace('/', '\\'); - var platformBit = Platform.Is64BitApp ? "64" : "32"; - var title = string.Format("Flax Editor - \'{0}\' ({1}-bit)", projectPath, platformBit); + var projectPath = Globals.ProjectFolder; +#if PLATFORM_WINDOWS + projectPath = projectPath.Replace('/', '\\'); +#endif + var engineVersion = Editor.EngineProject.Version; + var engineVersionText = engineVersion.Revision > 0 ? $"{engineVersion.Major}.{engineVersion.Minor}.{engineVersion.Revision}" : $"{engineVersion.Major}.{engineVersion.Minor}"; + var title = $"Flax Editor {engineVersionText} - \'{projectPath}\'"; mainWindow.Title = title; } } @@ -735,7 +739,6 @@ namespace FlaxEditor.Modules settings.Size = Platform.DesktopSize * 0.75f; settings.StartPosition = WindowStartPosition.CenterScreen; settings.ShowAfterFirstPaint = true; - #if PLATFORM_WINDOWS if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem) { @@ -747,12 +750,9 @@ namespace FlaxEditor.Modules #elif PLATFORM_LINUX settings.HasBorder = false; #endif - MainWindow = Platform.CreateWindow(ref settings); - if (MainWindow == null) { - // Error Editor.LogError("Failed to create editor main window!"); return; } diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 90d098bb6..e2e7d0e71 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -76,6 +76,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(230)] public InputBinding RotateSelection = new InputBinding(KeyboardKeys.R); + [DefaultValue(typeof(InputBinding), "F11")] + [EditorDisplay("Common"), EditorOrder(240)] + public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11); + #endregion #region File @@ -208,16 +212,20 @@ namespace FlaxEditor.Options [EditorDisplay("Debugger", "Continue"), EditorOrder(810)] public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5); + [DefaultValue(typeof(InputBinding), "Shift+F11")] + [EditorDisplay("Debugger", "Unlock mouse in Play Mode"), EditorOrder(820)] + public InputBinding DebuggerUnlockMouse = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift); + [DefaultValue(typeof(InputBinding), "F10")] - [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)] + [EditorDisplay("Debugger", "Step Over"), EditorOrder(830)] public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)] + [EditorDisplay("Debugger", "Step Into"), EditorOrder(840)] public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11); [DefaultValue(typeof(InputBinding), "Shift+F11")] - [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)] + [EditorDisplay("Debugger", "Step Out"), EditorOrder(850)] public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift); #endregion diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs index c09e1a246..ed6fc08d6 100644 --- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs +++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs @@ -7,6 +7,7 @@ using Real = System.Single; #endif using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.SceneGraph.Actors { @@ -30,6 +31,13 @@ namespace FlaxEditor.SceneGraph.Actors // Rotate to match the space (GUI uses upper left corner as a root) Actor.LocalOrientation = Quaternion.Euler(0, -180, -180); + var uiControl = new UIControl + { + Name = "Canvas Scalar", + Transform = Actor.Transform, + Control = new CanvasScaler() + }; + Root.Spawn(uiControl, Actor); } /// diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 163397768..f64e46385 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -266,7 +266,7 @@ namespace FlaxEditor.SceneGraph.GUI /// /// Starts the actor renaming action. /// - public void StartRenaming(EditorWindow window) + public void StartRenaming(EditorWindow window, Panel treePanel = null) { // Block renaming during scripts reload if (Editor.Instance.ProgressReporting.CompileScripts.IsActive) @@ -281,7 +281,13 @@ namespace FlaxEditor.SceneGraph.GUI (window as PrefabWindow).ScrollingOnTreeView(false); // Start renaming the actor - var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false); + var rect = TextRect; + if (treePanel != null) + { + treePanel.ScrollViewTo(this, true); + rect.Size = new Float2(treePanel.Width - TextRect.Location.X, TextRect.Height); + } + var dialog = RenamePopup.Show(this, rect, _actorNode.Name, false); dialog.Renamed += OnRenamed; dialog.Closed += popup => { diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs index 698dc192f..d48918e1b 100644 --- a/Source/Editor/States/LoadingState.cs +++ b/Source/Editor/States/LoadingState.cs @@ -56,12 +56,14 @@ namespace FlaxEditor.States else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile) { // Generate project files when Cache is missing or was cleared previously - if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) || - !Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects"))) + var projectFolderPath = Editor.GameProject?.ProjectFolderPath; + if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) || + !Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects"))) { - var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs; + var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs; ScriptsBuilder.GenerateProject(customArgs); } + // Compile scripts before loading any scenes, then we load them and can open scenes ScriptsBuilder.Compile(); } diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 9773e9695..7c3aa4f13 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -465,7 +465,7 @@ namespace FlaxEditor.Surface.Archetypes if (selectedIndex != -1) { var index = 5 + selectedIndex * 2; - SetValue(index, _animationPicker.SelectedID); + SetValue(index, _animationPicker.Validator.SelectedID); } } @@ -495,7 +495,7 @@ namespace FlaxEditor.Surface.Archetypes { if (isValid) { - _animationPicker.SelectedID = data1; + _animationPicker.Validator.SelectedID = data1; _animationSpeed.Value = data0.W; var path = string.Empty; @@ -505,7 +505,7 @@ namespace FlaxEditor.Surface.Archetypes } else { - _animationPicker.SelectedID = Guid.Empty; + _animationPicker.Validator.SelectedID = Guid.Empty; _animationSpeed.Value = 1.0f; } _animationPicker.Enabled = isValid; diff --git a/Source/Editor/Surface/Archetypes/Bitwise.cs b/Source/Editor/Surface/Archetypes/Bitwise.cs index 06f719adc..43dcb91b5 100644 --- a/Source/Editor/Surface/Archetypes/Bitwise.cs +++ b/Source/Editor/Surface/Archetypes/Bitwise.cs @@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes Op1(1, "Bitwise NOT", "Negates the value using bitwise operation", new[] { "!", "~" }), Op2(2, "Bitwise AND", "Performs a bitwise conjunction on two values", new[] { "&" }), Op2(3, "Bitwise OR", "", new[] { "|" }), - Op2(4, "Bitwise XOR", ""), + Op2(4, "Bitwise XOR", "", new[] { "^" }), }; } } diff --git a/Source/Editor/Surface/Archetypes/Boolean.cs b/Source/Editor/Surface/Archetypes/Boolean.cs index 153b2fead..ed97a9642 100644 --- a/Source/Editor/Surface/Archetypes/Boolean.cs +++ b/Source/Editor/Surface/Archetypes/Boolean.cs @@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes Op1(1, "Boolean NOT", "Negates the boolean value", new[] { "!", "~" }), Op2(2, "Boolean AND", "Performs a logical conjunction on two values", new[] { "&&" }), Op2(3, "Boolean OR", "Returns true if either (or both) of its operands is true", new[] { "||" }), - Op2(4, "Boolean XOR", ""), + Op2(4, "Boolean XOR", "", new [] { "^" } ), Op2(5, "Boolean NOR", ""), Op2(6, "Boolean NAND", ""), }; diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs index a0efbd92f..b72111c46 100644 --- a/Source/Editor/Surface/Archetypes/Comparisons.cs +++ b/Source/Editor/Surface/Archetypes/Comparisons.cs @@ -14,7 +14,7 @@ namespace FlaxEditor.Surface.Archetypes [HideInEditor] public static class Comparisons { - private static NodeArchetype Op(ushort id, string title, string desc) + private static NodeArchetype Op(ushort id, string title, string desc, string[] altTitles = null) { return new NodeArchetype { @@ -22,6 +22,7 @@ namespace FlaxEditor.Surface.Archetypes Title = title, Description = desc, Flags = NodeFlags.AllGraphs, + AlternativeTitles = altTitles, ConnectionsHints = ConnectionsHint.Value, Size = new Float2(100, 40), IndependentBoxes = new[] @@ -170,12 +171,12 @@ namespace FlaxEditor.Surface.Archetypes /// public static NodeArchetype[] Nodes = { - Op(1, "==", "Determines whether two values are equal"), - Op(2, "!=", "Determines whether two values are not equal"), - Op(3, ">", "Determines whether the first value is greater than the other"), - Op(4, "<", "Determines whether the first value is less than the other"), - Op(5, "<=", "Determines whether the first value is less or equal to the other"), - Op(6, ">=", "Determines whether the first value is greater or equal to the other"), + Op(1, "==", "Determines whether two values are equal", new[] { "equals" }), + Op(2, "!=", "Determines whether two values are not equal", new[] { "not equals" }), + Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than" }), + Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than" }), + Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than" }), + Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than" }), new NodeArchetype { TypeID = 7, diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs index cbe7822e3..e58d917d1 100644 --- a/Source/Editor/Surface/Archetypes/Constants.cs +++ b/Source/Editor/Surface/Archetypes/Constants.cs @@ -7,11 +7,12 @@ using Real = System.Single; #endif using System; -using System.Reflection; +using System.Linq; using FlaxEditor.CustomEditors.Editors; using FlaxEditor.GUI; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; +using FlaxEditor.Surface.Undo; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -24,6 +25,109 @@ namespace FlaxEditor.Surface.Archetypes [HideInEditor] public static class Constants { + /// + /// A special type of node that adds the functionality to convert nodes to parameters. + /// + internal class ConvertToParameterNode : SurfaceNode + { + private readonly ScriptType _type; + private readonly Func _convertFunction; + + /// + public ConvertToParameterNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch, ScriptType type, Func convertFunction = null) + : base(id, context, nodeArch, groupArch) + { + _type = type; + _convertFunction = convertFunction; + } + + /// + public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location) + { + base.OnShowSecondaryContextMenu(menu, location); + + menu.AddSeparator(); + menu.AddButton("Convert to Parameter", OnConvertToParameter); + } + + private void OnConvertToParameter() + { + if (Surface.Owner is not IVisjectSurfaceWindow window) + throw new Exception("Surface owner is not a Visject Surface Window"); + + Asset asset = Surface.Owner.SurfaceAsset; + if (asset == null || !asset.IsLoaded) + { + Editor.LogError("Asset is null or not loaded"); + return; + } + + // Add parameter to editor + var paramIndex = Surface.Parameters.Count; + var initValue = _convertFunction == null ? Values[0] : _convertFunction.Invoke(Values); + var paramAction = new AddRemoveParamAction + { + Window = window, + IsAdd = true, + Name = Utilities.Utils.IncrementNameNumber("New parameter", OnParameterRenameValidate), + Type = _type, + Index = paramIndex, + InitValue = initValue, + }; + paramAction.Do(); + Surface.AddBatchedUndoAction(paramAction); + + // Spawn Get Parameter Node based on the added parameter + Guid parameterGuid = Surface.Parameters[paramIndex].ID; + bool undoEnabled = Surface.Undo.Enabled; + Surface.Undo.Enabled = false; + NodeArchetype arch = Surface.GetParameterGetterNodeArchetype(out var groupId); + SurfaceNode node = Surface.Context.SpawnNode(groupId, arch.TypeID, Location, new object[] { parameterGuid }); + Surface.Undo.Enabled = undoEnabled; + if (node is not Parameters.SurfaceNodeParamsGet getNode) + throw new Exception("Node is not a ParamsGet node!"); + Surface.AddBatchedUndoAction(new AddRemoveNodeAction(getNode, true)); + + // Recreate connections of constant node + // Constant nodes and parameter nodes should have the same ports, so we can just iterate through the connections + var editConnectionsAction1 = new EditNodeConnections(Context, this); + var editConnectionsAction2 = new EditNodeConnections(Context, node); + var boxes = GetBoxes(); + for (int i = 0; i < boxes.Count; i++) + { + Box box = boxes[i]; + if (!box.HasAnyConnection) + continue; + if (!getNode.TryGetBox(box.ID, out Box paramBox)) + continue; + + // Iterating backwards, because the CreateConnection method deletes entries from the box connections when target box IsSingle is set to true + for (int k = box.Connections.Count - 1; k >= 0; k--) + { + Box connectedBox = box.Connections[k]; + paramBox.CreateConnection(connectedBox); + } + } + editConnectionsAction1.End(); + editConnectionsAction2.End(); + Surface.AddBatchedUndoAction(editConnectionsAction1); + Surface.AddBatchedUndoAction(editConnectionsAction2); + + // Add undo actions and remove constant node + var removeConstantAction = new AddRemoveNodeAction(this, false); + Surface.AddBatchedUndoAction(removeConstantAction); + removeConstantAction.Do(); + Surface.MarkAsEdited(); + } + + private bool OnParameterRenameValidate(string value) + { + if (Surface.Owner is not IVisjectSurfaceWindow window) + throw new Exception("Surface owner is not a Visject Surface Window"); + return !string.IsNullOrWhiteSpace(value) && window.VisjectSurface.Parameters.All(x => x.Name != value); + } + } + private class EnumNode : SurfaceNode { private EnumComboBox _picker; @@ -356,6 +460,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 1, Title = "Bool", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(bool))), Description = "Constant boolean value", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -388,6 +493,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 2, Title = "Integer", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(int))), Description = "Constant integer value", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -415,6 +521,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 3, Title = "Float", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(float))), Description = "Constant floating point", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -442,6 +549,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 4, Title = "Float2", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Float2))), Description = "Constant Float2", Flags = NodeFlags.AllGraphs, Size = new Float2(130, 60), @@ -472,6 +580,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 5, Title = "Float3", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Float3))), Description = "Constant Float3", Flags = NodeFlags.AllGraphs, Size = new Float2(130, 80), @@ -504,6 +613,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 6, Title = "Float4", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Float4))), Description = "Constant Float4", Flags = NodeFlags.AllGraphs, Size = new Float2(130, 100), @@ -538,6 +648,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 7, Title = "Color", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Color))), Description = "RGBA color", Flags = NodeFlags.AllGraphs, Size = new Float2(70, 100), @@ -570,6 +681,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 8, Title = "Rotation", + Create = (id, context, arch, groupArch) => + new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Quaternion)), values => Quaternion.Euler((float)values[0], (float)values[1], (float)values[2])), Description = "Euler angle rotation", Flags = NodeFlags.AnimGraph | NodeFlags.VisualScriptGraph | NodeFlags.ParticleEmitterGraph, Size = new Float2(110, 60), @@ -594,6 +707,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 9, Title = "String", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(string))), Description = "Text", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, Size = new Float2(200, 20), @@ -644,6 +758,8 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 12, Title = "Unsigned Integer", + AlternativeTitles = new[] { "UInt", "U Int" }, + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(uint))), Description = "Constant unsigned integer value", Flags = NodeFlags.AllGraphs, Size = new Float2(170, 20), @@ -683,6 +799,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 15, Title = "Double", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(double))), Description = "Constant floating point", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -700,6 +817,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 16, Title = "Vector2", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Vector2))), Description = "Constant Vector2", Flags = NodeFlags.AllGraphs, Size = new Float2(130, 60), @@ -720,6 +838,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 17, Title = "Vector3", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Vector3))), Description = "Constant Vector3", Flags = NodeFlags.AllGraphs, Size = new Float2(130, 80), @@ -742,6 +861,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 18, Title = "Vector4", + Create = (id, context, arch, groupArch) => new ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Vector4))), Description = "Constant Vector4", Flags = NodeFlags.AllGraphs, Size = new Float2(130, 100), diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 7d12a0625..53950dad2 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -95,7 +95,7 @@ namespace FlaxEditor.Surface.Archetypes private void OnAssetPickerSelectedItemChanged() { - SetValue(0, _assetPicker.SelectedID); + SetValue(0, _assetPicker.Validator.SelectedID); } private void TryRestoreConnections(Box box, Box[] prevBoxes, ref NodeElementArchetype arch) @@ -133,7 +133,7 @@ namespace FlaxEditor.Surface.Archetypes var prevOutputs = _outputs; // Extract function signature parameters (inputs and outputs packed) - _asset = LoadSignature(_assetPicker.SelectedID, out var typeNames, out var names); + _asset = LoadSignature(_assetPicker.Validator.SelectedID, out var typeNames, out var names); if (typeNames != null && names != null) { var types = new Type[typeNames.Length]; @@ -174,7 +174,7 @@ namespace FlaxEditor.Surface.Archetypes _outputs[i] = box; } - Title = _assetPicker.SelectedItem.ShortName; + Title = _assetPicker.Validator.SelectedItem.ShortName; } else { @@ -2470,6 +2470,7 @@ namespace FlaxEditor.Surface.Archetypes Title = string.Empty, Description = "Overrides the base class method with custom implementation", Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste, + SortScore = 10, IsInputCompatible = MethodOverrideNode.IsInputCompatible, IsOutputCompatible = MethodOverrideNode.IsOutputCompatible, Size = new Float2(240, 60), diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs index fe2f1e044..7f4afa6a6 100644 --- a/Source/Editor/Surface/Archetypes/Math.cs +++ b/Source/Editor/Surface/Archetypes/Math.cs @@ -13,6 +13,11 @@ namespace FlaxEditor.Surface.Archetypes public static class Math { private static NodeArchetype Op1(ushort id, string title, string desc, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null) + { + return Op1(id, title, desc, null, hints, type); + } + + private static NodeArchetype Op1(ushort id, string title, string desc, string[] altTitles, ConnectionsHint hints = ConnectionsHint.Numeric, Type type = null) { return new NodeArchetype { @@ -20,6 +25,7 @@ namespace FlaxEditor.Surface.Archetypes Title = title, Description = desc, Flags = NodeFlags.AllGraphs, + AlternativeTitles = altTitles, Size = new Float2(110, 20), DefaultType = new ScriptType(type), ConnectionsHints = hints, @@ -92,6 +98,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 11, Title = "Length", + AlternativeTitles = new[] { "Magnitude", "Mag" }, Description = "Returns the length of A vector", Flags = NodeFlags.AllGraphs, Size = new Float2(110, 20), @@ -107,10 +114,10 @@ namespace FlaxEditor.Surface.Archetypes Op1(13, "Round", "Rounds A to the nearest integer"), Op1(14, "Saturate", "Clamps A to the range [0, 1]"), Op1(15, "Sine", "Returns sine of A"), - Op1(16, "Sqrt", "Returns square root of A"), + Op1(16, "Sqrt", "Returns square root of A", new [] { "Square Root", "Square", "Root" }), Op1(17, "Tangent", "Returns tangent of A"), Op2(18, "Cross", "Returns the cross product of A and B", ConnectionsHint.None, typeof(Float3)), - Op2(19, "Distance", "Returns a distance scalar between A and B", ConnectionsHint.Vector, null, typeof(float), false), + Op2(19, "Distance", "Returns a distance scalar between A and B", new [] { "Magnitude", "Mag", "Length" }, ConnectionsHint.Vector, null, typeof(float), false), Op2(20, "Dot", "Returns the dot product of A and B", ConnectionsHint.Vector, null, typeof(float), false), Op2(21, "Max", "Selects the greater of A and B"), Op2(22, "Min", "Selects the lesser of A and B"), @@ -185,7 +192,7 @@ namespace FlaxEditor.Surface.Archetypes } }, // - Op1(27, "Negate", "Returns opposite value"), + Op1(27, "Negate", "Returns opposite value", new [] { "Invert" }), Op1(28, "One Minus", "Returns 1 - value"), // new NodeArchetype @@ -225,6 +232,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 31, Title = "Mad", + AlternativeTitles = new [] { "Multiply", "Add", "*+" }, Description = "Performs value multiplication and addition at once", Flags = NodeFlags.AllGraphs, Size = new Float2(160, 60), diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index 18c21ecea..e35af7192 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -3,6 +3,7 @@ using System; using FlaxEditor.Content.Settings; using FlaxEditor.GUI; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.Surface.Archetypes @@ -95,6 +96,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 1, Title = "Texture", + Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(Texture))), Description = "Two dimensional texture object", Flags = NodeFlags.MaterialGraph, Size = new Float2(140, 120), @@ -131,6 +133,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 3, Title = "Cube Texture", + Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(CubeTexture))), Description = "Set of 6 textures arranged in a cube", Flags = NodeFlags.MaterialGraph, Size = new Float2(140, 120), @@ -154,6 +157,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 4, Title = "Normal Map", + Create = (id, context, arch, groupArch) => new Constants.ConvertToParameterNode(id, context, arch, groupArch, new ScriptType(typeof(NormalMap))), Description = "Two dimensional texture object sampled as a normal map", Flags = NodeFlags.MaterialGraph, Size = new Float2(140, 120), diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 6d2d31b8c..029422d05 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -1483,7 +1483,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 11, Title = "Comment", - AlternativeTitles = new[] { "//" }, + AlternativeTitles = new[] { "//" , "Group" }, TryParseText = (string filterText, out object[] data) => { data = null; @@ -1510,6 +1510,7 @@ namespace FlaxEditor.Surface.Archetypes "Comment", // Title new Color(1.0f, 1.0f, 1.0f, 0.2f), // Color new Float2(400.0f, 400.0f), // Size + -1, // Order }, }, CurveNode.GetArchetype(12, "Curve", typeof(float), 0.0f, 1.0f), @@ -1638,6 +1639,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 22, Title = "As", + AlternativeTitles = new [] { "Cast" }, Create = (id, context, arch, groupArch) => new AsNode(id, context, arch, groupArch), Description = "Casts the object to a different type. Returns null if cast fails.", Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 37da7d74d..207875a92 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -78,6 +78,7 @@ namespace FlaxEditor.Surface.ContextMenu if (!Visible) return; + SortScore += _archetype.SortScore; if (selectedBox != null && CanConnectTo(selectedBox)) SortScore += 1; if (Data != null) diff --git a/Source/Editor/Surface/Elements/AssetSelect.cs b/Source/Editor/Surface/Elements/AssetSelect.cs index e38989e08..5984ce9aa 100644 --- a/Source/Editor/Surface/Elements/AssetSelect.cs +++ b/Source/Editor/Surface/Elements/AssetSelect.cs @@ -38,13 +38,13 @@ namespace FlaxEditor.Surface.Elements private void OnNodeValuesChanged() { - SelectedID = (Guid)ParentNode.Values[Archetype.ValueIndex]; + Validator.SelectedID = (Guid)ParentNode.Values[Archetype.ValueIndex]; } /// protected override void OnSelectedItemChanged() { - var selectedId = SelectedID; + var selectedId = Validator.SelectedID; if (ParentNode != null && (Guid)ParentNode.Values[Archetype.ValueIndex] != selectedId) { ParentNode.SetValue(Archetype.ValueIndex, selectedId); diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index 0284fc76d..3a77cef84 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -149,6 +149,11 @@ namespace FlaxEditor.Surface /// public object Tag; + /// + /// Custom score value to use when sorting node archetypes in Editor. If positive (eg. 1, 2) can be used to add more importance for a specific node type. + /// + public float SortScore; + /// /// Default node values. This array supports types: bool, int, float, Vector2, Vector3, Vector4, Color, Rectangle, Guid, string, Matrix and byte[]. /// @@ -204,14 +209,17 @@ namespace FlaxEditor.Surface Size = Size, Flags = Flags, Title = Title, - Description = Title, + SubTitle = SubTitle, + Description = Description, AlternativeTitles = (string[])AlternativeTitles?.Clone(), Tag = Tag, + SortScore = SortScore, DefaultValues = (object[])DefaultValues?.Clone(), DefaultType = DefaultType, ConnectionsHints = ConnectionsHints, IndependentBoxes = (int[])IndependentBoxes?.Clone(), DependentBoxes = (int[])DependentBoxes?.Clone(), + DependentBoxFilter = DependentBoxFilter, Elements = (NodeElementArchetype[])Elements?.Clone(), TryParseText = TryParseText, }; diff --git a/Source/Editor/Surface/ParticleEmitterSurface.cs b/Source/Editor/Surface/ParticleEmitterSurface.cs index 76f96f06c..b7cf83c62 100644 --- a/Source/Editor/Surface/ParticleEmitterSurface.cs +++ b/Source/Editor/Surface/ParticleEmitterSurface.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Surface } /// - protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId) + protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId) { groupId = 6; return Archetypes.Parameters.Nodes[1]; diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs index ff38b1aa8..aad45190e 100644 --- a/Source/Editor/Surface/SurfaceComment.cs +++ b/Source/Editor/Surface/SurfaceComment.cs @@ -2,6 +2,7 @@ using System; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEngine.GUI; @@ -52,6 +53,12 @@ namespace FlaxEditor.Surface set => SetValue(2, value, false); } + private int OrderValue + { + get => (int)Values[3]; + set => SetValue(3, value, false); + } + /// public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) @@ -67,6 +74,19 @@ namespace FlaxEditor.Surface Title = TitleValue; Color = ColorValue; Size = SizeValue; + + // Order + // Backwards compatibility - When opening with an older version send the old comments to the back + if (Values.Length < 4) + { + if (IndexInParent > 0) + IndexInParent = 0; + OrderValue = IndexInParent; + } + else if(OrderValue != -1) + { + IndexInParent = OrderValue; + } } /// @@ -76,6 +96,10 @@ namespace FlaxEditor.Surface // Randomize color Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f); + + if(OrderValue == -1) + OrderValue = Context.CommentCount - 1; + IndexInParent = OrderValue; } /// @@ -314,5 +338,38 @@ namespace FlaxEditor.Surface Color = ColorValue = color; Surface.MarkAsEdited(false); } + + /// + public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location) + { + base.OnShowSecondaryContextMenu(menu, location); + + menu.AddSeparator(); + ContextMenuChildMenu cmOrder = menu.AddChildMenu("Order"); + { + cmOrder.ContextMenu.AddButton("Bring Forward", () => + { + if(IndexInParent < Context.CommentCount-1) + IndexInParent++; + OrderValue = IndexInParent; + }); + cmOrder.ContextMenu.AddButton("Bring to Front", () => + { + IndexInParent = Context.CommentCount-1; + OrderValue = IndexInParent; + }); + cmOrder.ContextMenu.AddButton("Send Backward", () => + { + if(IndexInParent > 0) + IndexInParent--; + OrderValue = IndexInParent; + }); + cmOrder.ContextMenu.AddButton("Send to Back", () => + { + IndexInParent = 0; + OrderValue = IndexInParent; + }); + } + } } } diff --git a/Source/Editor/Surface/SurfaceParameter.cs b/Source/Editor/Surface/SurfaceParameter.cs index 6eafdc2aa..dae82e111 100644 --- a/Source/Editor/Surface/SurfaceParameter.cs +++ b/Source/Editor/Surface/SurfaceParameter.cs @@ -3,7 +3,6 @@ using System; using FlaxEditor.Scripting; using FlaxEngine; -using FlaxEngine.Utilities; namespace FlaxEditor.Surface { @@ -27,7 +26,7 @@ namespace FlaxEditor.Surface /// /// Parameter unique ID /// - public Guid ID; + public Guid ID = Guid.Empty; /// /// Parameter name @@ -49,23 +48,5 @@ namespace FlaxEditor.Surface /// [NoSerialize, HideInEditor] public readonly SurfaceMeta Meta = new SurfaceMeta(); - - /// - /// Creates the new parameter of the given type. - /// - /// The type. - /// The name. - /// The created parameter. - public static SurfaceParameter Create(ScriptType type, string name) - { - return new SurfaceParameter - { - ID = Guid.NewGuid(), - IsPublic = true, - Name = name, - Type = type, - Value = TypeUtils.GetDefaultValue(type), - }; - } } } diff --git a/Source/Editor/Surface/VisjectSurface.DragDrop.cs b/Source/Editor/Surface/VisjectSurface.DragDrop.cs index 1728c282f..2ff82c269 100644 --- a/Source/Editor/Surface/VisjectSurface.DragDrop.cs +++ b/Source/Editor/Surface/VisjectSurface.DragDrop.cs @@ -151,7 +151,7 @@ namespace FlaxEditor.Surface /// /// The group ID. /// The node archetype. - protected virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId) + protected internal virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId) { groupId = 6; return Archetypes.Parameters.Nodes[0]; diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 3fe722091..0185e364a 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -716,7 +716,18 @@ namespace FlaxEditor.Surface return null; Rectangle surfaceArea = GetNodesBounds(selection).MakeExpanded(80.0f); - return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f)); + // Order below other selected comments + bool hasCommentsSelected = false; + int lowestCommentOrder = int.MaxValue; + for (int i = 0; i < selection.Count; i++) + { + if (selection[i] is not SurfaceComment || selection[i].IndexInParent >= lowestCommentOrder) + continue; + hasCommentsSelected = true; + lowestCommentOrder = selection[i].IndexInParent; + } + + return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f), hasCommentsSelected ? lowestCommentOrder : -1); } private static Rectangle GetNodesBounds(List nodes) diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs index 7d75e006b..12f01d4f1 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs @@ -920,12 +920,6 @@ namespace FlaxEditor.Surface // Link control control.OnLoaded(action); control.Parent = RootControl; - - if (control is SurfaceComment) - { - // Move comments to the background - control.IndexInParent = 0; - } } /// diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 36e811c48..0886996b6 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -85,6 +85,27 @@ namespace FlaxEditor.Surface } } + /// + /// Gets the amount of surface comments + /// + /// + /// This is used as an alternative to , if only the amount of comments is important. + /// Is faster and doesn't allocate as much memory + /// + public int CommentCount + { + get + { + int count = 0; + for (int i = 0; i < RootControl.Children.Count; i++) + { + if (RootControl.Children[i] is SurfaceComment) + count++; + } + return count; + } + } + /// /// Gets a value indicating whether this context is modified (needs saving and flushing with surface data context source). /// @@ -285,14 +306,16 @@ namespace FlaxEditor.Surface /// The surface area to create comment. /// The comment title. /// The comment color. + /// The comment order or -1 to use default. /// The comment object - public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color) + public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1) { var values = new object[] { title, // Title color, // Color surfaceArea.Size, // Size + customOrder, // Order }; return (SurfaceComment)SpawnNode(7, 11, surfaceArea.Location, values); } @@ -303,11 +326,12 @@ namespace FlaxEditor.Surface /// The surface area to create comment. /// The comment title. /// The comment color. + /// The comment order or -1 to use default. /// The comment object - public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color) + public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1) { // Create comment - var comment = SpawnComment(ref surfaceArea, title, color); + var comment = SpawnComment(ref surfaceArea, title, color, customOrder); if (comment == null) { Editor.LogWarning("Failed to create comment."); diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index 305121e11..cce86d5c0 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -17,6 +17,7 @@ using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Utilities; namespace FlaxEditor.Surface { @@ -258,6 +259,11 @@ namespace FlaxEditor.Surface /// public IVisjectSurfaceWindow Window; + /// + /// The identifier of the parameter. Empty to auto generate it. + /// + public Guid Id = Guid.NewGuid(); + /// /// True if adding, false if removing parameter. /// @@ -278,6 +284,11 @@ namespace FlaxEditor.Surface /// public ScriptType Type; + /// + /// The value to initialize the parameter with. Can be null to use default one for the parameter type. + /// + public object InitValue; + /// public string ActionString => IsAdd ? "Add parameter" : "Remove parameter"; @@ -304,7 +315,14 @@ namespace FlaxEditor.Surface var type = Type; if (IsAdd && type.Type == typeof(NormalMap)) type = new ScriptType(typeof(Texture)); - var param = SurfaceParameter.Create(type, Name); + var param = new SurfaceParameter + { + ID = Id, + IsPublic = true, + Name = Name, + Type = type, + Value = InitValue ?? TypeUtils.GetDefaultValue(type), + }; if (IsAdd && Type.Type == typeof(NormalMap)) param.Value = FlaxEngine.Content.LoadAsyncInternal("Engine/Textures/NormalTexture"); Window.VisjectSurface.Parameters.Insert(Index, param); @@ -725,6 +743,8 @@ namespace FlaxEditor.Surface protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new FlaxEditor.Undo(); _undo.UndoDone += OnUndoRedo; @@ -775,10 +795,10 @@ namespace FlaxEditor.Surface // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)"); + _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})"); _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph"); // Setup input actions @@ -1058,7 +1078,6 @@ namespace FlaxEditor.Surface public virtual void OnParamRemoveUndo() { _refreshPropertiesOnLoad = true; - //_propertiesEditor.BuildLayoutOnUpdate(); _propertiesEditor.BuildLayout(); } diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 902582311..2f8d4cda8 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Surface } /// - protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId) + protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId) { groupId = 6; return Archetypes.Parameters.Nodes[2]; diff --git a/Source/Editor/Tools/Terrain/EditTab.cs b/Source/Editor/Tools/Terrain/EditTab.cs index 008be64f4..d52c1ae1d 100644 --- a/Source/Editor/Tools/Terrain/EditTab.cs +++ b/Source/Editor/Tools/Terrain/EditTab.cs @@ -290,7 +290,7 @@ namespace FlaxEditor.Tools.Terrain var patchCoord = Gizmo.SelectedPatchCoord; var chunkCoord = Gizmo.SelectedChunkCoord; - var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.SelectedAsset as MaterialBase); + var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.Validator.SelectedAsset as MaterialBase); action.Do(); CarveTab.Editor.Undo.AddAction(action); } @@ -336,12 +336,12 @@ namespace FlaxEditor.Tools.Terrain _isUpdatingUI = true; if (terrain.HasPatch(ref patchCoord)) { - _chunkOverrideMaterial.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord); + _chunkOverrideMaterial.Validator.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord); _chunkOverrideMaterial.Enabled = true; } else { - _chunkOverrideMaterial.SelectedAsset = null; + _chunkOverrideMaterial.Validator.SelectedAsset = null; _chunkOverrideMaterial.Enabled = false; } _isUpdatingUI = false; diff --git a/Source/Editor/Windows/AssetReferencesGraphWindow.cs b/Source/Editor/Windows/AssetReferencesGraphWindow.cs index d49896e2e..b9e0e7257 100644 --- a/Source/Editor/Windows/AssetReferencesGraphWindow.cs +++ b/Source/Editor/Windows/AssetReferencesGraphWindow.cs @@ -46,14 +46,14 @@ namespace FlaxEditor.Windows if (asset != null) { var path = asset.Path; - picker.SelectedAsset = asset; + picker.Validator.SelectedAsset = asset; Title = System.IO.Path.GetFileNameWithoutExtension(path); TooltipText = asset.TypeName + '\n' + path; } else { - picker.SelectedID = AssetId; - var assetItem = picker.SelectedItem as AssetItem; + picker.Validator.SelectedID = AssetId; + var assetItem = picker.Validator.SelectedItem as AssetItem; if (assetItem != null) { Title = assetItem.ShortName; diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index a765c2faa..8f88d93f6 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -230,6 +230,8 @@ namespace FlaxEditor.Windows.Assets public AnimationWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -265,8 +267,8 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/animation/index.html")).LinkTooltip("See documentation to learn more"); diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 8cbf6cf75..01ee9cb8a 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -130,6 +130,8 @@ namespace FlaxEditor.Windows.Assets public BehaviorTreeWindow(Editor editor, BinaryAssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -172,10 +174,10 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)"); + _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})"); _toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/ai/behavior-trees/index.html")).LinkTooltip("See documentation to learn more"); diff --git a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs index 623c4ef5b..a8121162a 100644 --- a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs +++ b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs @@ -395,6 +395,8 @@ namespace FlaxEditor.Windows.Assets public GameplayGlobalsWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + _undo = new Undo(); _undo.ActionDone += OnUndo; _undo.UndoDone += OnUndo; @@ -411,10 +413,10 @@ namespace FlaxEditor.Windows.Assets _proxy = new PropertiesProxy(); _propertiesEditor.Select(_proxy); - _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save asset"); + _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip($"Save asset ({inputOptions.Save})"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); _resetButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Rotate32, Reset).LinkTooltip("Resets the variables values to the default values"); diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index 097d4992a..a1178fb68 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -34,6 +34,8 @@ namespace FlaxEditor.Windows.Assets public JsonAssetWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -43,8 +45,8 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); // Panel var panel = new Panel(ScrollBars.Vertical) diff --git a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs index 0de5ce315..85f351fef 100644 --- a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs +++ b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs @@ -126,6 +126,8 @@ namespace FlaxEditor.Windows.Assets public LocalizedStringTableWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -135,8 +137,8 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); _toolstrip.AddButton(Editor.Icons.Up64, OnExport).LinkTooltip("Export localization table entries for translation to .pot file"); diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index 3e0525fb7..775e0c0dc 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -375,6 +375,8 @@ namespace FlaxEditor.Windows.Assets public MaterialInstanceWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -384,8 +386,8 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); _toolstrip.AddButton(Editor.Icons.Rotate64, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values"); _toolstrip.AddSeparator(); diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index 6fbe32d7e..5aa77dbc3 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -41,8 +41,6 @@ namespace FlaxEditor.Windows.Assets new ScriptType(typeof(Vector3)), new ScriptType(typeof(Vector4)), new ScriptType(typeof(Color)), - new ScriptType(typeof(Quaternion)), - new ScriptType(typeof(Transform)), new ScriptType(typeof(Matrix)), }; diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 17eda1358..4a5e02e6e 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -306,6 +306,8 @@ namespace FlaxEditor.Windows.Assets public ParticleSystemWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -359,8 +361,8 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 70aa1dca3..a8d9ae1be 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -339,7 +339,7 @@ namespace FlaxEditor.Windows.Assets { if (selection.Count != 0) Select(actor); - actor.TreeNode.StartRenaming(this); + actor.TreeNode.StartRenaming(this, _treePanel); } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 16f7c21c6..f50a832a1 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -94,6 +94,8 @@ namespace FlaxEditor.Windows.Assets public PrefabWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoEvent; @@ -176,12 +178,12 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); - _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)"); - _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); - _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); + _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip($"Change Gizmo tool mode to Translate ({inputOptions.TranslateMode})"); + _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip($"Change Gizmo tool mode to Rotate ({inputOptions.RotateMode})"); + _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})"); _toolstrip.AddSeparator(); _toolStripLiveReload = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, () => LiveReload = !LiveReload).SetChecked(true).SetAutoCheck(true).LinkTooltip("Live changes preview (applies prefab changes on modification by auto)"); diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index 162944144..05435cc77 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -627,6 +627,8 @@ namespace FlaxEditor.Windows.Assets public SceneAnimationWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -652,8 +654,8 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); _previewButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, OnPreviewButtonClicked).SetAutoCheck(true).LinkTooltip("If checked, enables live-preview of the animation on a scene while editing"); _renderButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Build64, OnRenderButtonClicked).LinkTooltip("Open the scene animation rendering utility..."); diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index d8790172b..ccfd5233c 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -837,7 +837,7 @@ namespace FlaxEditor.Windows.Assets sourceAssetPicker.CheckValid = CheckSourceAssetValid; sourceAssetPicker.SelectedItemChanged += () => { - proxy.Setups.Add(sourceAssetPicker.SelectedAsset, new SetupProxy()); + proxy.Setups.Add(sourceAssetPicker.Validator.SelectedAsset, new SetupProxy()); proxy.Window.MarkAsEdited(); RebuildLayout(); }; @@ -856,7 +856,7 @@ namespace FlaxEditor.Windows.Assets // Source asset picker var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom().CustomControl; - sourceAssetPicker.SelectedAsset = sourceAsset; + sourceAssetPicker.Validator.SelectedAsset = sourceAsset; sourceAssetPicker.CanEdit = false; sourceAssetPicker.Height = 48; @@ -916,12 +916,12 @@ namespace FlaxEditor.Windows.Assets { // Show skeleton asset picker var sourceSkeletonPicker = setupGroup.AddPropertyItem("Skeleton", "Skinned model that contains a skeleton for this animation retargeting.").Custom().CustomControl; - sourceSkeletonPicker.AssetType = new ScriptType(typeof(SkinnedModel)); - sourceSkeletonPicker.SelectedAsset = setup.Value.Skeleton; + sourceSkeletonPicker.Validator.AssetType = new ScriptType(typeof(SkinnedModel)); + sourceSkeletonPicker.Validator.SelectedAsset = setup.Value.Skeleton; sourceSkeletonPicker.Height = 48; sourceSkeletonPicker.SelectedItemChanged += () => { - setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.SelectedAsset; + setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.Validator.SelectedAsset; proxy.Window.MarkAsEdited(); }; } diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs index 7ae6ae8be..e3575d0e8 100644 --- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs +++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs @@ -61,6 +61,8 @@ namespace FlaxEditor.Windows.Assets protected VisjectFunctionSurfaceWindow(Editor editor, AssetItem item) : base(editor, item) { + var inputOptions = Editor.Options.Options.Input; + // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; @@ -70,10 +72,10 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)"); + _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})"); _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph"); // Panel diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 584f1c307..79157f850 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -561,6 +561,7 @@ namespace FlaxEditor.Windows.Assets : base(editor, item) { var isPlayMode = Editor.IsPlayMode; + var inputOptions = Editor.Options.Options.Input; // Undo _undo = new Undo(); @@ -598,21 +599,21 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); - _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); _toolstrip.AddSeparator(); - _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)"); + _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})"); _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph"); _toolstrip.AddSeparator(); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more"); _debugToolstripControls = new[] { _toolstrip.AddSeparator(), - _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"), + _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"), _toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"), - _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip("Step Over (F10)"), - _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip("Step Into (F11)"), - _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip("Step Out (Shift+F11)"), + _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip($"Step Over ({inputOptions.DebuggerStepOver})"), + _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip($"Step Into ({inputOptions.DebuggerStepInto})"), + _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip($"Step Out ({inputOptions.DebuggerStepOut})"), _toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"), }; foreach (var control in _debugToolstripControls) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 1071e6b01..168067977 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -187,12 +187,12 @@ namespace FlaxEditor.Windows continue; // Get context proxy - ContentProxy p; + ContentProxy p = null; if (type.Type.IsSubclassOf(typeof(ContentProxy))) { p = Editor.ContentDatabase.Proxy.Find(x => x.GetType() == type.Type); } - else + else if (type.CanCreateInstance) { // User can use attribute to put their own assets into the content context menu var generic = typeof(SpawnableJsonAssetProxy<>).MakeGenericType(type.Type); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index fdedbb3c2..8c6f5dc06 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -271,8 +271,6 @@ namespace FlaxEditor.Windows Title = "Game"; AutoFocus = true; - FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); - var task = MainRenderTask.Instance; // Setup viewport @@ -304,6 +302,12 @@ namespace FlaxEditor.Windows // Link editor options Editor.Options.OptionsChanged += OnOptionsChanged; OnOptionsChanged(Editor.Options.Options); + + InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty)); + InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay); + InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; }); + + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } private void ChangeViewportRatio(ViewportScaleOptions v) @@ -945,27 +949,6 @@ namespace FlaxEditor.Windows /// public override bool OnKeyDown(KeyboardKeys key) { - switch (key) - { - case KeyboardKeys.F12: - Screenshot.Capture(string.Empty); - return true; - case KeyboardKeys.F11: - if (Root.GetKey(KeyboardKeys.Shift)) - { - // Unlock mouse in game mode - UnlockMouseInPlay(); - return true; - } - else if (Editor.IsPlayMode) - { - // Maximized game window toggle - IsMaximized = !IsMaximized; - return true; - } - break; - } - // Prevent closing the game window tab during a play session if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key)) { diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 3665b7073..6526d7c8a 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -467,6 +467,7 @@ namespace FlaxEditor.Windows if (_isDirty) { _isDirty = false; + var wasEmpty = _output.TextLength == 0; // Cache fonts _output.DefaultStyle.Font.GetFont(); @@ -589,7 +590,7 @@ namespace FlaxEditor.Windows // Update the output var cachedScrollValue = _vScroll.Value; var cachedSelection = _output.SelectionRange; - var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f; + var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f || wasEmpty; _output.Text = _textBuffer.ToString(); _textBufferCount = _entries.Count; if (!_vScroll.IsThumbClicked) diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index cbaa27371..250bd2301 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -142,7 +142,7 @@ namespace FlaxEditor.Windows { if (selection.Count != 0) Editor.SceneEditing.Select(actor); - actor.TreeNode.StartRenaming(this); + actor.TreeNode.StartRenaming(this, _sceneTreePanel); } } diff --git a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs index c84a9ba33..57f10f2f4 100644 --- a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs +++ b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs @@ -399,6 +399,8 @@ namespace FlaxEditor.Windows { Title = "Visual Script Debugger"; + var inputOptions = editor.Options.Options.Input; + var toolstrip = new ToolStrip { Parent = this @@ -407,7 +409,7 @@ namespace FlaxEditor.Windows _debugToolstripControls = new[] { toolstrip.AddSeparator(), - toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"), + toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"), toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"), toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"), }; diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 0518fe248..5cceb31e3 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -2109,7 +2109,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.LoopsLeft--; bucket.LoopsDone++; } - value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed); + // Speed is accounted for in the new time pos, so keep sample speed at 1 + value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1); bucket.TimePosition = newTimePos; if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition) { diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 613c7c2c2..afe38f0a7 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -16,11 +16,13 @@ AssetReferenceBase::~AssetReferenceBase() { - if (_asset) + Asset* asset = _asset; + if (asset) { - _asset->OnLoaded.Unbind(this); - _asset->OnUnloaded.Unbind(this); - _asset->RemoveReference(); + _asset = nullptr; + asset->OnLoaded.Unbind(this); + asset->OnUnloaded.Unbind(this); + asset->RemoveReference(); } } @@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset) WeakAssetReferenceBase::~WeakAssetReferenceBase() { - if (_asset) - _asset->OnUnloaded.Unbind(this); + Asset* asset = _asset; + if (asset) + { + _asset = nullptr; + asset->OnUnloaded.Unbind(this); + } } String WeakAssetReferenceBase::ToString() const @@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset) _asset = nullptr; } +SoftAssetReferenceBase::~SoftAssetReferenceBase() +{ + Asset* asset = _asset; + if (asset) + { + _asset = nullptr; + asset->OnUnloaded.Unbind(this); + asset->RemoveReference(); + } +#if !BUILD_RELEASE + _id = Guid::Empty; +#endif +} + String SoftAssetReferenceBase::ToString() const { return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("")); diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index 6aee12246..b9d54f30a 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -9,9 +9,6 @@ /// class FLAXENGINE_API AssetReferenceBase { -public: - typedef Delegate<> EventType; - protected: Asset* _asset = nullptr; @@ -19,17 +16,17 @@ public: /// /// The asset loaded event (fired when asset gets loaded or is already loaded after change). /// - EventType Loaded; + Action Loaded; /// /// The asset unloading event (should cleanup refs to it). /// - EventType Unload; + Action Unload; /// /// Action fired when field gets changed (link a new asset or change to the another value). /// - EventType Changed; + Action Changed; public: NON_COPYABLE(AssetReferenceBase); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 105d4ad2d..9748ba60c 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1428,6 +1428,10 @@ Asset::LoadResult VisualScript::load() #if USE_EDITOR if (_instances.HasItems()) { + // Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use + _loadFailed = false; + _isLoaded = true; + // Setup scripting type CacheScriptingType(); @@ -1512,7 +1516,7 @@ void VisualScript::unload(bool isReloading) // Note: preserve the registered scripting type but invalidate the locally cached handle if (_scriptingTypeHandle) { - VisualScriptingModule.Locker.Lock(); + VisualScriptingBinaryModule::Locker.Lock(); auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex]; if (type.Script.DefaultInstance) { @@ -1523,7 +1527,7 @@ void VisualScript::unload(bool isReloading) VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr; _scriptingTypeHandleCached = _scriptingTypeHandle; _scriptingTypeHandle = ScriptingTypeHandle(); - VisualScriptingModule.Locker.Unlock(); + VisualScriptingBinaryModule::Locker.Unlock(); } } @@ -1534,8 +1538,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const void VisualScript::CacheScriptingType() { + ScopeLock lock(VisualScriptingBinaryModule::Locker); auto& binaryModule = VisualScriptingModule; - ScopeLock lock(binaryModule.Locker); // Find base type const StringAnsi baseTypename(Meta.BaseTypename); diff --git a/Source/Engine/Content/SoftAssetReference.h b/Source/Engine/Content/SoftAssetReference.h index fe1cde8c2..d237b5fd7 100644 --- a/Source/Engine/Content/SoftAssetReference.h +++ b/Source/Engine/Content/SoftAssetReference.h @@ -30,9 +30,7 @@ public: /// /// Finalizes an instance of the class. /// - ~SoftAssetReferenceBase() - { - } + ~SoftAssetReferenceBase(); public: /// diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h index 01238d434..eeadc82e9 100644 --- a/Source/Engine/Core/Collections/BitArray.h +++ b/Source/Engine/Core/Collections/BitArray.h @@ -22,6 +22,16 @@ private: int32 _capacity; AllocationData _allocation; + FORCE_INLINE static int32 ToItemCount(int32 size) + { + return Math::DivideAndRoundUp(size, sizeof(ItemType)); + } + + FORCE_INLINE static int32 ToItemCapacity(int32 size) + { + return Math::Max(Math::DivideAndRoundUp(size, sizeof(ItemType)), 1); + } + public: /// /// Initializes a new instance of the class. @@ -41,7 +51,7 @@ public: , _capacity(capacity) { if (capacity > 0) - _allocation.Allocate(Math::Max(capacity / sizeof(ItemType), 1)); + _allocation.Allocate(ToItemCapacity(capacity)); } /// @@ -53,7 +63,7 @@ public: _count = _capacity = other.Count(); if (_capacity > 0) { - const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1); + const int32 itemsCapacity = ToItemCapacity(_capacity); _allocation.Allocate(itemsCapacity); Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType)); } @@ -69,7 +79,7 @@ public: _count = _capacity = other.Count(); if (_capacity > 0) { - const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1); + const int32 itemsCapacity = ToItemCapacity(_capacity); _allocation.Allocate(itemsCapacity); Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType)); } @@ -101,7 +111,7 @@ public: { _allocation.Free(); _capacity = other._count; - const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1); + const int32 itemsCapacity = ToItemCapacity(_capacity); _allocation.Allocate(itemsCapacity); Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType)); } @@ -246,7 +256,7 @@ public: return; ASSERT(capacity >= 0); const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0; - _allocation.Relocate(Math::Max(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType)); + _allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count)); _capacity = capacity; _count = count; } @@ -272,7 +282,7 @@ public: { if (_capacity < minCapacity) { - const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max(_capacity / sizeof(ItemType), 1), minCapacity); + const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity); SetCapacity(capacity, preserveContents); } } @@ -284,7 +294,7 @@ public: void SetAll(const bool value) { if (_count != 0) - Platform::MemorySet(_allocation.Get(), Math::Max(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0); + Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0); } /// diff --git a/Source/Engine/Core/Collections/Config.h b/Source/Engine/Core/Collections/Config.h index 792ae57c8..ce7656dcd 100644 --- a/Source/Engine/Core/Collections/Config.h +++ b/Source/Engine/Core/Collections/Config.h @@ -2,13 +2,26 @@ #pragma once -/// -/// Default capacity for the dictionaries (amount of space for the elements) -/// -#define DICTIONARY_DEFAULT_CAPACITY 256 +#include "Engine/Platform/Defines.h" /// -/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size) +/// Default capacity for the dictionaries (amount of space for the elements). +/// +#ifndef DICTIONARY_DEFAULT_CAPACITY +#if PLATFORM_DESKTOP +#define DICTIONARY_DEFAULT_CAPACITY 256 +#else +#define DICTIONARY_DEFAULT_CAPACITY 64 +#endif +#endif + +/// +/// Default slack space divider for the dictionaries. +/// +#define DICTIONARY_DEFAULT_SLACK_SCALE 3 + +/// +/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size). /// #define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks) //#define DICTIONARY_PROB_FUNC(size, numChecks) (1) diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 575863dc9..4d65a4123 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -40,7 +40,7 @@ public: private: State _state; - void Free() + FORCE_INLINE void Free() { if (_state == Occupied) { @@ -50,7 +50,7 @@ public: _state = Empty; } - void Delete() + FORCE_INLINE void Delete() { _state = Deleted; Memory::DestructItem(&Key); @@ -58,7 +58,7 @@ public: } template - void Occupy(const KeyComparableType& key) + FORCE_INLINE void Occupy(const KeyComparableType& key) { Memory::ConstructItems(&Key, &key, 1); Memory::ConstructItem(&Value); @@ -66,7 +66,7 @@ public: } template - void Occupy(const KeyComparableType& key, const ValueType& value) + FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value) { Memory::ConstructItems(&Key, &key, 1); Memory::ConstructItems(&Value, &value, 1); @@ -74,7 +74,7 @@ public: } template - void Occupy(const KeyComparableType& key, ValueType&& value) + FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value) { Memory::ConstructItems(&Key, &key, 1); Memory::MoveItems(&Value, &value, 1); @@ -132,9 +132,6 @@ public: /// /// The other collection to move. Dictionary(Dictionary&& other) noexcept - : _elementsCount(other._elementsCount) - , _deletedCount(other._deletedCount) - , _size(other._size) { _elementsCount = other._elementsCount; _deletedCount = other._deletedCount; @@ -375,8 +372,12 @@ public: template ValueType& At(const KeyComparableType& key) { + // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) + if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) + Compact(); + // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + _deletedCount + 1); + EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount); // Find location of the item or place to insert it FindPositionResult pos; @@ -388,9 +389,9 @@ public: // Insert ASSERT(pos.FreeSlotIndex != -1); + _elementsCount++; Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; bucket.Occupy(key); - _elementsCount++; return bucket.Value; } @@ -493,7 +494,7 @@ public: for (Iterator i = Begin(); i.IsNotEnd(); ++i) { if (i->Value) - Delete(i->Value); + ::Delete(i->Value); } Clear(); } @@ -533,13 +534,22 @@ public: } _size = capacity; Bucket* oldData = oldAllocation.Get(); - if (oldElementsCount != 0 && preserveContents) + if (oldElementsCount != 0 && capacity != 0 && preserveContents) { - // TODO; move keys and values on realloc + FindPositionResult pos; for (int32 i = 0; i < oldSize; i++) { - if (oldData[i].IsOccupied()) - Add(oldData[i].Key, MoveTemp(oldData[i].Value)); + Bucket& oldBucket = oldData[i]; + if (oldBucket.IsOccupied()) + { + FindPosition(oldBucket.Key, pos); + ASSERT(pos.FreeSlotIndex != -1); + Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; + Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1); + Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1); + bucket->_state = Bucket::Occupied; + _elementsCount++; + } } } if (oldElementsCount != 0) @@ -558,9 +568,9 @@ public: { if (_size >= minCapacity) return; - if (minCapacity < DICTIONARY_DEFAULT_CAPACITY) - minCapacity = DICTIONARY_DEFAULT_CAPACITY; - const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + if (capacity < DICTIONARY_DEFAULT_CAPACITY) + capacity = DICTIONARY_DEFAULT_CAPACITY; SetCapacity(capacity, preserveContents); } @@ -584,24 +594,10 @@ public: /// The value. /// Weak reference to the stored bucket. template - Bucket* Add(const KeyComparableType& key, const ValueType& value) + FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value) { - // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + _deletedCount + 1); - - // Find location of the item or place to insert it - FindPositionResult pos; - FindPosition(key, pos); - - // Ensure key is unknown - ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary."); - - // Insert - ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; + Bucket* bucket = OnAdd(key); bucket->Occupy(key, value); - _elementsCount++; - return bucket; } @@ -612,24 +608,10 @@ public: /// The value. /// Weak reference to the stored bucket. template - Bucket* Add(const KeyComparableType& key, ValueType&& value) + FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value) { - // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + _deletedCount + 1); - - // Find location of the item or place to insert it - FindPositionResult pos; - FindPosition(key, pos); - - // Ensure key is unknown - ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary."); - - // Insert - ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; + Bucket* bucket = OnAdd(key); bucket->Occupy(key, MoveTemp(value)); - _elementsCount++; - return bucket; } @@ -851,7 +833,7 @@ public: return Iterator(this, _size); } -protected: +private: /// /// The result container of the dictionary item lookup searching. /// @@ -911,4 +893,66 @@ protected: result.ObjectIndex = -1; result.FreeSlotIndex = insertPos; } + + template + Bucket* OnAdd(const KeyComparableType& key) + { + // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) + if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) + Compact(); + + // Ensure to have enough memory for the next item (in case of new element insertion) + EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount); + + // Find location of the item or place to insert it + FindPositionResult pos; + FindPosition(key, pos); + + // Ensure key is unknown + ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary."); + + // Insert + ASSERT(pos.FreeSlotIndex != -1); + _elementsCount++; + return &_allocation.Get()[pos.FreeSlotIndex]; + } + + void Compact() + { + if (_elementsCount == 0) + { + // Fast path if it's empty + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i]._state = Bucket::Empty; + } + else + { + // Rebuild entire table completely + AllocationData oldAllocation; + oldAllocation.Swap(_allocation); + _allocation.Allocate(_size); + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i]._state = Bucket::Empty; + Bucket* oldData = oldAllocation.Get(); + FindPositionResult pos; + for (int32 i = 0; i < _size; i++) + { + Bucket& oldBucket = oldData[i]; + if (oldBucket.IsOccupied()) + { + FindPosition(oldBucket.Key, pos); + ASSERT(pos.FreeSlotIndex != -1); + Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; + Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1); + Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1); + bucket->_state = Bucket::Occupied; + } + } + for (int32 i = 0; i < _size; i++) + oldData[i].Free(); + } + _deletedCount = 0; + } }; diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index fba8ca823..4a4f3e924 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -37,26 +37,33 @@ public: private: State _state; - void Free() + FORCE_INLINE void Free() { if (_state == Occupied) Memory::DestructItem(&Item); _state = Empty; } - void Delete() + FORCE_INLINE void Delete() { _state = Deleted; Memory::DestructItem(&Item); } template - void Occupy(const ItemType& item) + FORCE_INLINE void Occupy(const ItemType& item) { Memory::ConstructItems(&Item, &item, 1); _state = Occupied; } + template + FORCE_INLINE void Occupy(ItemType& item) + { + Memory::MoveItems(&Item, &item, 1); + _state = Occupied; + } + FORCE_INLINE bool IsEmpty() const { return _state == Empty; @@ -82,6 +89,7 @@ public: private: int32 _elementsCount = 0; + int32 _deletedCount = 0; int32 _size = 0; AllocationData _allocation; @@ -107,12 +115,12 @@ public: /// /// The other collection to move. HashSet(HashSet&& other) noexcept - : _elementsCount(other._elementsCount) - , _size(other._size) { _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; _size = other._size; other._elementsCount = 0; + other._deletedCount = 0; other._size = 0; _allocation.Swap(other._allocation); } @@ -150,8 +158,10 @@ public: Clear(); _allocation.Free(); _elementsCount = other._elementsCount; + _deletedCount = other._deletedCount; _size = other._size; other._elementsCount = 0; + other._deletedCount = 0; other._size = 0; _allocation.Swap(other._allocation); } @@ -163,7 +173,7 @@ public: /// ~HashSet() { - SetCapacity(0, false); + Clear(); } public: @@ -210,6 +220,7 @@ public: HashSet* _collection; int32 _index; + public: Iterator(HashSet* collection, const int32 index) : _collection(collection) , _index(index) @@ -222,7 +233,12 @@ public: { } - public: + Iterator() + : _collection(nullptr) + , _index(-1) + { + } + Iterator(const Iterator& i) : _collection(i._collection) , _index(i._index) @@ -236,6 +252,11 @@ public: } public: + FORCE_INLINE int32 Index() const + { + return _index; + } + FORCE_INLINE bool IsEnd() const { return _index == _collection->_size; @@ -331,12 +352,12 @@ public: /// void Clear() { - if (_elementsCount != 0) + if (_elementsCount + _deletedCount != 0) { Bucket* data = _allocation.Get(); for (int32 i = 0; i < _size; i++) data[i].Free(); - _elementsCount = 0; + _elementsCount = _deletedCount = 0; } } @@ -371,7 +392,7 @@ public: oldAllocation.Swap(_allocation); const int32 oldSize = _size; const int32 oldElementsCount = _elementsCount; - _elementsCount = 0; + _deletedCount = _elementsCount = 0; if (capacity != 0 && (capacity & (capacity - 1)) != 0) { // Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) @@ -392,13 +413,21 @@ public: } _size = capacity; Bucket* oldData = oldAllocation.Get(); - if (oldElementsCount != 0 && preserveContents) + if (oldElementsCount != 0 && capacity != 0 && preserveContents) { - // TODO; move keys and values on realloc + FindPositionResult pos; for (int32 i = 0; i < oldSize; i++) { - if (oldData[i].IsOccupied()) - Add(oldData[i].Item); + Bucket& oldBucket = oldData[i]; + if (oldBucket.IsOccupied()) + { + FindPosition(oldBucket.Item, pos); + ASSERT(pos.FreeSlotIndex != -1); + Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; + Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1); + bucket->_state = Bucket::Occupied; + _elementsCount++; + } } } if (oldElementsCount != 0) @@ -415,14 +444,26 @@ public: /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. void EnsureCapacity(int32 minCapacity, bool preserveContents = true) { - if (Capacity() >= minCapacity) + if (_size >= minCapacity) return; - if (minCapacity < DICTIONARY_DEFAULT_CAPACITY) - minCapacity = DICTIONARY_DEFAULT_CAPACITY; - const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity); + if (capacity < DICTIONARY_DEFAULT_CAPACITY) + capacity = DICTIONARY_DEFAULT_CAPACITY; SetCapacity(capacity, preserveContents); } + /// + /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange. + /// + /// The other collection. + void Swap(HashSet& other) + { + ::Swap(_elementsCount, other._elementsCount); + ::Swap(_deletedCount, other._deletedCount); + ::Swap(_size, other._size); + _allocation.Swap(other._allocation); + } + public: /// /// Add element to the collection. @@ -432,24 +473,23 @@ public: template bool Add(const ItemType& item) { - // Ensure to have enough memory for the next item (in case of new element insertion) - EnsureCapacity(_elementsCount + 1); + Bucket* bucket = OnAdd(item); + if (bucket) + bucket->Occupy(item); + return bucket != nullptr; + } - // Find location of the item or place to insert it - FindPositionResult pos; - FindPosition(item, pos); - - // Check if object has been already added - if (pos.ObjectIndex != -1) - return false; - - // Insert - ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - bucket->Occupy(item); - _elementsCount++; - - return true; + /// + /// Add element to the collection. + /// + /// The element to add to the set. + /// True if element has been added to the collection, otherwise false if the element is already present. + bool Add(T&& item) + { + Bucket* bucket = OnAdd(item); + if (bucket) + bucket->Occupy(MoveTemp(item)); + return bucket != nullptr; } /// @@ -479,6 +519,7 @@ public: { _allocation.Get()[pos.ObjectIndex].Delete(); _elementsCount--; + _deletedCount++; return true; } return false; @@ -497,6 +538,7 @@ public: ASSERT(_allocation.Get()[i._index].IsOccupied()); _allocation.Get()[i._index].Delete(); _elementsCount--; + _deletedCount++; return true; } return false; @@ -585,7 +627,7 @@ public: return Iterator(this, _size); } -protected: +private: /// /// The result container of the set item lookup searching. /// @@ -646,4 +688,66 @@ protected: result.ObjectIndex = -1; result.FreeSlotIndex = insertPos; } + + template + Bucket* OnAdd(const ItemType& key) + { + // Check if need to rehash elements (prevent many deleted elements that use too much of capacity) + if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE) + Compact(); + + // Ensure to have enough memory for the next item (in case of new element insertion) + EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount); + + // Find location of the item or place to insert it + FindPositionResult pos; + FindPosition(key, pos); + + // Check if object has been already added + if (pos.ObjectIndex != -1) + return nullptr; + + // Insert + ASSERT(pos.FreeSlotIndex != -1); + _elementsCount++; + return &_allocation.Get()[pos.FreeSlotIndex]; + } + + void Compact() + { + if (_elementsCount == 0) + { + // Fast path if it's empty + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i]._state = Bucket::Empty; + } + else + { + // Rebuild entire table completely + AllocationData oldAllocation; + oldAllocation.Swap(_allocation); + _allocation.Allocate(_size); + Bucket* data = _allocation.Get(); + for (int32 i = 0; i < _size; i++) + data[i]._state = Bucket::Empty; + Bucket* oldData = oldAllocation.Get(); + FindPositionResult pos; + for (int32 i = 0; i < _size; i++) + { + Bucket& oldBucket = oldData[i]; + if (oldBucket.IsOccupied()) + { + FindPosition(oldBucket.Item, pos); + ASSERT(pos.FreeSlotIndex != -1); + Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; + Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1); + bucket->_state = Bucket::Occupied; + } + } + for (int32 i = 0; i < _size; i++) + oldData[i].Free(); + } + _deletedCount = 0; + } }; diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h index 945dd6ce5..f0f8fdef0 100644 --- a/Source/Engine/Core/Delegate.h +++ b/Source/Engine/Core/Delegate.h @@ -226,7 +226,7 @@ public: /// Function result FORCE_INLINE ReturnType operator()(Params... params) const { - ASSERT(_function); + ASSERT_LOW_LAYER(_function); return _function(_callee, Forward(params)...); } @@ -289,8 +289,13 @@ protected: intptr volatile _ptr = 0; intptr volatile _size = 0; #else - HashSet* _functions = nullptr; - CriticalSection* _locker = nullptr; + struct Data + { + HashSet Functions; + CriticalSection Locker; + }; + // Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations. + intptr volatile _data = 0; #endif typedef void (*StubSignature)(void*, Params...); @@ -314,15 +319,12 @@ public: _ptr = (intptr)newBindings; _size = newSize; #else - if (other._functions == nullptr) + Data* otherData = (Data*)Platform::AtomicRead(&_data); + if (otherData == nullptr) return; - _functions = New>(*other._functions); - for (auto i = _functions->Begin(); i.IsNotEnd(); ++i) - { - if (i->Item._function && i->Item._lambda) - i->Item.LambdaCtor(); - } - _locker = other._locker; + ScopeLock lock(otherData->Locker); + for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i) + Bind(i->Item); #endif } @@ -334,10 +336,8 @@ public: other._ptr = 0; other._size = 0; #else - _functions = other._functions; - _locker = other._locker; - other._functions = nullptr; - other._locker = nullptr; + _data = other._data; + other._data = 0; #endif } @@ -356,20 +356,11 @@ public: Allocator::Free((void*)_ptr); } #else - if (_locker != nullptr) + Data* data = (Data*)_data; + if (data) { - Allocator::Free(_locker); - _locker = nullptr; - } - if (_functions != nullptr) - { - for (auto i = _functions->Begin(); i.IsNotEnd(); ++i) - { - if (i->Item._lambda) - i->Item.LambdaCtor(); - } - Allocator::Free(_functions); - _functions = nullptr; + _data = 0; + Delete(data); } #endif } @@ -385,8 +376,13 @@ public: for (intptr i = 0; i < size; i++) Bind(bindings[i]); #else - for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i) - Bind(i->Item); + Data* otherData = (Data*)Platform::AtomicRead(&_data); + if (otherData != nullptr) + { + ScopeLock lock(otherData->Locker); + for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i) + Bind(i->Item); + } #endif } return *this; @@ -402,10 +398,8 @@ public: other._ptr = 0; other._size = 0; #else - _functions = other._functions; - _locker = other._locker; - other._functions = nullptr; - other._locker = nullptr; + _data = other._data; + other._data = 0; #endif } return *this; @@ -507,12 +501,20 @@ public: Allocator::Free(bindings); } #else - if (_locker == nullptr) - _locker = New(); - ScopeLock lock(*_locker); - if (_functions == nullptr) - _functions = New>(); - _functions->Add(f); + Data* data = (Data*)Platform::AtomicRead(&_data); + while (!data) + { + Data* newData = New(); + Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data); + if (oldData != data) + { + // Other thread already set the new data so free it and try again + Delete(newData); + } + data = (Data*)Platform::AtomicRead(&_data); + } + ScopeLock lock(data->Locker); + data->Functions.Add(f); #endif } @@ -568,13 +570,22 @@ public: } } #else - if (_locker == nullptr) - _locker = New(); - ScopeLock lock(*_locker); - if (_functions && _functions->Contains(f)) - return; + Data* data = (Data*)Platform::AtomicRead(&_data); + if (data) + { + data->Locker.Lock(); + if (data->Functions.Contains(f)) + { + data->Locker.Unlock(); + return; + } + } #endif Bind(f); +#if !DELEGATE_USE_ATOMIC + if (data) + data->Locker.Unlock(); +#endif } /// @@ -640,10 +651,11 @@ public: Unbind(f); } #else - if (_functions == nullptr) + Data* data = (Data*)Platform::AtomicRead(&_data); + if (!data) return; - ScopeLock lock(*_locker); - _functions->Remove(f); + ScopeLock lock(data->Locker); + data->Functions.Remove(f); #endif } @@ -666,15 +678,11 @@ public: Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0); } #else - if (_functions == nullptr) + Data* data = (Data*)Platform::AtomicRead(&_data); + if (!data) return; - ScopeLock lock(*_locker); - for (auto i = _functions->Begin(); i.IsNotEnd(); ++i) - { - if (i->Item._lambda) - i->Item.LambdaDtor(); - } - _functions->Clear(); + ScopeLock lock(data->Locker); + data->Functions.Clear(); #endif } @@ -684,22 +692,24 @@ public: /// The bound functions count. int32 Count() const { + int32 result = 0; #if DELEGATE_USE_ATOMIC - int32 count = 0; const intptr size = Platform::AtomicRead((intptr volatile*)&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); for (intptr i = 0; i < size; i++) { if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0) - count++; + result++; } - return count; #else - if (_functions == nullptr) - return 0; - ScopeLock lock(*_locker); - return _functions->Count(); + Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); + if (data) + { + ScopeLock lock(data->Locker); + result = data->Functions.Count(); + } #endif + return result; } /// @@ -710,10 +720,14 @@ public: #if DELEGATE_USE_ATOMIC return (int32)Platform::AtomicRead((intptr volatile*)&_size); #else - if (_functions == nullptr) - return 0; - ScopeLock lock(*_locker); - return _functions->Capacity(); + int32 result = 0; + Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); + if (data) + { + ScopeLock lock(data->Locker); + result = data->Functions.Capacity(); + } + return result; #endif } @@ -733,10 +747,14 @@ public: } return false; #else - if (_functions == nullptr) - return false; - ScopeLock lock(*_locker); - return _functions->Count() > 0; + bool result = false; + Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); + if (data) + { + ScopeLock lock(data->Locker); + result = data->Functions.Count() != 0; + } + return result; #endif } @@ -765,18 +783,13 @@ public: } } #else - if (_functions == nullptr) - return 0; - ScopeLock lock(*_locker); - for (auto i = _functions->Begin(); i.IsNotEnd(); ++i) + Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); + if (data) { - if (i->Item._function != nullptr) + ScopeLock lock(data->Locker); + for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i) { - buffer[count]._function = (StubSignature)i->Item._function; - buffer[count]._callee = (void*)i->Item._callee; - buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda; - if (buffer[count]._lambda) - buffer[count].LambdaCtor(); + new(buffer + count) FunctionType((const FunctionType&)i->Item); count++; } } @@ -802,15 +815,15 @@ public: ++bindings; } #else - if (_functions == nullptr) + Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); + if (!data) return; - ScopeLock lock(*_locker); - for (auto i = _functions->Begin(); i.IsNotEnd(); ++i) + ScopeLock lock(data->Locker); + for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i) { - auto function = (StubSignature)(i->Item._function); - auto callee = (void*)(i->Item._callee); - if (function != nullptr) - function(callee, Forward(params)...); + const FunctionType& item = i->Item; + ASSERT_LOW_LAYER(item._function); + item._function(item._callee, Forward(params)...); } #endif } diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp index bdbe3037d..1ca05a9c3 100644 --- a/Source/Engine/Core/Math/Quaternion.cpp +++ b/Source/Engine/Core/Math/Quaternion.cpp @@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern v0.Normalize(); v1.Normalize(); - const float d = Float3::Dot(v0, v1); - // If dot == 1, vectors are the same + const float d = Float3::Dot(v0, v1); if (d >= 1.0f) { result = Identity; diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index ff50a98bf..34ee160f0 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -1077,6 +1077,115 @@ namespace FlaxEngine } } + /// + /// Gets the shortest arc quaternion to rotate this vector to the destination vector. + /// + /// The source vector. + /// The destination vector. + /// The result. + /// The fallback axis. + public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis) + { + // Based on Stan Melax's article in Game Programming Gems + + Float3 v0 = from; + Float3 v1 = to; + v0.Normalize(); + v1.Normalize(); + + // If dot == 1, vectors are the same + float d = Float3.Dot(ref v0, ref v1); + if (d >= 1.0f) + { + result = Identity; + return; + } + + if (d < 1e-6f - 1.0f) + { + if (fallbackAxis != Float3.Zero) + { + // Rotate 180 degrees about the fallback axis + RotationAxis(ref fallbackAxis, Mathf.Pi, out result); + } + else + { + // Generate an axis + Float3 axis = Float3.Cross(Float3.UnitX, from); + if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear + axis = Float3.Cross(Float3.UnitY, from); + axis.Normalize(); + RotationAxis(ref axis, Mathf.Pi, out result); + } + } + else + { + float s = Mathf.Sqrt((1 + d) * 2); + float invS = 1 / s; + Float3.Cross(ref v0, ref v1, out var c); + result.X = c.X * invS; + result.Y = c.Y * invS; + result.Z = c.Z * invS; + result.W = s * 0.5f; + result.Normalize(); + } + } + + /// + /// Gets the shortest arc quaternion to rotate this vector to the destination vector. + /// + /// The source vector. + /// The destination vector. + /// The fallback axis. + /// The rotation. + public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis) + { + GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis); + return result; + } + + /// + /// 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. + /// The result. + public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result) + { + // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final + float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared); + if (normFromNormTo < Mathf.Epsilon) + { + result = Identity; + return; + } + float w = normFromNormTo + Float3.Dot(from, to); + if (w < 1.0-6f * normFromNormTo) + { + result = Mathf.Abs(from.X) > Mathf.Abs(from.Z) + ? new Quaternion(-from.Y, from.X, 0.0f, 0.0f) + : new Quaternion(0.0f, -from.Z, from.Y, 0.0f); + } + else + { + Float3 cross = Float3.Cross(from, to); + result = new Quaternion(cross.X, cross.Y, cross.Z, w); + } + result.Normalize(); + } + + /// + /// 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. + /// The rotation. + public static Quaternion FindBetween(Float3 from, Float3 to) + { + FindBetween(ref from, ref to, out var result); + return result; + } + /// /// Creates a left-handed spherical billboard that rotates around a specified object position. /// diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h index 89d2f2003..8d7188d91 100644 --- a/Source/Engine/Core/Memory/Allocation.h +++ b/Source/Engine/Core/Memory/Allocation.h @@ -43,14 +43,14 @@ public: return Capacity; } - FORCE_INLINE void Allocate(uint64 capacity) + FORCE_INLINE void Allocate(int32 capacity) { #if ENABLE_ASSERTION_LOW_LAYERS ASSERT(capacity <= Capacity); #endif } - FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount) + FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount) { #if ENABLE_ASSERTION_LOW_LAYERS ASSERT(capacity <= Capacity); @@ -120,12 +120,15 @@ public: capacity |= capacity >> 4; capacity |= capacity >> 8; capacity |= capacity >> 16; - capacity = (capacity + 1) * 2; + uint64 capacity64 = (uint64)(capacity + 1) * 2; + if (capacity64 > MAX_int32) + capacity64 = MAX_int32; + capacity = (int32)capacity64; } return capacity; } - FORCE_INLINE void Allocate(uint64 capacity) + FORCE_INLINE void Allocate(int32 capacity) { #if ENABLE_ASSERTION_LOW_LAYERS ASSERT(!_data); @@ -137,7 +140,7 @@ public: #endif } - FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount) + FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount) { T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr; #if !BUILD_RELEASE @@ -210,7 +213,7 @@ public: return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity); } - FORCE_INLINE void Allocate(uint64 capacity) + FORCE_INLINE void Allocate(int32 capacity) { if (capacity > Capacity) { @@ -219,7 +222,7 @@ public: } } - FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount) + FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount) { // Check if the new allocation will fit into inlined storage if (capacity <= Capacity) diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 27c63c999..3e2bbd5f3 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -327,6 +327,18 @@ public: bool operator!=(const String& other) const; public: + using StringViewBase::StartsWith; + FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const + { + return StringViewBase::StartsWith(prefix, searchCase); + } + + using StringViewBase::EndsWith; + FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const + { + return StringViewBase::EndsWith(suffix, searchCase); + } + /// /// Gets the left most given number of characters. /// @@ -511,6 +523,18 @@ public: bool operator!=(const StringAnsi& other) const; public: + using StringViewBase::StartsWith; + FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const + { + return StringViewBase::StartsWith(prefix, searchCase); + } + + using StringViewBase::EndsWith; + FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const + { + return StringViewBase::EndsWith(suffix, searchCase); + } + /// /// Retrieves substring created from characters starting from startIndex to the String end. /// diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 48fa31c91..059ebbd5d 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext() void DebugDraw::FreeContext(void* context) { + ASSERT(context); Memory::DestructItem((DebugDrawContext*)context); Allocator::Free(context); } void DebugDraw::UpdateContext(void* context, float deltaTime) { + if (!context) + context = &GlobalContext; ((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime); ((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime); } diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 1a57c4f10..c0cfad34c 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -20,7 +20,6 @@ #include "Engine/Threading/MainThreadTask.h" #include "Engine/Threading/ThreadRegistry.h" #include "Engine/Graphics/GPUDevice.h" -#include "Engine/Scripting/ManagedCLR/MCore.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" @@ -327,14 +326,6 @@ void Engine::OnUpdate() // Update services EngineService::OnUpdate(); - -#ifdef USE_NETCORE - // Force GC to run in background periodically to avoid large blocking collections causing hitches - if (Time::Update.TicksCount % 60 == 0) - { - MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false); - } -#endif } void Engine::OnLateUpdate() diff --git a/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h b/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h index 86c723530..f57327fa4 100644 --- a/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h +++ b/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h @@ -26,12 +26,12 @@ public: , _srcResource(src) , _dstResource(dst) { - _srcResource.OnUnload.Bind(this); - _dstResource.OnUnload.Bind(this); + _srcResource.Released.Bind(this); + _dstResource.Released.Bind(this); } private: - void OnResourceUnload(GPUResourceReference* ref) + void OnResourceReleased() { Cancel(); } @@ -47,14 +47,11 @@ protected: // [GPUTask] Result run(GPUTasksContext* context) override { - if (_srcResource.IsMissing() || _dstResource.IsMissing()) + if (!_srcResource || !_dstResource) return Result::MissingResources; - context->GPU->CopyResource(_dstResource, _srcResource); - return Result::Ok; } - void OnEnd() override { _srcResource.Unlink(); diff --git a/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h b/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h index ab8f1ffad..193eb965d 100644 --- a/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h +++ b/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h @@ -31,12 +31,12 @@ public: , _srcSubresource(srcSubresource) , _dstSubresource(dstSubresource) { - _srcResource.OnUnload.Bind(this); - _dstResource.OnUnload.Bind(this); + _srcResource.Released.Bind(this); + _dstResource.Released.Bind(this); } private: - void OnResourceUnload(GPUResourceReference* ref) + void OnResourceReleased() { Cancel(); } @@ -52,14 +52,11 @@ protected: // [GPUTask] Result run(GPUTasksContext* context) override { - if (_srcResource.IsMissing() || _dstResource.IsMissing()) + if (!_srcResource || !_dstResource) return Result::MissingResources; - context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource); - return Result::Ok; } - void OnEnd() override { _srcResource.Unlink(); diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h index d2f20449c..3d38ce58b 100644 --- a/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h +++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h @@ -31,7 +31,7 @@ public: , _buffer(buffer) , _offset(offset) { - _buffer.OnUnload.Bind(this); + _buffer.Released.Bind(this); if (copyData) _data.Copy(data); @@ -40,7 +40,7 @@ public: } private: - void OnResourceUnload(BufferReference* ref) + void OnResourceReleased() { Cancel(); } @@ -56,14 +56,11 @@ protected: // [GPUTask] Result run(GPUTasksContext* context) override { - if (_buffer.IsMissing()) + if (!_buffer) return Result::MissingResources; - context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset); - return Result::Ok; } - void OnEnd() override { _buffer.Unlink(); diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h index 6e9cca7fd..2aff3511b 100644 --- a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h +++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h @@ -35,7 +35,7 @@ public: , _rowPitch(rowPitch) , _slicePitch(slicePitch) { - _texture.OnUnload.Bind(this); + _texture.Released.Bind(this); if (copyData) _data.Copy(data); @@ -44,7 +44,7 @@ public: } private: - void OnResourceUnload(GPUTextureReference* ref) + void OnResourceReleased() { Cancel(); } diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index f0056e26c..2a54c7c4d 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -3,6 +3,7 @@ #include "GPUDevice.h" #include "RenderTargetPool.h" #include "GPUPipelineState.h" +#include "GPUResourceProperty.h" #include "GPUSwapChain.h" #include "RenderTask.h" #include "RenderTools.h" @@ -25,6 +26,39 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Scripting/Enums.h" +GPUResourcePropertyBase::~GPUResourcePropertyBase() +{ + const auto e = _resource; + if (e) + { + _resource = nullptr; + e->Releasing.Unbind(this); + } +} + +void GPUResourcePropertyBase::OnSet(GPUResource* resource) +{ + auto e = _resource; + if (e != resource) + { + if (e) + e->Releasing.Unbind(this); + _resource = e = resource; + if (e) + e->Releasing.Bind(this); + } +} + +void GPUResourcePropertyBase::OnReleased() +{ + auto e = _resource; + if (e) + { + _resource = nullptr; + e->Releasing.Unbind(this); + } +} + GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params) { return GPUDevice::Instance->CreatePipelineState(); diff --git a/Source/Engine/Graphics/GPUResourceProperty.h b/Source/Engine/Graphics/GPUResourceProperty.h index b3c56007d..0b5d73c40 100644 --- a/Source/Engine/Graphics/GPUResourceProperty.h +++ b/Source/Engine/Graphics/GPUResourceProperty.h @@ -8,28 +8,39 @@ /// /// GPU Resource container utility object. /// -template -class GPUResourceProperty +class FLAXENGINE_API GPUResourcePropertyBase { -private: - T* _resource; +protected: + GPUResource* _resource = nullptr; -private: - // Disable copy actions - GPUResourceProperty(const GPUResourceProperty& other) = delete; +public: + NON_COPYABLE(GPUResourcePropertyBase); + + GPUResourcePropertyBase() = default; + ~GPUResourcePropertyBase(); public: /// - /// Action fired when resource gets unloaded (reference gets cleared bu async tasks should stop execution). + /// Action fired when resource gets released (reference gets cleared bu async tasks should stop execution). /// - Delegate OnUnload; + Action Released; +protected: + void OnSet(GPUResource* resource); + void OnReleased(); +}; + +/// +/// GPU Resource container utility object. +/// +template +class GPUResourceProperty : public GPUResourcePropertyBase +{ public: /// /// Initializes a new instance of the class. /// GPUResourceProperty() - : _resource(nullptr) { } @@ -38,9 +49,37 @@ public: /// /// The resource. GPUResourceProperty(T* resource) - : _resource(nullptr) { - Set(resource); + OnSet(resource); + } + + /// + /// Initializes a new instance of the class. + /// + /// The other value. + GPUResourceProperty(const GPUResourceProperty& other) + { + OnSet(other.Get()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The other value. + GPUResourceProperty(GPUResourceProperty&& other) + { + OnSet(other.Get()); + other.OnSet(nullptr); + } + + GPUResourceProperty& operator=(GPUResourceProperty&& other) + { + if (&other != this) + { + OnSet(other._resource); + other.OnSet(nullptr); + } + return *this; } /// @@ -48,13 +87,6 @@ public: /// ~GPUResourceProperty() { - // Check if object has been binded - if (_resource) - { - // Unlink - _resource->Releasing.template Unbind(this); - _resource = nullptr; - } } public: @@ -63,43 +95,34 @@ public: return Get() == other; } - FORCE_INLINE bool operator==(GPUResourceProperty& other) const + FORCE_INLINE bool operator==(const GPUResourceProperty& other) const { return Get() == other.Get(); } - GPUResourceProperty& operator=(const GPUResourceProperty& other) - { - if (this != &other) - Set(other.Get()); - return *this; - } - FORCE_INLINE GPUResourceProperty& operator=(T& other) { - Set(&other); + OnSet(&other); return *this; } FORCE_INLINE GPUResourceProperty& operator=(T* other) { - Set(other); + OnSet(other); return *this; } /// /// Implicit conversion to GPU Resource /// - /// Resource FORCE_INLINE operator T*() const { - return _resource; + return (T*)_resource; } /// /// Implicit conversion to resource /// - /// True if resource has been binded, otherwise false FORCE_INLINE operator bool() const { return _resource != nullptr; @@ -108,37 +131,17 @@ public: /// /// Implicit conversion to resource /// - /// Resource FORCE_INLINE T* operator->() const { - return _resource; + return (T*)_resource; } /// /// Gets linked resource /// - /// Resource FORCE_INLINE T* Get() const { - return _resource; - } - - /// - /// Checks if resource has been binded - /// - /// True if resource has been binded, otherwise false - FORCE_INLINE bool IsBinded() const - { - return _resource != nullptr; - } - - /// - /// Checks if resource is missing - /// - /// True if resource is missing, otherwise false - FORCE_INLINE bool IsMissing() const - { - return _resource == nullptr; + return (T*)_resource; } public: @@ -148,19 +151,7 @@ public: /// Value to assign void Set(T* value) { - if (_resource != value) - { - // Remove reference from the old one - if (_resource) - _resource->Releasing.template Unbind(this); - - // Change referenced object - _resource = value; - - // Add reference to the new one - if (_resource) - _resource->Releasing.template Bind(this); - } + OnSet(value); } /// @@ -168,22 +159,7 @@ public: /// void Unlink() { - if (_resource) - { - // Remove reference from the old one - _resource->Releasing.template Unbind(this); - _resource = nullptr; - } - } - -private: - void onResourceUnload() - { - if (_resource) - { - _resource = nullptr; - OnUnload(this); - } + OnSet(nullptr); } }; diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 32caa2d01..d7359d905 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -90,6 +90,21 @@ public: /// Array BlendShapes; + /// + /// Global translation for this mesh to be at it's local origin. + /// + Vector3 OriginTranslation = Vector3::Zero; + + /// + /// Orientation for this mesh at it's local origin. + /// + Quaternion OriginOrientation = Quaternion::Identity; + + /// + /// Meshes scaling. + /// + Vector3 Scaling = Vector3::One; + public: /// /// Determines whether this instance has any mesh data. diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index f3440c4a5..d2ace6cea 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -5,6 +5,7 @@ #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Vector4.h" #include "Engine/Content/AssetReference.h" +#include "Engine/Content/SoftAssetReference.h" #include "Engine/Core/ISerializable.h" #include "Engine/Content/Assets/Texture.h" #include "Engine/Content/Assets/MaterialBase.h" @@ -850,7 +851,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable /// The Lookup Table (LUT) used to perform color correction. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)") - AssetReference LutTexture; + SoftAssetReference LutTexture; /// /// The LUT blending weight (normalized to range 0-1). Default is 1.0. @@ -1277,7 +1278,7 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable /// Fullscreen lens dirt texture. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)") - AssetReference LensDirt; + SoftAssetReference LensDirt; /// /// Fullscreen lens dirt intensity parameter. Allows to tune dirt visibility. @@ -1289,13 +1290,13 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable /// Custom lens color texture (1D) used for lens color spectrum. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)") - AssetReference LensColor; + SoftAssetReference LensColor; /// /// Custom lens star texture sampled by lens flares. /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)") - AssetReference LensStar; + SoftAssetReference LensStar; public: /// @@ -1487,7 +1488,7 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable /// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px). /// API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)") - AssetReference BokehShapeCustom; + SoftAssetReference BokehShapeCustom; /// /// The minimum pixel brightness to create bokeh. Pixels with lower brightness will be skipped. diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 409125838..173b0ef85 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -22,10 +22,10 @@ TextureHeader::TextureHeader() TextureGroup = -1; } -TextureHeader::TextureHeader(TextureHeader_Deprecated& old) +TextureHeader::TextureHeader(const TextureHeader_Deprecated& old) { Platform::MemoryClear(this, sizeof(*this)); - Width = old.Width;; + Width = old.Width; Height = old.Height; MipLevels = old.MipLevels; Format = old.Format; @@ -49,7 +49,7 @@ StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name) , _texture(nullptr) , _isBlockCompressed(false) { - ASSERT(_owner != nullptr); + ASSERT(parent != nullptr); // Always have created texture object ASSERT(GPUDevice::Instance); @@ -329,11 +329,11 @@ public: , _dataLock(_streamingTexture->GetOwner()->LockData()) { _streamingTexture->_streamingTasks.Add(this); - _texture.OnUnload.Bind(this); + _texture.Released.Bind(this); } private: - void onResourceUnload2(GPUTextureReference* ref) + void OnResourceReleased2() { // Unlink texture if (_streamingTexture) diff --git a/Source/Engine/Graphics/Textures/Types.h b/Source/Engine/Graphics/Textures/Types.h index 9593cbbc2..31e0b1afc 100644 --- a/Source/Engine/Graphics/Textures/Types.h +++ b/Source/Engine/Graphics/Textures/Types.h @@ -106,5 +106,5 @@ struct FLAXENGINE_API TextureHeader byte CustomData[10]; TextureHeader(); - TextureHeader(TextureHeader_Deprecated& old); + TextureHeader(const TextureHeader_Deprecated& old); }; diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 75e3c12d4..addb4861e 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1406,7 +1406,7 @@ Script* Actor::FindScript(const MClass* type) const CHECK_RETURN(type, nullptr); for (auto script : Scripts) { - if (script->GetClass()->IsSubClassOf(type)) + if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type)) return script; } for (auto child : Children) diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index 22d950dd4..c63a5dcf6 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -66,7 +66,7 @@ public: /// /// Gets the value indicating if camera should use perspective rendering mode, otherwise it will use orthographic projection. /// - API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Camera\"), Tooltip(\"Enables perspective projection mode, otherwise uses orthographic.\")") + API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Camera\")") bool GetUsePerspective() const; /// @@ -77,7 +77,7 @@ public: /// /// Gets the camera's field of view (in degrees). /// - API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), Tooltip(\"Field of view angle in degrees.\")") + API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))") float GetFieldOfView() const; /// @@ -88,7 +88,7 @@ public: /// /// Gets the custom aspect ratio. 0 if not use custom value. /// - API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Custom aspect ratio to use. Set to 0 to disable.\")") + API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective))") float GetCustomAspectRatio() const; /// @@ -99,7 +99,7 @@ public: /// /// Gets camera's near plane distance. /// - API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), Tooltip(\"Near clipping plane distance\")") + API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")") float GetNearPlane() const; /// @@ -110,7 +110,7 @@ public: /// /// Gets camera's far plane distance. /// - API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), Tooltip(\"Far clipping plane distance\")") + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")") float GetFarPlane() const; /// @@ -121,7 +121,7 @@ public: /// /// Gets the orthographic projection scale. /// - API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Orthographic projection scale\")") + API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective), true)") float GetOrthographicScale() const; /// diff --git a/Source/Engine/Level/Actors/SpotLight.h b/Source/Engine/Level/Actors/SpotLight.h index 37735692a..2c16d38e4 100644 --- a/Source/Engine/Level/Actors/SpotLight.h +++ b/Source/Engine/Level/Actors/SpotLight.h @@ -24,7 +24,7 @@ private: public: /// - /// Light source bulb radius + /// Light source bulb radius. /// API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)") float SourceRadius = 0.0f; @@ -42,54 +42,51 @@ public: float FallOffExponent = 8.0f; /// - /// IES texture (light profiles from real world measured data) + /// IES texture (light profiles from real world measured data). /// API_FIELD(Attributes="EditorOrder(211), DefaultValue(null), EditorDisplay(\"IES Profile\", \"IES Texture\")") AssetReference IESTexture; /// - /// Enable/disable using light brightness from IES profile + /// Enable/disable using light brightness from IES profile. /// API_FIELD(Attributes="EditorOrder(212), DefaultValue(false), EditorDisplay(\"IES Profile\", \"Use IES Brightness\")") bool UseIESBrightness = false; /// - /// Global scale for IES brightness contribution + /// Global scale for IES brightness contribution. /// API_FIELD(Attributes="EditorOrder(213), DefaultValue(1.0f), Limit(0, 10000, 0.01f), EditorDisplay(\"IES Profile\", \"Brightness Scale\")") float IESBrightnessScale = 1.0f; public: /// - /// Computes light brightness value + /// Computes light brightness value. /// - /// Brightness float ComputeBrightness() const; /// - /// Gets scaled light radius + /// Gets scaled light radius. /// float GetScaledRadius() const; /// - /// Gets light radius + /// Gets light radius. /// - API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Tooltip(\"Light radius\"), Limit(0, 10000, 0.1f)") + API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Limit(0, 10000, 0.1f)") FORCE_INLINE float GetRadius() const { return _radius; } /// - /// Sets light radius + /// Sets light radius. /// - /// New radius API_PROPERTY() void SetRadius(float value); /// - /// Gets the spot light's outer cone angle (in degrees) + /// Gets the spot light's outer cone angle (in degrees). /// - /// Outer angle (in degrees) API_PROPERTY(Attributes="EditorOrder(22), DefaultValue(43.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)") FORCE_INLINE float GetOuterConeAngle() const { @@ -97,15 +94,13 @@ public: } /// - /// Sets the spot light's outer cone angle (in degrees) + /// Sets the spot light's outer cone angle (in degrees). /// - /// Value to assign API_PROPERTY() void SetOuterConeAngle(float value); /// - /// Sets the spot light's inner cone angle (in degrees) + /// Sets the spot light's inner cone angle (in degrees). /// - /// Inner angle (in degrees) API_PROPERTY(Attributes="EditorOrder(21), DefaultValue(10.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)") FORCE_INLINE float GetInnerConeAngle() const { @@ -113,9 +108,8 @@ public: } /// - /// Sets the spot light's inner cone angle (in degrees) + /// Sets the spot light's inner cone angle (in degrees). /// - /// Value to assign API_PROPERTY() void SetInnerConeAngle(float value); private: diff --git a/Source/Engine/Level/LargeWorlds.h b/Source/Engine/Level/LargeWorlds.h index 514c1d5ac..2d92b7228 100644 --- a/Source/Engine/Level/LargeWorlds.h +++ b/Source/Engine/Level/LargeWorlds.h @@ -19,7 +19,7 @@ API_CLASS(Static) class FLAXENGINE_API LargeWorlds /// /// Defines the size of a single chunk. Large world (64-bit) gets divided into smaller chunks so all the math operations (32-bit) can be performed relative to the chunk origin without precision loss. /// - API_FIELD() static constexpr Real ChunkSize = 262144; + API_FIELD() static constexpr Real ChunkSize = 8192; /// /// Updates the large world origin to match the input position. The origin is snapped to the best matching chunk location. diff --git a/Source/Engine/Localization/CultureInfo.cpp b/Source/Engine/Localization/CultureInfo.cpp index 3e02ccf6e..fc678d419 100644 --- a/Source/Engine/Localization/CultureInfo.cpp +++ b/Source/Engine/Localization/CultureInfo.cpp @@ -30,6 +30,20 @@ typedef struct } MonoCultureInfo; #endif +namespace +{ + const CultureInfoEntry* FindEntry(const StringAnsiView& name) + { + for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++) + { + const CultureInfoEntry& e = culture_entries[i]; + if (name == idx2string(e.name)) + return &e; + } + return nullptr; + } +}; + CultureInfo::CultureInfo(int32 lcid) { _lcid = lcid; @@ -80,23 +94,24 @@ CultureInfo::CultureInfo(const StringAnsiView& name) _englishName = TEXT("Invariant Culture"); return; } - for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++) + const CultureInfoEntry* e = FindEntry(name); + if (!e && name.Find('-') != -1) { - auto& e = culture_entries[i]; - if (name == idx2string(e.name)) - { - _data = (void*)&e; - _lcid = (int32)e.lcid; - _lcidParent = (int32)e.parent_lcid; - _name.SetUTF8(name.Get(), name.Length()); - const char* nativename = idx2string(e.nativename); - _nativeName.SetUTF8(nativename, StringUtils::Length(nativename)); - const char* englishname = idx2string(e.englishname); - _englishName.SetUTF8(englishname, StringUtils::Length(englishname)); - break; - } + e = FindEntry(name.Substring(0, name.Find('-'))); } - if (!_data) + if (e) + { + _data = (void*)e; + _lcid = (int32)e->lcid; + _lcidParent = (int32)e->parent_lcid; + const char* ename = idx2string(e->name); + _name.SetUTF8(ename, StringUtils::Length(ename)); + const char* nativename = idx2string(e->nativename); + _nativeName.SetUTF8(nativename, StringUtils::Length(nativename)); + const char* englishname = idx2string(e->englishname); + _englishName.SetUTF8(englishname, StringUtils::Length(englishname)); + } + else { _lcid = 127; _lcidParent = 0; diff --git a/Source/Engine/Online/Online.cpp b/Source/Engine/Online/Online.cpp index bb79c6d60..d8f85baea 100644 --- a/Source/Engine/Online/Online.cpp +++ b/Source/Engine/Online/Online.cpp @@ -4,6 +4,9 @@ #include "IOnlinePlatform.h" #include "Engine/Core/Log.h" #include "Engine/Engine/EngineService.h" +#if USE_EDITOR +#include "Engine/Scripting/Scripting.h" +#endif #include "Engine/Scripting/ScriptingObject.h" class OnlineService : public EngineService @@ -25,6 +28,16 @@ IOnlinePlatform* Online::Platform = nullptr; Action Online::PlatformChanged; OnlineService OnlineServiceInstance; +#if USE_EDITOR + +void OnOnlineScriptsReloading() +{ + // Dispose any active platform + Online::Initialize(nullptr); +} + +#endif + bool Online::Initialize(IOnlinePlatform* platform) { if (Platform == platform) @@ -34,6 +47,9 @@ bool Online::Initialize(IOnlinePlatform* platform) if (Platform) { +#if USE_EDITOR + Scripting::ScriptsReloading.Unbind(OnOnlineScriptsReloading); +#endif Platform->Deinitialize(); } Platform = platform; @@ -45,6 +61,9 @@ bool Online::Initialize(IOnlinePlatform* platform) LOG(Error, "Failed to initialize online platform."); return true; } +#if USE_EDITOR + Scripting::ScriptsReloading.Bind(OnOnlineScriptsReloading); +#endif } return false; diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index e203e70fc..e7b929483 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -41,19 +41,8 @@ protected: public: /// - /// Enables kinematic mode for the rigidbody. + /// Enables kinematic mode for the rigidbody. Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. They are considered to have infinite mass and can push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects but are great for moving platforms or characters, where direct motion control is desired. /// - /// - /// Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. - /// They are considered to have infinite mass and can push regular dynamic actors out of the way. - /// Kinematics will not collide with static or other kinematic objects. - /// - /// Kinematic rigidbodies are great for moving platforms or characters, where direct motion control is desired. - /// - /// - /// Kinematic rigidbodies are incompatible with CCD. - /// - /// API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(false), EditorDisplay(\"Rigid Body\")") FORCE_INLINE bool GetIsKinematic() const { @@ -61,26 +50,13 @@ public: } /// - /// Enables kinematic mode for the rigidbody. + /// Enables kinematic mode for the rigidbody. Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. They are considered to have infinite mass and can push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects but are great for moving platforms or characters, where direct motion control is desired. /// - /// - /// Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. - /// They are considered to have infinite mass and can push regular dynamic actors out of the way. - /// Kinematics will not collide with static or other kinematic objects. - /// - /// Kinematic rigidbodies are great for moving platforms or characters, where direct motion control is desired. - /// - /// - /// Kinematic rigidbodies are incompatible with CCD. - /// - /// - /// The value. API_PROPERTY() void SetIsKinematic(const bool value); /// - /// Gets the 'drag' force added to reduce linear movement. + /// Gets the 'drag' force added to reduce linear movement. Linear damping can be used to slow down an object. The higher the drag the more the object slows down. /// - /// Linear damping can be used to slow down an object. The higher the drag the more the object slows down. API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(0.01f), Limit(0), EditorDisplay(\"Rigid Body\")") FORCE_INLINE float GetLinearDamping() const { @@ -88,16 +64,13 @@ public: } /// - /// Sets the 'drag' force added to reduce linear movement. + /// Sets the 'drag' force added to reduce linear movement. Linear damping can be used to slow down an object. The higher the drag the more the object slows down. /// - /// Linear damping can be used to slow down an object. The higher the drag the more the object slows down. - /// The value. API_PROPERTY() void SetLinearDamping(float value); /// - /// Gets the 'drag' force added to reduce angular movement. + /// Gets the 'drag' force added to reduce angular movement. Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down. /// - /// Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down. API_PROPERTY(Attributes="EditorOrder(70), DefaultValue(0.05f), Limit(0), EditorDisplay(\"Rigid Body\")") FORCE_INLINE float GetAngularDamping() const { @@ -105,9 +78,8 @@ public: } /// - /// Sets the 'drag' force added to reduce angular movement. + /// Sets the 'drag' force added to reduce angular movement. Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down. /// - /// Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down. /// The value. API_PROPERTY() void SetAngularDamping(float value); @@ -123,7 +95,6 @@ public: /// /// If true simulation and collisions detection will be enabled for the rigidbody. /// - /// The value. API_PROPERTY() void SetEnableSimulation(bool value); /// @@ -138,7 +109,6 @@ public: /// /// If true Continuous Collision Detection (CCD) will be used for this component. /// - /// The value. API_PROPERTY() void SetUseCCD(const bool value); /// @@ -153,7 +123,6 @@ public: /// /// If object should have the force of gravity applied. /// - /// The value. API_PROPERTY() void SetEnableGravity(bool value); /// @@ -168,7 +137,6 @@ public: /// /// If object should start awake, or if it should initially be sleeping. /// - /// The value. API_PROPERTY() void SetStartAwake(bool value); /// @@ -183,16 +151,11 @@ public: /// /// If true, it will update mass when actor scale changes. /// - /// The value. API_PROPERTY() void SetUpdateMassWhenScaleChanges(bool value); /// - /// Gets the maximum angular velocity that a simulated object can achieve. + /// Gets the maximum angular velocity that a simulated object can achieve. The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody. /// - /// - /// The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. - /// Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody. - /// API_PROPERTY(Attributes="EditorOrder(90), DefaultValue(7.0f), Limit(0), EditorDisplay(\"Rigid Body\")") FORCE_INLINE float GetMaxAngularVelocity() const { @@ -200,13 +163,8 @@ public: } /// - /// Sets the maximum angular velocity that a simulated object can achieve. + /// Sets the maximum angular velocity that a simulated object can achieve. The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody. /// - /// - /// The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. - /// Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody. - /// - /// The value. API_PROPERTY() void SetMaxAngularVelocity(float value); /// @@ -218,7 +176,6 @@ public: /// /// Override the auto computed mass. /// - /// The value. API_PROPERTY() void SetOverrideMass(bool value); /// @@ -230,8 +187,6 @@ public: /// /// Sets the mass value measured in kilograms (use override value only if OverrideMass is checked). /// - /// If set auto enables mass override. - /// The value. API_PROPERTY() void SetMass(float value); /// @@ -243,7 +198,6 @@ public: /// /// Sets the per-instance scaling of the mass. /// - /// The value. API_PROPERTY() void SetMassScale(float value); /// @@ -258,7 +212,6 @@ public: /// /// Sets the user specified offset for the center of mass of this object, from the calculated location. /// - /// The value. API_PROPERTY() void SetCenterOfMassOffset(const Float3& value); /// @@ -273,28 +226,27 @@ public: /// /// Sets the object movement constraint flags that define degrees of freedom are allowed for the simulation of object. /// - /// The value. API_PROPERTY() void SetConstraints(const RigidbodyConstraints value); public: /// /// Gets the linear velocity of the rigidbody. /// - /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour. + /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour. API_PROPERTY(Attributes="HideInEditor") Vector3 GetLinearVelocity() const; /// /// Sets the linear velocity of the rigidbody. /// - /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour. + /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour. /// The value. API_PROPERTY() void SetLinearVelocity(const Vector3& value) const; /// /// Gets the angular velocity of the rigidbody measured in radians per second. /// - /// It's used mostly to get the current angular velocity. Manual modifications may result in unrealistic behaviour. + /// It's used mostly to get the current angular velocity. Manual modifications may result in unrealistic behaviour. API_PROPERTY(Attributes="HideInEditor") Vector3 GetAngularVelocity() const; diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index 90f0dab38..fde3b4632 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -81,6 +81,13 @@ void BoxCollider::OnDebugDrawSelected() const Color color = Color::GreenYellow; DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.3f, 0, false); + if (_contactOffset > 0) + { + OrientedBoundingBox contactBounds = _bounds; + contactBounds.Extents += Vector3(_contactOffset) / contactBounds.Transformation.Scale; + DEBUG_DRAW_WIRE_BOX(contactBounds, Color::Blue.AlphaMultiplied(0.2f), 0, false); + } + Vector3 corners[8]; _bounds.GetCorners(corners); const float margin = 1.0f; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index ba133e8b6..98346b0a9 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -42,27 +42,33 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view) const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius); if (!view.CullingFrustum.Intersects(sphere)) return; - Quaternion rot; - Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot); + 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); if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger()) - DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), rot, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true); + DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), rotation, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true); else - DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow * 0.8f, 0, true); + DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rotation, radius, height, Color::GreenYellow * 0.8f, 0, true); } void CapsuleCollider::OnDebugDrawSelected() { - Quaternion rot; - Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot); + 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); - DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow, 0, false); + const Vector3 position = _transform.LocalToWorld(_center); + DEBUG_DRAW_WIRE_TUBE(position, rotation, radius, height, Color::GreenYellow, 0, false); + + if (_contactOffset > 0) + { + DEBUG_DRAW_WIRE_TUBE(position, rotation, radius + _contactOffset, height, Color::Blue.AlphaMultiplied(0.2f), 0, false); + } // Base Collider::OnDebugDrawSelected(); diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 59521e51c..41ee95d04 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -23,6 +23,7 @@ CharacterController::CharacterController(const SpawnParams& params) , _nonWalkableMode(NonWalkableModes::PreventClimbing) , _lastFlags(CollisionFlags::None) { + _contactOffset = 10.0f; } float CharacterController::GetRadius() const diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 49de1f799..6c292bf20 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -19,7 +19,7 @@ Collider::Collider(const SpawnParams& params) , _shape(nullptr) , _staticActor(nullptr) , _cachedScale(1.0f) - , _contactOffset(10.0f) + , _contactOffset(2.0f) { Material.Changed.Bind(this); } diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 6b9dfcb9d..d3bae9407 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -35,11 +35,8 @@ public: void* GetPhysicsShape() const; /// - /// Gets the 'IsTrigger' flag. + /// Gets the 'IsTrigger' flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter and OnTriggerExit message when a rigidbody enters or exits the trigger volume. /// - /// - /// A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume. - /// API_PROPERTY(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"Collider\")") FORCE_INLINE bool GetIsTrigger() const { @@ -47,11 +44,8 @@ public: } /// - /// Sets the `IsTrigger` flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume. + /// Sets the `IsTrigger` flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter and OnTriggerExit message when a rigidbody enters or exits the trigger volume. /// - /// - /// A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume. - /// API_PROPERTY() void SetIsTrigger(bool value); /// @@ -69,23 +63,17 @@ public: API_PROPERTY() void SetCenter(const Vector3& value); /// - /// Gets the contact offset. + /// 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. /// - /// - /// 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. - /// - API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(10.0f), Limit(0, 100), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\")") FORCE_INLINE float GetContactOffset() const { return _contactOffset; } /// - /// Sets the contact offset. + /// Sets 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. /// - /// - /// 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. - /// API_PROPERTY() void SetContactOffset(float value); /// diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp index e163484e8..fa2fb1cca 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.cpp +++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp @@ -40,6 +40,13 @@ void SphereCollider::OnDebugDrawSelected() { DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::GreenYellow, 0, false); + if (_contactOffset > 0) + { + BoundingSphere contactBounds = _sphere; + contactBounds.Radius += _contactOffset; + DEBUG_DRAW_WIRE_SPHERE(contactBounds, Color::Blue.AlphaMultiplied(0.2f), 0, false); + } + // Base Collider::OnDebugDrawSelected(); } diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index c61e2aadd..c844ab81d 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -665,6 +665,7 @@ void AndroidPlatform::PreInit(android_app* app) app->onInputEvent = OnAppInput; ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_KEEP_SCREEN_ON | AWINDOW_FLAG_TURN_SCREEN_ON | AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_DISMISS_KEYGUARD, 0); ANativeActivity_setWindowFormat(app->activity, WINDOW_FORMAT_RGBA_8888); + pthread_setname_np(pthread_self(), "Main"); } bool AndroidPlatform::Is64BitPlatform() diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp index d4348b107..c939c1af2 100644 --- a/Source/Engine/Platform/Apple/ApplePlatform.cpp +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -19,6 +19,7 @@ #include "Engine/Platform/StringUtils.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/Clipboard.h" +#include "Engine/Platform/Thread.h" #include "Engine/Platform/IGuiData.h" #include "Engine/Platform/Base/PlatformUtils.h" #include "Engine/Utilities/StringConverter.h" @@ -176,12 +177,21 @@ uint64 ApplePlatform::GetCurrentThreadID() void ApplePlatform::SetThreadPriority(ThreadPriority priority) { - // TODO: impl this + struct sched_param sched; + Platform::MemoryClear(&sched, sizeof(struct sched_param)); + int32 policy = SCHED_RR; + pthread_getschedparam(pthread_self(), &policy, &sched); + sched.sched_priority = AppleThread::GetAppleThreadPriority(priority); + pthread_setschedparam(pthread_self(), policy, &sched); } void ApplePlatform::SetThreadAffinityMask(uint64 affinityMask) { - // TODO: impl this +#if PLATFORM_MAC + thread_affinity_policy policy; + policy.affinity_tag = affinityMask; + thread_policy_set(pthread_mach_thread_np(pthread_self()), THREAD_AFFINITY_POLICY, (integer_t*)&policy, THREAD_AFFINITY_POLICY_COUNT); +#endif } void ApplePlatform::Sleep(int32 milliseconds) diff --git a/Source/Engine/Platform/Apple/AppleThread.h b/Source/Engine/Platform/Apple/AppleThread.h index 43d9d2966..a578092b6 100644 --- a/Source/Engine/Platform/Apple/AppleThread.h +++ b/Source/Engine/Platform/Apple/AppleThread.h @@ -40,10 +40,7 @@ public: return (AppleThread*)Setup(New(runnable, name, priority), stackSize); } -protected: - - // [UnixThread] - int32 GetThreadPriority(ThreadPriority priority) override + static int32 GetAppleThreadPriority(ThreadPriority priority) { switch (priority) { @@ -60,6 +57,14 @@ protected: } return 31; } + +protected: + + // [UnixThread] + int32 GetThreadPriority(ThreadPriority priority) override + { + return GetAppleThreadPriority(priority); + } int32 Start(pthread_attr_t& attr) override { return pthread_create(&_thread, &attr, ThreadProc, this); diff --git a/Source/Engine/Platform/Base/DragDropHelper.h b/Source/Engine/Platform/Base/DragDropHelper.h new file mode 100644 index 000000000..a5382afd6 --- /dev/null +++ b/Source/Engine/Platform/Base/DragDropHelper.h @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Threading/ThreadPoolTask.h" +#include "Engine/Threading/ThreadPool.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/ManagedCLR/MDomain.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Platform/Platform.h" +#if USE_EDITOR +#if !COMPILE_WITH_DEBUG_DRAW +#define COMPILE_WITH_DEBUG_DRAW 1 +#define COMPILE_WITH_DEBUG_DRAW_HACK +#endif +#include "Engine/Debug/DebugDraw.h" +#ifdef COMPILE_WITH_DEBUG_DRAW_HACK +#undef COMPILE_WITH_DEBUG_DRAW_HACK +#undef COMPILE_WITH_DEBUG_DRAW +#define COMPILE_WITH_DEBUG_DRAW 0 +#endif +#endif + +/// +/// Async DoDragDrop helper (used for rendering frames during main thread stall). +/// +class DoDragDropJob : public ThreadPoolTask +{ +public: + int64 ExitFlag = 0; + + // [ThreadPoolTask] + bool Run() override + { + Scripting::GetScriptsDomain()->Dispatch(); + while (Platform::AtomicRead(&ExitFlag) == 0) + { +#if USE_EDITOR + // Flush any single-frame shapes to prevent memory leaking (eg. via terrain collision debug during scene drawing with PhysicsColliders or PhysicsDebug flag) + DebugDraw::UpdateContext(nullptr, 0.0f); +#endif + Engine::OnDraw(); + Platform::Sleep(20); + } + return false; + } +}; diff --git a/Source/Engine/Platform/CreateWindowSettings.cs b/Source/Engine/Platform/CreateWindowSettings.cs index d4d9ce727..8f7cd8c0d 100644 --- a/Source/Engine/Platform/CreateWindowSettings.cs +++ b/Source/Engine/Platform/CreateWindowSettings.cs @@ -12,7 +12,7 @@ namespace FlaxEngine Position = new Float2(100, 100), Size = new Float2(640, 480), MinimumSize = Float2.One, - MaximumSize = new Float2(4100, 4100), + MaximumSize = Float2.Zero, // Unlimited size StartPosition = WindowStartPosition.CenterParent, HasBorder = true, ShowInTaskbar = true, diff --git a/Source/Engine/Platform/CreateWindowSettings.h b/Source/Engine/Platform/CreateWindowSettings.h index 1ff596df9..9c543a05d 100644 --- a/Source/Engine/Platform/CreateWindowSettings.h +++ b/Source/Engine/Platform/CreateWindowSettings.h @@ -59,9 +59,9 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(CreateWindowSettings); API_FIELD() Float2 MinimumSize = Float2(1, 1); /// - /// The maximum size. + /// The maximum size. Set to 0 to use unlimited size. /// - API_FIELD() Float2 MaximumSize = Float2(8192, 4096); + API_FIELD() Float2 MaximumSize = Float2(0, 0); /// /// The start position mode. diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 8da3a378b..97cde4a1c 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -679,8 +679,14 @@ void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& res result = TEXT("/usr/share"); break; case SpecialFolder::LocalAppData: - result = home; + { + String dataHome; + if (!Platform::GetEnvironmentVariable(TEXT("XDG_DATA_HOME"), dataHome)) + result = dataHome; + else + result = home / TEXT(".local/share"); break; + } case SpecialFolder::ProgramData: result = String::Empty; break; diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index a232dea55..b3bae0276 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -150,9 +150,9 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) { // Set resizing range hints.min_width = (int)settings.MinimumSize.X; - hints.max_width = (int)settings.MaximumSize.X; + hints.max_width = settings.MaximumSize.X > 0 ? (int)settings.MaximumSize.X : MAX_uint16; hints.min_height = (int)settings.MinimumSize.Y; - hints.max_height = (int)settings.MaximumSize.Y; + hints.max_height = settings.MaximumSize.Y > 0 ? (int)settings.MaximumSize.Y : MAX_uint16; hints.flags |= USSize; } // honor the WM placement except for manual (overriding) placements @@ -594,6 +594,12 @@ void LinuxWindow::OnButtonPress(void* event) case Button3: mouseButton = MouseButton::Right; break; + case 8: + mouseButton = MouseButton::Extended2; + break; + case 9: + mouseButton = MouseButton::Extended1; + break; default: return; } @@ -641,6 +647,12 @@ void LinuxWindow::OnButtonRelease(void* event) case Button5: Input::Mouse->OnMouseWheel(ClientToScreen(mousePos), -1.0f, this); break; + case 8: + Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Extended2, this); + break; + case 9: + Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Extended1, this); + break; default: return; } diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 38e8c95df..373d19bae 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -8,9 +8,7 @@ #include "Engine/Platform/IGuiData.h" #if USE_EDITOR #include "Engine/Platform/CriticalSection.h" -#include "Engine/Threading/ThreadPoolTask.h" -#include "Engine/Threading/ThreadPool.h" -#include "Engine/Engine/Engine.h" +#include "Engine/Platform/Base/DragDropHelper.h" #endif #include "Engine/Core/Log.h" #include "Engine/Input/Input.h" @@ -25,23 +23,7 @@ // Data for drawing window while doing drag&drop on Mac (engine is paused during platform tick) CriticalSection MacDragLocker; NSDraggingSession* MacDragSession = nullptr; -class DoDragDropJob* MacDragJob = nullptr; - -class DoDragDropJob : public ThreadPoolTask -{ -public: - int64 ExitFlag = 0; - - bool Run() override - { - while (Platform::AtomicRead(&ExitFlag) == 0) - { - Engine::OnDraw(); - Platform::Sleep(20); - } - return false; - } -}; +DoDragDropJob* MacDragJob = nullptr; #endif inline bool IsWindowInvalid(Window* win) @@ -751,7 +733,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) [window setWindow:this]; [window setReleasedWhenClosed:NO]; [window setMinSize:NSMakeSize(settings.MinimumSize.X, settings.MinimumSize.Y)]; - [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)]; + if (settings.MaximumSize.SumValues() > 0) + [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)]; [window setOpaque:!settings.SupportsTransparency]; [window setContentView:view]; if (settings.AllowInput) diff --git a/Source/Engine/Platform/Unix/UnixThread.cpp b/Source/Engine/Platform/Unix/UnixThread.cpp index f662bdba8..29dc5f881 100644 --- a/Source/Engine/Platform/Unix/UnixThread.cpp +++ b/Source/Engine/Platform/Unix/UnixThread.cpp @@ -26,6 +26,12 @@ int32 UnixThread::Start(pthread_attr_t& attr) void* UnixThread::ThreadProc(void* pThis) { auto thread = (UnixThread*)pThis; +#if PLATFORM_APPLE_FAMILY + // Apple doesn't support creating named thread so assign name here + { + pthread_setname_np(StringAnsi(thread->GetName()).Get()); + } +#endif const int32 exitCode = thread->Run(); return (void*)(uintptr)exitCode; } diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 474a92072..cc2e52725 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -1201,8 +1201,8 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) folder = StringView::Empty; if (folder.HasChars()) { - String folderNullTerminated(folder); - SetDllDirectoryW(folderNullTerminated.Get()); + const String folderNullTerminated(folder); + AddDllDirectory(folderNullTerminated.Get()); } // Avoiding windows dialog boxes if missing @@ -1210,7 +1210,10 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) DWORD prevErrorMode = 0; const BOOL hasErrorMode = SetThreadErrorMode(errorMode, &prevErrorMode); - // Load the DLL + // Ensure that dll is properly searched + SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS); + + // Load the library void* handle = ::LoadLibraryW(filename); if (!handle) { @@ -1221,10 +1224,6 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) { SetThreadErrorMode(prevErrorMode, nullptr); } - if (folder.HasChars()) - { - SetDllDirectoryW(nullptr); - } #if CRASH_LOG_ENABLE // Refresh modules info during next stack trace collecting to have valid debug symbols information diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp deleted file mode 100644 index b6e636697..000000000 --- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp +++ /dev/null @@ -1,698 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -#if PLATFORM_WINDOWS - -#include "WindowsWindow.h" - -#if USE_EDITOR - -#include "Engine/Core/Collections/Array.h" -#include "Engine/Engine/Engine.h" -#include "Engine/Platform/IGuiData.h" -#include "Engine/Input/Input.h" -#include "Engine/Input/Mouse.h" -#include "Engine/Threading/ThreadPoolTask.h" -#include "Engine/Threading/ThreadPool.h" -#include "Engine/Scripting/Scripting.h" -#include "Engine/Scripting/ManagedCLR/MDomain.h" -#include "../Win32/IncludeWindowsHeaders.h" -#include -#include - -HGLOBAL duplicateGlobalMem(HGLOBAL hMem) -{ - auto len = GlobalSize(hMem); - auto source = GlobalLock(hMem); - auto dest = GlobalAlloc(GMEM_FIXED, len); - Platform::MemoryCopy(dest, source, len); - GlobalUnlock(hMem); - return dest; -} - -DWORD dropEffect2OleEnum(DragDropEffect effect) -{ - DWORD result; - switch (effect) - { - case DragDropEffect::None: - result = DROPEFFECT_NONE; - break; - case DragDropEffect::Copy: - result = DROPEFFECT_COPY; - break; - case DragDropEffect::Move: - result = DROPEFFECT_MOVE; - break; - case DragDropEffect::Link: - result = DROPEFFECT_LINK; - break; - default: - result = DROPEFFECT_NONE; - break; - } - return result; -} - -DragDropEffect dropEffectFromOleEnum(DWORD effect) -{ - DragDropEffect result; - switch (effect) - { - case DROPEFFECT_NONE: - result = DragDropEffect::None; - break; - case DROPEFFECT_COPY: - result = DragDropEffect::Copy; - break; - case DROPEFFECT_MOVE: - result = DragDropEffect::Move; - break; - case DROPEFFECT_LINK: - result = DragDropEffect::Link; - break; - default: - result = DragDropEffect::None; - break; - } - return result; -} - -HANDLE StringToHandle(const StringView& str) -{ - // Allocate and lock a global memory buffer. - // Make it fixed data so we don't have to use GlobalLock - const int32 length = str.Length(); - char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1)); - - // Copy the string into the buffer as ANSI text - StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length); - ptr[length] = '\0'; - - return ptr; -} - -void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source) -{ - // Copy the source FORMATETC into dest - *dest = *source; - - if (source->ptd) - { - // Allocate memory for the DVTARGETDEVICE if necessary - dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE))); - - // Copy the contents of the source DVTARGETDEVICE into dest->ptd - *(dest->ptd) = *(source->ptd); - } -} - -HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc); - -/// -/// GUI data for Windows platform -/// -class WindowsGuiData : public IGuiData -{ -private: - - Type _type; - Array _data; - -public: - - /// - /// Init - /// - WindowsGuiData() - : _type(Type::Unknown) - , _data(1) - { - } - -public: - - /// - /// Init from Ole IDataObject - /// - /// Object - void Init(IDataObject* pDataObj) - { - // Temporary data - FORMATETC fmtetc; - STGMEDIUM stgmed; - - // Clear - _type = Type::Unknown; - _data.Clear(); - - // Check type - fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) - { - // Text - _type = Type::Text; - - // Get data - char* text = static_cast(GlobalLock(stgmed.hGlobal)); - _data.Add(String(text)); - GlobalUnlock(stgmed.hGlobal); - ReleaseStgMedium(&stgmed); - } - else - { - fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) - { - // Unicode Text - _type = Type::Text; - - // Get data - Char* text = static_cast(GlobalLock(stgmed.hGlobal)); - _data.Add(String(text)); - GlobalUnlock(stgmed.hGlobal); - ReleaseStgMedium(&stgmed); - } - else - { - fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) - { - // Files - _type = Type::Files; - - // Get data - Char item[MAX_PATH]; - HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal)); - UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); - for (UINT i = 0; i < filesCount; i++) - { - if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0) - { - _data.Add(String(item)); - } - } - GlobalUnlock(stgmed.hGlobal); - ReleaseStgMedium(&stgmed); - } - } - } - } - -public: - - // [IGuiData] - Type GetType() const override - { - return _type; - } - - String GetAsText() const override - { - String result; - if (_type == Type::Text) - { - result = _data[0]; - } - return result; - } - - void GetAsFiles(Array* files) const override - { - if (_type == Type::Files) - { - files->Add(_data); - } - } -}; - -/// -/// Tool class for Windows Ole support -/// -class WindowsEnumFormatEtc : public IEnumFORMATETC -{ -private: - - ULONG _refCount; - ULONG _index; - ULONG _formatsCount; - FORMATETC* _formatEtc; - -public: - - WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats) - : _refCount(1) - , _index(0) - , _formatsCount(nNumFormats) - , _formatEtc(nullptr) - { - // Allocate memory - _formatEtc = new FORMATETC[nNumFormats]; - - // Copy the FORMATETC structures - for (int32 i = 0; i < nNumFormats; i++) - { - DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]); - } - } - - ~WindowsEnumFormatEtc() - { - if (_formatEtc) - { - for (uint32 i = 0; i < _formatsCount; i++) - { - if (_formatEtc[i].ptd) - { - CoTaskMemFree(_formatEtc[i].ptd); - } - } - - delete[] _formatEtc; - } - } - -public: - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override - { - // Check to see what interface has been requested - if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown) - { - AddRef(); - *ppvObject = this; - return S_OK; - } - - // No interface - *ppvObject = nullptr; - return E_NOINTERFACE; - } - - ULONG STDMETHODCALLTYPE AddRef() override - { - _InterlockedIncrement(&_refCount); - return _refCount; - } - - ULONG STDMETHODCALLTYPE Release() override - { - ULONG ulRefCount = _InterlockedDecrement(&_refCount); - if (_refCount == 0) - { - delete this; - } - return ulRefCount; - } - - // [IEnumFormatEtc] - HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override - { - ULONG copied = 0; - - // validate arguments - if (celt == 0 || pFormatEtc == nullptr) - return E_INVALIDARG; - - // copy FORMATETC structures into caller's buffer - while (_index < _formatsCount && copied < celt) - { - DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]); - copied++; - _index++; - } - - // store result - if (pceltFetched != nullptr) - *pceltFetched = copied; - - // did we copy all that was requested? - return (copied == celt) ? S_OK : S_FALSE; - } - - HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override - { - _index += celt; - return (_index <= _formatsCount) ? S_OK : S_FALSE; - } - - HRESULT STDMETHODCALLTYPE Reset() override - { - _index = 0; - return S_OK; - } - - HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override - { - HRESULT result; - - // Make a duplicate enumerator - result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc); - - if (result == S_OK) - { - // Manually set the index state - static_cast(*ppEnumFormatEtc)->_index = _index; - } - - return result; - } -}; - -HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc) -{ - if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr) - return E_INVALIDARG; - - *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats); - - return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY; -} - -/// -/// Drag drop source and data container for Ole -/// -class WindowsDragSource : public IDataObject, public IDropSource -{ -private: - - ULONG _refCount; - int32 _formatsCount; - FORMATETC* _formatEtc; - STGMEDIUM* _stgMedium; - -public: - - WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count) - : _refCount(1) - , _formatsCount(count) - , _formatEtc(nullptr) - , _stgMedium(nullptr) - { - // Allocate memory - _formatEtc = new FORMATETC[count]; - _stgMedium = new STGMEDIUM[count]; - - // Copy descriptors - for (int32 i = 0; i < count; i++) - { - _formatEtc[i] = fmtetc[i]; - _stgMedium[i] = stgmed[i]; - } - } - - virtual ~WindowsDragSource() - { - if (_formatEtc) - delete[] _formatEtc; - if (_stgMedium) - delete[] _stgMedium; - } - -public: - - // [IUnknown] - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override - { - // Check to see what interface has been requested - if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource) - { - AddRef(); - *ppvObject = this; - return S_OK; - } - - // No interface - *ppvObject = nullptr; - return E_NOINTERFACE; - } - - ULONG STDMETHODCALLTYPE AddRef() override - { - _InterlockedIncrement(&_refCount); - return _refCount; - } - - ULONG STDMETHODCALLTYPE Release() override - { - ULONG ulRefCount = _InterlockedDecrement(&_refCount); - if (_refCount == 0) - { - delete this; - } - return ulRefCount; - } - - // [IDropSource] - HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override - { - // If the Escape key has been pressed since the last call, cancel the drop - if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON) - return DRAGDROP_S_CANCEL; - - // If the LeftMouse button has been released, then do the drop! - if ((grfKeyState & MK_LBUTTON) == 0) - return DRAGDROP_S_DROP; - - // Continue with the drag-drop - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override - { - // TODO: allow to use custom mouse cursor during drop and drag operation - return DRAGDROP_S_USEDEFAULTCURSORS; - } - - // [IDataObject] - HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override - { - if (pformatetcIn == nullptr || pmedium == nullptr) - return E_INVALIDARG; - - // Try to match the specified FORMATETC with one of our supported formats - int32 index = lookupFormatEtc(pformatetcIn); - if (index == INVALID_INDEX) - return DV_E_FORMATETC; - - // Found a match - transfer data into supplied storage medium - pmedium->tymed = _formatEtc[index].tymed; - pmedium->pUnkForRelease = nullptr; - - // Copy the data into the caller's storage medium - switch (_formatEtc[index].tymed) - { - case TYMED_HGLOBAL: - pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal); - break; - - default: - return DV_E_FORMATETC; - } - - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override - { - return DATA_E_FORMATETC; - } - - HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override - { - return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK; - } - - HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override - { - // Apparently we have to set this field to NULL even though we don't do anything else - pformatetcOut->ptd = nullptr; - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override - { - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override - { - // Only the get direction is supported for OLE - if (dwDirection == DATADIR_GET) - { - // TODO: use SHCreateStdEnumFmtEtc API call - return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc); - } - - // The direction specified is not supported for drag+drop - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override - { - return OLE_E_ADVISENOTSUPPORTED; - } - - HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override - { - return OLE_E_ADVISENOTSUPPORTED; - } - - HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override - { - return OLE_E_ADVISENOTSUPPORTED; - } - -private: - - int32 lookupFormatEtc(FORMATETC* pFormatEtc) const - { - // Check each of our formats in turn to see if one matches - for (int32 i = 0; i < _formatsCount; i++) - { - if ((_formatEtc[i].tymed & pFormatEtc->tymed) && - _formatEtc[i].cfFormat == pFormatEtc->cfFormat && - _formatEtc[i].dwAspect == pFormatEtc->dwAspect) - { - // Return index of stored format - return i; - } - } - - // Format not found - return INVALID_INDEX; - } -}; - -WindowsGuiData GuiDragDropData; - -/// -/// Async DoDragDrop helper (used for rendering frames during main thread stall). -/// -class DoDragDropJob : public ThreadPoolTask -{ -public: - - int64 ExitFlag = 0; - - // [ThreadPoolTask] - bool Run() override - { - Scripting::GetScriptsDomain()->Dispatch(); - while (Platform::AtomicRead(&ExitFlag) == 0) - { - Engine::OnDraw(); - Platform::Sleep(20); - } - return false; - } -}; - -DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) -{ - // Create background worker that will keep updating GUI (perform rendering) - const auto task = New(); - Task::StartNew(task); - while (task->GetState() == TaskState::Queued) - { - Platform::Sleep(1); - } - - // Create descriptors - FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr }; - - // Create a HGLOBAL inside the storage medium - stgmed.hGlobal = StringToHandle(data); - - // Create drop source - auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1); - - // Do the drag drop operation - DWORD dwEffect; - HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect); - - // Wait for job end - Platform::AtomicStore(&task->ExitFlag, 1); - task->Wait(); - - // Release allocated data - dropSource->Release(); - ReleaseStgMedium(&stgmed); - - // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop) - if (Input::GetMouseButton(MouseButton::Left)) - { - ::POINT point; - ::GetCursorPos(&point); - Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this); - } - - return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None; -} - -HRESULT WindowsWindow::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) -{ - // Call GUI - POINT p = { pt.x, pt.y }; - ::ScreenToClient(_handle, &p); - GuiDragDropData.Init((IDataObject*)pDataObj); - DragDropEffect effect = DragDropEffect::None; - OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); - - // Focus - Focus(); - - // Translate effect into Ole Api type - *pdwEffect = dropEffect2OleEnum(effect); - - return S_OK; -} - -HRESULT WindowsWindow::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) -{ - // Call GUI - POINT p = { pt.x, pt.y }; - ::ScreenToClient(_handle, &p); - DragDropEffect effect = DragDropEffect::None; - OnDragOver(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); - - // Translate effect into Ole Api type - *pdwEffect = dropEffect2OleEnum(effect); - - return S_OK; -} - -HRESULT WindowsWindow::DragLeave() -{ - // Call GUI - OnDragLeave(); - - return S_OK; -} - -HRESULT WindowsWindow::Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) -{ - // Call GUI - POINT p = { pt.x, pt.y }; - ::ScreenToClient(_handle, &p); - GuiDragDropData.Init((IDataObject*)pDataObj); - DragDropEffect effect = DragDropEffect::None; - OnDragDrop(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); - - // Translate effect into Ole Api type - *pdwEffect = dropEffect2OleEnum(effect); - - return S_OK; -} - -#else - -DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) -{ - // Not supported - return DragDropEffect::None; -} - -#endif - -#endif diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index ff65e9864..181077125 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -10,8 +10,19 @@ #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/GPUDevice.h" +#if USE_EDITOR +#include "Engine/Core/Collections/Array.h" +#include "Engine/Platform/IGuiData.h" +#include "Engine/Platform/Base/DragDropHelper.h" +#include "Engine/Input/Input.h" +#include "Engine/Input/Mouse.h" +#endif #include "../Win32/IncludeWindowsHeaders.h" #include +#if USE_EDITOR +#include +#include +#endif #define DefaultDPI 96 @@ -139,7 +150,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings) const HMODULE user32Dll = LoadLibraryW(L"user32.dll"); if (user32Dll) { - typedef UINT (STDAPICALLTYPE* GetDpiForWindowProc)(HWND hwnd); + typedef UINT (STDAPICALLTYPE*GetDpiForWindowProc)(HWND hwnd); const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow"); if (getDpiForWindowProc) { @@ -262,7 +273,7 @@ void WindowsWindow::Maximize() void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) { ASSERT(HasHWND()); - + if (IsFullscreen()) SetIsFullscreen(false); @@ -278,7 +289,7 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) { LONG lStyle = GetWindowLong(_handle, GWL_STYLE); lStyle &= ~(WS_THICKFRAME | WS_SYSMENU | WS_OVERLAPPED | WS_BORDER | WS_CAPTION); - lStyle |= WS_POPUP; + lStyle |= WS_POPUP; lStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; #if WINDOWS_USE_NEW_BORDER_LESS if (_settings.IsRegularWindow) @@ -289,8 +300,8 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) #endif SetWindowLong(_handle, GWL_STYLE, lStyle); - SetWindowPos(_handle, HWND_TOP, 0, 0,0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); - + SetWindowPos(_handle, HWND_TOP, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + if (maximized) { ShowWindow(_handle, SW_SHOWMAXIMIZED); @@ -311,10 +322,10 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) if (_settings.HasSizingFrame) lStyle |= WS_THICKFRAME; lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; - + SetWindowLong(_handle, GWL_STYLE, lStyle); - SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); - + SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + if (maximized) { Maximize(); @@ -727,7 +738,7 @@ void WindowsWindow::CheckForWindowResize() MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(monitor, &monitorInfo); - + auto cwidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left; auto cheight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; if (width > cwidth && height > cheight) @@ -1056,22 +1067,23 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) case WM_GETMINMAXINFO: { const auto minMax = reinterpret_cast(lParam); - - int32 borderWidth = 0, borderHeight = 0; - if (_settings.HasBorder) - { - const DWORD windowStyle = GetWindowLongW(_handle, GWL_STYLE); - const DWORD windowExStyle = GetWindowLongW(_handle, GWL_EXSTYLE); - RECT borderRect = { 0, 0, 0, 0 }; - AdjustWindowRectEx(&borderRect, windowStyle, false, windowExStyle); - borderWidth = borderRect.right - borderRect.left; - borderHeight = borderRect.bottom - borderRect.top; - } - minMax->ptMinTrackSize.x = (int32)_settings.MinimumSize.X; minMax->ptMinTrackSize.y = (int32)_settings.MinimumSize.Y; - minMax->ptMaxTrackSize.x = (int32)_settings.MaximumSize.X + borderWidth; - minMax->ptMaxTrackSize.y = (int32)_settings.MaximumSize.Y + borderHeight; + if (_settings.MaximumSize.SumValues() > 0) + { + int32 borderWidth = 0, borderHeight = 0; + if (_settings.HasBorder) + { + const DWORD windowStyle = GetWindowLongW(_handle, GWL_STYLE); + const DWORD windowExStyle = GetWindowLongW(_handle, GWL_EXSTYLE); + RECT borderRect = { 0, 0, 0, 0 }; + AdjustWindowRectEx(&borderRect, windowStyle, false, windowExStyle); + borderWidth = borderRect.right - borderRect.left; + borderHeight = borderRect.bottom - borderRect.top; + } + minMax->ptMaxTrackSize.x = (int32)_settings.MaximumSize.X + borderWidth; + minMax->ptMaxTrackSize.y = (int32)_settings.MaximumSize.Y + borderHeight; + } // Include Windows task bar size into maximized tool window WINDOWPLACEMENT e; @@ -1296,4 +1308,610 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(_handle, msg, wParam, lParam); } +#if USE_EDITOR + +HGLOBAL duplicateGlobalMem(HGLOBAL hMem) +{ + auto len = GlobalSize(hMem); + auto source = GlobalLock(hMem); + auto dest = GlobalAlloc(GMEM_FIXED, len); + Platform::MemoryCopy(dest, source, len); + GlobalUnlock(hMem); + return dest; +} + +DWORD dropEffect2OleEnum(DragDropEffect effect) +{ + DWORD result; + switch (effect) + { + case DragDropEffect::None: + result = DROPEFFECT_NONE; + break; + case DragDropEffect::Copy: + result = DROPEFFECT_COPY; + break; + case DragDropEffect::Move: + result = DROPEFFECT_MOVE; + break; + case DragDropEffect::Link: + result = DROPEFFECT_LINK; + break; + default: + result = DROPEFFECT_NONE; + break; + } + return result; +} + +DragDropEffect dropEffectFromOleEnum(DWORD effect) +{ + DragDropEffect result; + switch (effect) + { + case DROPEFFECT_NONE: + result = DragDropEffect::None; + break; + case DROPEFFECT_COPY: + result = DragDropEffect::Copy; + break; + case DROPEFFECT_MOVE: + result = DragDropEffect::Move; + break; + case DROPEFFECT_LINK: + result = DragDropEffect::Link; + break; + default: + result = DragDropEffect::None; + break; + } + return result; +} + +HANDLE StringToHandle(const StringView& str) +{ + // Allocate and lock a global memory buffer. + // Make it fixed data so we don't have to use GlobalLock + const int32 length = str.Length(); + char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1)); + + // Copy the string into the buffer as ANSI text + StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length); + ptr[length] = '\0'; + + return ptr; +} + +void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source) +{ + // Copy the source FORMATETC into dest + *dest = *source; + + if (source->ptd) + { + // Allocate memory for the DVTARGETDEVICE if necessary + dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE))); + + // Copy the contents of the source DVTARGETDEVICE into dest->ptd + *(dest->ptd) = *(source->ptd); + } +} + +HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc); + +/// +/// GUI data for Windows platform +/// +class WindowsGuiData : public IGuiData +{ +private: + Type _type; + Array _data; + +public: + /// + /// Init + /// + WindowsGuiData() + : _type(Type::Unknown) + , _data(1) + { + } + +public: + /// + /// Init from Ole IDataObject + /// + /// Object + void Init(IDataObject* pDataObj) + { + // Temporary data + FORMATETC fmtetc; + STGMEDIUM stgmed; + + // Clear + _type = Type::Unknown; + _data.Clear(); + + // Check type + fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) + { + // Text + _type = Type::Text; + + // Get data + char* text = static_cast(GlobalLock(stgmed.hGlobal)); + _data.Add(String(text)); + GlobalUnlock(stgmed.hGlobal); + ReleaseStgMedium(&stgmed); + } + else + { + fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) + { + // Unicode Text + _type = Type::Text; + + // Get data + Char* text = static_cast(GlobalLock(stgmed.hGlobal)); + _data.Add(String(text)); + GlobalUnlock(stgmed.hGlobal); + ReleaseStgMedium(&stgmed); + } + else + { + fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) + { + // Files + _type = Type::Files; + + // Get data + Char item[MAX_PATH]; + HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal)); + UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); + for (UINT i = 0; i < filesCount; i++) + { + if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0) + { + _data.Add(String(item)); + } + } + GlobalUnlock(stgmed.hGlobal); + ReleaseStgMedium(&stgmed); + } + } + } + } + +public: + // [IGuiData] + Type GetType() const override + { + return _type; + } + String GetAsText() const override + { + String result; + if (_type == Type::Text) + { + result = _data[0]; + } + return result; + } + void GetAsFiles(Array* files) const override + { + if (_type == Type::Files) + { + files->Add(_data); + } + } +}; + +/// +/// Tool class for Windows Ole support +/// +class WindowsEnumFormatEtc : public IEnumFORMATETC +{ +private: + ULONG _refCount; + ULONG _index; + ULONG _formatsCount; + FORMATETC* _formatEtc; + +public: + WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats) + : _refCount(1) + , _index(0) + , _formatsCount(nNumFormats) + , _formatEtc(nullptr) + { + // Allocate memory + _formatEtc = new FORMATETC[nNumFormats]; + + // Copy the FORMATETC structures + for (int32 i = 0; i < nNumFormats; i++) + { + DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]); + } + } + + ~WindowsEnumFormatEtc() + { + if (_formatEtc) + { + for (uint32 i = 0; i < _formatsCount; i++) + { + if (_formatEtc[i].ptd) + { + CoTaskMemFree(_formatEtc[i].ptd); + } + } + + delete[] _formatEtc; + } + } + +public: + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override + { + // Check to see what interface has been requested + if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown) + { + AddRef(); + *ppvObject = this; + return S_OK; + } + + // No interface + *ppvObject = nullptr; + return E_NOINTERFACE; + } + ULONG STDMETHODCALLTYPE AddRef() override + { + _InterlockedIncrement(&_refCount); + return _refCount; + } + ULONG STDMETHODCALLTYPE Release() override + { + ULONG ulRefCount = _InterlockedDecrement(&_refCount); + if (_refCount == 0) + { + delete this; + } + return ulRefCount; + } + + // [IEnumFormatEtc] + HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override + { + ULONG copied = 0; + + // validate arguments + if (celt == 0 || pFormatEtc == nullptr) + return E_INVALIDARG; + + // copy FORMATETC structures into caller's buffer + while (_index < _formatsCount && copied < celt) + { + DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]); + copied++; + _index++; + } + + // store result + if (pceltFetched != nullptr) + *pceltFetched = copied; + + // did we copy all that was requested? + return (copied == celt) ? S_OK : S_FALSE; + } + HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override + { + _index += celt; + return (_index <= _formatsCount) ? S_OK : S_FALSE; + } + HRESULT STDMETHODCALLTYPE Reset() override + { + _index = 0; + return S_OK; + } + HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override + { + HRESULT result; + + // Make a duplicate enumerator + result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc); + + if (result == S_OK) + { + // Manually set the index state + static_cast(*ppEnumFormatEtc)->_index = _index; + } + + return result; + } +}; + +HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc) +{ + if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr) + return E_INVALIDARG; + *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats); + return *ppEnumFormatEtc ? S_OK : E_OUTOFMEMORY; +} + +/// +/// Drag drop source and data container for Ole +/// +class WindowsDragSource : public IDataObject, public IDropSource +{ +private: + ULONG _refCount; + int32 _formatsCount; + FORMATETC* _formatEtc; + STGMEDIUM* _stgMedium; + +public: + WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count) + : _refCount(1) + , _formatsCount(count) + , _formatEtc(nullptr) + , _stgMedium(nullptr) + { + // Allocate memory + _formatEtc = new FORMATETC[count]; + _stgMedium = new STGMEDIUM[count]; + + // Copy descriptors + for (int32 i = 0; i < count; i++) + { + _formatEtc[i] = fmtetc[i]; + _stgMedium[i] = stgmed[i]; + } + } + + virtual ~WindowsDragSource() + { + delete[] _formatEtc; + delete[] _stgMedium; + } + +public: + // [IUnknown] + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override + { + // Check to see what interface has been requested + if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource) + { + AddRef(); + *ppvObject = this; + return S_OK; + } + + // No interface + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() override + { + _InterlockedIncrement(&_refCount); + return _refCount; + } + + ULONG STDMETHODCALLTYPE Release() override + { + ULONG ulRefCount = _InterlockedDecrement(&_refCount); + if (_refCount == 0) + { + delete this; + } + return ulRefCount; + } + + // [IDropSource] + HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override + { + // If the Escape key has been pressed since the last call, cancel the drop + if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON) + return DRAGDROP_S_CANCEL; + + // If the LeftMouse button has been released, then do the drop! + if ((grfKeyState & MK_LBUTTON) == 0) + return DRAGDROP_S_DROP; + + // Continue with the drag-drop + return S_OK; + } + HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override + { + // TODO: allow to use custom mouse cursor during drop and drag operation + return DRAGDROP_S_USEDEFAULTCURSORS; + } + + // [IDataObject] + HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override + { + if (pformatetcIn == nullptr || pmedium == nullptr) + return E_INVALIDARG; + + // Try to match the specified FORMATETC with one of our supported formats + int32 index = lookupFormatEtc(pformatetcIn); + if (index == INVALID_INDEX) + return DV_E_FORMATETC; + + // Found a match - transfer data into supplied storage medium + pmedium->tymed = _formatEtc[index].tymed; + pmedium->pUnkForRelease = nullptr; + + // Copy the data into the caller's storage medium + switch (_formatEtc[index].tymed) + { + case TYMED_HGLOBAL: + pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal); + break; + + default: + return DV_E_FORMATETC; + } + + return S_OK; + } + HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override + { + return DATA_E_FORMATETC; + } + HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override + { + return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK; + } + HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override + { + // Apparently we have to set this field to NULL even though we don't do anything else + pformatetcOut->ptd = nullptr; + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override + { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override + { + // Only the get direction is supported for OLE + if (dwDirection == DATADIR_GET) + { + // TODO: use SHCreateStdEnumFmtEtc API call + return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc); + } + + // The direction specified is not supported for drag+drop + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override + { + return OLE_E_ADVISENOTSUPPORTED; + } + HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override + { + return OLE_E_ADVISENOTSUPPORTED; + } + HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override + { + return OLE_E_ADVISENOTSUPPORTED; + } + +private: + int32 lookupFormatEtc(FORMATETC* pFormatEtc) const + { + // Check each of our formats in turn to see if one matches + for (int32 i = 0; i < _formatsCount; i++) + { + if ((_formatEtc[i].tymed & pFormatEtc->tymed) && + _formatEtc[i].cfFormat == pFormatEtc->cfFormat && + _formatEtc[i].dwAspect == pFormatEtc->dwAspect) + { + // Return index of stored format + return i; + } + } + + // Format not found + return INVALID_INDEX; + } +}; + +WindowsGuiData GuiDragDropData; + +DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) +{ + // Create background worker that will keep updating GUI (perform rendering) + const auto task = New(); + Task::StartNew(task); + while (task->GetState() == TaskState::Queued) + Platform::Sleep(1); + + // Create descriptors + FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr }; + + // Create a HGLOBAL inside the storage medium + stgmed.hGlobal = StringToHandle(data); + + // Create drop source + auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1); + + // Do the drag drop operation + DWORD dwEffect; + HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect); + + // Wait for job end + Platform::AtomicStore(&task->ExitFlag, 1); + task->Wait(); + + // Release allocated data + dropSource->Release(); + ReleaseStgMedium(&stgmed); + + // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop) + if (Input::GetMouseButton(MouseButton::Left)) + { + ::POINT point; + ::GetCursorPos(&point); + Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this); + } + + return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None; +} + +HRESULT WindowsWindow::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) +{ + POINT p = { pt.x, pt.y }; + ::ScreenToClient(_handle, &p); + GuiDragDropData.Init((IDataObject*)pDataObj); + DragDropEffect effect = DragDropEffect::None; + OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); + Focus(); + *pdwEffect = dropEffect2OleEnum(effect); + return S_OK; +} + +HRESULT WindowsWindow::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) +{ + POINT p = { pt.x, pt.y }; + ::ScreenToClient(_handle, &p); + DragDropEffect effect = DragDropEffect::None; + OnDragOver(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); + *pdwEffect = dropEffect2OleEnum(effect); + return S_OK; +} + +HRESULT WindowsWindow::DragLeave() +{ + OnDragLeave(); + return S_OK; +} + +HRESULT WindowsWindow::Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect) +{ + POINT p = { pt.x, pt.y }; + ::ScreenToClient(_handle, &p); + GuiDragDropData.Init((IDataObject*)pDataObj); + DragDropEffect effect = DragDropEffect::None; + OnDragDrop(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect); + *pdwEffect = dropEffect2OleEnum(effect); + return S_OK; +} + +#else + +DragDropEffect WindowsWindow::DoDragDrop(const StringView& data) +{ + return DragDropEffect::None; +} + +#endif + #endif diff --git a/Source/Engine/Renderer/Config.h b/Source/Engine/Renderer/Config.h index 659369a71..876da3100 100644 --- a/Source/Engine/Renderer/Config.h +++ b/Source/Engine/Renderer/Config.h @@ -114,6 +114,3 @@ PACK_STRUCT(struct ProbeData { // Maximum amount of directional light cascades (using CSM technique) #define MAX_CSM_CASCADES 4 - -// Default format for the shadow map textures -#define SHADOW_MAPS_FORMAT PixelFormat::D16_UNorm diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index edac958bc..833daed8e 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -343,6 +343,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Prepare renderContext.View.Prepare(renderContext); renderContext.Buffers->Prepare(); + ShadowsPass::Instance()->Prepare(); // Build batch of render contexts (main view and shadow projections) { diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 7e0cd1053..ba326b728 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -4,11 +4,11 @@ #include "GBufferPass.h" #include "VolumetricFogPass.h" #include "Engine/Graphics/Graphics.h" +#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Content/Content.h" -#include "Engine/Graphics/GPUContext.h" #include "Engine/Scripting/Enums.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" @@ -81,19 +81,22 @@ bool ShadowsPass::Init() _shader.Get()->OnReloading.Bind(this); #endif - // If GPU doesn't support linear sampling for the shadow map then fallback to the single sample on lowest quality - const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(SHADOW_MAPS_FORMAT, false); - const auto formatFeaturesDepth = GPUDevice::Instance->GetFormatFeatures(SHADOW_MAPS_FORMAT); - const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture); - _supportsShadows = EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D) - && EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison); - // TODO: fallback to 32-bit shadow map format if 16-bit is not supported - if (!_supportsShadows) + // Select format for shadow maps + _shadowMapFormat = PixelFormat::Unknown; + for (const PixelFormat format : { PixelFormat::D16_UNorm, PixelFormat::D24_UNorm_S8_UInt, PixelFormat::D32_Float }) { - LOG(Warning, "GPU doesn't support shadows rendering"); - LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(SHADOW_MAPS_FORMAT), (uint32)formatFeaturesDepth.Support); - LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(formatTexture), (uint32)formatFeaturesTexture.Support); + const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(format, false); + const auto formatFeaturesDepth = GPUDevice::Instance->GetFormatFeatures(format); + const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture); + if (EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D) && + EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison)) + { + _shadowMapFormat = format; + break; + } } + if (_shadowMapFormat == PixelFormat::Unknown) + LOG(Warning, "GPU doesn't support shadows rendering"); return false; } @@ -148,7 +151,7 @@ void ShadowsPass::updateShadowMapSize() // Select new size _currentShadowMapsQuality = Graphics::ShadowMapsQuality; - if (_supportsShadows) + if (_shadowMapFormat != PixelFormat::Unknown) { switch (_currentShadowMapsQuality) { @@ -174,18 +177,18 @@ void ShadowsPass::updateShadowMapSize() // Check if size will change if (newSizeCSM > 0 && newSizeCSM != _shadowMapsSizeCSM) { - if (_shadowMapCSM->Init(GPUTextureDescription::New2D(newSizeCSM, newSizeCSM, SHADOW_MAPS_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil, 1, MAX_CSM_CASCADES))) + if (_shadowMapCSM->Init(GPUTextureDescription::New2D(newSizeCSM, newSizeCSM, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil, 1, MAX_CSM_CASCADES))) { - LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("CSM"), newSizeCSM, (int32)SHADOW_MAPS_FORMAT); + LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("CSM"), newSizeCSM, ScriptingEnum::ToString(_shadowMapFormat)); return; } _shadowMapsSizeCSM = newSizeCSM; } if (newSizeCube > 0 && newSizeCube != _shadowMapsSizeCube) { - if (_shadowMapCube->Init(GPUTextureDescription::NewCube(newSizeCube, SHADOW_MAPS_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil))) + if (_shadowMapCube->Init(GPUTextureDescription::NewCube(newSizeCube, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil))) { - LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("Cube"), newSizeCube, (int32)SHADOW_MAPS_FORMAT); + LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("Cube"), newSizeCube, ScriptingEnum::ToString(_shadowMapFormat)); return; } _shadowMapsSizeCube = newSizeCube; @@ -546,10 +549,17 @@ void ShadowsPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_shadowMapCube); } +void ShadowsPass::Prepare() +{ + // Clear cached data + _shadowData.Clear(); + LastDirLightIndex = -1; + LastDirLightShadowMap = nullptr; +} + void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch) { PROFILE_CPU(); - _shadowData.Clear(); auto& view = renderContext.View; // Update shadow map @@ -586,7 +596,7 @@ bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const Rend const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - return fade > ZeroTolerance && _supportsShadows; + return fade > ZeroTolerance && _shadowMapFormat != PixelFormat::Unknown; } bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RendererSpotLightData& light) @@ -598,12 +608,12 @@ bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const Rend const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - return fade > ZeroTolerance && _supportsShadows; + return fade > ZeroTolerance && _shadowMapFormat != PixelFormat::Unknown; } bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RendererDirectionalLightData& light) { - return _supportsShadows; + return _shadowMapFormat != PixelFormat::Unknown; } void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererPointLightData& light, GPUTextureView* shadowMask) diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index 176fbd9a0..d3589d008 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -55,7 +55,7 @@ private: GPUPipelineStatePermutationsPs(Quality::MAX) * 2 * 2> _psShadowDir; GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowPoint; GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowSpot; - bool _supportsShadows; + PixelFormat _shadowMapFormat; // Shadow maps stuff int32 _shadowMapsSizeCSM; @@ -94,6 +94,8 @@ public: LightShadowData LastDirLight; public: + void Prepare(); + /// /// Setups the shadows rendering for batched scene drawing. Checks which lights will cast a shadow. /// diff --git a/Source/Engine/Scripting/Attributes/PluginLoadOrder.cs b/Source/Engine/Scripting/Attributes/PluginLoadOrder.cs new file mode 100644 index 000000000..18a6a1dac --- /dev/null +++ b/Source/Engine/Scripting/Attributes/PluginLoadOrder.cs @@ -0,0 +1,21 @@ +using System; + +namespace FlaxEngine; + +/// +/// This attribute allows for specifying initialization and deinitialization order for plugins. +/// +[Serializable] +[AttributeUsage(AttributeTargets.Class)] +public class PluginLoadOrderAttribute : Attribute +{ + /// + /// The plugin type to initialize this plugin after. + /// + public Type InitializeAfter; + + /// + /// The plugin type to deinitialize this plugin before. + /// + public Type DeinitializeBefore; +} diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp index a8198474c..61d7bc698 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.cpp +++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp @@ -13,6 +13,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Engine/EngineService.h" #include "Engine/Core/Log.h" +#include "Engine/Scripting/ManagedCLR/MField.h" Plugin::Plugin(const SpawnParams& params) : ScriptingObject(params) @@ -74,14 +75,57 @@ Action PluginManager::PluginsChanged; namespace PluginManagerImpl { + bool Initialized = false; Array GamePlugins; Array EditorPlugins; - void LoadPlugin(MClass* klass, bool isEditor); void OnAssemblyLoaded(MAssembly* assembly); void OnAssemblyUnloading(MAssembly* assembly); void OnBinaryModuleLoaded(BinaryModule* module); void OnScriptsReloading(); + void InitializePlugins(); + void DeinitializePlugins(); + + template + Array SortPlugins(Array plugins, MClass* pluginLoadOrderAttribute, MField* typeField) + { + // Sort plugins + Array newPlugins; + for (int i = 0; i < plugins.Count(); i++) + { + PluginType* plugin = plugins[i]; + int32 insertIndex = -1; + for (int j = 0; j < newPlugins.Count(); j++) + { + // Get first instance where a game plugin needs another one before it + auto attribute = newPlugins[j]->GetClass()->GetAttribute(pluginLoadOrderAttribute); + if (attribute == nullptr || MCore::Object::GetClass(attribute) != pluginLoadOrderAttribute) + continue; + + // Check if attribute references a valid class + MTypeObject* refType = nullptr; + typeField->GetValue(attribute, &refType); + if (refType == nullptr) + continue; + + MType* type = INTERNAL_TYPE_OBJECT_GET(refType); + if (type == nullptr) + continue; + MClass* typeClass = MCore::Type::GetClass(type); + + if (plugin->GetClass() == typeClass) + { + insertIndex = j; + break; + } + } + if (insertIndex == -1) + newPlugins.Add(plugin); + else + newPlugins.Insert(insertIndex, plugin); + } + return newPlugins; + } } using namespace PluginManagerImpl; @@ -107,7 +151,7 @@ void PluginManagerService::InvokeInitialize(Plugin* plugin) { if (plugin->_initialized) return; - StringAnsiView typeName = plugin->GetType().GetName(); + const StringAnsiView typeName = plugin->GetType().GetName(); PROFILE_CPU(); ZoneName(typeName.Get(), typeName.Length()); @@ -125,7 +169,7 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin) { if (!plugin->_initialized) return; - StringAnsiView typeName = plugin->GetType().GetName(); + const StringAnsiView typeName = plugin->GetType().GetName(); PROFILE_CPU(); ZoneName(typeName.Get(), typeName.Length()); @@ -139,30 +183,6 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin) PluginManager::PluginUnloaded(plugin); } -void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor) -{ - // Create and check if use it - auto plugin = (Plugin*)Scripting::NewObject(klass); - if (!plugin) - return; - - if (!isEditor) - { - GamePlugins.Add((GamePlugin*)plugin); -#if !USE_EDITOR - PluginManagerService::InvokeInitialize(plugin); -#endif - } -#if USE_EDITOR - else - { - EditorPlugins.Add(plugin); - PluginManagerService::InvokeInitialize(plugin); - } -#endif - PluginManager::PluginsChanged(); -} - void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly) { PROFILE_CPU_NAMED("Load Assembly Plugins"); @@ -183,6 +203,7 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly) #endif // Process all classes to find plugins + bool loadedAnyPlugin = false; auto& classes = assembly->GetClasses(); for (auto i = classes.Begin(); i.IsNotEnd(); ++i) { @@ -192,40 +213,69 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly) if (mclass->IsGeneric() || mclass->IsStatic() || mclass->IsAbstract()) continue; - if (mclass->IsSubClassOf(gamePluginClass)) - { - LoadPlugin(mclass, false); - } - + if (mclass->IsSubClassOf(gamePluginClass) #if USE_EDITOR - if (mclass->IsSubClassOf(editorPluginClass)) - { - LoadPlugin(mclass, true); - } + || mclass->IsSubClassOf(editorPluginClass) #endif + ) + { + auto plugin = (Plugin*)Scripting::NewObject(mclass); + if (plugin) + { +#if USE_EDITOR + if (mclass->IsSubClassOf(editorPluginClass)) + { + EditorPlugins.Add(plugin); + } + else +#endif + { + GamePlugins.Add((GamePlugin*)plugin); + } + loadedAnyPlugin = true; + } + } + } + + // Send event and initialize newly added plugins (ignore during startup) + if (loadedAnyPlugin && Initialized) + { + InitializePlugins(); + PluginManager::PluginsChanged(); } } void PluginManagerImpl::OnAssemblyUnloading(MAssembly* assembly) { bool changed = false; - for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--) + + auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; + auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); + auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); + ASSERT(beforeTypeField); + +#if USE_EDITOR + auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField); + for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--) { - auto plugin = EditorPlugins[i]; + auto plugin = editorPlugins[i]; if (plugin->GetType().ManagedClass->GetAssembly() == assembly) { PluginManagerService::InvokeDeinitialize(plugin); - EditorPlugins.RemoveAtKeepOrder(i); + EditorPlugins.Remove(plugin); changed = true; } } - for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--) +#endif + + auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); + for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--) { - auto plugin = GamePlugins[i]; + auto plugin = gamePlugins[i]; if (plugin->GetType().ManagedClass->GetAssembly() == assembly) { PluginManagerService::InvokeDeinitialize(plugin); - GamePlugins.RemoveAtKeepOrder(i); + GamePlugins.Remove(plugin); changed = true; } } @@ -260,38 +310,83 @@ void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module) void PluginManagerImpl::OnScriptsReloading() { // When scripting is reloading (eg. for hot-reload in Editor) we have to deinitialize plugins (Scripting service destroys C# objects later on) - bool changed = false; - for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--) + DeinitializePlugins(); +} + +void PluginManagerImpl::InitializePlugins() +{ + if (EditorPlugins.Count() + GamePlugins.Count() == 0) + return; + PROFILE_CPU_NAMED("InitializePlugins"); + + auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; + auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); + auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter"); + ASSERT(afterTypeField); + +#if USE_EDITOR + auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, afterTypeField); + for (auto plugin : editorPlugins) { - auto plugin = EditorPlugins[i]; - { - PluginManagerService::InvokeDeinitialize(plugin); - EditorPlugins.RemoveAtKeepOrder(i); - changed = true; - } + PluginManagerService::InvokeInitialize(plugin); } - for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--) +#else + // Game plugins are managed via InitializeGamePlugins/DeinitializeGamePlugins by Editor for play mode + auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField); + for (auto plugin : gamePlugins) { - auto plugin = GamePlugins[i]; - { - PluginManagerService::InvokeDeinitialize(plugin); - GamePlugins.RemoveAtKeepOrder(i); - changed = true; - } + PluginManagerService::InvokeInitialize(plugin); } - if (changed) - PluginManager::PluginsChanged(); +#endif +} + +void PluginManagerImpl::DeinitializePlugins() +{ + if (EditorPlugins.Count() + GamePlugins.Count() == 0) + return; + PROFILE_CPU_NAMED("DeinitializePlugins"); + + auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; + auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); + auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); + ASSERT(beforeTypeField); + +#if USE_EDITOR + auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField); + for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--) + { + auto plugin = editorPlugins[i]; + PluginManagerService::InvokeDeinitialize(plugin); + EditorPlugins.Remove(plugin); + } +#endif + + auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); + for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--) + { + auto plugin = gamePlugins[i]; + PluginManagerService::InvokeDeinitialize(plugin); + GamePlugins.Remove(plugin); + } + + PluginManager::PluginsChanged(); } bool PluginManagerService::Init() { + Initialized = false; + // Process already loaded modules for (auto module : BinaryModule::GetModules()) { OnBinaryModuleLoaded(module); } + // Invoke plugins initialization for all of them + InitializePlugins(); + // Register for new binary modules load actions + Initialized = true; Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded); Scripting::ScriptsReloading.Bind(&OnScriptsReloading); @@ -300,28 +395,13 @@ bool PluginManagerService::Init() void PluginManagerService::Dispose() { + // Unregister from new modules loading + Initialized = false; Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded); Scripting::ScriptsReloading.Unbind(&OnScriptsReloading); // Cleanup all plugins - PROFILE_CPU_NAMED("Dispose Plugins"); - const int32 pluginsCount = EditorPlugins.Count() + GamePlugins.Count(); - if (pluginsCount == 0) - return; - LOG(Info, "Unloading {0} plugins", pluginsCount); - for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--) - { - auto plugin = EditorPlugins[i]; - InvokeDeinitialize(plugin); - EditorPlugins.RemoveAtKeepOrder(i); - } - for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--) - { - auto plugin = GamePlugins[i]; - InvokeDeinitialize(plugin); - GamePlugins.RemoveAtKeepOrder(i); - } - PluginManager::PluginsChanged(); + DeinitializePlugins(); } const Array& PluginManager::GetGamePlugins() @@ -336,11 +416,13 @@ const Array& PluginManager::GetEditorPlugins() Plugin* PluginManager::GetPlugin(const StringView& name) { +#if USE_EDITOR for (Plugin* p : EditorPlugins) { if (p->GetDescription().Name == name) return p; } +#endif for (GamePlugin* gp : GamePlugins) { if (gp->GetDescription().Name == name) @@ -352,11 +434,13 @@ Plugin* PluginManager::GetPlugin(const StringView& name) Plugin* PluginManager::GetPlugin(const MClass* type) { CHECK_RETURN(type, nullptr); +#if USE_EDITOR for (Plugin* p : EditorPlugins) { if (p->GetClass()->IsSubClassOf(type)) return p; } +#endif for (GamePlugin* gp : GamePlugins) { if (gp->GetClass()->IsSubClassOf(type)) @@ -368,11 +452,13 @@ Plugin* PluginManager::GetPlugin(const MClass* type) Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type) { CHECK_RETURN(type, nullptr); +#if USE_EDITOR for (Plugin* p : EditorPlugins) { if (p->Is(type)) return p; } +#endif for (GamePlugin* gp : GamePlugins) { if (gp->Is(type)) @@ -386,18 +472,32 @@ Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type) void PluginManager::InitializeGamePlugins() { PROFILE_CPU(); - for (int32 i = 0; i < GamePlugins.Count(); i++) + + auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; + auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); + auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter"); + ASSERT(afterTypeField); + + auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField); + for (int32 i = 0; i < gamePlugins.Count(); i++) { - PluginManagerService::InvokeInitialize(GamePlugins[i]); + PluginManagerService::InvokeInitialize(gamePlugins[i]); } } void PluginManager::DeinitializeGamePlugins() { PROFILE_CPU(); - for (int32 i = GamePlugins.Count() - 1; i >= 0; i--) + + auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; + auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); + auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); + ASSERT(beforeTypeField); + + auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); + for (int32 i = gamePlugins.Count() - 1; i >= 0; i--) { - PluginManagerService::InvokeDeinitialize(GamePlugins[i]); + PluginManagerService::InvokeDeinitialize(gamePlugins[i]); } } diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 47f9b0a7c..200efa650 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -183,22 +183,15 @@ Dictionary CachedAssemblyHandles; /// void* GetStaticMethodPointer(const String& methodName); -/// -/// Calls the managed static method in NativeInterop class with given parameters. -/// -template -FORCE_INLINE RetType CallStaticMethodByName(const String& methodName, Args... args) -{ - typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...); - return ((fun)GetStaticMethodPointer(methodName))(args...); -} - /// /// Calls the managed static method with given parameters. /// template FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args) { +#if DOTNET_HOST_MONO + ASSERT_LOW_LAYER(mono_domain_get()); // Ensure that Mono runtime has been attached to this thread +#endif typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...); return ((fun)methodPtr)(args...); } @@ -274,7 +267,7 @@ bool MCore::LoadEngine() return true; // Prepare managed side - CallStaticMethodByName(TEXT("Init")); + CallStaticMethod(GetStaticMethodPointer(TEXT("Init"))); #ifdef MCORE_MAIN_MODULE_NAME // MCORE_MAIN_MODULE_NAME define is injected by Scripting.Build.cs on platforms that use separate shared library for engine symbols ::String flaxLibraryPath(Platform::GetMainDirectory() / TEXT(MACRO_TO_STR(MCORE_MAIN_MODULE_NAME))); @@ -293,7 +286,8 @@ bool MCore::LoadEngine() MRootDomain = New("Root"); MDomains.Add(MRootDomain); - char* buildInfo = CallStaticMethodByName(TEXT("GetRuntimeInformation")); + void* GetRuntimeInformationPtr = GetStaticMethodPointer(TEXT("GetRuntimeInformation")); + char* buildInfo = CallStaticMethod(GetRuntimeInformationPtr); LOG(Info, ".NET runtime version: {0}", ::String(buildInfo)); MCore::GC::FreeMemory(buildInfo); @@ -305,7 +299,7 @@ void MCore::UnloadEngine() if (!MRootDomain) return; PROFILE_CPU(); - CallStaticMethodByName(TEXT("Exit")); + CallStaticMethod(GetStaticMethodPointer(TEXT("Exit"))); MDomains.ClearDelete(); MRootDomain = nullptr; ShutdownHostfxr(); @@ -523,8 +517,7 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem) void MCore::Thread::Attach() { - // TODO: find a way to properly register native thread so Mono Stop The World (stw) won't freeze when native threads (eg. Job System) are running native code only -#if DOTNET_HOST_MONO && 0 +#if DOTNET_HOST_MONO if (!IsInMainThread() && !mono_domain_get()) { mono_thread_attach(MonoDomainHandle); @@ -1793,6 +1786,7 @@ void* GetStaticMethodPointer(const String& methodName) void* fun; if (CachedFunctions.TryGet(methodName, fun)) return fun; + PROFILE_CPU(); const int rc = get_function_pointer(NativeInteropTypeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun); if (rc != 0) LOG(Fatal, "Failed to get unmanaged function pointer for method {0}: 0x{1:x}", methodName.Get(), (unsigned int)rc); @@ -2014,6 +2008,9 @@ bool InitHostfxr() //Platform::SetEnvironmentVariable(TEXT("MONO_GC_DEBUG"), TEXT("6:gc-log.txt,check-remset-consistency,nursery-canaries")); #endif + // Adjust GC threads suspending mode to not block attached native threads (eg. Job System) + Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("preemptive")); + #if defined(USE_MONO_AOT_MODE) // Enable AOT mode (per-platform) mono_jit_set_aot_mode(USE_MONO_AOT_MODE); @@ -2166,6 +2163,7 @@ void* GetStaticMethodPointer(const String& methodName) void* fun; if (CachedFunctions.TryGet(methodName, fun)) return fun; + PROFILE_CPU(); static MonoClass* nativeInteropClass = nullptr; if (!nativeInteropClass) diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index a57833314..bdf9f60f5 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -28,8 +28,8 @@ #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" +#include "Engine/Engine/Time.h" #include "Engine/Graphics/RenderTask.h" -#include "Engine/Platform/MemoryStats.h" #include "Engine/Serialization/JsonTools.h" extern void registerFlaxEngineInternalCalls(); @@ -193,6 +193,14 @@ void ScriptingService::Update() { PROFILE_CPU_NAMED("Scripting::Update"); INVOKE_EVENT(Update); + +#ifdef USE_NETCORE + // Force GC to run in background periodically to avoid large blocking collections causing hitches + if (Time::Update.TicksCount % 60 == 0) + { + MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false); + } +#endif } void ScriptingService::LateUpdate() diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 67ba854e0..0630985f3 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -135,13 +135,10 @@ namespace FlaxEngine { if (e.ExceptionObject is Exception exception) { + Debug.LogError($"Unhandled Exception: {exception.Message}"); + Debug.LogException(exception); if (e.IsTerminating && !System.Diagnostics.Debugger.IsAttached) Platform.Fatal($"Unhandled Exception: {exception}"); - else - { - Debug.LogError($"Unhandled Exception: {exception.Message}"); - Debug.LogException(exception); - } } } diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h index 0012c89ba..0945d6fc2 100644 --- a/Source/Engine/Scripting/ScriptingObjectReference.h +++ b/Source/Engine/Scripting/ScriptingObjectReference.h @@ -47,8 +47,12 @@ public: /// ~ScriptingObjectReferenceBase() { - if (_object) - _object->Deleted.Unbind(this); + ScriptingObject* obj = _object; + if (obj) + { + _object = nullptr; + obj->Deleted.Unbind(this); + } } public: diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h index 3366dbbc7..174cf4188 100644 --- a/Source/Engine/Scripting/SoftObjectReference.h +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -38,8 +38,12 @@ public: /// ~SoftObjectReferenceBase() { - if (_object) - _object->Deleted.Unbind(this); + ScriptingObject* obj = _object; + if (obj) + { + _object = nullptr; + obj->Deleted.Unbind(this); + } } public: diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index c0fda4d6e..da67ef3b4 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -3,6 +3,7 @@ #if COMPILE_WITH_SHADER_COMPILER #include "ShaderCompiler.h" +#include "ShadersCompilation.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Engine/Globals.h" @@ -14,10 +15,6 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Utilities/StringConverter.h" -#if USE_EDITOR -#include "Editor/Editor.h" -#include "Editor/ProjectInfo.h" -#endif namespace IncludedFiles { @@ -30,31 +27,6 @@ namespace IncludedFiles CriticalSection Locker; Dictionary Files; - -#if USE_EDITOR - bool FindProject(const ProjectInfo* project, HashSet& projects, const StringView& projectName, String& path) - { - if (!project || projects.Contains(project)) - return false; - projects.Add(project); - - // Check the project name - if (project->Name == projectName) - { - path = project->ProjectFolderPath; - return true; - } - - // Initialize referenced projects - for (const auto& reference : project->References) - { - if (reference.Project && FindProject(reference.Project, projects, projectName, path)) - return true; - } - - return false; - } -#endif } bool ShaderCompiler::Compile(ShaderCompilationContext* context) @@ -124,7 +96,8 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context) output->WriteInt32(context->Includes.Count()); for (auto& include : context->Includes) { - output->WriteString(include.Item, 11); + String compactPath = ShadersCompilation::CompactShaderPath(include.Item); + output->WriteString(compactPath, 11); const auto date = FileSystem::GetFileLastEditTime(include.Item); output->Write(date); } @@ -138,75 +111,17 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co source = nullptr; sourceLength = 0; - // Skip to the last root start './' but preserve the leading one - const int32 includedFileLength = StringUtils::Length(includedFile); - for (int32 i = includedFileLength - 2; i >= 2; i--) + // Get actual file path + const String includedFileName(includedFile); + String path = ShadersCompilation::ResolveShaderPath(includedFileName); + if (!FileSystem::FileExists(path)) { - if (StringUtils::Compare(includedFile + i, "./", 2) == 0) - { - includedFile = includedFile + i; - break; - } + LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", includedFileName, String(sourceFile), String::Empty); + return true; } ScopeLock lock(IncludedFiles::Locker); - // Find the included file path - String path; -#if USE_EDITOR - if (StringUtils::Compare(includedFile, "./", 2) == 0) - { - int32 projectNameEnd = -1; - for (int32 i = 2; i < includedFileLength; i++) - { - if (includedFile[i] == '/') - { - projectNameEnd = i; - break; - } - } - if (projectNameEnd == -1) - { - LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), TEXT("Missing project name after root path.")); - return true; - } - const StringAsUTF16<120> projectName(includedFile + 2, projectNameEnd - 2); - if (StringUtils::Compare(projectName.Get(), TEXT("FlaxPlatforms")) == 0) - { - // Hard-coded redirect to platform-specific includes - path /= Globals::StartupFolder / TEXT("Source/Platforms"); - } - else - { - HashSet projects; - if (!IncludedFiles::FindProject(Editor::Project, projects, StringView(projectName.Get(), projectNameEnd - 2), path)) - { - LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), TEXT("Failed to find the project of the given name.")); - return true; - } - path /= TEXT("Source/Shaders/"); - } - const StringAsUTF16<250> localPath(includedFile + projectNameEnd + 1, includedFileLength - projectNameEnd - 1); - path /= localPath.Get(); - } -#else - if (StringUtils::Compare(includedFile, "./Flax/", 7) == 0) - { - // Engine project relative shader path - const auto includedFileStr = String(includedFile + 6); - path = Globals::StartupFolder / TEXT("Source/Shaders") / includedFileStr; - } -#endif - else if (FileSystem::FileExists(path = String(includedFile))) - { - // Absolute shader path - } - else - { - LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), String::Empty); - return true; - } - // Try to reuse file IncludedFiles::File* result = nullptr; if (!IncludedFiles::Files.TryGet(path, result) || FileSystem::GetFileLastEditTime(path) > result->LastEditTime) diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index de2ba6e54..793a3c1c3 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -33,7 +33,6 @@ #include "Editor/Editor.h" #include "Editor/ProjectInfo.h" #endif - #if COMPILE_WITH_D3D_SHADER_COMPILER #include "DirectX/ShaderCompilerD3D.h" #endif @@ -55,6 +54,49 @@ namespace ShadersCompilationImpl CriticalSection Locker; Array Compilers; Array ReadyCompilers; + +#if USE_EDITOR + const ProjectInfo* FindProjectByName(const ProjectInfo* project, HashSet& projects, const StringView& projectName) + { + if (!project || projects.Contains(project)) + return nullptr; + projects.Add(project); + + // Check the project name + if (project->Name == projectName) + return project; + + // Search referenced projects + for (const auto& reference : project->References) + { + const ProjectInfo* result = FindProjectByName(reference.Project, projects, projectName); + if (result) + return result; + } + return nullptr; + } + + const ProjectInfo* FindProjectByPath(const ProjectInfo* project, HashSet& projects, const StringView& projectPath) + { + if (!project || projects.Contains(project)) + return nullptr; + projects.Add(project); + + // Search referenced projects (depth first to handle plugin projects first) + for (const auto& reference : project->References) + { + const ProjectInfo* result = FindProjectByPath(reference.Project, projects, projectPath); + if (result) + return result; + } + + // Check the project path + if (projectPath.StartsWith(project->ProjectFolderPath)) + return project; + + return nullptr; + } +#endif } using namespace ShadersCompilationImpl; @@ -361,11 +403,89 @@ void ShadersCompilation::ExtractShaderIncludes(byte* shaderCache, int32 shaderCa { String& include = includes.AddOne(); stream.ReadString(&include, 11); + include = ShadersCompilation::ResolveShaderPath(include); DateTime lastEditTime; stream.Read(lastEditTime); } } +String ShadersCompilation::ResolveShaderPath(StringView path) +{ + // Skip to the last root start './' but preserve the leading one + for (int32 i = path.Length() - 2; i >= 2; i--) + { + if (StringUtils::Compare(path.Get() + i, TEXT("./"), 2) == 0) + { + path = path.Substring(i); + break; + } + } + + // Find the included file path + String result; +#if USE_EDITOR + if (path.StartsWith(StringView(TEXT("./"), 2))) + { + int32 projectNameEnd = -1; + for (int32 i = 2; i < path.Length(); i++) + { + if (path[i] == '/') + { + projectNameEnd = i; + break; + } + } + if (projectNameEnd == -1) + return String::Empty; // Invalid project path + StringView projectName = path.Substring(2, projectNameEnd - 2); + if (projectName.StartsWith(StringView(TEXT("FlaxPlatforms")))) + { + // Hard-coded redirect to platform-specific includes + result = Globals::StartupFolder / TEXT("Source/Platforms"); + } + else + { + HashSet projects; + const ProjectInfo* project = FindProjectByName(Editor::Project, projects, StringView(projectName.Get(), projectNameEnd - 2)); + if (project) + result = project->ProjectFolderPath / TEXT("/Source/Shaders/"); + else + return String::Empty; + } + result /= path.Substring(projectNameEnd + 1); + } +#else + if (path.StartsWith(StringView(TEXT("./Flax/"), 7))) + { + // Engine project relative shader path + result = Globals::StartupFolder / TEXT("Source/Shaders") / path.Substring(6); + } +#endif + else + { + // Absolute shader path + result = path; + } + + return result; +} + +String ShadersCompilation::CompactShaderPath(StringView path) +{ +#if USE_EDITOR + // Try to use file path relative to the project shader sources folder + HashSet projects; + const ProjectInfo* project = FindProjectByPath(Editor::Project, projects, path); + if (project) + { + String projectSourcesPath = project->ProjectFolderPath / TEXT("/Source/Shaders/"); + if (path.StartsWith(projectSourcesPath)) + return String::Format(TEXT("./{}/{}"), project->Name, path.Substring(projectSourcesPath.Length())); + } +#endif + return String(path); +} + #if USE_EDITOR namespace diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.h b/Source/Engine/ShadersCompilation/ShadersCompilation.h index fca6a6ebe..fd907acf7 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.h +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.h @@ -14,7 +14,6 @@ class Asset; class FLAXENGINE_API ShadersCompilation { public: - /// /// Compiles the shader. /// @@ -43,6 +42,11 @@ public: /// The output included. static void ExtractShaderIncludes(byte* shaderCache, int32 shaderCacheLength, Array& includes); + // Resolves shader path name into absolute file path. Resolves './/ShaderFile.hlsl' cases into a full path. + static String ResolveShaderPath(StringView path); + // Compacts the full shader file path into portable format with project name prefix such as './/ShaderFile.hlsl'. + static String CompactShaderPath(StringView path); + private: static ShaderCompiler* CreateCompiler(ShaderProfile profile); diff --git a/Source/Engine/Tests/TestCollections.cpp b/Source/Engine/Tests/TestCollections.cpp index 21aeb1c36..ff5f34bf6 100644 --- a/Source/Engine/Tests/TestCollections.cpp +++ b/Source/Engine/Tests/TestCollections.cpp @@ -3,6 +3,8 @@ #include "Engine/Core/RandomStream.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/BitArray.h" +#include "Engine/Core/Collections/HashSet.h" +#include "Engine/Core/Collections/Dictionary.h" #include TEST_CASE("Array") @@ -107,4 +109,185 @@ TEST_CASE("BitArray") a1 = testData; CHECK(a1 == testData); } + + SECTION("Test Set All") + { + BitArray<> a1; + a1.Resize(9); + CHECK(a1.Count() == 9); + a1.SetAll(true); + for (int32 i = 0; i < a1.Count(); i++) + CHECK(a1[i] == true); + a1.SetAll(false); + for (int32 i = 0; i < a1.Count(); i++) + CHECK(a1[i] == false); + } +} + +TEST_CASE("HashSet") +{ + SECTION("Test Allocators") + { + HashSet a1; + HashSet> a2; + HashSet> a3; + for (int32 i = 0; i < 7; i++) + { + a1.Add(i); + a2.Add(i); + a3.Add(i); + } + CHECK(a1.Count() == 7); + CHECK(a2.Count() == 7); + CHECK(a3.Count() == 7); + for (int32 i = 0; i < 7; i++) + { + CHECK(a1.Contains(i)); + CHECK(a2.Contains(i)); + CHECK(a3.Contains(i)); + } + } + + SECTION("Test Resizing") + { + HashSet a1; + for (int32 i = 0; i < 4000; i++) + a1.Add(i); + CHECK(a1.Count() == 4000); + int32 capacity = a1.Capacity(); + for (int32 i = 0; i < 4000; i++) + { + CHECK(a1.Contains(i)); + } + a1.Clear(); + CHECK(a1.Count() == 0); + CHECK(a1.Capacity() == capacity); + for (int32 i = 0; i < 4000; i++) + a1.Add(i); + CHECK(a1.Count() == 4000); + CHECK(a1.Capacity() == capacity); + for (int32 i = 0; i < 4000; i++) + a1.Remove(i); + CHECK(a1.Count() == 0); + CHECK(a1.Capacity() == capacity); + for (int32 i = 0; i < 4000; i++) + a1.Add(i); + CHECK(a1.Count() == 4000); + CHECK(a1.Capacity() == capacity); + } + + SECTION("Test Default Capacity") + { + HashSet a1; + a1.Add(1); + CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + } + + SECTION("Test Add/Remove") + { + HashSet a1; + for (int32 i = 0; i < 4000; i++) + { + a1.Add(i); + a1.Remove(i); + } + CHECK(a1.Count() == 0); + CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + a1.Clear(); + for (int32 i = 1; i <= 10; i++) + a1.Add(-i); + for (int32 i = 0; i < 4000; i++) + { + a1.Add(i); + a1.Remove(i); + } + CHECK(a1.Count() == 10); + CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + } +} + +TEST_CASE("Dictionary") +{ + SECTION("Test Allocators") + { + Dictionary a1; + Dictionary> a2; + Dictionary> a3; + for (int32 i = 0; i < 7; i++) + { + a1.Add(i, i); + a2.Add(i, i); + a3.Add(i, i); + } + CHECK(a1.Count() == 7); + CHECK(a2.Count() == 7); + CHECK(a3.Count() == 7); + for (int32 i = 0; i < 7; i++) + { + CHECK(a1.ContainsKey(i)); + CHECK(a2.ContainsKey(i)); + CHECK(a3.ContainsKey(i)); + CHECK(a1.ContainsValue(i)); + CHECK(a2.ContainsValue(i)); + CHECK(a3.ContainsValue(i)); + } + } + + SECTION("Test Resizing") + { + Dictionary a1; + for (int32 i = 0; i < 4000; i++) + a1.Add(i, i); + CHECK(a1.Count() == 4000); + int32 capacity = a1.Capacity(); + for (int32 i = 0; i < 4000; i++) + { + CHECK(a1.ContainsKey(i)); + CHECK(a1.ContainsValue(i)); + } + a1.Clear(); + CHECK(a1.Count() == 0); + CHECK(a1.Capacity() == capacity); + for (int32 i = 0; i < 4000; i++) + a1.Add(i, i); + CHECK(a1.Count() == 4000); + CHECK(a1.Capacity() == capacity); + for (int32 i = 0; i < 4000; i++) + a1.Remove(i); + CHECK(a1.Count() == 0); + CHECK(a1.Capacity() == capacity); + for (int32 i = 0; i < 4000; i++) + a1.Add(i, i); + CHECK(a1.Count() == 4000); + CHECK(a1.Capacity() == capacity); + } + + SECTION("Test Default Capacity") + { + Dictionary a1; + a1.Add(1, 1); + CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + } + + SECTION("Test Add/Remove") + { + Dictionary a1; + for (int32 i = 0; i < 4000; i++) + { + a1.Add(i, i); + a1.Remove(i); + } + CHECK(a1.Count() == 0); + CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + a1.Clear(); + for (int32 i = 1; i <= 10; i++) + a1.Add(-i, -i); + for (int32 i = 0; i < 4000; i++) + { + a1.Add(i, i); + a1.Remove(i); + } + CHECK(a1.Count() == 10); + CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY); + } } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 8aa300731..830a2ab06 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -393,8 +393,8 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) // Sphere Mask case 28: { - const auto a = tryGetValue(node->GetBox(0), 0, Value::Zero); - const auto b = tryGetValue(node->GetBox(1), 1, Value::Zero).Cast(a.Type); + const auto a = tryGetValue(node->GetBox(0), getUVs); + const auto b = tryGetValue(node->GetBox(1), Value::Half).Cast(a.Type); const auto radius = tryGetValue(node->GetBox(2), node->Values[0]).AsFloat(); const auto hardness = tryGetValue(node->GetBox(3), node->Values[1]).AsFloat(); const auto invert = tryGetValue(node->GetBox(4), node->Values[2]).AsBool(); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index 01eb8d369..3d9be61d2 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -648,6 +648,60 @@ bool ImportMesh(int32 i, ImportedModelData& result, AssimpImporterData& data, St result.LODs.Resize(lodIndex + 1); result.LODs[lodIndex].Meshes.Add(meshData); } + + auto root = data.Scene->mRootNode; + Array points; + if (root->mNumChildren == 0) + { + aiQuaternion aiQuat; + aiVector3D aiPos; + aiVector3D aiScale; + root->mTransformation.Decompose(aiScale, aiQuat, aiPos); + auto quat = ToQuaternion(aiQuat); + auto pos = ToFloat3(aiPos); + auto scale = ToFloat3(aiScale); + Transform trans = Transform(pos, quat, scale); + points.Add(trans); + } + else + { + for (unsigned int j = 0; j < root->mNumChildren; j++) + { + aiQuaternion aiQuat; + aiVector3D aiPos; + aiVector3D aiScale; + root->mChildren[j]->mTransformation.Decompose(aiScale, aiQuat, aiPos); + auto quat = ToQuaternion(aiQuat); + auto pos = ToFloat3(aiPos); + auto scale = ToFloat3(aiScale); + Transform trans = Transform(pos, quat, scale); + points.Add(trans); + } + } + + Float3 translation = Float3::Zero; + Float3 scale = Float3::Zero; + Quaternion orientation = Quaternion::Identity; + for (auto point : points) + { + translation += point.Translation; + scale += point.Scale; + orientation *= point.Orientation; + } + + if (points.Count() > 0) + { + meshData->OriginTranslation = translation / (float)points.Count(); + meshData->OriginOrientation = Quaternion::Invert(orientation); + meshData->Scaling = scale / (float)points.Count(); + } + else + { + meshData->OriginTranslation = translation; + meshData->OriginOrientation = Quaternion::Invert(orientation); + meshData->Scaling = Float3(1); + } + return false; } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index f1e89d6bc..c7ee11b42 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -836,6 +836,20 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb mesh.TransformBuffer(geometryTransform); }*/ + // Get local transform for origin shifting translation + auto translation = ToMatrix(aMesh->getGlobalTransform()).GetTranslation(); + auto scale = data.GlobalSettings.UnitScaleFactor; + if (data.GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded) + mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, -translation.Z); + else + mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, translation.Z); + + auto rot = aMesh->getLocalRotation(); + auto quat = Quaternion::Euler(-(float)rot.x, -(float)rot.y, -(float)rot.z); + mesh.OriginOrientation = quat; + + auto scaling = aMesh->getLocalScaling(); + mesh.Scaling = Vector3(scale * (float)scaling.x, scale * (float)scaling.y, scale * (float)scaling.z); return false; } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 116f63407..a07cd8f23 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -367,11 +367,13 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(ImportLODs); SERIALIZE(ImportVertexColors); SERIALIZE(ImportBlendShapes); + SERIALIZE(CalculateBoneOffsetMatrices); SERIALIZE(LightmapUVsSource); SERIALIZE(CollisionMeshesPrefix); SERIALIZE(Scale); SERIALIZE(Rotation); SERIALIZE(Translation); + SERIALIZE(UseLocalOrigin); SERIALIZE(CenterGeometry); SERIALIZE(Duration); SERIALIZE(FramesRange); @@ -413,11 +415,13 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(ImportLODs); DESERIALIZE(ImportVertexColors); DESERIALIZE(ImportBlendShapes); + DESERIALIZE(CalculateBoneOffsetMatrices); DESERIALIZE(LightmapUVsSource); DESERIALIZE(CollisionMeshesPrefix); DESERIALIZE(Scale); DESERIALIZE(Rotation); DESERIALIZE(Translation); + DESERIALIZE(UseLocalOrigin); DESERIALIZE(CenterGeometry); DESERIALIZE(Duration); DESERIALIZE(FramesRange); @@ -1089,11 +1093,16 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op // Prepare import transformation Transform importTransform(options.Translation, options.Rotation, Float3(options.Scale)); + if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) + { + importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale; + } if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) { // Calculate the bounding box (use LOD0 as a reference) BoundingBox box = data.LODs[0].GetBox(); - importTransform.Translation -= box.GetCenter(); + auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling; + importTransform.Translation -= center; } const bool applyImportTransform = !importTransform.IsIdentity(); @@ -1418,6 +1427,15 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op SkeletonUpdater hierarchyUpdater(data.Nodes); hierarchyUpdater.UpdateMatrices(); + if (options.CalculateBoneOffsetMatrices) + { + // Calculate offset matrix (inverse bind pose transform) for every bone manually + for (SkeletonBone& bone : data.Skeleton.Bones) + { + CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex); + } + } + // Move meshes in the new nodes for (int32 lodIndex = 0; lodIndex < data.LODs.Count(); lodIndex++) { @@ -1439,15 +1457,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op } } - // TODO: allow to link skeleton asset to model to retarget model bones skeleton for an animation - // use SkeletonMapping to map bones? - - // Calculate offset matrix (inverse bind pose transform) for every bone manually - /*for (SkeletonBone& bone : data.Skeleton.Bones) - { - CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex); - }*/ - #if USE_SKELETON_NODES_SORTING // Sort skeleton nodes and bones hierarchy (parents first) // Then it can be used with a simple linear loop update diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index bad10b86e..c7876e826 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -258,6 +258,9 @@ public: // Enable/disable importing blend shapes (morph targets). API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))") bool ImportBlendShapes = false; + // Enable skeleton bones offset matrices recalculating. + API_FIELD(Attributes="EditorOrder(86), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))") + bool CalculateBoneOffsetMatrices = false; // The lightmap UVs source. API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Geometry\", \"Lightmap UVs Source\"), VisibleIf(nameof(ShowModel))") ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable; @@ -279,8 +282,11 @@ public: // Custom import geometry offset. API_FIELD(Attributes="EditorOrder(520), EditorDisplay(\"Transform\")") Float3 Translation = Float3::Zero; - // If checked, the imported geometry will be shifted to the center of mass. + // If checked, the imported geometry will be shifted to its local transform origin. API_FIELD(Attributes="EditorOrder(530), EditorDisplay(\"Transform\")") + bool UseLocalOrigin = false; + // If checked, the imported geometry will be shifted to the center of mass. + API_FIELD(Attributes="EditorOrder(540), EditorDisplay(\"Transform\")") bool CenterGeometry = false; public: // Animation diff --git a/Source/Engine/UI/GUI/CanvasContainer.cs b/Source/Engine/UI/GUI/CanvasContainer.cs index f032a99d3..084a22503 100644 --- a/Source/Engine/UI/GUI/CanvasContainer.cs +++ b/Source/Engine/UI/GUI/CanvasContainer.cs @@ -41,11 +41,12 @@ namespace FlaxEngine.GUI protected override void DrawChildren() { // Draw all screen space canvases + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = 0; i < _children.Count; i++) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Is2D) + if (child.Visible && child.Is2D && layerMask.HasLayer(child.Canvas.Layer)) { child.Draw(); } @@ -69,10 +70,11 @@ namespace FlaxEngine.GUI UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Enabled && child.Is3D) + if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer)) { if (child.Intersects3D(ref ray, out var childLocation)) { @@ -91,10 +93,11 @@ namespace FlaxEngine.GUI // Check all children collisions with mouse and fire events for them bool isFirst3DHandled = false; + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Enabled) + if (child.Visible && child.Enabled && layerMask.HasLayer(child.Canvas.Layer)) { // Fire events if (child.Is2D) @@ -156,10 +159,11 @@ namespace FlaxEngine.GUI UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Enabled && child.Is3D) + if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer)) { if (child.Intersects3D(ref ray, out var childLocation)) { @@ -183,10 +187,11 @@ namespace FlaxEngine.GUI UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Enabled && child.Is3D) + if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer)) { if (child.Intersects3D(ref ray, out var childLocation)) { @@ -210,10 +215,11 @@ namespace FlaxEngine.GUI UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Enabled && child.Is3D) + if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer)) { if (child.Intersects3D(ref ray, out var childLocation)) { @@ -237,10 +243,11 @@ namespace FlaxEngine.GUI UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D + var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; - if (child.Visible && child.Enabled && child.Is3D) + if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer)) { if (child.Intersects3D(ref ray, out var childLocation)) { diff --git a/Source/Engine/UI/GUI/CanvasRootControl.cs b/Source/Engine/UI/GUI/CanvasRootControl.cs index b4fa067e3..b2ea9aaa0 100644 --- a/Source/Engine/UI/GUI/CanvasRootControl.cs +++ b/Source/Engine/UI/GUI/CanvasRootControl.cs @@ -74,6 +74,8 @@ namespace FlaxEngine.GUI return false; } + private bool SkipEvents => !_canvas.ReceivesEvents || !_canvas.IsVisible(); + /// public override CursorType Cursor { @@ -197,7 +199,7 @@ namespace FlaxEngine.GUI public override void Update(float deltaTime) { // UI navigation - if (_canvas.ReceivesEvents) + if (SkipEvents) { UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp); UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown); @@ -267,7 +269,7 @@ namespace FlaxEngine.GUI /// public override bool OnCharInput(char c) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnCharInput(c); @@ -276,7 +278,7 @@ namespace FlaxEngine.GUI /// public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return DragDropEffect.None; return base.OnDragDrop(ref location, data); @@ -285,7 +287,7 @@ namespace FlaxEngine.GUI /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return DragDropEffect.None; return base.OnDragEnter(ref location, data); @@ -294,7 +296,7 @@ namespace FlaxEngine.GUI /// public override void OnDragLeave() { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; base.OnDragLeave(); @@ -303,7 +305,7 @@ namespace FlaxEngine.GUI /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return DragDropEffect.None; return base.OnDragMove(ref location, data); @@ -312,7 +314,7 @@ namespace FlaxEngine.GUI /// public override bool OnKeyDown(KeyboardKeys key) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnKeyDown(key); @@ -321,7 +323,7 @@ namespace FlaxEngine.GUI /// public override void OnKeyUp(KeyboardKeys key) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; base.OnKeyUp(key); @@ -330,7 +332,7 @@ namespace FlaxEngine.GUI /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnMouseDoubleClick(location, button); @@ -339,7 +341,7 @@ namespace FlaxEngine.GUI /// public override bool OnMouseDown(Float2 location, MouseButton button) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnMouseDown(location, button); @@ -348,7 +350,7 @@ namespace FlaxEngine.GUI /// public override void OnMouseEnter(Float2 location) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; _mousePosition = location; @@ -359,8 +361,7 @@ namespace FlaxEngine.GUI public override void OnMouseLeave() { _mousePosition = Float2.Zero; - - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; base.OnMouseLeave(); @@ -369,7 +370,7 @@ namespace FlaxEngine.GUI /// public override void OnMouseMove(Float2 location) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; _mousePosition = location; @@ -379,7 +380,7 @@ namespace FlaxEngine.GUI /// public override bool OnMouseUp(Float2 location, MouseButton button) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnMouseUp(location, button); @@ -388,7 +389,7 @@ namespace FlaxEngine.GUI /// public override bool OnMouseWheel(Float2 location, float delta) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnMouseWheel(location, delta); @@ -397,7 +398,7 @@ namespace FlaxEngine.GUI /// public override void OnTouchEnter(Float2 location, int pointerId) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; base.OnTouchEnter(location, pointerId); @@ -406,7 +407,7 @@ namespace FlaxEngine.GUI /// public override bool OnTouchDown(Float2 location, int pointerId) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnTouchDown(location, pointerId); @@ -415,7 +416,7 @@ namespace FlaxEngine.GUI /// public override void OnTouchMove(Float2 location, int pointerId) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; base.OnTouchMove(location, pointerId); @@ -424,7 +425,7 @@ namespace FlaxEngine.GUI /// public override bool OnTouchUp(Float2 location, int pointerId) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return false; return base.OnTouchUp(location, pointerId); @@ -433,7 +434,7 @@ namespace FlaxEngine.GUI /// public override void OnTouchLeave(int pointerId) { - if (!_canvas.ReceivesEvents) + if (SkipEvents) return; base.OnTouchLeave(pointerId); diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index edbec7f7d..bc8c360d1 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -66,6 +66,8 @@ namespace FlaxEngine /// public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) { + if (!Canvas.IsVisible(renderContext.View.RenderLayersMask)) + return; var bounds = Canvas.Bounds; bounds.Transformation.Translation -= renderContext.View.Origin; if (renderContext.View.Frustum.Contains(bounds.GetBoundingBox()) == ContainmentType.Disjoint) @@ -873,6 +875,20 @@ namespace FlaxEngine } } + internal bool IsVisible() + { + return IsVisible(MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default); + } + + internal bool IsVisible(LayersMask layersMask) + { +#if FLAX_EDITOR + if (_editorTask != null || _editorRoot != null) + return true; +#endif + return layersMask.HasLayer(Layer); + } + #if FLAX_EDITOR private SceneRenderTask _editorTask; private ContainerControl _editorRoot; diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl index ad3168016..67ddc1887 100644 --- a/Source/Shaders/Math.hlsl +++ b/Source/Shaders/Math.hlsl @@ -218,10 +218,10 @@ float4 ClampedPow(float4 x, float4 y) float4 FindQuatBetween(float3 from, float3 to) { + // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final float normAB = 1.0f; float w = normAB + dot(from, to); float4 result; - if (w >= 1e-6f * normAB) { result = float4 @@ -234,12 +234,10 @@ float4 FindQuatBetween(float3 from, float3 to) } else { - w = 0.f; result = abs(from.x) > abs(from.y) - ? float4(-from.z, 0.f, from.x, w) - : float4(0.f, -from.z, from.y, w); + ? float4(-from.z, 0.f, from.x, 0.0f) + : float4(0.f, -from.z, from.y, 0.0f); } - return normalize(result); } diff --git a/Source/ThirdParty/tracy/TracyClient.cpp b/Source/ThirdParty/tracy/TracyClient.cpp index 3548c5752..1fbd78f96 100644 --- a/Source/ThirdParty/tracy/TracyClient.cpp +++ b/Source/ThirdParty/tracy/TracyClient.cpp @@ -11,10 +11,10 @@ // Define TRACY_ENABLE to enable profiler. -#ifdef TRACY_ENABLE - #include "common/TracySystem.cpp" +#ifdef TRACY_ENABLE + #ifdef _MSC_VER # pragma warning(push, 0) #endif @@ -22,12 +22,13 @@ #include #include "client/TracyProfiler.cpp" #include "client/TracyCallstack.cpp" +#include "client/TracySysPower.cpp" #include "client/TracySysTime.cpp" #include "client/TracySysTrace.cpp" #include "common/TracySocket.cpp" #include "client/tracy_rpmalloc.cpp" #include "client/TracyAlloc.cpp" - +#include "client/TracyOverride.cpp" #if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 # include "libbacktrace/alloc.cpp" diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.cpp b/Source/ThirdParty/tracy/client/TracyCallstack.cpp index ca19a543b..0de7c9d2e 100644 --- a/Source/ThirdParty/tracy/client/TracyCallstack.cpp +++ b/Source/ThirdParty/tracy/client/TracyCallstack.cpp @@ -154,7 +154,6 @@ void InitCallstack() DBGHELP_LOCK; #endif - //SymInitialize( GetCurrentProcess(), "C:\\Flax\\FlaxEngine\\Binaries\\Editor\\Win64\\Debug;C:\\Flax\\FlaxEngine\\Cache\\Projects", true ); SymInitialize( GetCurrentProcess(), nullptr, true ); SymSetOptions( SYMOPT_LOAD_LINES ); @@ -228,6 +227,10 @@ void InitCallstack() const auto res = GetModuleFileNameA( mod[i], name, 1021 ); if( res > 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++; @@ -683,7 +686,9 @@ void InitCallstackCritical() void InitCallstack() { cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr ); +#ifndef TRACY_DEMANGLE ___tracy_init_demangle_buffer(); +#endif #ifdef __linux InitKernelSymbols(); @@ -758,7 +763,9 @@ debuginfod_client* GetDebuginfodClient() void EndCallstack() { +#ifndef TRACY_DEMANGLE ___tracy_free_demangle_buffer(); +#endif #ifdef TRACY_DEBUGINFOD ClearDebugInfoVector( s_di_known ); debuginfod_end( s_debuginfod ); diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.hpp b/Source/ThirdParty/tracy/client/TracyCallstack.hpp index 8cfede8fb..96bee3f51 100644 --- a/Source/ThirdParty/tracy/client/TracyCallstack.hpp +++ b/Source/ThirdParty/tracy/client/TracyCallstack.hpp @@ -10,7 +10,14 @@ #endif -#ifdef TRACY_HAS_CALLSTACK +#ifndef TRACY_HAS_CALLSTACK + +namespace tracy +{ +static tracy_force_inline void* Callstack( int depth ) { return nullptr; } +} + +#else #ifdef TRACY_DEBUGINFOD # include diff --git a/Source/ThirdParty/tracy/client/TracyLock.hpp b/Source/ThirdParty/tracy/client/TracyLock.hpp index 296a41ba1..d12a3c16d 100644 --- a/Source/ThirdParty/tracy/client/TracyLock.hpp +++ b/Source/ThirdParty/tracy/client/TracyLock.hpp @@ -21,7 +21,7 @@ public: , m_active( false ) #endif { - assert( m_id != std::numeric_limits::max() ); + assert( m_id != (std::numeric_limits::max)() ); auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::LockAnnounce ); @@ -154,7 +154,7 @@ public: tracy_force_inline void CustomName( const char* name, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, name, size ); auto item = Profiler::QueueSerial(); @@ -235,7 +235,7 @@ public: , m_active( false ) #endif { - assert( m_id != std::numeric_limits::max() ); + assert( m_id != (std::numeric_limits::max)() ); auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::LockAnnounce ); @@ -450,7 +450,7 @@ public: tracy_force_inline void CustomName( const char* name, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, name, size ); auto item = Profiler::QueueSerial(); diff --git a/Source/ThirdParty/tracy/client/TracyOverride.cpp b/Source/ThirdParty/tracy/client/TracyOverride.cpp new file mode 100644 index 000000000..591508a7f --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracyOverride.cpp @@ -0,0 +1,26 @@ +#ifdef TRACY_ENABLE +# ifdef __linux__ +# include "TracyDebug.hpp" +# ifdef TRACY_VERBOSE +# include +# include +# endif + +extern "C" int dlclose( void* hnd ) +{ +#ifdef TRACY_VERBOSE + struct link_map* lm; + if( dlinfo( hnd, RTLD_DI_LINKMAP, &lm ) == 0 ) + { + TracyDebug( "Overriding dlclose for %s\n", lm->l_name ); + } + else + { + TracyDebug( "Overriding dlclose for unknown object (%s)\n", dlerror() ); + } +#endif + return 0; +} + +# endif +#endif diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.cpp b/Source/ThirdParty/tracy/client/TracyProfiler.cpp index dfbb22a83..59c31d6f8 100644 --- a/Source/ThirdParty/tracy/client/TracyProfiler.cpp +++ b/Source/ThirdParty/tracy/client/TracyProfiler.cpp @@ -81,7 +81,9 @@ #endif #ifdef __APPLE__ -# define TRACY_DELAYED_INIT +# ifndef TRACY_DELAYED_INIT +# define TRACY_DELAYED_INIT +# endif #else # ifdef __GNUC__ # define init_order( val ) __attribute__ ((init_priority(val))) @@ -1074,7 +1076,9 @@ static void CrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ ) } closedir( dp ); +#ifdef TRACY_HAS_CALLSTACK if( selfTid == s_symbolTid ) s_symbolThreadGone.store( true, std::memory_order_release ); +#endif TracyLfqPrepare( QueueType::Crash ); TracyLfqCommit; @@ -1355,6 +1359,7 @@ Profiler::Profiler() , m_queryImage( nullptr ) , m_queryData( nullptr ) , m_crashHandlerInstalled( false ) + , m_programName( nullptr ) { assert( !s_instance ); s_instance = this; @@ -1417,7 +1422,9 @@ void Profiler::SpawnWorkerThreads() #if defined _WIN32 && !defined TRACY_UWP && !defined TRACY_NO_CRASH_HANDLER s_profilerThreadId = GetThreadId( s_thread->Handle() ); +# ifdef TRACY_HAS_CALLSTACK s_symbolThreadId = GetThreadId( s_symbolThread->Handle() ); +# endif m_exceptionHandler = AddVectoredExceptionHandler( 1, CrashFilter ); #endif @@ -1456,7 +1463,7 @@ Profiler::~Profiler() if( m_crashHandlerInstalled ) RemoveVectoredExceptionHandler( m_exceptionHandler ); #endif -#ifdef __linux__ +#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER if( m_crashHandlerInstalled ) { sigaction( TRACY_CRASH_SIGNAL, &m_prevSignal.pwr, nullptr ); @@ -1522,7 +1529,7 @@ bool Profiler::ShouldExit() void Profiler::Worker() { -#ifdef __linux__ +#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER s_profilerTid = syscall( SYS_gettid ); #endif @@ -1711,6 +1718,9 @@ void Profiler::Worker() if( m_sock ) break; #ifndef TRACY_ON_DEMAND ProcessSysTime(); +# ifdef TRACY_HAS_SYSPOWER + m_sysPower.Tick(); +# endif #endif if( m_broadcast ) @@ -1718,6 +1728,14 @@ void Profiler::Worker() const auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count(); if( t - lastBroadcast > 3000000000 ) // 3s { + m_programNameLock.lock(); + if( m_programName ) + { + broadcastMsg = GetBroadcastMessage( m_programName, strlen( m_programName ), broadcastLen, dataPort ); + m_programName = nullptr; + } + m_programNameLock.unlock(); + lastBroadcast = t; const auto ts = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); broadcastMsg.activeTime = int32_t( ts - m_epoch ); @@ -1828,6 +1846,9 @@ void Profiler::Worker() for(;;) { ProcessSysTime(); +#ifdef TRACY_HAS_SYSPOWER + m_sysPower.Tick(); +#endif const auto status = Dequeue( token ); const auto serialStatus = DequeueSerial(); if( status == DequeueStatus::ConnectionLost || serialStatus == DequeueStatus::ConnectionLost ) @@ -3026,9 +3047,9 @@ void Profiler::SendSourceLocation( uint64_t ptr ) MemWrite( &item.srcloc.file, (uint64_t)srcloc->file ); MemWrite( &item.srcloc.function, (uint64_t)srcloc->function ); MemWrite( &item.srcloc.line, srcloc->line ); - MemWrite( &item.srcloc.r, uint8_t( ( srcloc->color ) & 0xFF ) ); + MemWrite( &item.srcloc.b, uint8_t( ( srcloc->color ) & 0xFF ) ); MemWrite( &item.srcloc.g, uint8_t( ( srcloc->color >> 8 ) & 0xFF ) ); - MemWrite( &item.srcloc.b, uint8_t( ( srcloc->color >> 16 ) & 0xFF ) ); + MemWrite( &item.srcloc.r, uint8_t( ( srcloc->color >> 16 ) & 0xFF ) ); AppendData( &item, QueueDataSize[(int)QueueType::SourceLocation] ); } @@ -3331,10 +3352,8 @@ bool Profiler::HandleServerQuery() uint8_t type; uint64_t ptr; - uint32_t extra; memcpy( &type, &payload.type, sizeof( payload.type ) ); memcpy( &ptr, &payload.ptr, sizeof( payload.ptr ) ); - memcpy( &extra, &payload.extra, sizeof( payload.extra ) ); switch( type ) { @@ -3381,7 +3400,7 @@ bool Profiler::HandleServerQuery() break; #ifndef TRACY_NO_CODE_TRANSFER case ServerQuerySymbolCode: - HandleSymbolCodeQuery( ptr, extra ); + HandleSymbolCodeQuery( ptr, payload.extra ); break; #endif case ServerQuerySourceCode: @@ -3398,7 +3417,7 @@ bool Profiler::HandleServerQuery() break; case ServerQueryDataTransferPart: memcpy( m_queryDataPtr, &ptr, 8 ); - memcpy( m_queryDataPtr+8, &extra, 4 ); + memcpy( m_queryDataPtr+8, &payload.extra, 4 ); m_queryDataPtr += 12; AckServerQuery(); break; @@ -3753,7 +3772,7 @@ void Profiler::SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_ { #ifndef TRACY_NO_FRAME_IMAGE auto& profiler = GetProfiler(); - assert( profiler.m_frameCount.load( std::memory_order_relaxed ) < std::numeric_limits::max() ); + assert( profiler.m_frameCount.load( std::memory_order_relaxed ) < (std::numeric_limits::max)() ); # ifdef TRACY_ON_DEMAND if( !profiler.IsConnected() ) return; # endif @@ -3770,6 +3789,12 @@ void Profiler::SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_ fi->flip = flip; profiler.m_fiQueue.commit_next(); profiler.m_fiLock.unlock(); +#else + static_cast(image); // unused + static_cast(w); // unused + static_cast(h); // unused + static_cast(offset); // unused + static_cast(flip); // unused #endif } @@ -3827,7 +3852,7 @@ void Profiler::ConfigurePlot( const char* name, PlotFormatType type, bool step, void Profiler::Message( const char* txt, size_t size, int callstack ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -3864,7 +3889,7 @@ void Profiler::Message( const char* txt, int callstack ) void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int callstack ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -3879,9 +3904,9 @@ void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int c TracyQueuePrepare( callstack == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack ); MemWrite( &item->messageColorFat.time, GetTime() ); MemWrite( &item->messageColorFat.text, (uint64_t)ptr ); - MemWrite( &item->messageColorFat.r, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->messageColorFat.b, uint8_t( ( color ) & 0xFF ) ); MemWrite( &item->messageColorFat.g, uint8_t( ( color >> 8 ) & 0xFF ) ); - MemWrite( &item->messageColorFat.b, uint8_t( ( color >> 16 ) & 0xFF ) ); + MemWrite( &item->messageColorFat.r, uint8_t( ( color >> 16 ) & 0xFF ) ); MemWrite( &item->messageColorFat.size, (uint16_t)size ); TracyQueueCommit( messageColorFatThread ); } @@ -3899,15 +3924,15 @@ void Profiler::MessageColor( const char* txt, uint32_t color, int callstack ) TracyQueuePrepare( callstack == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack ); MemWrite( &item->messageColorLiteral.time, GetTime() ); MemWrite( &item->messageColorLiteral.text, (uint64_t)txt ); - MemWrite( &item->messageColorLiteral.r, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->messageColorLiteral.b, uint8_t( ( color ) & 0xFF ) ); MemWrite( &item->messageColorLiteral.g, uint8_t( ( color >> 8 ) & 0xFF ) ); - MemWrite( &item->messageColorLiteral.b, uint8_t( ( color >> 16 ) & 0xFF ) ); + MemWrite( &item->messageColorLiteral.r, uint8_t( ( color >> 16 ) & 0xFF ) ); TracyQueueCommit( messageColorLiteralThread ); } void Profiler::MessageAppInfo( const char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); TracyLfqPrepare( QueueType::MessageAppInfo ); @@ -3922,7 +3947,7 @@ void Profiler::MessageAppInfo( const char* txt, size_t size ) TracyLfqCommit; } -void Profiler::MemAlloc(const void* ptr, size_t size, bool secure) +void Profiler::MemAlloc( const void* ptr, size_t size, bool secure ) { if( secure && !ProfilerAvailable() ) return; #ifdef TRACY_ON_DEMAND @@ -4085,7 +4110,6 @@ void Profiler::SendCallstack( int depth ) #endif } -void Profiler::ParameterRegister( ParameterCallback cb ) { GetProfiler().m_paramCallback = cb; } void Profiler::ParameterRegister( ParameterCallback cb, void* data ) { auto& profiler = GetProfiler(); @@ -4301,486 +4325,4 @@ 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, int 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, int depth, int 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 - tracy::GetProfiler().SendCallstack( depth ); - { - TracyQueuePrepareC( tracy::QueueType::ZoneBeginCallstack ); - 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, int 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, int depth, int 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 - tracy::GetProfiler().SendCallstack( depth ); - { - TracyQueuePrepareC( tracy::QueueType::ZoneBeginAllocSrcLocCallstack ); - 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.r, uint8_t( ( color ) & 0xFF ) ); - tracy::MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) ); - tracy::MemWrite( &item->zoneColor.b, 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, int secure ) { tracy::Profiler::MemAlloc( ptr, size, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int depth, int secure ) { tracy::Profiler::MemAllocCallstack( ptr, size, depth, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_free( const void* ptr, int secure ) { tracy::Profiler::MemFree( ptr, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int depth, int secure ) { tracy::Profiler::MemFreeCallstack( ptr, depth, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, int 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, int depth, int secure, const char* name ) { tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, secure != 0, name ); } -TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, int secure, const char* name ) { tracy::Profiler::MemFreeNamed( ptr, secure != 0, name ); } -TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int depth, int secure, const char* name ) { tracy::Profiler::MemFreeCallstackNamed( ptr, depth, 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, int flip ) { tracy::Profiler::SendFrameImage( image, w, h, offset, flip ); } -TRACY_API void ___tracy_emit_plot( const char* name, double val ) { tracy::Profiler::PlotData( name, val ); } -TRACY_API void ___tracy_emit_message( const char* txt, size_t size, int callstack ) { tracy::Profiler::Message( txt, size, callstack ); } -TRACY_API void ___tracy_emit_messageL( const char* txt, int callstack ) { tracy::Profiler::Message( txt, callstack ); } -TRACY_API void ___tracy_emit_messageC( const char* txt, size_t size, uint32_t color, int callstack ) { tracy::Profiler::MessageColor( txt, size, color, callstack ); } -TRACY_API void ___tracy_emit_messageLC( const char* txt, uint32_t color, int callstack ) { tracy::Profiler::MessageColor( txt, color, callstack ); } -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 ) { - return tracy::Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz ); -} - -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 ) { - return tracy::Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); -} - -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->gpuNewContext.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->gpuNewContext.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->gpuNewContext.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 ); - 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 ); - 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_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->gpuNewContext.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->gpuNewContext.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 int ___tracy_connected( void ) -{ - return tracy::GetProfiler().IsConnected(); -} - -#ifdef TRACY_FIBERS -TRACY_API void ___tracy_fiber_enter( const char* fiber ){ tracy::Profiler::EnterFiber( fiber ); } -TRACY_API void ___tracy_fiber_leave( void ){ tracy::Profiler::LeaveFiber(); } -#endif - -# ifdef TRACY_MANUAL_LIFETIME -TRACY_API void ___tracy_startup_profiler( void ) -{ - tracy::StartupProfiler(); -} - -TRACY_API void ___tracy_shutdown_profiler( void ) -{ - tracy::ShutdownProfiler(); -} -# endif - -#ifdef __cplusplus -} -#endif -#endif - #endif diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.hpp b/Source/ThirdParty/tracy/client/TracyProfiler.hpp index 99ae63e4f..8892fb14f 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 "TracySysPower.hpp" #include "TracySysTime.hpp" #include "TracyFastVector.hpp" #include "../common/TracyQueue.hpp" @@ -190,7 +191,22 @@ public: if( HardwareSupportsInvariantTSC() ) { uint64_t rax, rdx; +#ifdef TRACY_PATCHABLE_NOPSLEDS + // Some external tooling (such as rr) wants to patch our rdtsc and replace it by a + // branch to control the external input seen by a program. This kind of patching is + // not generally possible depending on the surrounding code and can lead to significant + // slowdowns if the compiler generated unlucky code and rr and tracy are used together. + // To avoid this, use the rr-safe `nopl 0(%rax, %rax, 1); rdtsc` instruction sequence, + // which rr promises will be patchable independent of the surrounding code. + asm volatile ( + // This is nopl 0(%rax, %rax, 1), but assemblers are inconsistent about whether + // they emit that as a 4 or 5 byte sequence and we need to be guaranteed to use + // the 5 byte one. + ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n\t" + "rdtsc" : "=a" (rax), "=d" (rdx) ); +#else asm volatile ( "rdtsc" : "=a" (rax), "=d" (rdx) ); +#endif return (int64_t)(( rdx << 32 ) + rax); } # else @@ -240,6 +256,30 @@ public: p.m_serialLock.unlock(); } + static void SendFrameMark( const char* name ); + static void SendFrameMark( const char* name, QueueType type ); + static void SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_t offset, bool flip ); + static void PlotData( const char* name, int64_t val ); + static void PlotData( const char* name, float val ); + static void PlotData( const char* name, double val ); + static void ConfigurePlot( const char* name, PlotFormatType type, bool step, bool fill, uint32_t color ); + static void Message( const char* txt, size_t size, int callstack ); + static void Message( const char* txt, int callstack ); + static void MessageColor( const char* txt, size_t size, uint32_t color, int callstack ); + static void MessageColor( const char* txt, uint32_t color, int callstack ); + static void MessageAppInfo( const char* txt, size_t size ); + static void MemAlloc( const void* ptr, size_t size, bool secure ); + static void MemFree( const void* ptr, bool secure ); + static void MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure ); + static void MemFreeCallstack( const void* ptr, int depth, bool secure ); + static void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ); + 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 SendCallstack( int depth ); + static void ParameterRegister( ParameterCallback cb, void* data ); + static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ); + static tracy_force_inline void SourceCallbackRegister( SourceContentsCallback cb, void* data ) { auto& profiler = GetProfiler(); @@ -264,31 +304,6 @@ public: } #endif - static void SendFrameMark( const char* name ); - static void SendFrameMark( const char* name, QueueType type ); - static void SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_t offset, bool flip ); - static void PlotData( const char* name, int64_t val ); - static void PlotData( const char* name, float val ); - static void PlotData( const char* name, double val ); - static void ConfigurePlot( const char* name, PlotFormatType type, bool step, bool fill, uint32_t color ); - static void Message( const char* txt, size_t size, int callstack ); - static void Message( const char* txt, int callstack ); - static void MessageColor( const char* txt, size_t size, uint32_t color, int callstack ); - static void MessageColor( const char* txt, uint32_t color, int callstack ); - static void MessageAppInfo( const char* txt, size_t size ); - static void MemAlloc( const void* ptr, size_t size, bool secure ); - static void MemFree( const void* ptr, bool secure ); - static void MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure ); - static void MemFreeCallstack( const void* ptr, int depth, bool secure ); - static void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ); - 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 SendCallstack( int depth ); - static void ParameterRegister( ParameterCallback cb ); - static void ParameterRegister( ParameterCallback cb, void* data ); - static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ); - void SendCallstack( int depth, const char* skipBefore ); static void CutCallstack( void* callstack, const char* skipBefore ); @@ -299,6 +314,13 @@ public: return m_isConnected.load( std::memory_order_acquire ); } + tracy_force_inline void SetProgramName( const char* name ) + { + m_programNameLock.lock(); + m_programName = name; + m_programNameLock.unlock(); + } + #ifdef TRACY_ON_DEMAND tracy_force_inline uint64_t ConnectionId() const { @@ -347,13 +369,13 @@ public: static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz ) { - return AllocSourceLocation( line, source, sourceSz, function, functionSz, (const char*)nullptr, 0 ); + return AllocSourceLocation( line, source, sourceSz, function, functionSz, nullptr, 0 ); } 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 ) { const auto sz32 = uint32_t( 2 + 4 + 4 + functionSz + 1 + sourceSz + 1 + nameSz ); - assert( sz32 <= std::numeric_limits::max() ); + assert( sz32 <= (std::numeric_limits::max)() ); const auto sz = uint16_t( sz32 ); auto ptr = (char*)tracy_malloc( sz ); memcpy( ptr, &sz, 2 ); @@ -370,28 +392,6 @@ public: return uint64_t( ptr ); } - 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 ) - { - 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 + 6, &line, 4 ); - memcpy( ptr + 10, function, functionSz ); - ptr[10 + functionSz] = '\0'; - memcpy( ptr + 10 + functionSz + 1, source, sourceSz ); - ptr[10 + functionSz + 1 + sourceSz] = '\0'; - if( nameSz != 0 ) - { - char* dst = ptr + 10 + functionSz + 1 + sourceSz + 1; - for ( size_t i = 0; i < nameSz; i++) - dst[i] = (char)name[i]; - } - return uint64_t( ptr ); - } - private: enum class DequeueStatus { DataDequeued, ConnectionLost, QueueEmpty }; enum class ThreadCtxStatus { Same, Changed, ConnectionLost }; @@ -586,6 +586,10 @@ private: void ProcessSysTime() {} #endif +#ifdef TRACY_HAS_SYSPOWER + SysPower m_sysPower; +#endif + ParameterCallback m_paramCallback; void* m_paramCallbackData; SourceContentsCallback m_sourceCallback; @@ -604,6 +608,9 @@ private: } m_prevSignal; #endif bool m_crashHandlerInstalled; + + const char* m_programName; + TracyMutex m_programNameLock; }; } diff --git a/Source/ThirdParty/tracy/client/TracyScoped.hpp b/Source/ThirdParty/tracy/client/TracyScoped.hpp index 1e5b6c809..bb916aa57 100644 --- a/Source/ThirdParty/tracy/client/TracyScoped.hpp +++ b/Source/ThirdParty/tracy/client/TracyScoped.hpp @@ -20,19 +20,7 @@ void ScopedZone::Begin(const SourceLocationData* srcloc) TracyLfqPrepare( QueueType::ZoneBegin ); MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); - TracyLfqCommit; -} - -void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz) -{ -#ifdef TRACY_ON_DEMAND - if (!GetProfiler().IsConnected()) return; -#endif - TracyLfqPrepare( 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 ); - TracyLfqCommit; + TracyQueueCommit( zoneBeginThread ); } void ScopedZone::End() @@ -42,7 +30,7 @@ void ScopedZone::End() #endif TracyLfqPrepare( QueueType::ZoneEnd ); MemWrite( &item->zoneEnd.time, Profiler::GetTime() ); - TracyLfqCommit; + TracyQueueCommit( zoneEndThread ); } ScopedZone::ScopedZone( const SourceLocationData* srcloc, bool is_active ) @@ -132,7 +120,7 @@ ScopedZone::~ScopedZone() void ScopedZone::Text( const char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); if( !m_active ) return; #ifdef TRACY_ON_DEMAND if( GetProfiler().ConnectionId() != m_connectionId ) return; @@ -147,7 +135,7 @@ void ScopedZone::Text( const char* txt, size_t size ) void ScopedZone::Text( const Char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); if( !m_active ) return; #ifdef TRACY_ON_DEMAND if( GetProfiler().ConnectionId() != m_connectionId ) return; @@ -163,7 +151,7 @@ void ScopedZone::Text( const Char* txt, size_t size ) void ScopedZone::Name( const char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); if( !m_active ) return; #ifdef TRACY_ON_DEMAND if( GetProfiler().ConnectionId() != m_connectionId ) return; @@ -178,7 +166,7 @@ void ScopedZone::Name( const char* txt, size_t size ) void ScopedZone::Name( const Char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + assert( size < (std::numeric_limits::max)() ); if( !m_active ) return; #ifdef TRACY_ON_DEMAND if( GetProfiler().ConnectionId() != m_connectionId ) return; @@ -199,9 +187,9 @@ void ScopedZone::Color( uint32_t color ) if( GetProfiler().ConnectionId() != m_connectionId ) return; #endif TracyQueuePrepare( QueueType::ZoneColor ); - MemWrite( &item->zoneColor.r, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->zoneColor.b, uint8_t( ( color ) & 0xFF ) ); MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) ); - MemWrite( &item->zoneColor.b, uint8_t( ( color >> 16 ) & 0xFF ) ); + MemWrite( &item->zoneColor.r, uint8_t( ( color >> 16 ) & 0xFF ) ); TracyQueueCommit( zoneColorThread ); } diff --git a/Source/ThirdParty/tracy/client/TracySysPower.cpp b/Source/ThirdParty/tracy/client/TracySysPower.cpp new file mode 100644 index 000000000..bd5939da2 --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysPower.cpp @@ -0,0 +1,164 @@ +#include "TracySysPower.hpp" + +#ifdef TRACY_HAS_SYSPOWER + +#include +#include +#include +#include +#include +#include + +#include "TracyDebug.hpp" +#include "TracyProfiler.hpp" +#include "../common/TracyAlloc.hpp" + +namespace tracy +{ + +SysPower::SysPower() + : m_domains( 4 ) + , m_lastTime( 0 ) +{ + ScanDirectory( "/sys/devices/virtual/powercap/intel-rapl", -1 ); +} + +SysPower::~SysPower() +{ + for( auto& v : m_domains ) + { + fclose( v.handle ); + // Do not release v.name, as it may be still needed + } +} + +void SysPower::Tick() +{ + auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + if( t - m_lastTime > 10000000 ) // 10 ms + { + m_lastTime = t; + for( auto& v : m_domains ) + { + char tmp[32]; + if( fread( tmp, 1, 32, v.handle ) > 0 ) + { + rewind( v.handle ); + auto p = (uint64_t)atoll( tmp ); + uint64_t delta; + if( p >= v.value ) + { + delta = p - v.value; + } + else + { + delta = v.overflow - v.value + p; + } + v.value = p; + + TracyLfqPrepare( QueueType::SysPowerReport ); + MemWrite( &item->sysPower.time, Profiler::GetTime() ); + MemWrite( &item->sysPower.delta, delta ); + MemWrite( &item->sysPower.name, (uint64_t)v.name ); + TracyLfqCommit; + } + } + } +} + +void SysPower::ScanDirectory( const char* path, int parent ) +{ + DIR* dir = opendir( path ); + if( !dir ) return; + struct dirent* ent; + uint64_t maxRange = 0; + char* name = nullptr; + FILE* handle = nullptr; + while( ( ent = readdir( dir ) ) ) + { + if( ent->d_type == DT_REG ) + { + if( strcmp( ent->d_name, "max_energy_range_uj" ) == 0 ) + { + char tmp[PATH_MAX]; + snprintf( tmp, PATH_MAX, "%s/max_energy_range_uj", path ); + FILE* f = fopen( tmp, "r" ); + if( f ) + { + fscanf( f, "%" PRIu64, &maxRange ); + fclose( f ); + } + } + else if( strcmp( ent->d_name, "name" ) == 0 ) + { + char tmp[PATH_MAX]; + snprintf( tmp, PATH_MAX, "%s/name", path ); + FILE* f = fopen( tmp, "r" ); + if( f ) + { + char ntmp[128]; + if( fgets( ntmp, 128, f ) ) + { + // Last character is newline, skip it + const auto sz = strlen( ntmp ) - 1; + if( parent < 0 ) + { + name = (char*)tracy_malloc( sz + 1 ); + memcpy( name, ntmp, sz ); + name[sz] = '\0'; + } + else + { + const auto p = m_domains[parent]; + const auto psz = strlen( p.name ); + name = (char*)tracy_malloc( psz + sz + 2 ); + memcpy( name, p.name, psz ); + name[psz] = ':'; + memcpy( name+psz+1, ntmp, sz ); + name[psz+sz+1] = '\0'; + } + } + fclose( f ); + } + } + else if( strcmp( ent->d_name, "energy_uj" ) == 0 ) + { + char tmp[PATH_MAX]; + snprintf( tmp, PATH_MAX, "%s/energy_uj", path ); + handle = fopen( tmp, "r" ); + } + } + if( name && handle && maxRange > 0 ) break; + } + if( name && handle && maxRange > 0 ) + { + parent = (int)m_domains.size(); + Domain* domain = m_domains.push_next(); + domain->value = 0; + domain->overflow = maxRange; + domain->handle = handle; + domain->name = name; + TracyDebug( "Power domain id %i, %s found at %s\n", parent, name, path ); + } + else + { + if( name ) tracy_free( name ); + if( handle ) fclose( handle ); + } + + rewinddir( dir ); + while( ( ent = readdir( dir ) ) ) + { + if( ent->d_type == DT_DIR && strncmp( ent->d_name, "intel-rapl:", 11 ) == 0 ) + { + char tmp[PATH_MAX]; + snprintf( tmp, PATH_MAX, "%s/%s", path, ent->d_name ); + ScanDirectory( tmp, parent ); + } + } + closedir( dir ); +} + +} + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysPower.hpp b/Source/ThirdParty/tracy/client/TracySysPower.hpp new file mode 100644 index 000000000..210123bce --- /dev/null +++ b/Source/ThirdParty/tracy/client/TracySysPower.hpp @@ -0,0 +1,44 @@ +#ifndef __TRACYSYSPOWER_HPP__ +#define __TRACYSYSPOWER_HPP__ + +#if defined __linux__ +# define TRACY_HAS_SYSPOWER +#endif + +#ifdef TRACY_HAS_SYSPOWER + +#include +#include + +#include "TracyFastVector.hpp" + +namespace tracy +{ + +class SysPower +{ + struct Domain + { + uint64_t value; + uint64_t overflow; + FILE* handle; + const char* name; + }; + +public: + SysPower(); + ~SysPower(); + + void Tick(); + +private: + void ScanDirectory( const char* path, int parent ); + + FastVector m_domains; + uint64_t m_lastTime; +}; + +} +#endif + +#endif diff --git a/Source/ThirdParty/tracy/client/TracySysTrace.cpp b/Source/ThirdParty/tracy/client/TracySysTrace.cpp index 23b1020a5..af0641fef 100644 --- a/Source/ThirdParty/tracy/client/TracySysTrace.cpp +++ b/Source/ThirdParty/tracy/client/TracySysTrace.cpp @@ -409,6 +409,7 @@ bool SysTraceStart( int64_t& samplingPeriod ) return false; } +#ifndef TRACY_NO_SAMPLING if( isOs64Bit ) { CLASSIC_EVENT_ID stackId[2] = {}; @@ -423,6 +424,7 @@ bool SysTraceStart( int64_t& samplingPeriod ) return false; } } +#endif #ifdef UNICODE WCHAR KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; @@ -768,6 +770,13 @@ bool SysTraceStart( int64_t& samplingPeriod ) TracyDebug( "sched_wakeup id: %i\n", wakeupId ); TracyDebug( "drm_vblank_event id: %i\n", vsyncId ); +#ifdef TRACY_NO_SAMPLING + const bool noSoftwareSampling = true; +#else + const char* noSoftwareSamplingEnv = GetEnvVar( "TRACY_NO_SAMPLING" ); + const bool noSoftwareSampling = noSoftwareSamplingEnv && noSoftwareSamplingEnv[0] == '1'; +#endif + #ifdef TRACY_NO_SAMPLE_RETIREMENT const bool noRetirement = true; #else @@ -837,28 +846,31 @@ bool SysTraceStart( int64_t& samplingPeriod ) pe.clockid = CLOCK_MONOTONIC_RAW; #endif - TracyDebug( "Setup software sampling\n" ); - ProbePreciseIp( pe, currentPid ); - for( int i=0; i static inline bool circular_less_than(T a, T b) { static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); - return static_cast(a - b) > (static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1))); + return static_cast(a - b) > static_cast(static_cast(1) << (static_cast(sizeof(T) * CHAR_BIT - 1))); + // Note: extra parens around rhs of operator<< is MSVC bug: https://developercommunity2.visualstudio.com/t/C4554-triggers-when-both-lhs-and-rhs-is/10034931 + // silencing the bug requires #pragma warning(disable: 4554) around the calling code and has no effect when done here. } -#ifdef _MSC_VER -#pragma warning(pop) -#endif template static inline char* align_for(char* ptr) diff --git a/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp b/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp index 8efa626a9..711505d21 100644 --- a/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp +++ b/Source/ThirdParty/tracy/client/tracy_rpmalloc.cpp @@ -147,7 +147,7 @@ # if defined(__APPLE__) # include # if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR -# include +# include # include # endif # include diff --git a/Source/ThirdParty/tracy/common/TracyProtocol.hpp b/Source/ThirdParty/tracy/common/TracyProtocol.hpp index dd30e5391..5eb1639db 100644 --- a/Source/ThirdParty/tracy/common/TracyProtocol.hpp +++ b/Source/ThirdParty/tracy/common/TracyProtocol.hpp @@ -9,14 +9,14 @@ namespace tracy constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; } -enum : uint32_t { ProtocolVersion = 63 }; +enum : uint32_t { ProtocolVersion = 64 }; enum : uint16_t { BroadcastVersion = 3 }; using lz4sz_t = uint32_t; enum { TargetFrameSize = 256 * 1024 }; enum { LZ4Size = Lz4CompressBound( TargetFrameSize ) }; -static_assert( LZ4Size <= std::numeric_limits::max(), "LZ4Size greater than lz4sz_t" ); +static_assert( LZ4Size <= (std::numeric_limits::max)(), "LZ4Size greater than lz4sz_t" ); static_assert( TargetFrameSize * 2 >= 64 * 1024, "Not enough space for LZ4 stream buffer" ); enum { HandshakeShibbolethSize = 8 }; diff --git a/Source/ThirdParty/tracy/common/TracyQueue.hpp b/Source/ThirdParty/tracy/common/TracyQueue.hpp index 092d26969..051d412ab 100644 --- a/Source/ThirdParty/tracy/common/TracyQueue.hpp +++ b/Source/ThirdParty/tracy/common/TracyQueue.hpp @@ -1,6 +1,7 @@ #ifndef __TRACYQUEUE_HPP__ #define __TRACYQUEUE_HPP__ +#include #include namespace tracy @@ -89,6 +90,7 @@ enum class QueueType : uint8_t GpuNewContext, CallstackFrame, SysTimeReport, + SysPowerReport, TidToPid, HwSampleCpuCycle, HwSampleInstructionRetired, @@ -165,9 +167,9 @@ struct QueueZoneValidationThread : public QueueZoneValidation struct QueueZoneColor { - uint8_t r; - uint8_t g; uint8_t b; + uint8_t g; + uint8_t r; }; struct QueueZoneColorThread : public QueueZoneColor @@ -221,9 +223,9 @@ struct QueueSourceLocation uint64_t function; // ptr uint64_t file; // ptr uint32_t line; - uint8_t r; - uint8_t g; uint8_t b; + uint8_t g; + uint8_t r; }; struct QueueZoneTextFat @@ -324,7 +326,7 @@ struct QueuePlotDataInt : public QueuePlotDataBase int64_t val; }; -struct QueuePlotDataFloat : public QueuePlotDataBase +struct QueuePlotDataFloat : public QueuePlotDataBase { float val; }; @@ -341,9 +343,9 @@ struct QueueMessage struct QueueMessageColor : public QueueMessage { - uint8_t r; - uint8_t g; uint8_t b; + uint8_t g; + uint8_t r; }; struct QueueMessageLiteral : public QueueMessage @@ -562,6 +564,13 @@ struct QueueSysTime float sysTime; }; +struct QueueSysPower +{ + int64_t time; + uint64_t delta; + uint64_t name; // ptr +}; + struct QueueContextSwitch { int64_t time; @@ -590,6 +599,13 @@ struct QueueHwSample int64_t time; }; +enum class PlotFormatType : uint8_t +{ + Number, + Memory, + Percentage +}; + struct QueuePlotConfig { uint64_t name; // ptr @@ -721,6 +737,7 @@ struct QueueItem QueueCrashReport crashReport; QueueCrashReportThread crashReportThread; QueueSysTime sysTime; + QueueSysPower sysPower; QueueContextSwitch contextSwitch; QueueThreadWakeup threadWakeup; QueueTidToPid tidToPid; @@ -824,6 +841,7 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ) + sizeof( QueueGpuNewContext ), sizeof( QueueHeader ) + sizeof( QueueCallstackFrame ), sizeof( QueueHeader ) + sizeof( QueueSysTime ), + sizeof( QueueHeader ) + sizeof( QueueSysPower ), sizeof( QueueHeader ) + sizeof( QueueTidToPid ), sizeof( QueueHeader ) + sizeof( QueueHwSample ), // cpu cycle sizeof( QueueHeader ) + sizeof( QueueHwSample ), // instruction retired diff --git a/Source/ThirdParty/tracy/common/TracySocket.cpp b/Source/ThirdParty/tracy/common/TracySocket.cpp index 176bbc7aa..259678989 100644 --- a/Source/ThirdParty/tracy/common/TracySocket.cpp +++ b/Source/ThirdParty/tracy/common/TracySocket.cpp @@ -353,7 +353,7 @@ int Socket::Recv( void* _buf, int len, int timeout ) } } -int Socket::ReadUpTo( void* _buf, int len, int timeout ) +int Socket::ReadUpTo( void* _buf, int len ) { const auto sock = m_sock.load( std::memory_order_relaxed ); auto buf = (char*)_buf; @@ -678,10 +678,10 @@ bool UdpListen::Listen( uint16_t port ) #endif #if defined _WIN32 unsigned long reuse = 1; - setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof( reuse ) ); + setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof( reuse ) ); #else int reuse = 1; - setsockopt( m_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) ); + setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) ); #endif #if defined _WIN32 unsigned long broadcast = 1; diff --git a/Source/ThirdParty/tracy/common/TracySocket.hpp b/Source/ThirdParty/tracy/common/TracySocket.hpp index 4b3075e29..f7713aac6 100644 --- a/Source/ThirdParty/tracy/common/TracySocket.hpp +++ b/Source/ThirdParty/tracy/common/TracySocket.hpp @@ -29,7 +29,7 @@ public: int Send( const void* buf, int len ); int GetSendBufSize(); - int ReadUpTo( void* buf, int len, int timeout ); + int ReadUpTo( void* buf, int len ); bool Read( void* buf, int len, int timeout ); template diff --git a/Source/ThirdParty/tracy/common/TracySystem.cpp b/Source/ThirdParty/tracy/common/TracySystem.cpp index 5ca8e1f45..9a477aa31 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.cpp +++ b/Source/ThirdParty/tracy/common/TracySystem.cpp @@ -205,61 +205,60 @@ TRACY_API const char* GetThreadName( uint32_t id ) } ptr = ptr->next; } -#else -# if defined _WIN32 -# ifdef TRACY_UWP - static auto _GetThreadDescription = &::GetThreadDescription; -# else - static auto _GetThreadDescription = (t_GetThreadDescription)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetThreadDescription" ); -# endif +#endif + +#if defined _WIN32 +# ifdef TRACY_UWP + static auto _GetThreadDescription = &::GetThreadDescription; +# else + static auto _GetThreadDescription = (t_GetThreadDescription)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetThreadDescription" ); +# endif if( _GetThreadDescription ) { auto hnd = OpenThread( THREAD_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)id ); if( hnd != 0 ) { PWSTR tmp; - _GetThreadDescription( hnd, &tmp ); - auto ret = wcstombs( buf, tmp, 256 ); - CloseHandle( hnd ); - if( ret != 0 ) + if( SUCCEEDED( _GetThreadDescription( hnd, &tmp ) ) ) { - return buf; + auto ret = wcstombs( buf, tmp, 256 ); + CloseHandle( hnd ); + LocalFree( tmp ); + if( ret != static_cast( -1 ) ) + { + return buf; + } } } } -# elif defined __linux__ - int cs, fd; - char path[32]; -# ifdef __ANDROID__ - int tid = gettid(); -# else - int tid = (int) syscall( SYS_gettid ); -# endif - snprintf( path, sizeof( path ), "/proc/self/task/%d/comm", tid ); - sprintf( buf, "%" PRIu32, id ); -# ifndef __ANDROID__ - pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &cs ); -# endif - if ( ( fd = open( path, O_RDONLY ) ) > 0) { - int len = read( fd, buf, 255 ); - if( len > 0 ) - { - buf[len] = 0; - if( len > 1 && buf[len-1] == '\n' ) - { - buf[len-1] = 0; - } - } - close( fd ); - } -# ifndef __ANDROID__ - pthread_setcancelstate( cs, 0 ); -# endif - return buf; -# endif +#elif defined __linux__ + int cs, fd; + char path[32]; + snprintf( path, sizeof( path ), "/proc/self/task/%d/comm", id ); + sprintf( buf, "%" PRIu32, id ); +# ifndef __ANDROID__ + pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &cs ); +# endif + if ( ( fd = open( path, O_RDONLY ) ) > 0) { + int len = read( fd, buf, 255 ); + if( len > 0 ) + { + buf[len] = 0; + if( len > 1 && buf[len-1] == '\n' ) + { + buf[len-1] = 0; + } + } + close( fd ); + } +# ifndef __ANDROID__ + pthread_setcancelstate( cs, 0 ); +# endif + return buf; #endif - sprintf( buf, "%" PRIu32, id ); - return buf; + + sprintf( buf, "%" PRIu32, id ); + return buf; } TRACY_API const char* GetEnvVar( const char* name ) @@ -295,3 +294,13 @@ TRACY_API const char* GetEnvVar( const char* name ) } } + +#ifdef __cplusplus +extern "C" { +#endif + +TRACY_API void ___tracy_set_thread_name( const char* name ) { tracy::SetThreadName( name ); } + +#ifdef __cplusplus +} +#endif diff --git a/Source/ThirdParty/tracy/common/TracySystem.hpp b/Source/ThirdParty/tracy/common/TracySystem.hpp index edcc5cf31..7a88a00b1 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.hpp +++ b/Source/ThirdParty/tracy/common/TracySystem.hpp @@ -25,13 +25,6 @@ namespace tracy { -enum class PlotFormatType : uint8_t -{ - Number, - Memory, - Percentage -}; - typedef void(*ParameterCallback)( void* data, uint32_t idx, int32_t val ); struct TRACY_API SourceLocationData @@ -47,7 +40,6 @@ class TRACY_API ScopedZone { public: static void Begin( const SourceLocationData* srcloc ); - static void Begin( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz ); static void End(); ScopedZone( const ScopedZone& ) = delete; @@ -68,7 +60,6 @@ public: void Name( const Char* txt, size_t size ); void Color( uint32_t color ); void Value( uint64_t value ); - bool IsActive() const; private: const bool m_active; diff --git a/Source/ThirdParty/tracy/common/TracyVersion.hpp b/Source/ThirdParty/tracy/common/TracyVersion.hpp index 983d1c51f..2355279f7 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 = 9 }; +enum { Minor = 10 }; enum { Patch = 0 }; } } diff --git a/Source/ThirdParty/tracy/libbacktrace/config.h b/Source/ThirdParty/tracy/libbacktrace/config.h index aa3259d11..87e38a95b 100644 --- a/Source/ThirdParty/tracy/libbacktrace/config.h +++ b/Source/ThirdParty/tracy/libbacktrace/config.h @@ -1,4 +1,8 @@ #include +#if defined(__linux__) && !defined(__GLIBC__) && !defined(__WORDSIZE) +// include __WORDSIZE headers for musl +# include +#endif #if __WORDSIZE == 64 # define BACKTRACE_ELF_SIZE 64 #else diff --git a/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp b/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp index 246cb9f36..f3899cbce 100644 --- a/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp +++ b/Source/ThirdParty/tracy/libbacktrace/dwarf.cpp @@ -473,7 +473,7 @@ enum attr_val_encoding /* An address. */ ATTR_VAL_ADDRESS, /* An index into the .debug_addr section, whose value is relative to - * the DW_AT_addr_base attribute of the compilation unit. */ + the DW_AT_addr_base attribute of the compilation unit. */ ATTR_VAL_ADDRESS_INDEX, /* A unsigned integer. */ ATTR_VAL_UINT, @@ -611,8 +611,8 @@ struct function struct function_addrs { /* Range is LOW <= PC < HIGH. */ - uint64_t low; - uint64_t high; + uintptr_t low; + uintptr_t high; /* Function for this address range. */ struct function *function; }; @@ -693,8 +693,8 @@ struct unit struct unit_addrs { /* Range is LOW <= PC < HIGH. */ - uint64_t low; - uint64_t high; + uintptr_t low; + uintptr_t high; /* Compilation unit for this address range. */ struct unit *u; }; @@ -1431,7 +1431,7 @@ resolve_addr_index (const struct dwarf_sections *dwarf_sections, uint64_t addr_base, int addrsize, int is_bigendian, uint64_t addr_index, backtrace_error_callback error_callback, void *data, - uint64_t *address) + uintptr_t *address) { uint64_t offset; struct dwarf_buf addr_buf; @@ -1452,7 +1452,7 @@ resolve_addr_index (const struct dwarf_sections *dwarf_sections, addr_buf.data = data; addr_buf.reported_underflow = 0; - *address = read_address (&addr_buf, addrsize); + *address = (uintptr_t) read_address (&addr_buf, addrsize); return 1; } @@ -1531,7 +1531,7 @@ function_addrs_search (const void *vkey, const void *ventry) static int add_unit_addr (struct backtrace_state *state, void *rdata, - uint64_t lowpc, uint64_t highpc, + uintptr_t lowpc, uintptr_t highpc, backtrace_error_callback error_callback, void *data, void *pvec) { @@ -1867,10 +1867,10 @@ lookup_abbrev (struct abbrevs *abbrevs, uint64_t code, lowpc/highpc is set or ranges is set. */ struct pcrange { - uint64_t lowpc; /* The low PC value. */ + uintptr_t lowpc; /* The low PC value. */ int have_lowpc; /* Whether a low PC value was found. */ int lowpc_is_addr_index; /* Whether lowpc is in .debug_addr. */ - uint64_t highpc; /* The high PC value. */ + uintptr_t highpc; /* The high PC value. */ int have_highpc; /* Whether a high PC value was found. */ int highpc_is_relative; /* Whether highpc is relative to lowpc. */ int highpc_is_addr_index; /* Whether highpc is in .debug_addr. */ @@ -1890,12 +1890,12 @@ update_pcrange (const struct attr* attr, const struct attr_val* val, case DW_AT_low_pc: if (val->encoding == ATTR_VAL_ADDRESS) { - pcrange->lowpc = val->u.uint; + pcrange->lowpc = (uintptr_t) val->u.uint; pcrange->have_lowpc = 1; } else if (val->encoding == ATTR_VAL_ADDRESS_INDEX) { - pcrange->lowpc = val->u.uint; + pcrange->lowpc = (uintptr_t) val->u.uint; pcrange->have_lowpc = 1; pcrange->lowpc_is_addr_index = 1; } @@ -1904,18 +1904,18 @@ update_pcrange (const struct attr* attr, const struct attr_val* val, case DW_AT_high_pc: if (val->encoding == ATTR_VAL_ADDRESS) { - pcrange->highpc = val->u.uint; + pcrange->highpc = (uintptr_t) val->u.uint; pcrange->have_highpc = 1; } else if (val->encoding == ATTR_VAL_UINT) { - pcrange->highpc = val->u.uint; + pcrange->highpc = (uintptr_t) val->u.uint; pcrange->have_highpc = 1; pcrange->highpc_is_relative = 1; } else if (val->encoding == ATTR_VAL_ADDRESS_INDEX) { - pcrange->highpc = val->u.uint; + pcrange->highpc = (uintptr_t) val->u.uint; pcrange->have_highpc = 1; pcrange->highpc_is_addr_index = 1; } @@ -1950,16 +1950,16 @@ add_low_high_range (struct backtrace_state *state, uintptr_t base_address, int is_bigendian, struct unit *u, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, - void *rdata, uint64_t lowpc, - uint64_t highpc, + void *rdata, uintptr_t lowpc, + uintptr_t highpc, backtrace_error_callback error_callback, void *data, void *vec), void *rdata, backtrace_error_callback error_callback, void *data, void *vec) { - uint64_t lowpc; - uint64_t highpc; + uintptr_t lowpc; + uintptr_t highpc; lowpc = pcrange->lowpc; if (pcrange->lowpc_is_addr_index) @@ -1997,10 +1997,10 @@ add_ranges_from_ranges ( struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, uintptr_t base_address, int is_bigendian, - struct unit *u, uint64_t base, + struct unit *u, uintptr_t base, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, - uint64_t lowpc, uint64_t highpc, + uintptr_t lowpc, uintptr_t highpc, backtrace_error_callback error_callback, void *data, void *vec), void *rdata, @@ -2039,12 +2039,12 @@ add_ranges_from_ranges ( break; if (is_highest_address (low, u->addrsize)) - base = high; + base = (uintptr_t) high; else { if (!add_range (state, rdata, - low + base + base_address, - high + base + base_address, + (uintptr_t) low + base + base_address, + (uintptr_t) high + base + base_address, error_callback, data, vec)) return 0; } @@ -2064,10 +2064,10 @@ add_ranges_from_rnglists ( struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, uintptr_t base_address, int is_bigendian, - struct unit *u, uint64_t base, + struct unit *u, uintptr_t base, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, - uint64_t lowpc, uint64_t highpc, + uintptr_t lowpc, uintptr_t highpc, backtrace_error_callback error_callback, void *data, void *vec), void *rdata, @@ -2133,8 +2133,8 @@ add_ranges_from_rnglists ( case DW_RLE_startx_endx: { uint64_t index; - uint64_t low; - uint64_t high; + uintptr_t low; + uintptr_t high; index = read_uleb128 (&rnglists_buf); if (!resolve_addr_index (dwarf_sections, u->addr_base, @@ -2156,8 +2156,8 @@ add_ranges_from_rnglists ( case DW_RLE_startx_length: { uint64_t index; - uint64_t low; - uint64_t length; + uintptr_t low; + uintptr_t length; index = read_uleb128 (&rnglists_buf); if (!resolve_addr_index (dwarf_sections, u->addr_base, @@ -2187,16 +2187,16 @@ add_ranges_from_rnglists ( break; case DW_RLE_base_address: - base = read_address (&rnglists_buf, u->addrsize); + base = (uintptr_t) read_address (&rnglists_buf, u->addrsize); break; case DW_RLE_start_end: { - uint64_t low; - uint64_t high; + uintptr_t low; + uintptr_t high; - low = read_address (&rnglists_buf, u->addrsize); - high = read_address (&rnglists_buf, u->addrsize); + 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)) @@ -2206,11 +2206,11 @@ add_ranges_from_rnglists ( case DW_RLE_start_length: { - uint64_t low; - uint64_t length; + uintptr_t low; + uintptr_t length; - low = read_address (&rnglists_buf, u->addrsize); - length = read_uleb128 (&rnglists_buf); + low = (uintptr_t) read_address (&rnglists_buf, u->addrsize); + length = (uintptr_t) read_uleb128 (&rnglists_buf); low += base_address; if (!add_range (state, rdata, low, low + length, error_callback, data, vec)) @@ -2240,9 +2240,9 @@ static int add_ranges (struct backtrace_state *state, const struct dwarf_sections *dwarf_sections, uintptr_t base_address, int is_bigendian, - struct unit *u, uint64_t base, const struct pcrange *pcrange, + struct unit *u, uintptr_t base, const struct pcrange *pcrange, int (*add_range) (struct backtrace_state *state, void *rdata, - uint64_t lowpc, uint64_t highpc, + uintptr_t lowpc, uintptr_t highpc, backtrace_error_callback error_callback, void *data, void *vec), void *rdata, @@ -3520,7 +3520,7 @@ read_referenced_name (struct dwarf_data *ddata, struct unit *u, static int add_function_range (struct backtrace_state *state, void *rdata, - uint64_t lowpc, uint64_t highpc, + uintptr_t lowpc, uintptr_t highpc, backtrace_error_callback error_callback, void *data, void *pvec) { @@ -3560,7 +3560,7 @@ add_function_range (struct backtrace_state *state, void *rdata, static int read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, - struct unit *u, uint64_t base, struct dwarf_buf *unit_buf, + struct unit *u, uintptr_t base, struct dwarf_buf *unit_buf, const struct line_header *lhdr, backtrace_error_callback error_callback, void *data, struct function_vector *vec_function, @@ -3624,7 +3624,7 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, && abbrev->attrs[i].name == DW_AT_low_pc) { if (val.encoding == ATTR_VAL_ADDRESS) - base = val.u.uint; + base = (uintptr_t) val.u.uint; else if (val.encoding == ATTR_VAL_ADDRESS_INDEX) { if (!resolve_addr_index (&ddata->dwarf_sections, diff --git a/Source/ThirdParty/tracy/libbacktrace/elf.cpp b/Source/ThirdParty/tracy/libbacktrace/elf.cpp index 9e62f090d..c65bc4e76 100644 --- a/Source/ThirdParty/tracy/libbacktrace/elf.cpp +++ b/Source/ThirdParty/tracy/libbacktrace/elf.cpp @@ -193,6 +193,7 @@ dl_iterate_phdr (int (*callback) (struct dl_phdr_info *, #undef STT_FUNC #undef NT_GNU_BUILD_ID #undef ELFCOMPRESS_ZLIB +#undef ELFCOMPRESS_ZSTD /* Basic types. */ @@ -350,6 +351,7 @@ typedef struct #endif /* BACKTRACE_ELF_SIZE != 32 */ #define ELFCOMPRESS_ZLIB 1 +#define ELFCOMPRESS_ZSTD 2 /* Names of sections, indexed by enum dwarf_section in internal.h. */ @@ -1130,7 +1132,7 @@ elf_uncompress_failed(void) on error. */ static int -elf_zlib_fetch (const unsigned char **ppin, const unsigned char *pinend, +elf_fetch_bits (const unsigned char **ppin, const unsigned char *pinend, uint64_t *pval, unsigned int *pbits) { unsigned int bits; @@ -1177,6 +1179,118 @@ elf_zlib_fetch (const unsigned char **ppin, const unsigned char *pinend, return 1; } +/* This is like elf_fetch_bits, but it fetchs the bits backward, and ensures at + least 16 bits. This is for zstd. */ + +static int +elf_fetch_bits_backward (const unsigned char **ppin, + const unsigned char *pinend, + uint64_t *pval, unsigned int *pbits) +{ + unsigned int bits; + const unsigned char *pin; + uint64_t val; + uint32_t next; + + bits = *pbits; + if (bits >= 16) + return 1; + pin = *ppin; + val = *pval; + + if (unlikely (pin <= pinend)) + { + if (bits == 0) + { + elf_uncompress_failed (); + return 0; + } + return 1; + } + + pin -= 4; + +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) \ + && defined(__ORDER_BIG_ENDIAN__) \ + && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ \ + || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + /* We've ensured that PIN is aligned. */ + next = *(const uint32_t *)pin; + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + next = __builtin_bswap32 (next); +#endif +#else + next = pin[0] | (pin[1] << 8) | (pin[2] << 16) | (pin[3] << 24); +#endif + + val <<= 32; + val |= next; + bits += 32; + + if (unlikely (pin < pinend)) + { + val >>= (pinend - pin) * 8; + bits -= (pinend - pin) * 8; + } + + *ppin = pin; + *pval = val; + *pbits = bits; + return 1; +} + +/* Initialize backward fetching when the bitstream starts with a 1 bit in the + last byte in memory (which is the first one that we read). This is used by + zstd decompression. Returns 1 on success, 0 on error. */ + +static int +elf_fetch_backward_init (const unsigned char **ppin, + const unsigned char *pinend, + uint64_t *pval, unsigned int *pbits) +{ + const unsigned char *pin; + unsigned int stream_start; + uint64_t val; + unsigned int bits; + + pin = *ppin; + stream_start = (unsigned int)*pin; + if (unlikely (stream_start == 0)) + { + elf_uncompress_failed (); + return 0; + } + val = 0; + bits = 0; + + /* Align to a 32-bit boundary. */ + while ((((uintptr_t)pin) & 3) != 0) + { + val <<= 8; + val |= (uint64_t)*pin; + bits += 8; + --pin; + } + + val <<= 8; + val |= (uint64_t)*pin; + bits += 8; + + *ppin = pin; + *pval = val; + *pbits = bits; + if (!elf_fetch_bits_backward (ppin, pinend, pval, pbits)) + return 0; + + *pbits -= __builtin_clz (stream_start) - (sizeof (unsigned int) - 1) * 8 + 1; + + if (!elf_fetch_bits_backward (ppin, pinend, pval, pbits)) + return 0; + + return 1; +} + /* Huffman code tables, like the rest of the zlib format, are defined by RFC 1951. We store a Huffman code table as a series of tables stored sequentially in memory. Each entry in a table is 16 bits. @@ -1211,14 +1325,14 @@ elf_zlib_fetch (const unsigned char **ppin, const unsigned char *pinend, /* Number of entries we allocate to for one code table. We get a page for the two code tables we need. */ -#define HUFFMAN_TABLE_SIZE (1024) +#define ZLIB_HUFFMAN_TABLE_SIZE (1024) /* Bit masks and shifts for the values in the table. */ -#define HUFFMAN_VALUE_MASK 0x01ff -#define HUFFMAN_BITS_SHIFT 9 -#define HUFFMAN_BITS_MASK 0x7 -#define HUFFMAN_SECONDARY_SHIFT 12 +#define ZLIB_HUFFMAN_VALUE_MASK 0x01ff +#define ZLIB_HUFFMAN_BITS_SHIFT 9 +#define ZLIB_HUFFMAN_BITS_MASK 0x7 +#define ZLIB_HUFFMAN_SECONDARY_SHIFT 12 /* For working memory while inflating we need two code tables, we need an array of code lengths (max value 15, so we use unsigned char), @@ -1226,17 +1340,17 @@ elf_zlib_fetch (const unsigned char **ppin, const unsigned char *pinend, latter two arrays must be large enough to hold the maximum number of code lengths, which RFC 1951 defines as 286 + 30. */ -#define ZDEBUG_TABLE_SIZE \ - (2 * HUFFMAN_TABLE_SIZE * sizeof (uint16_t) \ +#define ZLIB_TABLE_SIZE \ + (2 * ZLIB_HUFFMAN_TABLE_SIZE * sizeof (uint16_t) \ + (286 + 30) * sizeof (uint16_t) \ + (286 + 30) * sizeof (unsigned char)) -#define ZDEBUG_TABLE_CODELEN_OFFSET \ - (2 * HUFFMAN_TABLE_SIZE * sizeof (uint16_t) \ +#define ZLIB_TABLE_CODELEN_OFFSET \ + (2 * ZLIB_HUFFMAN_TABLE_SIZE * sizeof (uint16_t) \ + (286 + 30) * sizeof (uint16_t)) -#define ZDEBUG_TABLE_WORK_OFFSET \ - (2 * HUFFMAN_TABLE_SIZE * sizeof (uint16_t)) +#define ZLIB_TABLE_WORK_OFFSET \ + (2 * ZLIB_HUFFMAN_TABLE_SIZE * sizeof (uint16_t)) #ifdef BACKTRACE_GENERATE_FIXED_HUFFMAN_TABLE @@ -1269,7 +1383,7 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, next value after VAL with the same bit length. */ next = (uint16_t *) (((unsigned char *) zdebug_table) - + ZDEBUG_TABLE_WORK_OFFSET); + + ZLIB_TABLE_WORK_OFFSET); memset (&count[0], 0, 16 * sizeof (uint16_t)); for (i = 0; i < codes_len; ++i) @@ -1297,7 +1411,7 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, /* For each length, fill in the table for the codes of that length. */ - memset (table, 0, HUFFMAN_TABLE_SIZE * sizeof (uint16_t)); + memset (table, 0, ZLIB_HUFFMAN_TABLE_SIZE * sizeof (uint16_t)); /* Handle the values that do not require a secondary table. */ @@ -1331,13 +1445,13 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, /* In the compressed bit stream, the value VAL is encoded as J bits with the value C. */ - if (unlikely ((val & ~HUFFMAN_VALUE_MASK) != 0)) + if (unlikely ((val & ~ZLIB_HUFFMAN_VALUE_MASK) != 0)) { elf_uncompress_failed (); return 0; } - tval = val | ((j - 1) << HUFFMAN_BITS_SHIFT); + tval = val | ((j - 1) << ZLIB_HUFFMAN_BITS_SHIFT); /* The table lookup uses 8 bits. If J is less than 8, we don't know what the other bits will be. We need to fill @@ -1487,7 +1601,7 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, { /* Start a new secondary table. */ - if (unlikely ((next_secondary & HUFFMAN_VALUE_MASK) + if (unlikely ((next_secondary & ZLIB_HUFFMAN_VALUE_MASK) != next_secondary)) { elf_uncompress_failed (); @@ -1498,22 +1612,23 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, secondary_bits = j - 8; next_secondary += 1 << secondary_bits; table[primary] = (secondary - + ((j - 8) << HUFFMAN_BITS_SHIFT) - + (1U << HUFFMAN_SECONDARY_SHIFT)); + + ((j - 8) << ZLIB_HUFFMAN_BITS_SHIFT) + + (1U << ZLIB_HUFFMAN_SECONDARY_SHIFT)); } else { /* There is an existing entry. It had better be a secondary table with enough bits. */ - if (unlikely ((tprimary & (1U << HUFFMAN_SECONDARY_SHIFT)) + if (unlikely ((tprimary + & (1U << ZLIB_HUFFMAN_SECONDARY_SHIFT)) == 0)) { elf_uncompress_failed (); return 0; } - secondary = tprimary & HUFFMAN_VALUE_MASK; - secondary_bits = ((tprimary >> HUFFMAN_BITS_SHIFT) - & HUFFMAN_BITS_MASK); + secondary = tprimary & ZLIB_HUFFMAN_VALUE_MASK; + secondary_bits = ((tprimary >> ZLIB_HUFFMAN_BITS_SHIFT) + & ZLIB_HUFFMAN_BITS_MASK); if (unlikely (secondary_bits < j - 8)) { elf_uncompress_failed (); @@ -1524,7 +1639,7 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, /* Fill in secondary table entries. */ - tval = val | ((j - 8) << HUFFMAN_BITS_SHIFT); + tval = val | ((j - 8) << ZLIB_HUFFMAN_BITS_SHIFT); for (ind = code >> 8; ind < (1U << secondary_bits); @@ -1567,7 +1682,7 @@ elf_zlib_inflate_table (unsigned char *codes, size_t codes_len, #include -static uint16_t table[ZDEBUG_TABLE_SIZE]; +static uint16_t table[ZLIB_TABLE_SIZE]; static unsigned char codes[288]; int @@ -1795,7 +1910,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, const uint16_t *tlit; const uint16_t *tdist; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; last = val & 1; @@ -1883,7 +1998,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, /* Read a Huffman encoding table. The various magic numbers here are from RFC 1951. */ - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; nlit = (val & 0x1f) + 257; @@ -1908,7 +2023,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, /* There are always at least 4 elements in the table. */ - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; codebits[16] = val & 7; @@ -1928,7 +2043,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, if (nclen == 5) goto codebitsdone; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; codebits[7] = val & 7; @@ -1966,7 +2081,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, if (nclen == 10) goto codebitsdone; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; codebits[11] = val & 7; @@ -2004,7 +2119,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, if (nclen == 15) goto codebitsdone; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; codebits[2] = val & 7; @@ -2043,7 +2158,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, at the end of zdebug_table to hold them. */ plenbase = (((unsigned char *) zdebug_table) - + ZDEBUG_TABLE_CODELEN_OFFSET); + + ZLIB_TABLE_CODELEN_OFFSET); plen = plenbase; plenend = plen + nlit + ndist; while (plen < plenend) @@ -2052,24 +2167,25 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, unsigned int b; uint16_t v; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; t = zdebug_table[val & 0xff]; /* The compression here uses bit lengths up to 7, so a secondary table is never necessary. */ - if (unlikely ((t & (1U << HUFFMAN_SECONDARY_SHIFT)) != 0)) + if (unlikely ((t & (1U << ZLIB_HUFFMAN_SECONDARY_SHIFT)) + != 0)) { elf_uncompress_failed (); return 0; } - b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; + b = (t >> ZLIB_HUFFMAN_BITS_SHIFT) & ZLIB_HUFFMAN_BITS_MASK; val >>= b + 1; bits -= b + 1; - v = t & HUFFMAN_VALUE_MASK; + v = t & ZLIB_HUFFMAN_VALUE_MASK; if (v < 16) *plen++ = v; else if (v == 16) @@ -2086,7 +2202,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, } /* We used up to 7 bits since the last - elf_zlib_fetch, so we have at least 8 bits + elf_fetch_bits, so we have at least 8 bits available here. */ c = 3 + (val & 0x3); @@ -2121,7 +2237,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, /* Store zero 3 to 10 times. */ /* We used up to 7 bits since the last - elf_zlib_fetch, so we have at least 8 bits + elf_fetch_bits, so we have at least 8 bits available here. */ c = 3 + (val & 0x7); @@ -2167,7 +2283,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, /* Store zero 11 to 138 times. */ /* We used up to 7 bits since the last - elf_zlib_fetch, so we have at least 8 bits + elf_fetch_bits, so we have at least 8 bits available here. */ c = 11 + (val & 0x7f); @@ -2204,10 +2320,11 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, zdebug_table)) return 0; if (!elf_zlib_inflate_table (plen + nlit, ndist, zdebug_table, - zdebug_table + HUFFMAN_TABLE_SIZE)) + (zdebug_table + + ZLIB_HUFFMAN_TABLE_SIZE))) return 0; tlit = zdebug_table; - tdist = zdebug_table + HUFFMAN_TABLE_SIZE; + tdist = zdebug_table + ZLIB_HUFFMAN_TABLE_SIZE; } /* Inflate values until the end of the block. This is the @@ -2220,14 +2337,14 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, uint16_t v; unsigned int lit; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; t = tlit[val & 0xff]; - b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; - v = t & HUFFMAN_VALUE_MASK; + b = (t >> ZLIB_HUFFMAN_BITS_SHIFT) & ZLIB_HUFFMAN_BITS_MASK; + v = t & ZLIB_HUFFMAN_VALUE_MASK; - if ((t & (1U << HUFFMAN_SECONDARY_SHIFT)) == 0) + if ((t & (1U << ZLIB_HUFFMAN_SECONDARY_SHIFT)) == 0) { lit = v; val >>= b + 1; @@ -2236,8 +2353,8 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, else { t = tlit[v + 0x100 + ((val >> 8) & ((1U << b) - 1))]; - b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; - lit = t & HUFFMAN_VALUE_MASK; + b = (t >> ZLIB_HUFFMAN_BITS_SHIFT) & ZLIB_HUFFMAN_BITS_MASK; + lit = t & ZLIB_HUFFMAN_VALUE_MASK; val >>= b + 8; bits -= b + 8; } @@ -2282,7 +2399,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, { unsigned int extra; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; /* This is an expression for the table of length @@ -2297,14 +2414,14 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, bits -= extra; } - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; t = tdist[val & 0xff]; - b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; - v = t & HUFFMAN_VALUE_MASK; + b = (t >> ZLIB_HUFFMAN_BITS_SHIFT) & ZLIB_HUFFMAN_BITS_MASK; + v = t & ZLIB_HUFFMAN_VALUE_MASK; - if ((t & (1U << HUFFMAN_SECONDARY_SHIFT)) == 0) + if ((t & (1U << ZLIB_HUFFMAN_SECONDARY_SHIFT)) == 0) { dist = v; val >>= b + 1; @@ -2313,8 +2430,9 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, else { t = tdist[v + 0x100 + ((val >> 8) & ((1U << b) - 1))]; - b = (t >> HUFFMAN_BITS_SHIFT) & HUFFMAN_BITS_MASK; - dist = t & HUFFMAN_VALUE_MASK; + b = ((t >> ZLIB_HUFFMAN_BITS_SHIFT) + & ZLIB_HUFFMAN_BITS_MASK); + dist = t & ZLIB_HUFFMAN_VALUE_MASK; val >>= b + 8; bits -= b + 8; } @@ -2354,7 +2472,7 @@ elf_zlib_inflate (const unsigned char *pin, size_t sin, uint16_t *zdebug_table, { unsigned int extra; - if (!elf_zlib_fetch (&pin, pinend, &val, &bits)) + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) return 0; /* This is an expression for the table of @@ -2559,6 +2677,2354 @@ elf_zlib_inflate_and_verify (const unsigned char *pin, size_t sin, return 1; } +/* For working memory during zstd compression, we need + - a literal length FSE table: 512 64-bit values == 4096 bytes + - a match length FSE table: 512 64-bit values == 4096 bytes + - a offset FSE table: 256 64-bit values == 2048 bytes + - a Huffman tree: 2048 uint16_t values == 4096 bytes + - scratch space, one of + - to build an FSE table: 512 uint16_t values == 1024 bytes + - to build a Huffman tree: 512 uint16_t + 256 uint32_t == 2048 bytes +*/ + +#define ZSTD_TABLE_SIZE \ + (2 * 512 * sizeof (struct elf_zstd_fse_baseline_entry) \ + + 256 * sizeof (struct elf_zstd_fse_baseline_entry) \ + + 2048 * sizeof (uint16_t) \ + + 512 * sizeof (uint16_t) + 256 * sizeof (uint32_t)) + +#define ZSTD_TABLE_LITERAL_FSE_OFFSET (0) + +#define ZSTD_TABLE_MATCH_FSE_OFFSET \ + (512 * sizeof (struct elf_zstd_fse_baseline_entry)) + +#define ZSTD_TABLE_OFFSET_FSE_OFFSET \ + (ZSTD_TABLE_MATCH_FSE_OFFSET \ + + 512 * sizeof (struct elf_zstd_fse_baseline_entry)) + +#define ZSTD_TABLE_HUFFMAN_OFFSET \ + (ZSTD_TABLE_OFFSET_FSE_OFFSET \ + + 256 * sizeof (struct elf_zstd_fse_baseline_entry)) + +#define ZSTD_TABLE_WORK_OFFSET \ + (ZSTD_TABLE_HUFFMAN_OFFSET + 2048 * sizeof (uint16_t)) + +/* An entry in a zstd FSE table. */ + +struct elf_zstd_fse_entry +{ + /* The value that this FSE entry represents. */ + unsigned char symbol; + /* The number of bits to read to determine the next state. */ + unsigned char bits; + /* Add the bits to this base to get the next state. */ + uint16_t base; +}; + +static int +elf_zstd_build_fse (const int16_t *, int, uint16_t *, int, + struct elf_zstd_fse_entry *); + +/* Read a zstd FSE table and build the decoding table in *TABLE, updating *PPIN + as it reads. ZDEBUG_TABLE is scratch space; it must be enough for 512 + uint16_t values (1024 bytes). MAXIDX is the maximum number of symbols + permitted. *TABLE_BITS is the maximum number of bits for symbols in the + table: the size of *TABLE is at least 1 << *TABLE_BITS. This updates + *TABLE_BITS to the actual number of bits. Returns 1 on success, 0 on + error. */ + +static int +elf_zstd_read_fse (const unsigned char **ppin, const unsigned char *pinend, + uint16_t *zdebug_table, int maxidx, + struct elf_zstd_fse_entry *table, int *table_bits) +{ + const unsigned char *pin; + int16_t *norm; + uint16_t *next; + uint64_t val; + unsigned int bits; + int accuracy_log; + uint32_t remaining; + uint32_t threshold; + int bits_needed; + int idx; + int prev0; + + pin = *ppin; + + norm = (int16_t *) zdebug_table; + next = zdebug_table + 256; + + if (unlikely (pin + 3 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + + /* Align PIN to a 32-bit boundary. */ + + val = 0; + bits = 0; + while ((((uintptr_t) pin) & 3) != 0) + { + val |= (uint64_t)*pin << bits; + bits += 8; + ++pin; + } + + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) + return 0; + + accuracy_log = (val & 0xf) + 5; + if (accuracy_log > *table_bits) + { + elf_uncompress_failed (); + return 0; + } + *table_bits = accuracy_log; + val >>= 4; + bits -= 4; + + /* This code is mostly copied from the reference implementation. */ + + /* The number of remaining probabilities, plus 1. This sets the number of + bits that need to be read for the next value. */ + remaining = (1 << accuracy_log) + 1; + + /* The current difference between small and large values, which depends on + the number of remaining values. Small values use one less bit. */ + threshold = 1 << accuracy_log; + + /* The number of bits used to compute threshold. */ + bits_needed = accuracy_log + 1; + + /* The next character value. */ + idx = 0; + + /* Whether the last count was 0. */ + prev0 = 0; + + while (remaining > 1 && idx <= maxidx) + { + uint32_t max; + int32_t count; + + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) + return 0; + + if (prev0) + { + int zidx; + + /* Previous count was 0, so there is a 2-bit repeat flag. If the + 2-bit flag is 0b11, it adds 3 and then there is another repeat + flag. */ + zidx = idx; + while ((val & 0xfff) == 0xfff) + { + zidx += 3 * 6; + val >>= 12; + bits -= 12; + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) + return 0; + } + while ((val & 3) == 3) + { + zidx += 3; + val >>= 2; + bits -= 2; + if (!elf_fetch_bits (&pin, pinend, &val, &bits)) + return 0; + } + /* We have at least 13 bits here, don't need to fetch. */ + zidx += val & 3; + val >>= 2; + bits -= 2; + + if (unlikely (zidx > maxidx)) + { + elf_uncompress_failed (); + return 0; + } + + for (; idx < zidx; idx++) + norm[idx] = 0; + + prev0 = 0; + continue; + } + + max = (2 * threshold - 1) - remaining; + if ((val & (threshold - 1)) < max) + { + /* A small value. */ + count = (int32_t) ((uint32_t) val & (threshold - 1)); + val >>= bits_needed - 1; + bits -= bits_needed - 1; + } + else + { + /* A large value. */ + count = (int32_t) ((uint32_t) val & (2 * threshold - 1)); + if (count >= (int32_t) threshold) + count -= (int32_t) max; + val >>= bits_needed; + bits -= bits_needed; + } + + count--; + if (count >= 0) + remaining -= count; + else + remaining--; + if (unlikely (idx >= 256)) + { + elf_uncompress_failed (); + return 0; + } + norm[idx] = (int16_t) count; + ++idx; + + prev0 = count == 0; + + while (remaining < threshold) + { + bits_needed--; + threshold >>= 1; + } + } + + if (unlikely (remaining != 1)) + { + elf_uncompress_failed (); + return 0; + } + + /* If we've read ahead more than a byte, back up. */ + while (bits >= 8) + { + --pin; + bits -= 8; + } + + *ppin = pin; + + for (; idx <= maxidx; idx++) + norm[idx] = 0; + + return elf_zstd_build_fse (norm, idx, next, *table_bits, table); +} + +/* Build the FSE decoding table from a list of probabilities. This reads from + NORM of length IDX, uses NEXT as scratch space, and writes to *TABLE, whose + size is TABLE_BITS. */ + +static int +elf_zstd_build_fse (const int16_t *norm, int idx, uint16_t *next, + int table_bits, struct elf_zstd_fse_entry *table) +{ + int table_size; + int high_threshold; + int i; + int pos; + int step; + int mask; + + table_size = 1 << table_bits; + high_threshold = table_size - 1; + for (i = 0; i < idx; i++) + { + int16_t n; + + n = norm[i]; + if (n >= 0) + next[i] = (uint16_t) n; + else + { + table[high_threshold].symbol = (unsigned char) i; + high_threshold--; + next[i] = 1; + } + } + + pos = 0; + step = (table_size >> 1) + (table_size >> 3) + 3; + mask = table_size - 1; + for (i = 0; i < idx; i++) + { + int n; + int j; + + n = (int) norm[i]; + for (j = 0; j < n; j++) + { + table[pos].symbol = (unsigned char) i; + pos = (pos + step) & mask; + while (unlikely (pos > high_threshold)) + pos = (pos + step) & mask; + } + } + if (unlikely (pos != 0)) + { + elf_uncompress_failed (); + return 0; + } + + for (i = 0; i < table_size; i++) + { + unsigned char sym; + uint16_t next_state; + int high_bit; + int bits; + + sym = table[i].symbol; + next_state = next[sym]; + ++next[sym]; + + if (next_state == 0) + { + elf_uncompress_failed (); + return 0; + } + high_bit = 31 - __builtin_clz (next_state); + + bits = table_bits - high_bit; + table[i].bits = (unsigned char) bits; + table[i].base = (uint16_t) ((next_state << bits) - table_size); + } + + return 1; +} + +/* Encode the baseline and bits into a single 32-bit value. */ + +#define ZSTD_ENCODE_BASELINE_BITS(baseline, basebits) \ + ((uint32_t)(baseline) | ((uint32_t)(basebits) << 24)) + +#define ZSTD_DECODE_BASELINE(baseline_basebits) \ + ((uint32_t)(baseline_basebits) & 0xffffff) + +#define ZSTD_DECODE_BASEBITS(baseline_basebits) \ + ((uint32_t)(baseline_basebits) >> 24) + +/* Given a literal length code, we need to read a number of bits and add that + to a baseline. For states 0 to 15 the baseline is the state and the number + of bits is zero. */ + +#define ZSTD_LITERAL_LENGTH_BASELINE_OFFSET (16) + +static const uint32_t elf_zstd_literal_length_base[] = +{ + ZSTD_ENCODE_BASELINE_BITS(16, 1), + ZSTD_ENCODE_BASELINE_BITS(18, 1), + ZSTD_ENCODE_BASELINE_BITS(20, 1), + ZSTD_ENCODE_BASELINE_BITS(22, 1), + ZSTD_ENCODE_BASELINE_BITS(24, 2), + ZSTD_ENCODE_BASELINE_BITS(28, 2), + ZSTD_ENCODE_BASELINE_BITS(32, 3), + ZSTD_ENCODE_BASELINE_BITS(40, 3), + ZSTD_ENCODE_BASELINE_BITS(48, 4), + ZSTD_ENCODE_BASELINE_BITS(64, 6), + ZSTD_ENCODE_BASELINE_BITS(128, 7), + ZSTD_ENCODE_BASELINE_BITS(256, 8), + ZSTD_ENCODE_BASELINE_BITS(512, 9), + ZSTD_ENCODE_BASELINE_BITS(1024, 10), + ZSTD_ENCODE_BASELINE_BITS(2048, 11), + ZSTD_ENCODE_BASELINE_BITS(4096, 12), + ZSTD_ENCODE_BASELINE_BITS(8192, 13), + ZSTD_ENCODE_BASELINE_BITS(16384, 14), + ZSTD_ENCODE_BASELINE_BITS(32768, 15), + ZSTD_ENCODE_BASELINE_BITS(65536, 16) +}; + +/* The same applies to match length codes. For states 0 to 31 the baseline is + the state + 3 and the number of bits is zero. */ + +#define ZSTD_MATCH_LENGTH_BASELINE_OFFSET (32) + +static const uint32_t elf_zstd_match_length_base[] = +{ + ZSTD_ENCODE_BASELINE_BITS(35, 1), + ZSTD_ENCODE_BASELINE_BITS(37, 1), + ZSTD_ENCODE_BASELINE_BITS(39, 1), + ZSTD_ENCODE_BASELINE_BITS(41, 1), + ZSTD_ENCODE_BASELINE_BITS(43, 2), + ZSTD_ENCODE_BASELINE_BITS(47, 2), + ZSTD_ENCODE_BASELINE_BITS(51, 3), + ZSTD_ENCODE_BASELINE_BITS(59, 3), + ZSTD_ENCODE_BASELINE_BITS(67, 4), + ZSTD_ENCODE_BASELINE_BITS(83, 4), + ZSTD_ENCODE_BASELINE_BITS(99, 5), + ZSTD_ENCODE_BASELINE_BITS(131, 7), + ZSTD_ENCODE_BASELINE_BITS(259, 8), + ZSTD_ENCODE_BASELINE_BITS(515, 9), + ZSTD_ENCODE_BASELINE_BITS(1027, 10), + ZSTD_ENCODE_BASELINE_BITS(2051, 11), + ZSTD_ENCODE_BASELINE_BITS(4099, 12), + ZSTD_ENCODE_BASELINE_BITS(8195, 13), + ZSTD_ENCODE_BASELINE_BITS(16387, 14), + ZSTD_ENCODE_BASELINE_BITS(32771, 15), + ZSTD_ENCODE_BASELINE_BITS(65539, 16) +}; + +/* An entry in an FSE table used for literal/match/length values. For these we + have to map the symbol to a baseline value, and we have to read zero or more + bits and add that value to the baseline value. Rather than look the values + up in a separate table, we grow the FSE table so that we get better memory + caching. */ + +struct elf_zstd_fse_baseline_entry +{ + /* The baseline for the value that this FSE entry represents.. */ + uint32_t baseline; + /* The number of bits to read to add to the baseline. */ + unsigned char basebits; + /* The number of bits to read to determine the next state. */ + unsigned char bits; + /* Add the bits to this base to get the next state. */ + uint16_t base; +}; + +/* Convert the literal length FSE table FSE_TABLE to an FSE baseline table at + BASELINE_TABLE. Note that FSE_TABLE and BASELINE_TABLE will overlap. */ + +static int +elf_zstd_make_literal_baseline_fse ( + const struct elf_zstd_fse_entry *fse_table, + int table_bits, + struct elf_zstd_fse_baseline_entry *baseline_table) +{ + size_t count; + const struct elf_zstd_fse_entry *pfse; + struct elf_zstd_fse_baseline_entry *pbaseline; + + /* Convert backward to avoid overlap. */ + + count = 1U << table_bits; + pfse = fse_table + count; + pbaseline = baseline_table + count; + while (pfse > fse_table) + { + unsigned char symbol; + unsigned char bits; + uint16_t base; + + --pfse; + --pbaseline; + symbol = pfse->symbol; + bits = pfse->bits; + base = pfse->base; + if (symbol < ZSTD_LITERAL_LENGTH_BASELINE_OFFSET) + { + pbaseline->baseline = (uint32_t)symbol; + pbaseline->basebits = 0; + } + else + { + unsigned int idx; + uint32_t basebits; + + if (unlikely (symbol > 35)) + { + elf_uncompress_failed (); + return 0; + } + idx = symbol - ZSTD_LITERAL_LENGTH_BASELINE_OFFSET; + basebits = elf_zstd_literal_length_base[idx]; + pbaseline->baseline = ZSTD_DECODE_BASELINE(basebits); + pbaseline->basebits = ZSTD_DECODE_BASEBITS(basebits); + } + pbaseline->bits = bits; + pbaseline->base = base; + } + + return 1; +} + +/* Convert the offset length FSE table FSE_TABLE to an FSE baseline table at + BASELINE_TABLE. Note that FSE_TABLE and BASELINE_TABLE will overlap. */ + +static int +elf_zstd_make_offset_baseline_fse ( + const struct elf_zstd_fse_entry *fse_table, + int table_bits, + struct elf_zstd_fse_baseline_entry *baseline_table) +{ + size_t count; + const struct elf_zstd_fse_entry *pfse; + struct elf_zstd_fse_baseline_entry *pbaseline; + + /* Convert backward to avoid overlap. */ + + count = 1U << table_bits; + pfse = fse_table + count; + pbaseline = baseline_table + count; + while (pfse > fse_table) + { + unsigned char symbol; + unsigned char bits; + uint16_t base; + + --pfse; + --pbaseline; + symbol = pfse->symbol; + bits = pfse->bits; + base = pfse->base; + if (unlikely (symbol > 31)) + { + elf_uncompress_failed (); + return 0; + } + + /* The simple way to write this is + + pbaseline->baseline = (uint32_t)1 << symbol; + pbaseline->basebits = symbol; + + That will give us an offset value that corresponds to the one + described in the RFC. However, for offset values > 3, we have to + subtract 3. And for offset values 1, 2, 3 we use a repeated offset. + The baseline is always a power of 2, and is never 0, so for these low + values we will see one entry that is baseline 1, basebits 0, and one + entry that is baseline 2, basebits 1. All other entries will have + baseline >= 4 and basebits >= 2. + + So we can check for RFC offset <= 3 by checking for basebits <= 1. + And that means that we can subtract 3 here and not worry about doing + it in the hot loop. */ + + pbaseline->baseline = (uint32_t)1 << symbol; + if (symbol >= 2) + pbaseline->baseline -= 3; + pbaseline->basebits = symbol; + pbaseline->bits = bits; + pbaseline->base = base; + } + + return 1; +} + +/* Convert the match length FSE table FSE_TABLE to an FSE baseline table at + BASELINE_TABLE. Note that FSE_TABLE and BASELINE_TABLE will overlap. */ + +static int +elf_zstd_make_match_baseline_fse ( + const struct elf_zstd_fse_entry *fse_table, + int table_bits, + struct elf_zstd_fse_baseline_entry *baseline_table) +{ + size_t count; + const struct elf_zstd_fse_entry *pfse; + struct elf_zstd_fse_baseline_entry *pbaseline; + + /* Convert backward to avoid overlap. */ + + count = 1U << table_bits; + pfse = fse_table + count; + pbaseline = baseline_table + count; + while (pfse > fse_table) + { + unsigned char symbol; + unsigned char bits; + uint16_t base; + + --pfse; + --pbaseline; + symbol = pfse->symbol; + bits = pfse->bits; + base = pfse->base; + if (symbol < ZSTD_MATCH_LENGTH_BASELINE_OFFSET) + { + pbaseline->baseline = (uint32_t)symbol + 3; + pbaseline->basebits = 0; + } + else + { + unsigned int idx; + uint32_t basebits; + + if (unlikely (symbol > 52)) + { + elf_uncompress_failed (); + return 0; + } + idx = symbol - ZSTD_MATCH_LENGTH_BASELINE_OFFSET; + basebits = elf_zstd_match_length_base[idx]; + pbaseline->baseline = ZSTD_DECODE_BASELINE(basebits); + pbaseline->basebits = ZSTD_DECODE_BASEBITS(basebits); + } + pbaseline->bits = bits; + pbaseline->base = base; + } + + return 1; +} + +#ifdef BACKTRACE_GENERATE_ZSTD_FSE_TABLES + +/* Used to generate the predefined FSE decoding tables for zstd. */ + +#include + +/* These values are straight from RFC 8878. */ + +static int16_t lit[36] = +{ + 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1, + -1,-1,-1,-1 +}; + +static int16_t match[53] = +{ + 1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, + -1,-1,-1,-1,-1 +}; + +static int16_t offset[29] = +{ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1,-1,-1,-1,-1,-1 +}; + +static uint16_t next[256]; + +static void +print_table (const struct elf_zstd_fse_baseline_entry *table, size_t size) +{ + size_t i; + + printf ("{\n"); + for (i = 0; i < size; i += 3) + { + int j; + + printf (" "); + for (j = 0; j < 3 && i + j < size; ++j) + printf (" { %u, %d, %d, %d },", table[i + j].baseline, + table[i + j].basebits, table[i + j].bits, + table[i + j].base); + printf ("\n"); + } + printf ("};\n"); +} + +int +main () +{ + struct elf_zstd_fse_entry lit_table[64]; + struct elf_zstd_fse_baseline_entry lit_baseline[64]; + struct elf_zstd_fse_entry match_table[64]; + struct elf_zstd_fse_baseline_entry match_baseline[64]; + struct elf_zstd_fse_entry offset_table[32]; + struct elf_zstd_fse_baseline_entry offset_baseline[32]; + + if (!elf_zstd_build_fse (lit, sizeof lit / sizeof lit[0], next, + 6, lit_table)) + { + fprintf (stderr, "elf_zstd_build_fse failed\n"); + exit (EXIT_FAILURE); + } + + if (!elf_zstd_make_literal_baseline_fse (lit_table, 6, lit_baseline)) + { + fprintf (stderr, "elf_zstd_make_literal_baseline_fse failed\n"); + exit (EXIT_FAILURE); + } + + printf ("static const struct elf_zstd_fse_baseline_entry " + "elf_zstd_lit_table[64] =\n"); + print_table (lit_baseline, + sizeof lit_baseline / sizeof lit_baseline[0]); + printf ("\n"); + + if (!elf_zstd_build_fse (match, sizeof match / sizeof match[0], next, + 6, match_table)) + { + fprintf (stderr, "elf_zstd_build_fse failed\n"); + exit (EXIT_FAILURE); + } + + if (!elf_zstd_make_match_baseline_fse (match_table, 6, match_baseline)) + { + fprintf (stderr, "elf_zstd_make_match_baseline_fse failed\n"); + exit (EXIT_FAILURE); + } + + printf ("static const struct elf_zstd_fse_baseline_entry " + "elf_zstd_match_table[64] =\n"); + print_table (match_baseline, + sizeof match_baseline / sizeof match_baseline[0]); + printf ("\n"); + + if (!elf_zstd_build_fse (offset, sizeof offset / sizeof offset[0], next, + 5, offset_table)) + { + fprintf (stderr, "elf_zstd_build_fse failed\n"); + exit (EXIT_FAILURE); + } + + if (!elf_zstd_make_offset_baseline_fse (offset_table, 5, offset_baseline)) + { + fprintf (stderr, "elf_zstd_make_offset_baseline_fse failed\n"); + exit (EXIT_FAILURE); + } + + printf ("static const struct elf_zstd_fse_baseline_entry " + "elf_zstd_offset_table[32] =\n"); + print_table (offset_baseline, + sizeof offset_baseline / sizeof offset_baseline[0]); + printf ("\n"); + + return 0; +} + +#endif + +/* The fixed tables generated by the #ifdef'ed out main function + above. */ + +static const struct elf_zstd_fse_baseline_entry elf_zstd_lit_table[64] = +{ + { 0, 0, 4, 0 }, { 0, 0, 4, 16 }, { 1, 0, 5, 32 }, + { 3, 0, 5, 0 }, { 4, 0, 5, 0 }, { 6, 0, 5, 0 }, + { 7, 0, 5, 0 }, { 9, 0, 5, 0 }, { 10, 0, 5, 0 }, + { 12, 0, 5, 0 }, { 14, 0, 6, 0 }, { 16, 1, 5, 0 }, + { 20, 1, 5, 0 }, { 22, 1, 5, 0 }, { 28, 2, 5, 0 }, + { 32, 3, 5, 0 }, { 48, 4, 5, 0 }, { 64, 6, 5, 32 }, + { 128, 7, 5, 0 }, { 256, 8, 6, 0 }, { 1024, 10, 6, 0 }, + { 4096, 12, 6, 0 }, { 0, 0, 4, 32 }, { 1, 0, 4, 0 }, + { 2, 0, 5, 0 }, { 4, 0, 5, 32 }, { 5, 0, 5, 0 }, + { 7, 0, 5, 32 }, { 8, 0, 5, 0 }, { 10, 0, 5, 32 }, + { 11, 0, 5, 0 }, { 13, 0, 6, 0 }, { 16, 1, 5, 32 }, + { 18, 1, 5, 0 }, { 22, 1, 5, 32 }, { 24, 2, 5, 0 }, + { 32, 3, 5, 32 }, { 40, 3, 5, 0 }, { 64, 6, 4, 0 }, + { 64, 6, 4, 16 }, { 128, 7, 5, 32 }, { 512, 9, 6, 0 }, + { 2048, 11, 6, 0 }, { 0, 0, 4, 48 }, { 1, 0, 4, 16 }, + { 2, 0, 5, 32 }, { 3, 0, 5, 32 }, { 5, 0, 5, 32 }, + { 6, 0, 5, 32 }, { 8, 0, 5, 32 }, { 9, 0, 5, 32 }, + { 11, 0, 5, 32 }, { 12, 0, 5, 32 }, { 15, 0, 6, 0 }, + { 18, 1, 5, 32 }, { 20, 1, 5, 32 }, { 24, 2, 5, 32 }, + { 28, 2, 5, 32 }, { 40, 3, 5, 32 }, { 48, 4, 5, 32 }, + { 65536, 16, 6, 0 }, { 32768, 15, 6, 0 }, { 16384, 14, 6, 0 }, + { 8192, 13, 6, 0 }, +}; + +static const struct elf_zstd_fse_baseline_entry elf_zstd_match_table[64] = +{ + { 3, 0, 6, 0 }, { 4, 0, 4, 0 }, { 5, 0, 5, 32 }, + { 6, 0, 5, 0 }, { 8, 0, 5, 0 }, { 9, 0, 5, 0 }, + { 11, 0, 5, 0 }, { 13, 0, 6, 0 }, { 16, 0, 6, 0 }, + { 19, 0, 6, 0 }, { 22, 0, 6, 0 }, { 25, 0, 6, 0 }, + { 28, 0, 6, 0 }, { 31, 0, 6, 0 }, { 34, 0, 6, 0 }, + { 37, 1, 6, 0 }, { 41, 1, 6, 0 }, { 47, 2, 6, 0 }, + { 59, 3, 6, 0 }, { 83, 4, 6, 0 }, { 131, 7, 6, 0 }, + { 515, 9, 6, 0 }, { 4, 0, 4, 16 }, { 5, 0, 4, 0 }, + { 6, 0, 5, 32 }, { 7, 0, 5, 0 }, { 9, 0, 5, 32 }, + { 10, 0, 5, 0 }, { 12, 0, 6, 0 }, { 15, 0, 6, 0 }, + { 18, 0, 6, 0 }, { 21, 0, 6, 0 }, { 24, 0, 6, 0 }, + { 27, 0, 6, 0 }, { 30, 0, 6, 0 }, { 33, 0, 6, 0 }, + { 35, 1, 6, 0 }, { 39, 1, 6, 0 }, { 43, 2, 6, 0 }, + { 51, 3, 6, 0 }, { 67, 4, 6, 0 }, { 99, 5, 6, 0 }, + { 259, 8, 6, 0 }, { 4, 0, 4, 32 }, { 4, 0, 4, 48 }, + { 5, 0, 4, 16 }, { 7, 0, 5, 32 }, { 8, 0, 5, 32 }, + { 10, 0, 5, 32 }, { 11, 0, 5, 32 }, { 14, 0, 6, 0 }, + { 17, 0, 6, 0 }, { 20, 0, 6, 0 }, { 23, 0, 6, 0 }, + { 26, 0, 6, 0 }, { 29, 0, 6, 0 }, { 32, 0, 6, 0 }, + { 65539, 16, 6, 0 }, { 32771, 15, 6, 0 }, { 16387, 14, 6, 0 }, + { 8195, 13, 6, 0 }, { 4099, 12, 6, 0 }, { 2051, 11, 6, 0 }, + { 1027, 10, 6, 0 }, +}; + +static const struct elf_zstd_fse_baseline_entry elf_zstd_offset_table[32] = +{ + { 1, 0, 5, 0 }, { 61, 6, 4, 0 }, { 509, 9, 5, 0 }, + { 32765, 15, 5, 0 }, { 2097149, 21, 5, 0 }, { 5, 3, 5, 0 }, + { 125, 7, 4, 0 }, { 4093, 12, 5, 0 }, { 262141, 18, 5, 0 }, + { 8388605, 23, 5, 0 }, { 29, 5, 5, 0 }, { 253, 8, 4, 0 }, + { 16381, 14, 5, 0 }, { 1048573, 20, 5, 0 }, { 1, 2, 5, 0 }, + { 125, 7, 4, 16 }, { 2045, 11, 5, 0 }, { 131069, 17, 5, 0 }, + { 4194301, 22, 5, 0 }, { 13, 4, 5, 0 }, { 253, 8, 4, 16 }, + { 8189, 13, 5, 0 }, { 524285, 19, 5, 0 }, { 2, 1, 5, 0 }, + { 61, 6, 4, 16 }, { 1021, 10, 5, 0 }, { 65533, 16, 5, 0 }, + { 268435453, 28, 5, 0 }, { 134217725, 27, 5, 0 }, { 67108861, 26, 5, 0 }, + { 33554429, 25, 5, 0 }, { 16777213, 24, 5, 0 }, +}; + +/* Read a zstd Huffman table and build the decoding table in *TABLE, reading + and updating *PPIN. This sets *PTABLE_BITS to the number of bits of the + table, such that the table length is 1 << *TABLE_BITS. ZDEBUG_TABLE is + scratch space; it must be enough for 512 uint16_t values + 256 32-bit values + (2048 bytes). Returns 1 on success, 0 on error. */ + +static int +elf_zstd_read_huff (const unsigned char **ppin, const unsigned char *pinend, + uint16_t *zdebug_table, uint16_t *table, int *ptable_bits) +{ + const unsigned char *pin; + unsigned char hdr; + unsigned char *weights; + size_t count; + uint32_t *weight_mark; + size_t i; + uint32_t weight_mask; + size_t table_bits; + + pin = *ppin; + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + hdr = *pin; + ++pin; + + weights = (unsigned char *) zdebug_table; + + if (hdr < 128) + { + /* Table is compressed using FSE. */ + + struct elf_zstd_fse_entry *fse_table; + int fse_table_bits; + uint16_t *scratch; + const unsigned char *pfse; + const unsigned char *pback; + uint64_t val; + unsigned int bits; + unsigned int state1, state2; + + /* SCRATCH is used temporarily by elf_zstd_read_fse. It overlaps + WEIGHTS. */ + scratch = zdebug_table; + fse_table = (struct elf_zstd_fse_entry *) (scratch + 512); + fse_table_bits = 6; + + pfse = pin; + if (!elf_zstd_read_fse (&pfse, pinend, scratch, 255, fse_table, + &fse_table_bits)) + return 0; + + if (unlikely (pin + hdr > pinend)) + { + elf_uncompress_failed (); + return 0; + } + + /* We no longer need SCRATCH. Start recording weights. We need up to + 256 bytes of weights and 64 bytes of rank counts, so it won't overlap + FSE_TABLE. */ + + pback = pin + hdr - 1; + + if (!elf_fetch_backward_init (&pback, pfse, &val, &bits)) + return 0; + + bits -= fse_table_bits; + state1 = (val >> bits) & ((1U << fse_table_bits) - 1); + bits -= fse_table_bits; + state2 = (val >> bits) & ((1U << fse_table_bits) - 1); + + /* There are two independent FSE streams, tracked by STATE1 and STATE2. + We decode them alternately. */ + + count = 0; + while (1) + { + struct elf_zstd_fse_entry *pt; + uint64_t v; + + pt = &fse_table[state1]; + + if (unlikely (pin < pinend) && bits < pt->bits) + { + if (unlikely (count >= 254)) + { + elf_uncompress_failed (); + return 0; + } + weights[count] = (unsigned char) pt->symbol; + weights[count + 1] = (unsigned char) fse_table[state2].symbol; + count += 2; + break; + } + + if (unlikely (pt->bits == 0)) + v = 0; + else + { + if (!elf_fetch_bits_backward (&pback, pfse, &val, &bits)) + return 0; + + bits -= pt->bits; + v = (val >> bits) & (((uint64_t)1 << pt->bits) - 1); + } + + state1 = pt->base + v; + + if (unlikely (count >= 255)) + { + elf_uncompress_failed (); + return 0; + } + + weights[count] = pt->symbol; + ++count; + + pt = &fse_table[state2]; + + if (unlikely (pin < pinend && bits < pt->bits)) + { + if (unlikely (count >= 254)) + { + elf_uncompress_failed (); + return 0; + } + weights[count] = (unsigned char) pt->symbol; + weights[count + 1] = (unsigned char) fse_table[state1].symbol; + count += 2; + break; + } + + if (unlikely (pt->bits == 0)) + v = 0; + else + { + if (!elf_fetch_bits_backward (&pback, pfse, &val, &bits)) + return 0; + + bits -= pt->bits; + v = (val >> bits) & (((uint64_t)1 << pt->bits) - 1); + } + + state2 = pt->base + v; + + if (unlikely (count >= 255)) + { + elf_uncompress_failed (); + return 0; + } + + weights[count] = pt->symbol; + ++count; + } + + pin += hdr; + } + else + { + /* Table is not compressed. Each weight is 4 bits. */ + + count = hdr - 127; + if (unlikely (pin + ((count + 1) / 2) >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + for (i = 0; i < count; i += 2) + { + unsigned char b; + + b = *pin; + ++pin; + weights[i] = b >> 4; + weights[i + 1] = b & 0xf; + } + } + + weight_mark = (uint32_t *) (weights + 256); + memset (weight_mark, 0, 13 * sizeof (uint32_t)); + weight_mask = 0; + for (i = 0; i < count; ++i) + { + unsigned char w; + + w = weights[i]; + if (unlikely (w > 12)) + { + elf_uncompress_failed (); + return 0; + } + ++weight_mark[w]; + if (w > 0) + weight_mask += 1U << (w - 1); + } + if (unlikely (weight_mask == 0)) + { + elf_uncompress_failed (); + return 0; + } + + table_bits = 32 - __builtin_clz (weight_mask); + if (unlikely (table_bits > 11)) + { + elf_uncompress_failed (); + return 0; + } + + /* Work out the last weight value, which is omitted because the weights must + sum to a power of two. */ + { + uint32_t left; + uint32_t high_bit; + + left = ((uint32_t)1 << table_bits) - weight_mask; + if (left == 0) + { + elf_uncompress_failed (); + return 0; + } + high_bit = 31 - __builtin_clz (left); + if (((uint32_t)1 << high_bit) != left) + { + elf_uncompress_failed (); + return 0; + } + + if (unlikely (count >= 256)) + { + elf_uncompress_failed (); + return 0; + } + + weights[count] = high_bit + 1; + ++count; + ++weight_mark[high_bit + 1]; + } + + if (weight_mark[1] < 2 || (weight_mark[1] & 1) != 0) + { + elf_uncompress_failed (); + return 0; + } + + /* Change WEIGHT_MARK from a count of weights to the index of the first + symbol for that weight. We shift the indexes to also store how many we + have seen so far, below. */ + { + uint32_t next; + + next = 0; + for (i = 0; i < table_bits; ++i) + { + uint32_t cur; + + cur = next; + next += weight_mark[i + 1] << i; + weight_mark[i + 1] = cur; + } + } + + for (i = 0; i < count; ++i) + { + unsigned char weight; + uint32_t length; + uint16_t tval; + size_t start; + uint32_t j; + + weight = weights[i]; + if (weight == 0) + continue; + + length = 1U << (weight - 1); + tval = (i << 8) | (table_bits + 1 - weight); + start = weight_mark[weight]; + for (j = 0; j < length; ++j) + table[start + j] = tval; + weight_mark[weight] += length; + } + + *ppin = pin; + *ptable_bits = (int)table_bits; + + return 1; +} + +/* Read and decompress the literals and store them ending at POUTEND. This + works because we are going to use all the literals in the output, so they + must fit into the output buffer. HUFFMAN_TABLE, and PHUFFMAN_TABLE_BITS + store the Huffman table across calls. SCRATCH is used to read a Huffman + table. Store the start of the decompressed literals in *PPLIT. Update + *PPIN. Return 1 on success, 0 on error. */ + +static int +elf_zstd_read_literals (const unsigned char **ppin, + const unsigned char *pinend, + unsigned char *pout, + unsigned char *poutend, + uint16_t *scratch, + uint16_t *huffman_table, + int *phuffman_table_bits, + unsigned char **pplit) +{ + const unsigned char *pin; + unsigned char *plit; + unsigned char hdr; + uint32_t regenerated_size; + uint32_t compressed_size; + int streams; + uint32_t total_streams_size; + unsigned int huffman_table_bits; + uint64_t huffman_mask; + + pin = *ppin; + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + hdr = *pin; + ++pin; + + if ((hdr & 3) == 0 || (hdr & 3) == 1) + { + int raw; + + /* Raw_Literals_Block or RLE_Literals_Block */ + + raw = (hdr & 3) == 0; + + switch ((hdr >> 2) & 3) + { + case 0: case 2: + regenerated_size = hdr >> 3; + break; + case 1: + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + regenerated_size = (hdr >> 4) + ((uint32_t)(*pin) << 4); + ++pin; + break; + case 3: + if (unlikely (pin + 1 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + regenerated_size = ((hdr >> 4) + + ((uint32_t)*pin << 4) + + ((uint32_t)pin[1] << 12)); + pin += 2; + break; + default: + elf_uncompress_failed (); + return 0; + } + + if (unlikely ((size_t)(poutend - pout) < regenerated_size)) + { + elf_uncompress_failed (); + return 0; + } + + plit = poutend - regenerated_size; + + if (raw) + { + if (unlikely (pin + regenerated_size >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + memcpy (plit, pin, regenerated_size); + pin += regenerated_size; + } + else + { + if (pin >= pinend) + { + elf_uncompress_failed (); + return 0; + } + memset (plit, *pin, regenerated_size); + ++pin; + } + + *ppin = pin; + *pplit = plit; + + return 1; + } + + /* Compressed_Literals_Block or Treeless_Literals_Block */ + + switch ((hdr >> 2) & 3) + { + case 0: case 1: + if (unlikely (pin + 1 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + regenerated_size = (hdr >> 4) | ((uint32_t)(*pin & 0x3f) << 4); + compressed_size = (uint32_t)*pin >> 6 | ((uint32_t)pin[1] << 2); + pin += 2; + streams = ((hdr >> 2) & 3) == 0 ? 1 : 4; + break; + case 2: + if (unlikely (pin + 2 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + regenerated_size = (((uint32_t)hdr >> 4) + | ((uint32_t)*pin << 4) + | (((uint32_t)pin[1] & 3) << 12)); + compressed_size = (((uint32_t)pin[1] >> 2) + | ((uint32_t)pin[2] << 6)); + pin += 3; + streams = 4; + break; + case 3: + if (unlikely (pin + 3 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + regenerated_size = (((uint32_t)hdr >> 4) + | ((uint32_t)*pin << 4) + | (((uint32_t)pin[1] & 0x3f) << 12)); + compressed_size = (((uint32_t)pin[1] >> 6) + | ((uint32_t)pin[2] << 2) + | ((uint32_t)pin[3] << 10)); + pin += 4; + streams = 4; + break; + default: + elf_uncompress_failed (); + return 0; + } + + if (unlikely (pin + compressed_size > pinend)) + { + elf_uncompress_failed (); + return 0; + } + + pinend = pin + compressed_size; + *ppin = pinend; + + if (unlikely ((size_t)(poutend - pout) < regenerated_size)) + { + elf_uncompress_failed (); + return 0; + } + + plit = poutend - regenerated_size; + + *pplit = plit; + + total_streams_size = compressed_size; + if ((hdr & 3) == 2) + { + const unsigned char *ptable; + + /* Compressed_Literals_Block. Read Huffman tree. */ + + ptable = pin; + if (!elf_zstd_read_huff (&ptable, pinend, scratch, huffman_table, + phuffman_table_bits)) + return 0; + + if (unlikely (total_streams_size < (size_t)(ptable - pin))) + { + elf_uncompress_failed (); + return 0; + } + + total_streams_size -= ptable - pin; + pin = ptable; + } + else + { + /* Treeless_Literals_Block. Reuse previous Huffman tree. */ + if (unlikely (*phuffman_table_bits == 0)) + { + elf_uncompress_failed (); + return 0; + } + } + + /* Decompress COMPRESSED_SIZE bytes of data at PIN using the huffman table, + storing REGENERATED_SIZE bytes of decompressed data at PLIT. */ + + huffman_table_bits = (unsigned int)*phuffman_table_bits; + huffman_mask = ((uint64_t)1 << huffman_table_bits) - 1; + + if (streams == 1) + { + const unsigned char *pback; + const unsigned char *pbackend; + uint64_t val; + unsigned int bits; + uint32_t i; + + pback = pin + total_streams_size - 1; + pbackend = pin; + if (!elf_fetch_backward_init (&pback, pbackend, &val, &bits)) + return 0; + + /* This is one of the inner loops of the decompression algorithm, so we + put some effort into optimization. We can't get more than 64 bytes + from a single call to elf_fetch_bits_backward, and we can't subtract + more than 11 bits at a time. */ + + if (regenerated_size >= 64) + { + unsigned char *plitstart; + unsigned char *plitstop; + + plitstart = plit; + plitstop = plit + regenerated_size - 64; + while (plit < plitstop) + { + uint16_t t; + + if (!elf_fetch_bits_backward (&pback, pbackend, &val, &bits)) + return 0; + + if (bits < 16) + break; + + while (bits >= 33) + { + t = huffman_table[(val >> (bits - huffman_table_bits)) + & huffman_mask]; + *plit = t >> 8; + ++plit; + bits -= t & 0xff; + + t = huffman_table[(val >> (bits - huffman_table_bits)) + & huffman_mask]; + *plit = t >> 8; + ++plit; + bits -= t & 0xff; + + t = huffman_table[(val >> (bits - huffman_table_bits)) + & huffman_mask]; + *plit = t >> 8; + ++plit; + bits -= t & 0xff; + } + + while (bits > 11) + { + t = huffman_table[(val >> (bits - huffman_table_bits)) + & huffman_mask]; + *plit = t >> 8; + ++plit; + bits -= t & 0xff; + } + } + + regenerated_size -= plit - plitstart; + } + + for (i = 0; i < regenerated_size; ++i) + { + uint16_t t; + + if (!elf_fetch_bits_backward (&pback, pbackend, &val, &bits)) + return 0; + + if (unlikely (bits < huffman_table_bits)) + { + t = huffman_table[(val << (huffman_table_bits - bits)) + & huffman_mask]; + if (unlikely (bits < (t & 0xff))) + { + elf_uncompress_failed (); + return 0; + } + } + else + t = huffman_table[(val >> (bits - huffman_table_bits)) + & huffman_mask]; + + *plit = t >> 8; + ++plit; + bits -= t & 0xff; + } + + return 1; + } + + { + uint32_t stream_size1, stream_size2, stream_size3, stream_size4; + uint32_t tot; + const unsigned char *pback1, *pback2, *pback3, *pback4; + const unsigned char *pbackend1, *pbackend2, *pbackend3, *pbackend4; + uint64_t val1, val2, val3, val4; + unsigned int bits1, bits2, bits3, bits4; + unsigned char *plit1, *plit2, *plit3, *plit4; + uint32_t regenerated_stream_size; + uint32_t regenerated_stream_size4; + uint16_t t1, t2, t3, t4; + uint32_t i; + uint32_t limit; + + /* Read jump table. */ + if (unlikely (pin + 5 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + stream_size1 = (uint32_t)*pin | ((uint32_t)pin[1] << 8); + pin += 2; + stream_size2 = (uint32_t)*pin | ((uint32_t)pin[1] << 8); + pin += 2; + stream_size3 = (uint32_t)*pin | ((uint32_t)pin[1] << 8); + pin += 2; + tot = stream_size1 + stream_size2 + stream_size3; + if (unlikely (tot > total_streams_size - 6)) + { + elf_uncompress_failed (); + return 0; + } + stream_size4 = total_streams_size - 6 - tot; + + pback1 = pin + stream_size1 - 1; + pbackend1 = pin; + + pback2 = pback1 + stream_size2; + pbackend2 = pback1 + 1; + + pback3 = pback2 + stream_size3; + pbackend3 = pback2 + 1; + + pback4 = pback3 + stream_size4; + pbackend4 = pback3 + 1; + + if (!elf_fetch_backward_init (&pback1, pbackend1, &val1, &bits1)) + return 0; + if (!elf_fetch_backward_init (&pback2, pbackend2, &val2, &bits2)) + return 0; + if (!elf_fetch_backward_init (&pback3, pbackend3, &val3, &bits3)) + return 0; + if (!elf_fetch_backward_init (&pback4, pbackend4, &val4, &bits4)) + return 0; + + regenerated_stream_size = (regenerated_size + 3) / 4; + + plit1 = plit; + plit2 = plit1 + regenerated_stream_size; + plit3 = plit2 + regenerated_stream_size; + plit4 = plit3 + regenerated_stream_size; + + regenerated_stream_size4 = regenerated_size - regenerated_stream_size * 3; + + /* We can't get more than 64 literal bytes from a single call to + elf_fetch_bits_backward. The fourth stream can be up to 3 bytes less, + so use as the limit. */ + + limit = regenerated_stream_size4 <= 64 ? 0 : regenerated_stream_size4 - 64; + i = 0; + while (i < limit) + { + if (!elf_fetch_bits_backward (&pback1, pbackend1, &val1, &bits1)) + return 0; + if (!elf_fetch_bits_backward (&pback2, pbackend2, &val2, &bits2)) + return 0; + if (!elf_fetch_bits_backward (&pback3, pbackend3, &val3, &bits3)) + return 0; + if (!elf_fetch_bits_backward (&pback4, pbackend4, &val4, &bits4)) + return 0; + + /* We can't subtract more than 11 bits at a time. */ + + do + { + t1 = huffman_table[(val1 >> (bits1 - huffman_table_bits)) + & huffman_mask]; + t2 = huffman_table[(val2 >> (bits2 - huffman_table_bits)) + & huffman_mask]; + t3 = huffman_table[(val3 >> (bits3 - huffman_table_bits)) + & huffman_mask]; + t4 = huffman_table[(val4 >> (bits4 - huffman_table_bits)) + & huffman_mask]; + + *plit1 = t1 >> 8; + ++plit1; + bits1 -= t1 & 0xff; + + *plit2 = t2 >> 8; + ++plit2; + bits2 -= t2 & 0xff; + + *plit3 = t3 >> 8; + ++plit3; + bits3 -= t3 & 0xff; + + *plit4 = t4 >> 8; + ++plit4; + bits4 -= t4 & 0xff; + + ++i; + } + while (bits1 > 11 && bits2 > 11 && bits3 > 11 && bits4 > 11); + } + + while (i < regenerated_stream_size) + { + int use4; + + use4 = i < regenerated_stream_size4; + + if (!elf_fetch_bits_backward (&pback1, pbackend1, &val1, &bits1)) + return 0; + if (!elf_fetch_bits_backward (&pback2, pbackend2, &val2, &bits2)) + return 0; + if (!elf_fetch_bits_backward (&pback3, pbackend3, &val3, &bits3)) + return 0; + if (use4) + { + if (!elf_fetch_bits_backward (&pback4, pbackend4, &val4, &bits4)) + return 0; + } + + if (unlikely (bits1 < huffman_table_bits)) + { + t1 = huffman_table[(val1 << (huffman_table_bits - bits1)) + & huffman_mask]; + if (unlikely (bits1 < (t1 & 0xff))) + { + elf_uncompress_failed (); + return 0; + } + } + else + t1 = huffman_table[(val1 >> (bits1 - huffman_table_bits)) + & huffman_mask]; + + if (unlikely (bits2 < huffman_table_bits)) + { + t2 = huffman_table[(val2 << (huffman_table_bits - bits2)) + & huffman_mask]; + if (unlikely (bits2 < (t2 & 0xff))) + { + elf_uncompress_failed (); + return 0; + } + } + else + t2 = huffman_table[(val2 >> (bits2 - huffman_table_bits)) + & huffman_mask]; + + if (unlikely (bits3 < huffman_table_bits)) + { + t3 = huffman_table[(val3 << (huffman_table_bits - bits3)) + & huffman_mask]; + if (unlikely (bits3 < (t3 & 0xff))) + { + elf_uncompress_failed (); + return 0; + } + } + else + t3 = huffman_table[(val3 >> (bits3 - huffman_table_bits)) + & huffman_mask]; + + if (use4) + { + if (unlikely (bits4 < huffman_table_bits)) + { + t4 = huffman_table[(val4 << (huffman_table_bits - bits4)) + & huffman_mask]; + if (unlikely (bits4 < (t4 & 0xff))) + { + elf_uncompress_failed (); + return 0; + } + } + else + t4 = huffman_table[(val4 >> (bits4 - huffman_table_bits)) + & huffman_mask]; + + *plit4 = t4 >> 8; + ++plit4; + bits4 -= t4 & 0xff; + } + + *plit1 = t1 >> 8; + ++plit1; + bits1 -= t1 & 0xff; + + *plit2 = t2 >> 8; + ++plit2; + bits2 -= t2 & 0xff; + + *plit3 = t3 >> 8; + ++plit3; + bits3 -= t3 & 0xff; + + ++i; + } + } + + return 1; +} + +/* The information used to decompress a sequence code, which can be a literal + length, an offset, or a match length. */ + +struct elf_zstd_seq_decode +{ + const struct elf_zstd_fse_baseline_entry *table; + int table_bits; +}; + +/* Unpack a sequence code compression mode. */ + +static int +elf_zstd_unpack_seq_decode (int mode, + const unsigned char **ppin, + const unsigned char *pinend, + const struct elf_zstd_fse_baseline_entry *predef, + int predef_bits, + uint16_t *scratch, + int maxidx, + struct elf_zstd_fse_baseline_entry *table, + int table_bits, + int (*conv)(const struct elf_zstd_fse_entry *, + int, + struct elf_zstd_fse_baseline_entry *), + struct elf_zstd_seq_decode *decode) +{ + switch (mode) + { + case 0: + decode->table = predef; + decode->table_bits = predef_bits; + break; + + case 1: + { + struct elf_zstd_fse_entry entry; + + if (unlikely (*ppin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + entry.symbol = **ppin; + ++*ppin; + entry.bits = 0; + entry.base = 0; + decode->table_bits = 0; + if (!conv (&entry, 0, table)) + return 0; + } + break; + + case 2: + { + struct elf_zstd_fse_entry *fse_table; + + /* We use the same space for the simple FSE table and the baseline + table. */ + fse_table = (struct elf_zstd_fse_entry *)table; + decode->table_bits = table_bits; + if (!elf_zstd_read_fse (ppin, pinend, scratch, maxidx, fse_table, + &decode->table_bits)) + return 0; + if (!conv (fse_table, decode->table_bits, table)) + return 0; + decode->table = table; + } + break; + + case 3: + if (unlikely (decode->table_bits == -1)) + { + elf_uncompress_failed (); + return 0; + } + break; + + default: + elf_uncompress_failed (); + return 0; + } + + return 1; +} + +/* Decompress a zstd stream from PIN/SIN to POUT/SOUT. Code based on RFC 8878. + Return 1 on success, 0 on error. */ + +static int +elf_zstd_decompress (const unsigned char *pin, size_t sin, + unsigned char *zdebug_table, unsigned char *pout, + size_t sout) +{ + const unsigned char *pinend; + unsigned char *poutstart; + unsigned char *poutend; + struct elf_zstd_seq_decode literal_decode; + struct elf_zstd_fse_baseline_entry *literal_fse_table; + struct elf_zstd_seq_decode match_decode; + struct elf_zstd_fse_baseline_entry *match_fse_table; + struct elf_zstd_seq_decode offset_decode; + struct elf_zstd_fse_baseline_entry *offset_fse_table; + uint16_t *huffman_table; + int huffman_table_bits; + uint32_t repeated_offset1; + uint32_t repeated_offset2; + uint32_t repeated_offset3; + uint16_t *scratch; + unsigned char hdr; + int has_checksum; + uint64_t content_size; + int last_block; + + pinend = pin + sin; + poutstart = pout; + poutend = pout + sout; + + literal_decode.table = NULL; + literal_decode.table_bits = -1; + literal_fse_table = ((struct elf_zstd_fse_baseline_entry *) + (zdebug_table + ZSTD_TABLE_LITERAL_FSE_OFFSET)); + + match_decode.table = NULL; + match_decode.table_bits = -1; + match_fse_table = ((struct elf_zstd_fse_baseline_entry *) + (zdebug_table + ZSTD_TABLE_MATCH_FSE_OFFSET)); + + offset_decode.table = NULL; + offset_decode.table_bits = -1; + offset_fse_table = ((struct elf_zstd_fse_baseline_entry *) + (zdebug_table + ZSTD_TABLE_OFFSET_FSE_OFFSET)); + huffman_table = ((uint16_t *) + (zdebug_table + ZSTD_TABLE_HUFFMAN_OFFSET)); + huffman_table_bits = 0; + scratch = ((uint16_t *) + (zdebug_table + ZSTD_TABLE_WORK_OFFSET)); + + repeated_offset1 = 1; + repeated_offset2 = 4; + repeated_offset3 = 8; + + if (unlikely (sin < 4)) + { + elf_uncompress_failed (); + return 0; + } + + /* These values are the zstd magic number. */ + if (unlikely (pin[0] != 0x28 + || pin[1] != 0xb5 + || pin[2] != 0x2f + || pin[3] != 0xfd)) + { + elf_uncompress_failed (); + return 0; + } + + pin += 4; + + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + + hdr = *pin++; + + /* We expect a single frame. */ + if (unlikely ((hdr & (1 << 5)) == 0)) + { + elf_uncompress_failed (); + return 0; + } + /* Reserved bit must be zero. */ + if (unlikely ((hdr & (1 << 3)) != 0)) + { + elf_uncompress_failed (); + return 0; + } + /* We do not expect a dictionary. */ + if (unlikely ((hdr & 3) != 0)) + { + elf_uncompress_failed (); + return 0; + } + has_checksum = (hdr & (1 << 2)) != 0; + switch (hdr >> 6) + { + case 0: + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + content_size = (uint64_t) *pin++; + break; + case 1: + if (unlikely (pin + 1 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + content_size = (((uint64_t) pin[0]) | (((uint64_t) pin[1]) << 8)) + 256; + pin += 2; + break; + case 2: + if (unlikely (pin + 3 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + content_size = ((uint64_t) pin[0] + | (((uint64_t) pin[1]) << 8) + | (((uint64_t) pin[2]) << 16) + | (((uint64_t) pin[3]) << 24)); + pin += 4; + break; + case 3: + if (unlikely (pin + 7 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + content_size = ((uint64_t) pin[0] + | (((uint64_t) pin[1]) << 8) + | (((uint64_t) pin[2]) << 16) + | (((uint64_t) pin[3]) << 24) + | (((uint64_t) pin[4]) << 32) + | (((uint64_t) pin[5]) << 40) + | (((uint64_t) pin[6]) << 48) + | (((uint64_t) pin[7]) << 56)); + pin += 8; + break; + default: + elf_uncompress_failed (); + return 0; + } + + if (unlikely (content_size != (size_t) content_size + || (size_t) content_size != sout)) + { + elf_uncompress_failed (); + return 0; + } + + last_block = 0; + while (!last_block) + { + uint32_t block_hdr; + int block_type; + uint32_t block_size; + + if (unlikely (pin + 2 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + block_hdr = ((uint32_t) pin[0] + | (((uint32_t) pin[1]) << 8) + | (((uint32_t) pin[2]) << 16)); + pin += 3; + + last_block = block_hdr & 1; + block_type = (block_hdr >> 1) & 3; + block_size = block_hdr >> 3; + + switch (block_type) + { + case 0: + /* Raw_Block */ + if (unlikely ((size_t) block_size > (size_t) (pinend - pin))) + { + elf_uncompress_failed (); + return 0; + } + if (unlikely ((size_t) block_size > (size_t) (poutend - pout))) + { + elf_uncompress_failed (); + return 0; + } + memcpy (pout, pin, block_size); + pout += block_size; + pin += block_size; + break; + + case 1: + /* RLE_Block */ + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + if (unlikely ((size_t) block_size > (size_t) (poutend - pout))) + { + elf_uncompress_failed (); + return 0; + } + memset (pout, *pin, block_size); + pout += block_size; + pin++; + break; + + case 2: + { + const unsigned char *pblockend; + unsigned char *plitstack; + unsigned char *plit; + uint32_t literal_count; + unsigned char seq_hdr; + size_t seq_count; + size_t seq; + const unsigned char *pback; + uint64_t val; + unsigned int bits; + unsigned int literal_state; + unsigned int offset_state; + unsigned int match_state; + + /* Compressed_Block */ + if (unlikely ((size_t) block_size > (size_t) (pinend - pin))) + { + elf_uncompress_failed (); + return 0; + } + + pblockend = pin + block_size; + + /* Read the literals into the end of the output space, and leave + PLIT pointing at them. */ + + if (!elf_zstd_read_literals (&pin, pblockend, pout, poutend, + scratch, huffman_table, + &huffman_table_bits, + &plitstack)) + return 0; + plit = plitstack; + literal_count = poutend - plit; + + seq_hdr = *pin; + pin++; + if (seq_hdr < 128) + seq_count = seq_hdr; + else if (seq_hdr < 255) + { + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + seq_count = ((seq_hdr - 128) << 8) + *pin; + pin++; + } + else + { + if (unlikely (pin + 1 >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + seq_count = *pin + (pin[1] << 8) + 0x7f00; + pin += 2; + } + + if (seq_count > 0) + { + int (*pfn)(const struct elf_zstd_fse_entry *, + int, struct elf_zstd_fse_baseline_entry *); + + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + seq_hdr = *pin; + ++pin; + + pfn = elf_zstd_make_literal_baseline_fse; + if (!elf_zstd_unpack_seq_decode ((seq_hdr >> 6) & 3, + &pin, pinend, + &elf_zstd_lit_table[0], 6, + scratch, 35, + literal_fse_table, 9, pfn, + &literal_decode)) + return 0; + + pfn = elf_zstd_make_offset_baseline_fse; + if (!elf_zstd_unpack_seq_decode ((seq_hdr >> 4) & 3, + &pin, pinend, + &elf_zstd_offset_table[0], 5, + scratch, 31, + offset_fse_table, 8, pfn, + &offset_decode)) + return 0; + + pfn = elf_zstd_make_match_baseline_fse; + if (!elf_zstd_unpack_seq_decode ((seq_hdr >> 2) & 3, + &pin, pinend, + &elf_zstd_match_table[0], 6, + scratch, 52, + match_fse_table, 9, pfn, + &match_decode)) + return 0; + } + + pback = pblockend - 1; + if (!elf_fetch_backward_init (&pback, pin, &val, &bits)) + return 0; + + bits -= literal_decode.table_bits; + literal_state = ((val >> bits) + & ((1U << literal_decode.table_bits) - 1)); + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= offset_decode.table_bits; + offset_state = ((val >> bits) + & ((1U << offset_decode.table_bits) - 1)); + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= match_decode.table_bits; + match_state = ((val >> bits) + & ((1U << match_decode.table_bits) - 1)); + + seq = 0; + while (1) + { + const struct elf_zstd_fse_baseline_entry *pt; + uint32_t offset_basebits; + uint32_t offset_baseline; + uint32_t offset_bits; + uint32_t offset_base; + uint32_t offset; + uint32_t match_baseline; + uint32_t match_bits; + uint32_t match_base; + uint32_t match; + uint32_t literal_baseline; + uint32_t literal_bits; + uint32_t literal_base; + uint32_t literal; + uint32_t need; + uint32_t add; + + pt = &offset_decode.table[offset_state]; + offset_basebits = pt->basebits; + offset_baseline = pt->baseline; + offset_bits = pt->bits; + offset_base = pt->base; + + /* This case can be more than 16 bits, which is all that + elf_fetch_bits_backward promises. */ + need = offset_basebits; + add = 0; + if (unlikely (need > 16)) + { + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= 16; + add = (val >> bits) & ((1U << 16) - 1); + need -= 16; + add <<= need; + } + if (need > 0) + { + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= need; + add += (val >> bits) & ((1U << need) - 1); + } + + offset = offset_baseline + add; + + pt = &match_decode.table[match_state]; + need = pt->basebits; + match_baseline = pt->baseline; + match_bits = pt->bits; + match_base = pt->base; + + add = 0; + if (need > 0) + { + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= need; + add = (val >> bits) & ((1U << need) - 1); + } + + match = match_baseline + add; + + pt = &literal_decode.table[literal_state]; + need = pt->basebits; + literal_baseline = pt->baseline; + literal_bits = pt->bits; + literal_base = pt->base; + + add = 0; + if (need > 0) + { + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= need; + add = (val >> bits) & ((1U << need) - 1); + } + + literal = literal_baseline + add; + + /* See the comment in elf_zstd_make_offset_baseline_fse. */ + if (offset_basebits > 1) + { + repeated_offset3 = repeated_offset2; + repeated_offset2 = repeated_offset1; + repeated_offset1 = offset; + } + else + { + if (unlikely (literal == 0)) + ++offset; + switch (offset) + { + case 1: + offset = repeated_offset1; + break; + case 2: + offset = repeated_offset2; + repeated_offset2 = repeated_offset1; + repeated_offset1 = offset; + break; + case 3: + offset = repeated_offset3; + repeated_offset3 = repeated_offset2; + repeated_offset2 = repeated_offset1; + repeated_offset1 = offset; + break; + case 4: + offset = repeated_offset1 - 1; + repeated_offset3 = repeated_offset2; + repeated_offset2 = repeated_offset1; + repeated_offset1 = offset; + break; + } + } + + ++seq; + if (seq < seq_count) + { + uint32_t v; + + /* Update the three states. */ + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + + need = literal_bits; + bits -= need; + v = (val >> bits) & (((uint32_t)1 << need) - 1); + + literal_state = literal_base + v; + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + + need = match_bits; + bits -= need; + v = (val >> bits) & (((uint32_t)1 << need) - 1); + + match_state = match_base + v; + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + + need = offset_bits; + bits -= need; + v = (val >> bits) & (((uint32_t)1 << need) - 1); + + offset_state = offset_base + v; + } + + /* The next sequence is now in LITERAL, OFFSET, MATCH. */ + + /* Copy LITERAL bytes from the literals. */ + + if (unlikely ((size_t)(poutend - pout) < literal)) + { + elf_uncompress_failed (); + return 0; + } + + if (unlikely (literal_count < literal)) + { + elf_uncompress_failed (); + return 0; + } + + literal_count -= literal; + + /* Often LITERAL is small, so handle small cases quickly. */ + switch (literal) + { + case 8: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 7: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 6: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 5: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 4: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 3: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 2: + *pout++ = *plit++; + /* FALLTHROUGH */ + case 1: + *pout++ = *plit++; + break; + + case 0: + break; + + default: + if (unlikely ((size_t)(plit - pout) < literal)) + { + uint32_t move; + + move = plit - pout; + while (literal > move) + { + memcpy (pout, plit, move); + pout += move; + plit += move; + literal -= move; + } + } + + memcpy (pout, plit, literal); + pout += literal; + plit += literal; + } + + if (match > 0) + { + /* Copy MATCH bytes from the decoded output at OFFSET. */ + + if (unlikely ((size_t)(poutend - pout) < match)) + { + elf_uncompress_failed (); + return 0; + } + + if (unlikely ((size_t)(pout - poutstart) < offset)) + { + elf_uncompress_failed (); + return 0; + } + + if (offset >= match) + { + memcpy (pout, pout - offset, match); + pout += match; + } + else + { + while (match > 0) + { + uint32_t copy; + + copy = match < offset ? match : offset; + memcpy (pout, pout - offset, copy); + match -= copy; + pout += copy; + } + } + } + + if (unlikely (seq >= seq_count)) + { + /* Copy remaining literals. */ + if (literal_count > 0 && plit != pout) + { + if (unlikely ((size_t)(poutend - pout) + < literal_count)) + { + elf_uncompress_failed (); + return 0; + } + + if ((size_t)(plit - pout) < literal_count) + { + uint32_t move; + + move = plit - pout; + while (literal_count > move) + { + memcpy (pout, plit, move); + pout += move; + plit += move; + literal_count -= move; + } + } + + memcpy (pout, plit, literal_count); + } + + pout += literal_count; + + break; + } + } + + pin = pblockend; + } + break; + + case 3: + default: + elf_uncompress_failed (); + return 0; + } + } + + if (has_checksum) + { + if (unlikely (pin + 4 > pinend)) + { + elf_uncompress_failed (); + return 0; + } + + /* We don't currently verify the checksum. Currently running GNU ld with + --compress-debug-sections=zstd does not seem to generate a + checksum. */ + + pin += 4; + } + + if (pin != pinend) + { + elf_uncompress_failed (); + return 0; + } + + return 1; +} + +#define ZDEBUG_TABLE_SIZE \ + (ZLIB_TABLE_SIZE > ZSTD_TABLE_SIZE ? ZLIB_TABLE_SIZE : ZSTD_TABLE_SIZE) + /* Uncompress the old compressed debug format, the one emitted by --compress-debug-sections=zlib-gnu. The compressed data is in COMPRESSED / COMPRESSED_SIZE, and the function writes to @@ -2628,6 +5094,8 @@ elf_uncompress_chdr (struct backtrace_state *state, unsigned char **uncompressed, size_t *uncompressed_size) { const b_elf_chdr *chdr; + char *alc; + size_t alc_len; unsigned char *po; *uncompressed = NULL; @@ -2639,31 +5107,50 @@ elf_uncompress_chdr (struct backtrace_state *state, chdr = (const b_elf_chdr *) compressed; - if (chdr->ch_type != ELFCOMPRESS_ZLIB) - { - /* Unsupported compression algorithm. */ - return 1; - } - + alc = NULL; + alc_len = 0; if (*uncompressed != NULL && *uncompressed_size >= chdr->ch_size) po = *uncompressed; else { - po = (unsigned char *) backtrace_alloc (state, chdr->ch_size, - error_callback, data); - if (po == NULL) + alc_len = chdr->ch_size; + alc = (char*)backtrace_alloc (state, alc_len, error_callback, data); + if (alc == NULL) return 0; + po = (unsigned char *) alc; } - if (!elf_zlib_inflate_and_verify (compressed + sizeof (b_elf_chdr), - compressed_size - sizeof (b_elf_chdr), - zdebug_table, po, chdr->ch_size)) - return 1; + 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)) + goto skip; + break; + + case ELFCOMPRESS_ZSTD: + if (!elf_zstd_decompress (compressed + sizeof (b_elf_chdr), + compressed_size - sizeof (b_elf_chdr), + (unsigned char *)zdebug_table, po, + chdr->ch_size)) + goto skip; + break; + + default: + /* Unsupported compression algorithm. */ + goto skip; + } *uncompressed = po; *uncompressed_size = chdr->ch_size; return 1; + + skip: + if (alc != NULL && alc_len > 0) + backtrace_free (state, alc, alc_len, error_callback, data); + return 1; } /* This function is a hook for testing the zlib support. It is only @@ -2692,6 +5179,31 @@ backtrace_uncompress_zdebug (struct backtrace_state *state, return ret; } +/* This function is a hook for testing the zstd support. It is only used by + tests. */ + +int +backtrace_uncompress_zstd (struct backtrace_state *state, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback error_callback, + void *data, unsigned char *uncompressed, + size_t uncompressed_size) +{ + unsigned char *zdebug_table; + int ret; + + zdebug_table = ((unsigned char *) backtrace_alloc (state, ZDEBUG_TABLE_SIZE, + error_callback, data)); + if (zdebug_table == NULL) + return 0; + ret = elf_zstd_decompress (compressed, compressed_size, + zdebug_table, uncompressed, uncompressed_size); + backtrace_free (state, zdebug_table, ZDEBUG_TABLE_SIZE, + error_callback, data); + return ret; +} + /* Number of LZMA states. */ #define LZMA_STATES (12) @@ -4688,7 +7200,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, if (zdebug_table == NULL) { zdebug_table = ((uint16_t *) - backtrace_alloc (state, ZDEBUG_TABLE_SIZE, + backtrace_alloc (state, ZLIB_TABLE_SIZE, error_callback, data)); if (zdebug_table == NULL) goto fail; @@ -4714,8 +7226,15 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, } } + if (zdebug_table != NULL) + { + backtrace_free (state, zdebug_table, ZLIB_TABLE_SIZE, + error_callback, data); + zdebug_table = NULL; + } + /* Uncompress the official ELF format - (--compress-debug-sections=zlib-gabi). */ + (--compress-debug-sections=zlib-gabi, --compress-debug-sections=zstd). */ for (i = 0; i < (int) DEBUG_MAX; ++i) { unsigned char *uncompressed_data; diff --git a/Source/ThirdParty/tracy/libbacktrace/internal.hpp b/Source/ThirdParty/tracy/libbacktrace/internal.hpp index 96c097e02..f871844b6 100644 --- a/Source/ThirdParty/tracy/libbacktrace/internal.hpp +++ b/Source/ThirdParty/tracy/libbacktrace/internal.hpp @@ -371,6 +371,15 @@ extern int backtrace_uncompress_zdebug (struct backtrace_state *, unsigned char **uncompressed, size_t *uncompressed_size); +/* A test-only hook for elf_zstd_decompress. */ + +extern int backtrace_uncompress_zstd (struct backtrace_state *, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback, void *data, + unsigned char *uncompressed, + size_t uncompressed_size); + /* A test-only hook for elf_uncompress_lzma. */ extern int backtrace_uncompress_lzma (struct backtrace_state *, diff --git a/Source/ThirdParty/tracy/tracy/Tracy.hpp b/Source/ThirdParty/tracy/tracy/Tracy.hpp index 8ef26d59e..e9c943d2f 100644 --- a/Source/ThirdParty/tracy/tracy/Tracy.hpp +++ b/Source/ThirdParty/tracy/tracy/Tracy.hpp @@ -1,6 +1,18 @@ #ifndef __TRACY_HPP__ #define __TRACY_HPP__ +#ifndef TracyFunction +# define TracyFunction __FUNCTION__ +#endif + +#ifndef TracyFile +# define TracyFile __FILE__ +#endif + +#ifndef TracyLine +# define TracyLine __LINE__ +#endif + #ifndef TRACY_ENABLE #define ZoneNamed(x,y) @@ -80,6 +92,8 @@ #define TracySourceCallbackRegister(x,y) #define TracyParameterRegister(x,y) #define TracyParameterSetup(x,y,z,w) +#define TracyIsConnected false +#define TracySetProgramName(x) #define TracyFiberEnter(x) #define TracyFiberLeave @@ -124,21 +138,21 @@ public: } #if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK -# define ZoneNamed( varname, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ) -# define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ) -# define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ) -# define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), TRACY_CALLSTACK, active ) +# 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( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), nullptr, 0, TRACY_CALLSTACK, active ) -# define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name, strlen( name ), 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,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ) -# define ZoneNamedN( varname, name, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ) -# define ZoneNamedC( varname, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ) -# define ZoneNamedNC( varname, name, color, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), active ) +# 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( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), nullptr, 0, active ) -# define ZoneTransientN( varname, name, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name, strlen( name ), 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 ) #endif #define ZoneScoped ZoneNamed( ___tracy_scoped_zone, true ) @@ -164,13 +178,13 @@ public: #define FrameImage( image, width, height, offset, flip ) tracy::Profiler::SendFrameImage( image, width, height, offset, flip ) -#define TracyLockable( type, varname ) tracy::Lockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, #type " " #varname, __FILE__, __LINE__, 0 }; return &srcloc; }() } -#define TracyLockableN( type, varname, desc ) tracy::Lockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, desc, __FILE__, __LINE__, 0 }; return &srcloc; }() } -#define TracySharedLockable( type, varname ) tracy::SharedLockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, #type " " #varname, __FILE__, __LINE__, 0 }; return &srcloc; }() } -#define TracySharedLockableN( type, varname, desc ) tracy::SharedLockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, desc, __FILE__, __LINE__, 0 }; return &srcloc; }() } +#define TracyLockable( type, varname ) tracy::Lockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, #type " " #varname, TracyFile, TracyLine, 0 }; return &srcloc; }() } +#define TracyLockableN( type, varname, desc ) tracy::Lockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, desc, TracyFile, TracyLine, 0 }; return &srcloc; }() } +#define TracySharedLockable( type, varname ) tracy::SharedLockable varname { [] () -> const tracy::SourceLocationData* { static constexpr tracy::SourceLocationData srcloc { nullptr, #type " " #varname, TracyFile, TracyLine, 0 }; return &srcloc; }() } +#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, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; varname.Mark( &__tracy_lock_location_##varname ) +#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 LockableName( varname, txt, size ) varname.CustomName( txt, size ) #define TracyPlot( name, val ) tracy::Profiler::PlotData( name, val ) @@ -211,13 +225,13 @@ public: #endif #ifdef TRACY_HAS_CALLSTACK -# define ZoneNamedS( varname, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ) -# define ZoneNamedNS( varname, name, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ) -# define ZoneNamedCS( varname, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ) -# define ZoneNamedNCS( varname, name, color, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,__LINE__) { name, __FUNCTION__, __FILE__, (uint32_t)__LINE__, color }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,__LINE__), depth, active ) +# 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 ZoneTransientS( varname, depth, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), nullptr, 0, depth, active ) -# define ZoneTransientNS( varname, name, depth, active ) tracy::ScopedZone varname( __LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name, strlen( name ), depth, active ) +# 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 ZoneScopedS( depth ) ZoneNamedS( ___tracy_scoped_zone, depth, true ) # define ZoneScopedNS( name, depth ) ZoneNamedNS( ___tracy_scoped_zone, name, depth, true ) @@ -272,6 +286,7 @@ public: #define TracyParameterRegister( cb, data ) tracy::Profiler::ParameterRegister( cb, data ) #define TracyParameterSetup( idx, name, isBool, val ) tracy::Profiler::ParameterSetup( idx, name, isBool, val ) #define TracyIsConnected tracy::GetProfiler().IsConnected() +#define TracySetProgramName( name ) tracy::GetProfiler().SetProgramName( name ); #ifdef TRACY_FIBERS # define TracyFiberEnter( fiber ) tracy::Profiler::EnterFiber( fiber )