diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index 80cf8fa1e..6d72e44ed 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -79,6 +79,9 @@
SDK
VS
<Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></Policy>
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></Policy>
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
@@ -220,6 +223,7 @@
True
True
True
+ True
True
True
True
diff --git a/Source/Editor/Content/Import/ImportFileEntry.cs b/Source/Editor/Content/Import/ImportFileEntry.cs
index b33f24408..3523369da 100644
--- a/Source/Editor/Content/Import/ImportFileEntry.cs
+++ b/Source/Editor/Content/Import/ImportFileEntry.cs
@@ -133,6 +133,7 @@ namespace FlaxEditor.Content.Import
FileTypes["dds"] = ImportTexture;
FileTypes["hdr"] = ImportTexture;
FileTypes["raw"] = ImportTexture;
+ FileTypes["exr"] = ImportTexture;
// Models
FileTypes["obj"] = ImportModel;
diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs
index 611c5728f..9842485c1 100644
--- a/Source/Editor/Content/Import/TextureImportEntry.cs
+++ b/Source/Editor/Content/Import/TextureImportEntry.cs
@@ -128,6 +128,11 @@ namespace FlaxEditor.Content.Import
_settings.Settings.Type = TextureFormatType.HdrRGBA;
_settings.Settings.Compress = false;
}
+ else if (extension == ".exr")
+ {
+ // HDR image
+ _settings.Settings.Type = TextureFormatType.HdrRGBA;
+ }
else if (extension == ".hdr")
{
// HDR sky texture
diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
index 09b0e3a1f..1561e6245 100644
--- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements;
@@ -9,6 +10,8 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
+using FlaxEditor.Windows;
+using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
@@ -111,6 +114,38 @@ namespace FlaxEditor.CustomEditors.Dedicated
var actor = (Actor)Values[0];
var scriptType = TypeUtils.GetType(actor.TypeName);
var item = scriptType.ContentItem;
+ if (Presenter.Owner is PropertiesWindow propertiesWindow)
+ {
+ var lockButton = cm.AddButton(propertiesWindow.LockObjects ? "Unlock" : "Lock");
+ lockButton.ButtonClicked += button =>
+ {
+ propertiesWindow.LockObjects = !propertiesWindow.LockObjects;
+
+ // Reselect current selection
+ if (!propertiesWindow.LockObjects && Editor.Instance.SceneEditing.SelectionCount > 0)
+ {
+ var cachedSelection = Editor.Instance.SceneEditing.Selection.ToArray();
+ Editor.Instance.SceneEditing.Select(null);
+ Editor.Instance.SceneEditing.Select(cachedSelection);
+ }
+ };
+ }
+ else if (Presenter.Owner is PrefabWindow prefabWindow)
+ {
+ var lockButton = cm.AddButton(prefabWindow.LockSelectedObjects ? "Unlock" : "Lock");
+ lockButton.ButtonClicked += button =>
+ {
+ prefabWindow.LockSelectedObjects = !prefabWindow.LockSelectedObjects;
+
+ // Reselect current selection
+ if (!prefabWindow.LockSelectedObjects && prefabWindow.Selection.Count > 0)
+ {
+ var cachedSelection = prefabWindow.Selection.ToList();
+ prefabWindow.Select(null);
+ prefabWindow.Select(cachedSelection);
+ }
+ };
+ }
cm.AddButton("Copy ID", OnClickCopyId);
cm.AddButton("Edit actor type", OnClickEditActorType).Enabled = item != null;
var showButton = cm.AddButton("Show in content window", OnClickShowActorType);
diff --git a/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs
index 8827dfb30..a40915b1b 100644
--- a/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs
@@ -37,6 +37,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
}
+ EvaluateVisibleIf(itemLayout, item, GetLabelIndex(itemLayout, item));
+
// Add labels with a check box
var label = new CheckablePropertyNameLabel(item.DisplayName);
label.CheckBox.Tag = setting.Bit;
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index 489e6aaba..63e103daa 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -582,55 +582,13 @@ namespace FlaxEditor.CustomEditors.Editors
}
///
- /// Spawns the property for the given item.
+ /// Evaluate the cache for a given property item.
///
/// The item layout.
- /// The item values.
/// The item.
- protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
+ /// The label index.
+ protected virtual void EvaluateVisibleIf(LayoutElementsContainer itemLayout, ItemInfo item, int labelIndex)
{
- int labelIndex = 0;
- if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
- itemLayout.Children.Count > 0 &&
- itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
- {
- labelIndex = propertiesListElement.Labels.Count;
- }
-
- itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
-
- if (item.IsReadOnly && itemLayout.Children.Count > 0)
- {
- PropertiesListElement list = null;
- int firstChildControlIndex = 0;
- bool disableSingle = true;
- var control = itemLayout.Children[itemLayout.Children.Count - 1];
- if (control is GroupElement group && group.Children.Count > 0)
- {
- list = group.Children[0] as PropertiesListElement;
- disableSingle = false; // Disable all nested editors
- }
- else if (control is PropertiesListElement list1)
- {
- list = list1;
- firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
- }
-
- if (list != null)
- {
- // Disable controls added to the editor
- var count = list.Properties.Children.Count;
- for (int j = firstChildControlIndex; j < count; j++)
- {
- var child = list.Properties.Children[j];
- if (disableSingle && child is PropertyNameLabel)
- break;
-
- if (child != null)
- child.Enabled = false;
- }
- }
- }
if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0)
{
PropertiesListElement list = null;
@@ -669,6 +627,73 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
+ ///
+ /// Get the label index.
+ ///
+ /// The item layout.
+ /// The item.
+ /// The label index.
+ protected virtual int GetLabelIndex(LayoutElementsContainer itemLayout, ItemInfo item)
+ {
+ int labelIndex = 0;
+ if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
+ itemLayout.Children.Count > 0 &&
+ itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
+ {
+ labelIndex = propertiesListElement.Labels.Count;
+ }
+
+ return labelIndex;
+ }
+
+ ///
+ /// Spawns the property for the given item.
+ ///
+ /// The item layout.
+ /// The item values.
+ /// The item.
+ protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
+ {
+ int labelIndex = GetLabelIndex(itemLayout, item);
+
+ itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
+
+ if (item.IsReadOnly && itemLayout.Children.Count > 0)
+ {
+ PropertiesListElement list = null;
+ int firstChildControlIndex = 0;
+ bool disableSingle = true;
+ var control = itemLayout.Children[itemLayout.Children.Count - 1];
+ if (control is GroupElement group && group.Children.Count > 0)
+ {
+ list = group.Children[0] as PropertiesListElement;
+ disableSingle = false; // Disable all nested editors
+ }
+ else if (control is PropertiesListElement list1)
+ {
+ list = list1;
+ firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
+ }
+
+ if (list != null)
+ {
+ // Disable controls added to the editor
+ var count = list.Properties.Children.Count;
+ for (int j = firstChildControlIndex; j < count; j++)
+ {
+ var child = list.Properties.Children[j];
+ if (disableSingle && child is PropertyNameLabel)
+ break;
+
+ if (child != null)
+ child.Enabled = false;
+ }
+ }
+ }
+
+ EvaluateVisibleIf(itemLayout, item, labelIndex);
+ }
+
///
internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
{
diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs
index 5a4905206..5662aea0c 100644
--- a/Source/Editor/CustomEditors/Editors/InputEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.CustomEditors.Editors
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
- private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
+ private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null;
@@ -106,7 +106,7 @@ namespace FlaxEditor.CustomEditors.Editors
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
- private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
+ private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null;
diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs
index c6efda318..c583b96c3 100644
--- a/Source/Editor/CustomEditors/Values/ValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs
@@ -79,7 +79,7 @@ namespace FlaxEditor.CustomEditors
var theFirstType = TypeUtils.GetObjectType(this[0]);
for (int i = 1; i < Count; i++)
{
- if (theFirstType != TypeUtils.GetObjectType(this[1]))
+ if (theFirstType != TypeUtils.GetObjectType(this[i]))
return true;
}
return false;
diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs
index 55a91eb82..bed67b63d 100644
--- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs
+++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs
@@ -47,7 +47,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
break;
default: throw new ArgumentOutOfRangeException();
}
- var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f);
+ var color = (_timeline.IsMovingPositionHandle ? style.SelectionBorder : style.Foreground).AlphaMultiplied(0.6f);
Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);
var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset);
Matrix3x3.Multiply(ref m1, ref m2, out var m3);
@@ -61,7 +61,8 @@ namespace FlaxEditor.GUI.Timeline.GUI
Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6));
Render2D.PopTransform();
- Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f));
+ color = _timeline.IsMovingPositionHandle ? style.SelectionBorder : style.Foreground.RGBMultiplied(0.8f);
+ Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), color);
base.Draw();
}
diff --git a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs
index 779eb7ddf..0b76c11ef 100644
--- a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs
+++ b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs
@@ -42,7 +42,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f;
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap;
- var moveColor = style.ProgressNormal;
+ var moveColor = style.SelectionBorder;
var thickness = 2.0f;
var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal);
Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor);
diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs
index e091f1e6a..a96ea21c5 100644
--- a/Source/Editor/GUI/Timeline/Media.cs
+++ b/Source/Editor/GUI/Timeline/Media.cs
@@ -100,7 +100,7 @@ namespace FlaxEditor.GUI.Timeline
private Track _tack;
private int _startFrame, _durationFrames;
private Float2 _mouseLocation = Float2.Minimum;
- private bool _isMoving;
+ internal bool _isMoving;
private Float2 _startMoveLocation;
private int _startMoveStartFrame;
private int _startMoveDuration;
@@ -347,7 +347,7 @@ namespace FlaxEditor.GUI.Timeline
var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge;
var borderHighlightColor = style.BorderHighlighted;
- var moveColor = style.ProgressNormal;
+ var moveColor = style.SelectionBorder;
var selectedColor = style.BackgroundSelected;
var moveThickness = 2.0f;
var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal));
diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
index 764b376dd..ef0cef279 100644
--- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
@@ -1,6 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -183,6 +182,49 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDurationFramesChanged();
}
+ ///
+ public override bool ContainsPoint(ref Float2 location, bool precise = false)
+ {
+ if (Timeline.Zoom > 0.5f && !IsContinuous)
+ {
+ // Hit-test dot
+ var size = Height - 2.0f;
+ var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
+ return rect.Contains(ref location);
+ }
+
+ return base.ContainsPoint(ref location, precise);
+ }
+
+ ///
+ public override void Draw()
+ {
+ if (Timeline.Zoom > 0.5f && !IsContinuous)
+ {
+ // Draw more visible dot for the event that maintains size even when zooming out
+ var style = Style.Current;
+ var icon = Editor.Instance.Icons.VisjectBoxClosed32;
+ var size = Height - 2.0f;
+ var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
+ var outline = Color.Black; // Shadow
+ if (_isMoving)
+ outline = style.SelectionBorder;
+ else if (IsMouseOver)
+ outline = style.BorderHighlighted;
+ else if (Timeline.SelectedMedia.Contains(this))
+ outline = style.BackgroundSelected;
+ Render2D.DrawSprite(icon, rect.MakeExpanded(6.0f), outline);
+ Render2D.DrawSprite(icon, rect, BackgroundColor);
+
+ DrawChildren();
+ }
+ else
+ {
+ // Default drawing
+ base.Draw();
+ }
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 22044d095..bf505394c 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -717,19 +717,6 @@ namespace FlaxEditor.Modules
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator();
- // Build scenes
- _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
-
- // Cook and run
- _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
- _toolStripCook.ContextMenu = new ContextMenu();
- _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
- _toolStripCook.ContextMenu.AddSeparator();
- var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
- _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
-
- ToolStrip.AddSeparator();
-
// Play
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})");
_toolStripPlay.ContextMenu = new ContextMenu();
@@ -741,7 +728,6 @@ namespace FlaxEditor.Modules
playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction;
playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
-
var windowModesGroup = new ContextMenuSingleSelectGroup();
var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode");
windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor");
@@ -754,7 +740,20 @@ namespace FlaxEditor.Modules
Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; };
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
- _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
+ _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip($"Step one frame in game ({inputOptions.StepFrame})");
+
+ ToolStrip.AddSeparator();
+
+ // Build scenes
+ _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
+
+ // Cook and run
+ _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
+ _toolStripCook.ContextMenu = new ContextMenu();
+ _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
+ _toolStripCook.ContextMenu.AddSeparator();
+ var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
+ _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
UpdateToolstrip();
}
diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs
index 7f35a5435..a867eec3d 100644
--- a/Source/Editor/Surface/VisjectSurface.Input.cs
+++ b/Source/Editor/Surface/VisjectSurface.Input.cs
@@ -17,6 +17,11 @@ namespace FlaxEditor.Surface
///
public readonly InputActionsContainer InputActions;
+ ///
+ /// Optional feature.
+ ///
+ public bool PanWithMiddleMouse = false;
+
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
private HashSet _movingNodes;
@@ -223,15 +228,18 @@ namespace FlaxEditor.Surface
if (_middleMouseDown)
{
- // Calculate delta
- var delta = location - _middleMouseDownPos;
- if (delta.LengthSquared > 0.01f)
+ if (PanWithMiddleMouse)
{
- // Move view
- _mouseMoveAmount += delta.Length;
- _rootControl.Location += delta;
- _middleMouseDownPos = location;
- Cursor = CursorType.SizeAll;
+ // Calculate delta
+ var delta = location - _middleMouseDownPos;
+ if (delta.LengthSquared > 0.01f)
+ {
+ // Move view
+ _mouseMoveAmount += delta.Length;
+ _rootControl.Location += delta;
+ _middleMouseDownPos = location;
+ Cursor = CursorType.SizeAll;
+ }
}
// Handled
@@ -300,7 +308,8 @@ namespace FlaxEditor.Surface
if (_middleMouseDown)
{
_middleMouseDown = false;
- Cursor = CursorType.Default;
+ if (PanWithMiddleMouse)
+ Cursor = CursorType.Default;
}
_isMovingSelection = false;
ConnectingEnd(null);
@@ -483,7 +492,7 @@ namespace FlaxEditor.Surface
Focus();
return true;
}
- if (_rightMouseDown || _middleMouseDown)
+ if (_rightMouseDown || (_middleMouseDown && _middleMouseDown))
{
// Start navigating
StartMouseCapture();
@@ -555,9 +564,12 @@ namespace FlaxEditor.Surface
if (_middleMouseDown && button == MouseButton.Middle)
{
_middleMouseDown = false;
- EndMouseCapture();
- Cursor = CursorType.Default;
- if (_mouseMoveAmount > 0)
+ if (_middleMouseDown)
+ {
+ EndMouseCapture();
+ Cursor = CursorType.Default;
+ }
+ if (_mouseMoveAmount > 0 && _middleMouseDown)
_mouseMoveAmount = 0;
else if (CanEdit)
{
diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs
index df00e4137..dfdb005dd 100644
--- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs
+++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs
@@ -66,8 +66,8 @@ namespace FlaxEditor.Tools.Terrain
[EditorOrder(410), EditorDisplay("Transform", "Rotation"), DefaultValue(typeof(Quaternion), "0,0,0,1"), Tooltip("Orientation of the terrain")]
public Quaternion Orientation = Quaternion.Identity;
- [EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(typeof(Float3), "1,1,1"), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
- public Float3 Scale = Float3.One;
+ [EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(1.0f), Limit(0.0001f, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
+ public float Scale = 1.0f;
}
private readonly Options _options = new Options();
@@ -147,7 +147,7 @@ namespace FlaxEditor.Tools.Terrain
// Create terrain object and setup some options
var terrain = new FlaxEngine.Terrain();
terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
- terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
+ terrain.Transform = new Transform(_options.Position, _options.Orientation, new Float3(_options.Scale));
terrain.Material = _options.Material;
terrain.CollisionLOD = _options.CollisionLOD;
if (_options.Heightmap)
@@ -238,24 +238,5 @@ namespace FlaxEditor.Tools.Terrain
return base.CanCloseWindow(reason);
}
-
- ///
- public override bool OnKeyDown(KeyboardKeys key)
- {
- if (_isWorking)
- return true;
-
- switch (key)
- {
- case KeyboardKeys.Escape:
- OnCancel();
- return true;
- case KeyboardKeys.Return:
- OnSubmit();
- return true;
- }
-
- return base.OnKeyDown(key);
- }
}
}
diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp
index 1d2f51237..3ba11cdf5 100644
--- a/Source/Editor/Tools/Terrain/TerrainTools.cpp
+++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp
@@ -80,7 +80,7 @@ struct TextureDataResult
}
};
-bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
+bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
{
// Lock asset chunks (if not virtual)
data.Lock = texture->LockData();
@@ -103,7 +103,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
// Decompress or convert data if need to
data.Mip0DataPtr = &data.Mip0Data;
- if (PixelFormatExtensions::IsCompressed(data.Format))
+ if (PixelFormatExtensions::IsCompressed(data.Format) || TextureTool::GetSampler(data.Format) == nullptr)
{
PROFILE_CPU_NAMED("Decompress");
@@ -122,7 +122,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
srcMip.Lines = src.Height;
// Decompress texture
- if (TextureTool::Convert(data.Tmp, src, PixelFormat::R8G8B8A8_UNorm))
+ if (TextureTool::Convert(data.Tmp, src, hdr ? PixelFormat::R16G16B16A16_Float : PixelFormat::R8G8B8A8_UNorm))
{
LOG(Warning, "Failed to decompress data.");
return true;
@@ -134,7 +134,6 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
data.SlicePitch = data.Tmp.Items[0].Mips[0].DepthPitch;
data.Mip0DataPtr = &data.Tmp.Items[0].Mips[0].Data;
}
- // TODO: convert to RGBA from other formats that cannot be sampled?
// Check if can even sample the given format
const auto sampler = TextureTool::GetSampler(data.Format);
@@ -155,7 +154,6 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
LOG(Warning, "Cannot setup terain with no patches.");
return false;
}
-
PROFILE_CPU_NAMED("Terrain.GenerateTerrain");
// Wait for assets to be loaded
@@ -188,7 +186,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
{
// Get data
TextureDataResult dataHeightmap;
- if (GetTextureDataForSampling(heightmap, dataHeightmap))
+ if (GetTextureDataForSampling(heightmap, dataHeightmap, true))
return true;
const auto sampler = TextureTool::GetSampler(dataHeightmap.Format);
@@ -198,7 +196,6 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
{
auto patch = terrain->GetPatch(patchIndex);
-
const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
// Sample heightmap pixels with interpolation to get actual heightmap vertices locations
diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs
index 9fb3d962e..df0f7a8ff 100644
--- a/Source/Editor/Viewport/EditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/EditorGizmoViewport.cs
@@ -76,7 +76,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
///
- public Float2 MouseDelta => _mouseDelta * 1000;
+ public Float2 MouseDelta => _mouseDelta;
///
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 5bcc2c2b9..1e24560d8 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -65,6 +65,11 @@ namespace FlaxEditor.Viewport
///
public bool IsAltDown;
+ ///
+ /// The is alt down flag cached from the previous input. Used to make consistent when user releases Alt while orbiting with Alt+LMB.
+ ///
+ public bool WasAltDownBefore;
+
///
/// The is mouse right down flag.
///
@@ -88,18 +93,20 @@ namespace FlaxEditor.Viewport
///
/// Gets a value indicating whether use is controlling mouse.
///
- public bool IsControllingMouse => IsMouseMiddleDown || IsMouseRightDown || (IsAltDown && IsMouseLeftDown) || Mathf.Abs(MouseWheelDelta) > 0.1f;
+ public bool IsControllingMouse => IsMouseMiddleDown || IsMouseRightDown || ((IsAltDown || WasAltDownBefore) && IsMouseLeftDown) || Mathf.Abs(MouseWheelDelta) > 0.1f;
///
/// Gathers input from the specified window.
///
/// The window.
/// True if use mouse input, otherwise will skip mouse.
- public void Gather(Window window, bool useMouse)
+ /// Previous input state.
+ public void Gather(Window window, bool useMouse, ref Input prevInput)
{
IsControlDown = window.GetKey(KeyboardKeys.Control);
IsShiftDown = window.GetKey(KeyboardKeys.Shift);
IsAltDown = window.GetKey(KeyboardKeys.Alt);
+ WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown;
IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right);
IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle);
@@ -114,6 +121,7 @@ namespace FlaxEditor.Viewport
IsControlDown = false;
IsShiftDown = false;
IsAltDown = false;
+ WasAltDownBefore = false;
IsMouseRightDown = false;
IsMouseMiddleDown = false;
@@ -1540,7 +1548,7 @@ namespace FlaxEditor.Viewport
_prevInput = _input;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
if (canUseInput && ContainsFocus && hit == null)
- _input.Gather(win.Window, useMouse);
+ _input.Gather(win.Window, useMouse, ref _prevInput);
else
_input.Clear();
@@ -1663,8 +1671,7 @@ namespace FlaxEditor.Viewport
{
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
- _mouseDelta = offset / size;
- _mouseDelta.Y *= size.Y / size.X;
+ _mouseDelta = offset;
// Update delta filtering buffer
_deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta;
@@ -1682,8 +1689,7 @@ namespace FlaxEditor.Viewport
}
else
{
- _mouseDelta = offset / size;
- _mouseDelta.Y *= size.Y / size.X;
+ _mouseDelta = offset;
mouseDelta = _mouseDelta;
}
@@ -1697,7 +1703,7 @@ namespace FlaxEditor.Viewport
// Update
moveDelta *= dt * (60.0f * 4.0f);
- mouseDelta *= 200.0f * MouseSpeed * _mouseSensitivity;
+ mouseDelta *= 0.1833f * MouseSpeed * _mouseSensitivity;
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
// Move mouse back to the root position
@@ -1723,7 +1729,7 @@ namespace FlaxEditor.Viewport
var offset = _viewMousePos - _startPos;
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
- _mouseDelta = offset / size;
+ _mouseDelta = offset;
_startPos = _viewMousePos;
}
else
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 32473c5ea..d7624892d 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -291,7 +291,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
///
- public Float2 MouseDelta => _mouseDelta * 1000;
+ public Float2 MouseDelta => _mouseDelta;
///
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);
diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
index ec9fa9b58..771fc4db1 100644
--- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
@@ -336,7 +336,8 @@ namespace FlaxEditor.Viewport.Previews
if (_showNodes)
{
// Draw bounding box at the node locations
- var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f));
+ var boxSize = Mathf.Min(1.0f, _previewModel.Sphere.Radius / 100.0f);
+ var localBox = new OrientedBoundingBox(new Vector3(-boxSize), new Vector3(boxSize));
for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
{
if (nodesMask != null && !nodesMask[nodeIndex])
diff --git a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
index 40dd51e5e..6899bfac6 100644
--- a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
+++ b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
@@ -494,6 +494,7 @@ namespace FlaxEditor.Windows.Assets
_undo.Enabled = false;
_undo.Clear();
_propertiesEditor.BuildLayoutOnUpdate();
+ UpdateToolstrip();
}
///
@@ -504,6 +505,7 @@ namespace FlaxEditor.Windows.Assets
_undo.Enabled = true;
_undo.Clear();
_propertiesEditor.BuildLayoutOnUpdate();
+ UpdateToolstrip();
}
///
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs
index be20c176c..85dfc1595 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs
@@ -54,6 +54,9 @@ namespace FlaxEditor.Windows.Assets
/// The selection before the change.
public void OnSelectionChanged(SceneGraphNode[] before)
{
+ if (LockSelectedObjects)
+ return;
+
Undo.AddAction(new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo));
OnSelectionChanges();
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index b788821cf..36841d515 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -68,6 +68,11 @@ namespace FlaxEditor.Windows.Assets
///
public readonly LocalSceneGraph Graph;
+ ///
+ /// Indication of if the prefab window selection is locked on specific objects.
+ ///
+ public bool LockSelectedObjects = false;
+
///
/// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto).
///
diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs
index 33946761a..fe26f80aa 100644
--- a/Source/Editor/Windows/DebugLogWindow.cs
+++ b/Source/Editor/Windows/DebugLogWindow.cs
@@ -665,7 +665,16 @@ namespace FlaxEditor.Windows
// Scroll to the new entry (if any added to view)
if (scrollView && anyVisible)
+ {
panelScroll.ScrollViewTo(newEntry);
+
+ bool scrollViewNew = (panelScroll.VScrollBar.Maximum - panelScroll.VScrollBar.TargetValue) < LogEntry.DefaultHeight * 1.5f;
+ if (scrollViewNew != scrollView)
+ {
+ // Make sure scrolling doesn't stop in case too many entries were added at once
+ panelScroll.ScrollViewTo(new Float2(float.MaxValue, float.MaxValue));
+ }
+ }
}
}
diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs
index 17b269c1b..9c1cbec0d 100644
--- a/Source/Editor/Windows/PropertiesWindow.cs
+++ b/Source/Editor/Windows/PropertiesWindow.cs
@@ -37,6 +37,11 @@ namespace FlaxEditor.Windows
///
public bool UIPivotRelative = true;
+ ///
+ /// Indication of if the properties window is locked on specific objects.
+ ///
+ public bool LockObjects = false;
+
///
/// Initializes a new instance of the class.
///
@@ -62,6 +67,9 @@ namespace FlaxEditor.Windows
private void OnSelectionChanged()
{
+ if (LockObjects)
+ return;
+
// Update selected objects
// TODO: use cached collection for less memory allocations
undoRecordObjects = Editor.SceneEditing.Selection.ConvertAll(x => x.UndoRecordObject).Distinct();
diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs
index 820b99298..ab9c218c5 100644
--- a/Source/Editor/Windows/ToolboxWindow.cs
+++ b/Source/Editor/Windows/ToolboxWindow.cs
@@ -196,6 +196,7 @@ namespace FlaxEditor.Windows
{
if (actorType.IsAbstract)
continue;
+ _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType));
ActorToolboxAttribute attribute = null;
foreach (var e in actorType.GetAttributes(false))
{
@@ -235,6 +236,7 @@ namespace FlaxEditor.Windows
group.AddChild(string.IsNullOrEmpty(attribute.Name) ? CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType) : CreateActorItem(attribute.Name, actorType));
group.SortChildren();
}
+ _groupSearch.SortChildren();
}
private void OnSearchBoxTextChanged()
@@ -260,6 +262,10 @@ namespace FlaxEditor.Windows
}
var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name;
+
+ // Display all actors on no search
+ if (string.IsNullOrEmpty(filterText))
+ _groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType));
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
continue;
@@ -278,6 +284,9 @@ namespace FlaxEditor.Windows
}
item.SetHighlights(highlights);
}
+
+ if (string.IsNullOrEmpty(filterText))
+ _groupSearch.SortChildren();
_groupSearch.UnlockChildrenRecursive();
PerformLayout();
diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h
index a1f7a7a81..c37396243 100644
--- a/Source/Engine/Animations/AnimationData.h
+++ b/Source/Engine/Animations/AnimationData.h
@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Animations/Curve.h"
@@ -68,6 +69,16 @@ public:
uint64 GetMemoryUsage() const;
};
+///
+/// Single track with events.
+///
+struct EventAnimationData
+{
+ float Duration = 0.0f;
+ StringAnsi TypeName;
+ StringAnsi JsonData;
+};
+
///
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
///
@@ -120,10 +131,15 @@ struct AnimationData
String RootNodeName;
///
- /// The per skeleton node animation channels.
+ /// The per-skeleton node animation channels.
///
Array Channels;
+ ///
+ /// The animation event tracks.
+ ///
+ Array>> Events;
+
public:
///
/// Gets the length of the animation (in seconds).
diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h
index ee84e0b71..ebee7e901 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.h
+++ b/Source/Engine/Animations/Graph/AnimGraph.h
@@ -5,6 +5,7 @@
#include "Engine/Visject/VisjectGraph.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Core/Collections/ChunkedArray.h"
+#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Animations/AlphaBlend.h"
#include "Engine/Core/Math/Matrix.h"
#include "../Config.h"
@@ -892,7 +893,7 @@ private:
int32 GetRootNodeIndex(Animation* anim);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
- void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
+ void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override, BitArray>* usedNodes = nullptr);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha);
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index b09624d65..21eb44265 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -25,7 +25,7 @@ namespace
{
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
- nodes->Nodes[i].Orientation.Normalize();
+ nodes->Nodes.Get()[i].Orientation.Normalize();
}
if (rootMotionMode != RootMotionExtraction::NoExtraction)
{
@@ -222,7 +222,7 @@ FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos,
prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
}
-void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode)
+void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode, BitArray>* usedNodes)
{
PROFILE_CPU_ASSET(anim);
@@ -240,9 +240,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
}
// Evaluate nested animations
- bool hasNested = false;
+ BitArray> usedNodesThis;
if (anim->NestedAnims.Count() != 0)
{
+ if (usedNodes == nullptr)
+ {
+ // Per-channel bit to indicate which channels were used by nested
+ usedNodesThis.Resize(nodes->Nodes.Count());
+ usedNodes = &usedNodesThis;
+ }
+
for (auto& e : anim->NestedAnims)
{
const auto& nestedAnim = e.Second;
@@ -262,8 +269,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
- ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
- hasNested = true;
+ ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode, usedNodes);
}
}
}
@@ -295,6 +301,15 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
}
+
+ // Mark node as used
+ if (usedNodes)
+ usedNodes->Set(i, true);
+ }
+ else if (usedNodes && usedNodes != &usedNodesThis)
+ {
+ // Skip for nested animations so other one or top-level anim will update remaining nodes
+ continue;
}
// Blend node
@@ -316,7 +331,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
dstNode.Scale = srcNode.Scale * weight;
dstNode.Orientation = srcNode.Orientation * weight;
}
- else if (!hasNested)
+ else
{
dstNode = srcNode;
}
@@ -1177,14 +1192,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
auto mask = node->Assets[0].As();
- auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node.
- // Check if have some mask asset connected with the mask node
- if (maskAssetBox->HasConnection())
+ // Use the mask connected with this node instead of default mask asset
+ auto maskAssetBox = node->TryGetBox(4);
+ if (maskAssetBox && maskAssetBox->HasConnection())
{
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
-
- // Use the mask connected with this node instead of default mask asset
if (assetBoxValue != Value::Null)
mask = (SkeletonMask*)assetBoxValue.AsAsset;
}
diff --git a/Source/Engine/Animations/InverseKinematics.cpp b/Source/Engine/Animations/InverseKinematics.cpp
index 098e612c1..06523be81 100644
--- a/Source/Engine/Animations/InverseKinematics.cpp
+++ b/Source/Engine/Animations/InverseKinematics.cpp
@@ -10,79 +10,102 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target,
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection);
}
-void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode, Transform& targetNode, const Vector3& target, const Vector3& jointTarget, bool allowStretching, float maxStretchScale)
-{
- Real lowerLimbLength = (targetNode.Translation - jointNode.Translation).Length();
- Real upperLimbLength = (jointNode.Translation - rootNode.Translation).Length();
- Vector3 jointPos = jointNode.Translation;
- Vector3 desiredDelta = target - rootNode.Translation;
- Real desiredLength = desiredDelta.Length();
- Real limbLengthLimit = lowerLimbLength + upperLimbLength;
- Vector3 desiredDir;
- if (desiredLength < ZeroTolerance)
+void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
+{
+ // Calculate limb segment lengths
+ Real lowerLimbLength = (endEffectorTransform.Translation - midJointTransform.Translation).Length();
+ Real upperLimbLength = (midJointTransform.Translation - rootTransform.Translation).Length();
+ Vector3 midJointPos = midJointTransform.Translation;
+
+ // Calculate the direction and length towards the target
+ Vector3 toTargetVector = targetPosition - rootTransform.Translation;
+ Real toTargetLength = toTargetVector.Length();
+ Real totalLimbLength = lowerLimbLength + upperLimbLength;
+
+ // Normalize the direction vector or set a default direction if too small
+ Vector3 toTargetDir;
+ if (toTargetLength < ZeroTolerance)
{
- desiredLength = ZeroTolerance;
- desiredDir = Vector3(1, 0, 0);
+ toTargetLength = ZeroTolerance;
+ toTargetDir = Vector3(1, 0, 0);
}
else
{
- desiredDir = desiredDelta.GetNormalized();
+ toTargetDir = toTargetVector.GetNormalized();
}
- Vector3 jointTargetDelta = jointTarget - rootNode.Translation;
- const Real jointTargetLengthSqr = jointTargetDelta.LengthSquared();
+ // Calculate the pole vector direction
+ Vector3 poleVectorDelta = poleVector - rootTransform.Translation;
+ const Real poleVectorLengthSqr = poleVectorDelta.LengthSquared();
- Vector3 jointPlaneNormal, jointBendDir;
- if (jointTargetLengthSqr < ZeroTolerance * ZeroTolerance)
+ Vector3 jointPlaneNormal, bendDirection;
+ if (poleVectorLengthSqr < ZeroTolerance * ZeroTolerance)
{
- jointBendDir = Vector3::Forward;
+ bendDirection = Vector3::Forward;
jointPlaneNormal = Vector3::Up;
}
else
{
- jointPlaneNormal = desiredDir ^ jointTargetDelta;
+ jointPlaneNormal = toTargetDir ^ poleVectorDelta;
if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance)
{
- desiredDir.FindBestAxisVectors(jointPlaneNormal, jointBendDir);
+ toTargetDir.FindBestAxisVectors(jointPlaneNormal, bendDirection);
}
else
{
jointPlaneNormal.Normalize();
- jointBendDir = jointTargetDelta - (jointTargetDelta | desiredDir) * desiredDir;
- jointBendDir.Normalize();
+ bendDirection = poleVectorDelta - (poleVectorDelta | toTargetDir) * toTargetDir;
+ bendDirection.Normalize();
}
}
+ // Handle limb stretching if allowed
if (allowStretching)
{
const Real initialStretchRatio = 1.0f;
- const Real range = maxStretchScale - initialStretchRatio;
- if (range > ZeroTolerance && limbLengthLimit > ZeroTolerance)
+ const Real stretchRange = maxStretchScale - initialStretchRatio;
+ if (stretchRange > ZeroTolerance && totalLimbLength > ZeroTolerance)
{
- const Real reachRatio = desiredLength / limbLengthLimit;
- const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / range);
+ const Real reachRatio = toTargetLength / totalLimbLength;
+ const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / stretchRange);
if (scalingFactor > ZeroTolerance)
{
lowerLimbLength *= 1.0f + scalingFactor;
upperLimbLength *= 1.0f + scalingFactor;
- limbLengthLimit *= 1.0f + scalingFactor;
+ totalLimbLength *= 1.0f + scalingFactor;
}
}
}
- Vector3 resultEndPos = target;
- Vector3 resultJointPos = jointPos;
+ // Calculate new positions for joint and end effector
+ Vector3 newEndEffectorPos = targetPosition;
+ Vector3 newMidJointPos = midJointPos;
- if (desiredLength >= limbLengthLimit)
- {
- resultEndPos = rootNode.Translation + limbLengthLimit * desiredDir;
- resultJointPos = rootNode.Translation + upperLimbLength * desiredDir;
+ if (toTargetLength >= totalLimbLength) {
+ // Target is beyond the reach of the limb
+ Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
+
+ // Calculate the slight offset towards the pole vector
+ Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
+ Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
+ if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) {
+ slightBendDirection = Vector3::Up;
+ }
+ else {
+ slightBendDirection.Normalize();
+ }
+
+ // Calculate the direction from root to mid joint with a slight offset towards the pole vector
+ Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized();
+ Real slightOffset = upperLimbLength * 0.01f; // Small percentage of the limb length for slight offset
+ newMidJointPos = rootTransform.Translation + rootToEnd * (upperLimbLength - slightOffset) + midJointDirection * slightOffset;
}
else
{
- const Real twoAb = 2.0f * upperLimbLength * desiredLength;
- const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + desiredLength * desiredLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
+ // Target is within reach, calculate joint position
+ const Real twoAb = 2.0f * upperLimbLength * toTargetLength;
+ const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + toTargetLength * toTargetLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
const bool reverseUpperBone = cosAngle < 0.0f;
const Real angle = Math::Acos(cosAngle);
const Real jointLineDist = upperLimbLength * Math::Sin(angle);
@@ -90,23 +113,66 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode
Real projJointDist = projJointDistSqr > 0.0f ? Math::Sqrt(projJointDistSqr) : 0.0f;
if (reverseUpperBone)
projJointDist *= -1.0f;
- resultJointPos = rootNode.Translation + projJointDist * desiredDir + jointLineDist * jointBendDir;
+ newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
}
+ // Update root joint orientation
{
- const Vector3 oldDir = (jointPos - rootNode.Translation).GetNormalized();
- const Vector3 newDir = (resultJointPos - rootNode.Translation).GetNormalized();
- const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
- rootNode.Orientation = deltaRotation * rootNode.Orientation;
+ // Vector from root joint to mid joint (local Y-axis direction)
+ Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
+
+ // Vector from mid joint to end effector (used to calculate plane normal)
+ Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
+
+ // Calculate the plane normal (local Z-axis direction)
+ Vector3 localZ = Vector3::Cross(localY, midToEnd).GetNormalized();
+
+ // Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
+ Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
+
+ // Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
+ localZ = Vector3::Cross(localX, localY).GetNormalized();
+
+ // Construct a rotation from the orthogonal basis vectors
+ Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY);
+
+ // Apply the new rotation to the root joint
+ rootTransform.Orientation = newRootJointOrientation;
}
+
+ // Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
{
- const Vector3 oldDir = (targetNode.Translation - jointPos).GetNormalized();
- const Vector3 newDir = (resultEndPos - resultJointPos).GetNormalized();
- const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
- jointNode.Orientation = deltaRotation * jointNode.Orientation;
- jointNode.Translation = resultJointPos;
+ // Vector from mid joint to end effector (local Y-axis direction after rotation)
+ Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
+
+ // Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
+ Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
+ Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
+
+
+ // Vector from mid joint to end effector (local Y-axis direction)
+ Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
+
+ // Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction)
+ Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized();
+
+ //// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
+ Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
+
+ // Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
+ localZ = Vector3::Cross(localX, localY).GetNormalized();
+
+
+ // Construct a rotation from the orthogonal basis vectors
+ // The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
+ Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up
+
+ // Apply the new rotation to the mid joint
+ midJointTransform.Orientation = newMidJointOrientation;
+ midJointTransform.Translation = newMidJointPos;
}
- targetNode.Translation = resultEndPos;
+ // Update end effector transform
+ endEffectorTransform.Translation = newEndEffectorPos;
}
diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp
index 48631d214..c990332a0 100644
--- a/Source/Engine/Audio/AudioSource.cpp
+++ b/Source/Engine/Audio/AudioSource.cpp
@@ -152,12 +152,17 @@ void AudioSource::Play()
RequestStreamingBuffersUpdate();
}
}
- else
+ else if (SourceIDs.HasItems())
{
// Play it right away
SetNonStreamingBuffer();
PlayInternal();
}
+ else
+ {
+ // Source was nt properly added to the Audio Backend
+ LOG(Warning, "Cannot play unitialized audio source.");
+ }
}
void AudioSource::Pause()
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index 1401d88ce..30771899f 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -184,9 +184,8 @@ void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
: ManagedScriptingObject(params)
, _refCount(0)
- , _loadingTask(nullptr)
- , _isLoaded(false)
- , _loadFailed(false)
+ , _loadState(0)
+ , _loadingTask(0)
, _deleteFileOnUnload(false)
, _isVirtual(false)
{
@@ -225,10 +224,10 @@ void Asset::OnDeleteObject()
// Unload asset data (in a safe way to protect asset data)
Locker.Lock();
- if (_isLoaded)
+ if (IsLoaded())
{
unload(false);
- _isLoaded = false;
+ Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
}
Locker.Unlock();
@@ -319,11 +318,6 @@ void Asset::ChangeID(const Guid& newId)
Content::onAssetChangeId(this, oldId, newId);
}
-bool Asset::LastLoadFailed() const
-{
- return _loadFailed != 0;
-}
-
#if USE_EDITOR
bool Asset::ShouldDeleteFileOnUnload() const
@@ -337,7 +331,7 @@ uint64 Asset::GetMemoryUsage() const
{
uint64 result = sizeof(Asset);
Locker.Lock();
- if (_loadingTask)
+ if (Platform::AtomicRead(&_loadingTask))
result += sizeof(ContentLoadTask);
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
Locker.Unlock();
@@ -368,7 +362,7 @@ void Asset::Reload()
{
// Unload current data
unload(true);
- _isLoaded = false;
+ Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
}
// Start reloading process
@@ -426,7 +420,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
// Check if has missing loading task
Platform::MemoryBarrier();
- const auto loadingTask = _loadingTask;
+ const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask == nullptr)
{
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
@@ -516,7 +510,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
Content::tryCallOnLoaded((Asset*)this);
}
- return _isLoaded == 0;
+ return !IsLoaded();
}
void Asset::InitAsVirtual()
@@ -525,14 +519,14 @@ void Asset::InitAsVirtual()
_isVirtual = true;
// Be a loaded thing
- _isLoaded = true;
+ Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
}
void Asset::CancelStreaming()
{
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
Locker.Lock();
- ContentLoadTask* loadTask = _loadingTask;
+ auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
Locker.Unlock();
if (loadTask)
{
@@ -575,10 +569,11 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
ASSERT(!IsLoaded());
- ASSERT(_loadingTask == nullptr);
- _loadingTask = createLoadingTask();
- ASSERT(_loadingTask != nullptr);
- _loadingTask->Start();
+ ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
+ auto loadingTask = createLoadingTask();
+ ASSERT(loadingTask != nullptr);
+ Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
+ loadingTask->Start();
}
void Asset::releaseStorage()
@@ -593,7 +588,7 @@ bool Asset::IsInternalType() const
bool Asset::onLoad(LoadAssetTask* task)
{
// It may fail when task is cancelled and new one is created later (don't crash but just end with an error)
- if (task->Asset.Get() != this || _loadingTask == nullptr)
+ if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
return true;
Locker.Lock();
@@ -606,15 +601,15 @@ bool Asset::onLoad(LoadAssetTask* task)
}
const bool isLoaded = result == LoadResult::Ok;
const bool failed = !isLoaded;
- _loadFailed = failed;
- _isLoaded = !failed;
+ LoadState state = LoadState::Loaded;
+ Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
if (failed)
{
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result));
}
// Unlink task
- _loadingTask = nullptr;
+ Platform::AtomicStore(&_loadingTask, 0);
ASSERT(failed || IsLoaded() == isLoaded);
Locker.Unlock();
@@ -663,12 +658,12 @@ void Asset::onUnload_MainThread()
OnUnloaded(this);
// Check if is during loading
- if (_loadingTask != nullptr)
+ auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
+ if (loadingTask != nullptr)
{
// Cancel loading
- auto task = _loadingTask;
- _loadingTask = nullptr;
+ Platform::AtomicStore(&_loadingTask, 0);
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
- task->Cancel();
+ loadingTask->Cancel();
}
}
diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h
index b14f81329..c4b89935d 100644
--- a/Source/Engine/Content/Asset.h
+++ b/Source/Engine/Content/Asset.h
@@ -34,11 +34,17 @@ public:
DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData);
protected:
- volatile int64 _refCount;
- ContentLoadTask* _loadingTask;
+ enum class LoadState : int64
+ {
+ Unloaded,
+ Loaded,
+ LoadFailed,
+ };
+
+ volatile int64 _refCount;
+ volatile int64 _loadState;
+ volatile intptr _loadingTask;
- int8 _isLoaded : 1; // Indicates that asset is loaded
- int8 _loadFailed : 1; // Indicates that last asset loading has failed
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
@@ -111,13 +117,16 @@ public:
///
API_PROPERTY() FORCE_INLINE bool IsLoaded() const
{
- return _isLoaded != 0;
+ return Platform::AtomicRead(&_loadState) == (int64)LoadState::Loaded;
}
///
/// Returns true if last asset loading failed, otherwise false.
///
- API_PROPERTY() bool LastLoadFailed() const;
+ API_PROPERTY() bool LastLoadFailed() const
+ {
+ return Platform::AtomicRead(&_loadState) == (int64)LoadState::LoadFailed;
+ }
///
/// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved).
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index faa4ace07..96c49bc05 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -1434,8 +1434,7 @@ Asset::LoadResult VisualScript::load()
if (_instances.HasItems())
{
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
- _loadFailed = false;
- _isLoaded = true;
+ Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
// Setup scripting type
CacheScriptingType();
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index 810c0bb3c..39fae81db 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -14,6 +14,7 @@
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Threading/Threading.h"
+#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Globals.h"
@@ -688,101 +689,135 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
return false;
}
-bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
+class CloneAssetFileTask : public MainThreadTask
{
- PROFILE_CPU();
- ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
+public:
+ StringView dstPath;
+ StringView srcPath;
+ Guid dstId;
+ bool* output;
- LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
-
- // Check source file
- if (!FileSystem::FileExists(srcPath))
+protected:
+ bool Run() override
{
- LOG(Warning, "Missing source file.");
- return true;
- }
-
- // Special case for json resources
- if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
- {
- if (FileSystem::CopyFile(dstPath, srcPath))
- {
- LOG(Warning, "Cannot copy file to destination.");
- return true;
- }
- if (JsonStorageProxy::ChangeId(dstPath, dstId))
- {
- LOG(Warning, "Cannot change asset ID.");
- return true;
- }
+ *output = Content::CloneAssetFile(dstPath, srcPath, dstId);
return false;
}
+};
- // Check if destination file is missing
- if (!FileSystem::FileExists(dstPath))
+bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
+{
+ // Best to run this on the main thread to avoid clone conflicts.
+ if (IsInMainThread())
{
- // Use quick file copy
- if (FileSystem::CopyFile(dstPath, srcPath))
+ PROFILE_CPU();
+ ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
+
+ LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
+
+ // Check source file
+ if (!FileSystem::FileExists(srcPath))
{
- LOG(Warning, "Cannot copy file to destination.");
+ LOG(Warning, "Missing source file.");
return true;
}
- // Change ID
- auto storage = ContentStorageManager::GetStorage(dstPath);
- FlaxStorage::Entry e;
- storage->GetEntry(0, e);
- if (storage == nullptr || storage->ChangeAssetID(e, dstId))
+ // Special case for json resources
+ if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
{
- LOG(Warning, "Cannot change asset ID.");
- return true;
+ if (FileSystem::CopyFile(dstPath, srcPath))
+ {
+ LOG(Warning, "Cannot copy file to destination.");
+ return true;
+ }
+ if (JsonStorageProxy::ChangeId(dstPath, dstId))
+ {
+ LOG(Warning, "Cannot change asset ID.");
+ return true;
+ }
+ return false;
+ }
+
+ // Check if destination file is missing
+ if (!FileSystem::FileExists(dstPath))
+ {
+ // Use quick file copy
+ if (FileSystem::CopyFile(dstPath, srcPath))
+ {
+ LOG(Warning, "Cannot copy file to destination.");
+ return true;
+ }
+
+ // Change ID
+ auto storage = ContentStorageManager::GetStorage(dstPath);
+ FlaxStorage::Entry e;
+ storage->GetEntry(0, e);
+ if (storage == nullptr || storage->ChangeAssetID(e, dstId))
+ {
+ LOG(Warning, "Cannot change asset ID.");
+ return true;
+ }
+ }
+ else
+ {
+ // Use temporary file
+ String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
+ if (FileSystem::CopyFile(tmpPath, srcPath))
+ {
+ LOG(Warning, "Cannot copy file.");
+ return true;
+ }
+
+ // Change asset ID
+ {
+ auto storage = ContentStorageManager::GetStorage(tmpPath);
+ if (!storage)
+ {
+ LOG(Warning, "Cannot change asset ID.");
+ return true;
+ }
+ FlaxStorage::Entry e;
+ storage->GetEntry(0, e);
+ if (storage->ChangeAssetID(e, dstId))
+ {
+ LOG(Warning, "Cannot change asset ID.");
+ return true;
+ }
+ }
+
+ // Unlock destination file
+ ContentStorageManager::EnsureAccess(dstPath);
+
+ // Copy temp file to the destination
+ if (FileSystem::CopyFile(dstPath, tmpPath))
+ {
+ LOG(Warning, "Cannot copy file to destination.");
+ return true;
+ }
+
+ // Cleanup
+ FileSystem::DeleteFile(tmpPath);
+
+ // Reload storage
+ if (auto storage = ContentStorageManager::GetStorage(dstPath))
+ {
+ storage->Reload();
+ }
}
}
else
{
- // Use temporary file
- String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
- if (FileSystem::CopyFile(tmpPath, srcPath))
- {
- LOG(Warning, "Cannot copy file.");
- return true;
- }
+ CloneAssetFileTask* task = New();
+ task->dstId = dstId;
+ task->dstPath = dstPath;
+ task->srcPath = srcPath;
- // Change asset ID
- {
- auto storage = ContentStorageManager::GetStorage(tmpPath);
- if (!storage)
- {
- LOG(Warning, "Cannot change asset ID.");
- return true;
- }
- FlaxStorage::Entry e;
- storage->GetEntry(0, e);
- if (storage->ChangeAssetID(e, dstId))
- {
- LOG(Warning, "Cannot change asset ID.");
- return true;
- }
- }
+ bool result = false;
+ task->output = &result;
+ task->Start();
+ task->Wait();
- // Unlock destination file
- ContentStorageManager::EnsureAccess(dstPath);
-
- // Copy temp file to the destination
- if (FileSystem::CopyFile(dstPath, tmpPath))
- {
- LOG(Warning, "Cannot copy file to destination.");
- return true;
- }
-
- // Cleanup
- FileSystem::DeleteFile(tmpPath);
-
- // Reload storage
- if (auto storage = ContentStorageManager::GetStorage(dstPath))
- {
- storage->Reload();
- }
+ return result;
}
return false;
diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
index f6991c8de..f71f2b4a6 100644
--- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
+++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
@@ -31,12 +31,11 @@ public:
if (Asset)
{
Asset->Locker.Lock();
- if (Asset->_loadingTask == this)
+ if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
{
- Asset->_loadFailed = true;
- Asset->_isLoaded = false;
+ Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
+ Platform::AtomicStore(&Asset->_loadingTask, 0);
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
- Asset->_loadingTask = nullptr;
}
Asset->Locker.Unlock();
}
@@ -77,8 +76,8 @@ protected:
if (Asset)
{
Asset->Locker.Lock();
- if (Asset->_loadingTask == this)
- Asset->_loadingTask = nullptr;
+ if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
+ Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock();
Asset = nullptr;
}
@@ -91,8 +90,8 @@ protected:
if (Asset)
{
Asset->Locker.Lock();
- if (Asset->_loadingTask == this)
- Asset->_loadingTask = nullptr;
+ if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
+ Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock();
Asset = nullptr;
}
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
index bdcbf5140..b689cdc62 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
@@ -413,6 +413,7 @@ bool AssetsImportingManagerService::Init()
{ TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import },
+ { TEXT("exr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
// IES Profiles
{ TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES },
diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp
index 4e48dc813..75a25b5fa 100644
--- a/Source/Engine/ContentImporters/ImportModel.cpp
+++ b/Source/Engine/ContentImporters/ImportModel.cpp
@@ -15,6 +15,7 @@
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h"
+#include "Engine/Animations/AnimEvent.h"
#include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/Prefabs/Prefab.h"
@@ -141,48 +142,6 @@ void RepackMeshLightmapUVs(ModelData& data)
}
}
-void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
-{
- // Skip if file is missing
- if (!FileSystem::FileExists(context.TargetAssetPath))
- return;
-
- // Try to load asset that gets reimported
- AssetReference asset = Content::LoadAsync(context.TargetAssetPath);
- if (asset == nullptr)
- return;
- if (asset->WaitForLoaded())
- return;
-
- // Get model object
- ModelBase* model = nullptr;
- if (asset.Get()->GetTypeName() == Model::TypeName)
- {
- model = ((Model*)asset.Get());
- }
- else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
- {
- model = ((SkinnedModel*)asset.Get());
- }
- if (!model)
- return;
-
- // Peek materials
- for (int32 i = 0; i < modelData.Materials.Count(); i++)
- {
- auto& dstSlot = modelData.Materials[i];
-
- if (model->MaterialSlots.Count() > i)
- {
- auto& srcSlot = model->MaterialSlots[i];
-
- dstSlot.Name = srcSlot.Name;
- dstSlot.ShadowsMode = srcSlot.ShadowsMode;
- dstSlot.AssetID = srcSlot.Material.GetID();
- }
- }
-}
-
void SetupMaterialSlots(ModelData& data, const Array& materials)
{
Array materialSlotsTable;
@@ -458,10 +417,62 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
data = &dataThis;
}
- // Check if restore materials on model reimport
- if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
+ // Check if restore local changes on asset reimport
+ constexpr bool RestoreAnimEventsOnReimport = true;
+ const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
+ const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
+ if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
{
- TryRestoreMaterials(context, *data);
+ AssetReference asset = Content::LoadAsync(context.TargetAssetPath);
+ if (asset && !asset->WaitForLoaded())
+ {
+ auto* model = ScriptingObject::Cast(asset);
+ auto* animation = ScriptingObject::Cast(asset);
+ if (restoreMaterials && model)
+ {
+ // Copy material settings
+ for (int32 i = 0; i < data->Materials.Count(); i++)
+ {
+ auto& dstSlot = data->Materials[i];
+ if (model->MaterialSlots.Count() > i)
+ {
+ auto& srcSlot = model->MaterialSlots[i];
+ dstSlot.Name = srcSlot.Name;
+ dstSlot.ShadowsMode = srcSlot.ShadowsMode;
+ dstSlot.AssetID = srcSlot.Material.GetID();
+ }
+ }
+ }
+ if (restoreAnimEvents && animation)
+ {
+ // Copy anim event tracks
+ for (const auto& e : animation->Events)
+ {
+ auto& clone = data->Animations[0].Events.AddOne();
+ clone.First = e.First;
+ const auto& eKeys = e.Second.GetKeyframes();
+ auto& cloneKeys = clone.Second.GetKeyframes();
+ clone.Second.Resize(eKeys.Count());
+ for (int32 i = 0; i < eKeys.Count(); i++)
+ {
+ const auto& eKey = eKeys[i];
+ auto& cloneKey = cloneKeys[i];
+ cloneKey.Time = eKey.Time;
+ cloneKey.Value.Duration = eKey.Value.Duration;
+ if (eKey.Value.Instance)
+ {
+ cloneKey.Value.TypeName = eKey.Value.Instance->GetType().Fullname;
+ rapidjson_flax::StringBuffer buffer;
+ CompactJsonWriter writer(buffer);
+ writer.StartObject();
+ eKey.Value.Instance->Serialize(writer, nullptr);
+ writer.EndObject();
+ cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize());
+ }
+ }
+ }
+ }
+ }
}
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space
diff --git a/Source/Engine/Core/Collections/Sorting.h b/Source/Engine/Core/Collections/Sorting.h
index c6ae99b13..171c855a3 100644
--- a/Source/Engine/Core/Collections/Sorting.h
+++ b/Source/Engine/Core/Collections/Sorting.h
@@ -44,6 +44,49 @@ public:
}
};
+private:
+ template
+ static void Merge(T* data, T* tmp, int32 start, int32 mid, int32 end)
+ {
+ int32 h = start;
+ int32 i = start;
+ int32 j = mid + 1;
+
+ while (h <= mid && j <= end)
+ {
+ if (data[h] < data[j])
+ tmp[i] = data[h++];
+ else
+ tmp[i] = data[j++];
+ i++;
+ }
+
+ if (h > mid)
+ {
+ for (int32 k = j; k <= end; k++)
+ tmp[i++] = data[k];
+ }
+ else
+ {
+ for (int32 k = h; k <= mid; k++)
+ tmp[i++] = data[k];
+ }
+
+ for (int32 k = start; k <= end; k++)
+ data[k] = tmp[k];
+ }
+
+ template
+ static void MergeSort(T* data, T* tmp, int32 start, int32 end)
+ {
+ if (start >= end)
+ return;
+ const int32 mid = (start + end) / 2;
+ MergeSort(data, tmp, start, mid);
+ MergeSort(data, tmp, mid + 1, end);
+ Merge(data, tmp, start, mid, end);
+ }
+
public:
///
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
@@ -263,6 +306,33 @@ public:
}
}
+ ///
+ /// Sorts the linear data array using Merge Sort algorithm (recursive version, uses temporary memory).
+ ///
+ /// The data pointer.
+ /// The elements count.
+ /// The additional temporary memory buffer for sorting data. If null then will be automatically allocated within this function call.
+ template
+ static void MergeSort(T* data, int32 count, T* tmp = nullptr)
+ {
+ if (count < 2)
+ return;
+ const bool alloc = tmp == nullptr;
+ if (alloc)
+ tmp = (T*)Platform::Allocate(sizeof(T) * count, 16);
+ MergeSort(data, tmp, 0, count - 1);
+ if (alloc)
+ Platform::Free(tmp);
+ }
+
+ template
+ FORCE_INLINE static void MergeSort(Array& data, Array* tmp = nullptr)
+ {
+ if (tmp)
+ tmp->Resize(data.Count());
+ MergeSort(data.Get(), data.Count(), tmp ? tmp->Get() : nullptr);
+ }
+
///
/// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection).
///
diff --git a/Source/Engine/Core/Compiler.h b/Source/Engine/Core/Compiler.h
index 66a411a1d..47edbc7d9 100644
--- a/Source/Engine/Core/Compiler.h
+++ b/Source/Engine/Core/Compiler.h
@@ -14,6 +14,8 @@
#define FORCE_INLINE inline
#define FORCE_NOINLINE __attribute__((noinline))
#define NO_RETURN __attribute__((noreturn))
+#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
+#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
#define PACK_BEGIN()
#define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align)
@@ -44,6 +46,8 @@
#define FORCE_INLINE inline
#define FORCE_NOINLINE __attribute__((noinline))
#define NO_RETURN __attribute__((noreturn))
+#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
+#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
#define PACK_BEGIN()
#define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align)
@@ -69,6 +73,8 @@
#define FORCE_INLINE __forceinline
#define FORCE_NOINLINE __declspec(noinline)
#define NO_RETURN __declspec(noreturn)
+#define NO_SANITIZE_ADDRESS
+#define NO_SANITIZE_THREAD
#define PACK_BEGIN() __pragma(pack(push, 1))
#define PACK_END() ; __pragma(pack(pop))
#define ALIGN_BEGIN(_align) __declspec(align(_align))
diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h
index afed898bf..1a637cfe7 100644
--- a/Source/Engine/Core/Utilities.h
+++ b/Source/Engine/Core/Utilities.h
@@ -94,4 +94,15 @@ namespace Utilities
return (x * 0x01010101) >> 24;
#endif
}
+
+ // Copy memory region but ignoring address sanatizer checks for memory regions.
+ NO_SANITIZE_ADDRESS static void UnsafeMemoryCopy(void* dst, const void* src, uint64 size)
+ {
+#if BUILD_RELEASE
+ memcpy(dst, src, static_cast(size));
+#else
+ for (uint64 i = 0; i < size; i++)
+ ((byte*)dst)[i] = ((byte*)src)[i];
+#endif
+ }
}
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index ea8cdb196..942069130 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -129,7 +129,7 @@ PACK_STRUCT(struct Data {
Matrix ViewProjection;
Float2 Padding;
float ClipPosZBias;
- bool EnableDepthTest;
+ uint32 EnableDepthTest;
});
struct PsData
diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp
index fd7547f48..c1d1a8540 100644
--- a/Source/Engine/Engine/CommandLine.cpp
+++ b/Source/Engine/Engine/CommandLine.cpp
@@ -2,6 +2,7 @@
#include "CommandLine.h"
#include "Engine/Core/Collections/Array.h"
+#include "Engine/Core/Utilities.h"
#include
CommandLine::OptionsData CommandLine::Options;
@@ -81,7 +82,7 @@ bool CommandLine::Parse(const Char* cmdLine)
if (pos) \
{ \
len = ARRAY_COUNT(text) - 1; \
- Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
+ Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
*(end - len) = 0; \
end -= len; \
Options.field = true; \
@@ -98,7 +99,7 @@ bool CommandLine::Parse(const Char* cmdLine)
} \
Options.field = String(argStart, static_cast(argEnd - argStart)); \
len = static_cast((argEnd - pos) + 1); \
- Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
+ Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
*(end - len) = 0; \
end -= len; \
}
@@ -114,7 +115,7 @@ bool CommandLine::Parse(const Char* cmdLine)
{ \
Options.field = String(argStart, static_cast(argEnd - argStart)); \
len = static_cast((argEnd - pos) + 1); \
- Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
+ Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
*(end - len) = 0; \
end -= len; \
} \
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index ffd6386ad..27b35a0b7 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -324,10 +324,12 @@ void Engine::OnUpdate()
// Call event
Update();
- UpdateGraph->Execute();
// Update services
EngineService::OnUpdate();
+
+ // Run async
+ UpdateGraph->Execute();
}
void Engine::OnLateUpdate()
diff --git a/Source/Engine/Engine/GameplayGlobals.cpp b/Source/Engine/Engine/GameplayGlobals.cpp
index 83874d5a5..eba975e2b 100644
--- a/Source/Engine/Engine/GameplayGlobals.cpp
+++ b/Source/Engine/Engine/GameplayGlobals.cpp
@@ -221,9 +221,7 @@ Asset::LoadResult GameplayGlobals::load()
// Get data
const auto chunk = GetChunk(0);
if (!chunk || !chunk->IsLoaded())
- {
return LoadResult::MissingDataChunk;
- }
MemoryReadStream stream(chunk->Get(), chunk->Size());
// Load all variables
@@ -234,15 +232,16 @@ Asset::LoadResult GameplayGlobals::load()
for (int32 i = 0; i < count; i++)
{
stream.ReadString(&name, 71);
- if (name.IsEmpty())
- {
- LOG(Warning, "Empty variable name");
- return LoadResult::InvalidData;
- }
auto& e = Variables[name];
stream.ReadVariant(&e.DefaultValue);
e.Value = e.DefaultValue;
}
+ if (stream.HasError())
+ {
+ // Failed to load data
+ Variables.Clear();
+ return LoadResult::InvalidData;
+ }
return LoadResult::Ok;
}
diff --git a/Source/Engine/Graphics/Async/GPUTask.h b/Source/Engine/Graphics/Async/GPUTask.h
index b6a05bc0a..2dde19109 100644
--- a/Source/Engine/Graphics/Async/GPUTask.h
+++ b/Source/Engine/Graphics/Async/GPUTask.h
@@ -115,7 +115,7 @@ public:
// Rollback state and cancel
_context = nullptr;
- _state = TaskState::Queued;
+ SetState(TaskState::Queued);
Cancel();
}
@@ -148,8 +148,7 @@ protected:
ASSERT(_context != nullptr);
_context->OnCancelSync(this);
_context = nullptr;
-
- _state = TaskState::Canceled;
+ SetState(TaskState::Canceled);
}
else
{
diff --git a/Source/Engine/Graphics/Async/GPUTasksManager.cpp b/Source/Engine/Graphics/Async/GPUTasksManager.cpp
index f5417558c..42df38c0c 100644
--- a/Source/Engine/Graphics/Async/GPUTasksManager.cpp
+++ b/Source/Engine/Graphics/Async/GPUTasksManager.cpp
@@ -9,9 +9,8 @@
void GPUTask::Execute(GPUTasksContext* context)
{
- // Begin
ASSERT(IsQueued() && _context == nullptr);
- _state = TaskState::Running;
+ SetState(TaskState::Running);
// Perform an operation
const auto result = run(context);
@@ -19,7 +18,7 @@ void GPUTask::Execute(GPUTasksContext* context)
// Process result
if (IsCancelRequested())
{
- _state = TaskState::Canceled;
+ SetState(TaskState::Canceled);
}
else if (result != Result::Ok)
{
diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp
index abb97d794..dc9527b1d 100644
--- a/Source/Engine/Graphics/Models/ModelData.cpp
+++ b/Source/Engine/Graphics/Models/ModelData.cpp
@@ -941,7 +941,19 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
}
// Animation events
- stream->WriteInt32(0);
+ stream->WriteInt32(anim.Events.Count());
+ for (auto& e : anim.Events)
+ {
+ stream->WriteString(e.First, 172);
+ stream->WriteInt32(e.Second.GetKeyframes().Count());
+ for (const auto& k : e.Second.GetKeyframes())
+ {
+ stream->WriteFloat(k.Time);
+ stream->WriteFloat(k.Value.Duration);
+ stream->WriteStringAnsi(k.Value.TypeName, 17);
+ stream->WriteJson(k.Value.JsonData);
+ }
+ }
// Nested animations
stream->WriteInt32(0);
diff --git a/Source/Engine/Graphics/PostProcessSettings.cs b/Source/Engine/Graphics/PostProcessSettings.cs
index fb6c0af48..d6fead9b2 100644
--- a/Source/Engine/Graphics/PostProcessSettings.cs
+++ b/Source/Engine/Graphics/PostProcessSettings.cs
@@ -13,4 +13,12 @@ namespace FlaxEngine
Bit = bit;
}
}
+
+ public partial struct AntiAliasingSettings
+ {
+ ///
+ /// Whether or not to show the TAA settings.
+ ///
+ public bool ShowTAASettings => (Mode == AntialiasingMode.TemporalAntialiasing);
+ }
}
diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h
index d1b8c8037..eaf32c069 100644
--- a/Source/Engine/Graphics/PostProcessSettings.h
+++ b/Source/Engine/Graphics/PostProcessSettings.h
@@ -1888,25 +1888,25 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
///
/// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output.
///
- API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\")")
+ API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_JitterSpread = 1.0f;
///
/// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts.
///
- API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\")")
+ API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_Sharpness = 0.1f;
///
/// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
///
- API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\")")
+ API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_StationaryBlending = 0.95f;
///
/// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
///
- API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")")
+ API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_MotionBlending = 0.85f;
public:
diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
index dcbbf18cc..b33c80f15 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
@@ -10,6 +10,7 @@
#include "GPUTimerQueryVulkan.h"
#endif
#include "DescriptorSetVulkan.h"
+#include "Engine/Engine/Engine.h"
#include "Engine/Profiler/ProfilerCPU.h"
void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore)
@@ -32,7 +33,7 @@ void CmdBufferVulkan::Begin()
// Acquire a descriptor pool set on
if (_descriptorPoolSetContainer == nullptr)
{
- _descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer();
+ _descriptorPoolSetContainer = _device->DescriptorPoolsManager->AcquirePoolSetContainer();
}
_state = State::IsInsideBegin;
@@ -138,7 +139,7 @@ void CmdBufferVulkan::RefreshFenceStatus()
if (_descriptorPoolSetContainer)
{
- _device->DescriptorPoolsManager->ReleasePoolSet(*_descriptorPoolSetContainer);
+ _descriptorPoolSetContainer->LastFrameUsed = Engine::FrameCount;
_descriptorPoolSetContainer = nullptr;
}
}
@@ -279,6 +280,7 @@ void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float
void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
{
PROFILE_CPU();
+ ASSERT_LOW_LAYER(_activeCmdBuffer == nullptr)
for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++)
{
auto cmdBuffer = _pool._cmdBuffers.Get()[i];
@@ -286,8 +288,7 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin)
{
_activeCmdBuffer = cmdBuffer;
- _activeCmdBuffer->Begin();
- return;
+ break;
}
else
{
@@ -295,8 +296,12 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
}
}
- // Always begin fresh command buffer for rendering
- _activeCmdBuffer = _pool.Create();
+ if (_activeCmdBuffer == nullptr)
+ {
+ // Always begin fresh command buffer for rendering
+ _activeCmdBuffer = _pool.Create();
+ }
+
_activeCmdBuffer->Begin();
#if VULKAN_USE_QUERIES
diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp
index 0648d917b..09404362d 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp
@@ -247,8 +247,7 @@ void TypedDescriptorPoolSetVulkan::Reset()
DescriptorPoolSetContainerVulkan::DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device)
: _device(device)
- , _lastFrameUsed(Engine::FrameCount)
- , _used(true)
+ , LastFrameUsed(Engine::FrameCount)
{
}
@@ -278,12 +277,6 @@ void DescriptorPoolSetContainerVulkan::Reset()
}
}
-void DescriptorPoolSetContainerVulkan::SetUsed(bool used)
-{
- _used = used;
- _lastFrameUsed = used ? Engine::FrameCount : _lastFrameUsed;
-}
-
DescriptorPoolsManagerVulkan::DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device)
: _device(device)
{
@@ -294,26 +287,21 @@ DescriptorPoolsManagerVulkan::~DescriptorPoolsManagerVulkan()
_poolSets.ClearDelete();
}
-DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
+DescriptorPoolSetContainerVulkan* DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
{
ScopeLock lock(_locker);
for (auto* poolSet : _poolSets)
{
- if (poolSet->IsUnused())
+ if (poolSet->Refs == 0)
{
- poolSet->SetUsed(true);
+ poolSet->LastFrameUsed = Engine::FrameCount;
poolSet->Reset();
- return *poolSet;
+ return poolSet;
}
}
const auto poolSet = New(_device);
_poolSets.Add(poolSet);
- return *poolSet;
-}
-
-void DescriptorPoolsManagerVulkan::ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet)
-{
- poolSet.SetUsed(false);
+ return poolSet;
}
void DescriptorPoolsManagerVulkan::GC()
@@ -322,7 +310,7 @@ void DescriptorPoolsManagerVulkan::GC()
for (int32 i = _poolSets.Count() - 1; i >= 0; i--)
{
const auto poolSet = _poolSets[i];
- if (poolSet->IsUnused() && Engine::FrameCount - poolSet->GetLastFrameUsed() > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
+ if (poolSet->Refs == 0 && Engine::FrameCount - poolSet->LastFrameUsed > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
{
_poolSets.RemoveAt(i);
Delete(poolSet);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h
index 1b3de5eaa..e924297c7 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h
@@ -212,8 +212,6 @@ class DescriptorPoolSetContainerVulkan
private:
GPUDeviceVulkan* _device;
Dictionary _typedDescriptorPools;
- uint64 _lastFrameUsed;
- bool _used;
public:
DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device);
@@ -222,17 +220,9 @@ public:
public:
TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout);
void Reset();
- void SetUsed(bool used);
- bool IsUnused() const
- {
- return !_used;
- }
-
- uint64 GetLastFrameUsed() const
- {
- return _lastFrameUsed;
- }
+ mutable uint64 Refs = 0;
+ mutable uint64 LastFrameUsed;
};
class DescriptorPoolsManagerVulkan
@@ -246,8 +236,7 @@ public:
DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device);
~DescriptorPoolsManagerVulkan();
- DescriptorPoolSetContainerVulkan& AcquirePoolSetContainer();
- void ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet);
+ DescriptorPoolSetContainerVulkan* AcquirePoolSetContainer();
void GC();
};
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp
index 7686d9c5b..3f3b3074c 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp
@@ -112,7 +112,11 @@ ComputePipelineStateVulkan::ComputePipelineStateVulkan(GPUDeviceVulkan* device,
ComputePipelineStateVulkan::~ComputePipelineStateVulkan()
{
DSWriteContainer.Release();
- CurrentTypedDescriptorPoolSet = nullptr;
+ if (CurrentTypedDescriptorPoolSet)
+ {
+ CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
+ CurrentTypedDescriptorPoolSet = nullptr;
+ }
DescriptorSetsLayout = nullptr;
DescriptorSetHandles.Resize(0);
DynamicOffsets.Resize(0);
@@ -206,7 +210,11 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass)
void GPUPipelineStateVulkan::OnReleaseGPU()
{
DSWriteContainer.Release();
- CurrentTypedDescriptorPoolSet = nullptr;
+ if (CurrentTypedDescriptorPoolSet)
+ {
+ CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
+ CurrentTypedDescriptorPoolSet = nullptr;
+ }
DescriptorSetsLayout = nullptr;
DescriptorSetHandles.Resize(0);
DynamicOffsets.Resize(0);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h
index b8b326e83..73e68a897 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h
@@ -41,17 +41,17 @@ public:
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
{
- ASSERT(cmdBufferPoolSet);
+ if (CurrentTypedDescriptorPoolSet)
+ CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
+ CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
return true;
}
-
return false;
}
inline bool AllocateDescriptorSets()
{
- ASSERT(CurrentTypedDescriptorPoolSet);
return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get());
}
@@ -165,7 +165,10 @@ public:
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
{
+ if (CurrentTypedDescriptorPoolSet)
+ CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
+ CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
return true;
}
return false;
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 6ab60a317..6f8137bc6 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -7,6 +7,8 @@
#include "Engine/Animations/Animations.h"
#include "Engine/Engine/Engine.h"
#if USE_EDITOR
+#include "Engine/Core/Math/OrientedBoundingBox.h"
+#include "Engine/Core/Math/Matrix3x3.h"
#include "Editor/Editor.h"
#endif
#include "Engine/Graphics/GPUContext.h"
@@ -1018,6 +1020,45 @@ void AnimatedModel::OnDebugDrawSelected()
ModelInstanceActor::OnDebugDrawSelected();
}
+void AnimatedModel::OnDebugDraw()
+{
+ if (ShowDebugDrawSkeleton && SkinnedModel && AnimationGraph)
+ {
+ if (GraphInstance.NodesPose.IsEmpty())
+ PreInitSkinningData();
+ Matrix world;
+ GetLocalToWorldMatrix(world);
+
+ // Draw bounding box at the node locations
+ const float boxSize = Math::Min(1.0f, (float)_sphere.Radius / 100.0f);
+ OrientedBoundingBox localBox(Vector3(-boxSize), Vector3(boxSize));
+ for (int32 nodeIndex = 0; nodeIndex < GraphInstance.NodesPose.Count(); nodeIndex++)
+ {
+ Matrix transform = GraphInstance.NodesPose[nodeIndex] * world;
+ Float3 scale, translation;
+ Matrix3x3 rotation;
+ transform.Decompose(scale, rotation, translation);
+ transform = Matrix::Invert(Matrix::Scaling(scale)) * transform;
+ OrientedBoundingBox box = localBox * transform;
+ DEBUG_DRAW_WIRE_BOX(box, Color::Green, 0, false);
+ }
+
+ // Nodes connections
+ for (int32 nodeIndex = 0; nodeIndex < SkinnedModel->Skeleton.Nodes.Count(); nodeIndex++)
+ {
+ int32 parentIndex = SkinnedModel->Skeleton.Nodes[nodeIndex].ParentIndex;
+ if (parentIndex != -1)
+ {
+ Float3 parentPos = (GraphInstance.NodesPose[parentIndex] * world).GetTranslation();
+ Float3 bonePos = (GraphInstance.NodesPose[nodeIndex] * world).GetTranslation();
+ DEBUG_DRAW_LINE(parentPos, bonePos, Color::Green, 0, false);
+ }
+ }
+ }
+
+ ModelInstanceActor::OnDebugDraw();
+}
+
BoundingBox AnimatedModel::GetEditorBox() const
{
if (SkinnedModel)
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index 009ee948d..bb475722a 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -168,6 +168,13 @@ public:
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
ScriptingObjectReference RootMotionTarget;
+#if USE_EDITOR
+ ///
+ /// If checked, the skeleton pose will be shawn during debug shapes drawing.
+ ///
+ API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
+#endif
+
public:
///
/// The graph instance data container. For dynamic usage only at runtime, not serialized.
@@ -416,6 +423,7 @@ public:
void Draw(RenderContextBatch& renderContextBatch) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
+ void OnDebugDraw() override;
BoundingBox GetEditorBox() const override;
#endif
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp
index a6076cbbd..5b5220898 100644
--- a/Source/Engine/Navigation/NavMesh.cpp
+++ b/Source/Engine/Navigation/NavMesh.cpp
@@ -105,10 +105,8 @@ void NavMesh::OnDataAssetLoaded()
if (Data.Tiles.HasItems())
return;
- const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy();
-
// Remove added tiles
- if (isEnabled)
+ if (_navMeshActive)
{
RemoveTiles();
}
@@ -120,7 +118,7 @@ void NavMesh::OnDataAssetLoaded()
IsDataDirty = false;
// Add loaded tiles
- if (isEnabled)
+ if (_navMeshActive)
{
AddTiles();
}
@@ -156,15 +154,36 @@ void NavMesh::OnEnable()
// Base
Actor::OnEnable();
- GetScene()->Navigation.Meshes.Add(this);
- AddTiles();
+ if (!_navMeshActive)
+ {
+ GetScene()->Navigation.Meshes.Add(this);
+ AddTiles();
+ _navMeshActive = true;
+ }
}
void NavMesh::OnDisable()
{
- RemoveTiles();
- GetScene()->Navigation.Meshes.Remove(this);
+ if (_navMeshActive)
+ {
+ RemoveTiles();
+ GetScene()->Navigation.Meshes.Remove(this);
+ _navMeshActive = false;
+ }
// Base
Actor::OnDisable();
}
+
+void NavMesh::Initialize()
+{
+ // Base
+ Actor::Initialize();
+
+ if (!_navMeshActive && IsActiveInHierarchy())
+ {
+ GetScene()->Navigation.Meshes.Add(this);
+ AddTiles();
+ _navMeshActive = true;
+ }
+}
diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h
index 76a4eb963..8e1659ff4 100644
--- a/Source/Engine/Navigation/NavMesh.h
+++ b/Source/Engine/Navigation/NavMesh.h
@@ -68,6 +68,9 @@ private:
void RemoveTiles();
void OnDataAssetLoaded();
+private:
+ bool _navMeshActive = false;
+
public:
// [Actor]
void Serialize(SerializeStream& stream, const void* otherObj) override;
@@ -77,4 +80,5 @@ protected:
// [Actor]
void OnEnable() override;
void OnDisable() override;
+ void Initialize() override;
};
diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h
index 03b56798f..4d8955627 100644
--- a/Source/Engine/Platform/Android/AndroidPlatform.h
+++ b/Source/Engine/Platform/Android/AndroidPlatform.h
@@ -55,13 +55,13 @@ public:
{
return __sync_fetch_and_add(dst, value);
}
- FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
+ FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
{
int32 result;
__atomic_load(dst, &result, __ATOMIC_RELAXED);
return result;
}
- FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
+ FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{
int64 result;
__atomic_load(dst, &result, __ATOMIC_RELAXED);
diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h
index 69576b5a9..9f0fddebf 100644
--- a/Source/Engine/Platform/Apple/ApplePlatform.h
+++ b/Source/Engine/Platform/Apple/ApplePlatform.h
@@ -45,21 +45,21 @@ public:
{
return __sync_fetch_and_add(dst, value);
}
- FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
+ FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
{
return __atomic_load_n(dst, __ATOMIC_RELAXED);
}
- FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
+ FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{
return __atomic_load_n(dst, __ATOMIC_RELAXED);
}
FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value)
{
- __atomic_store(dst, &value, __ATOMIC_SEQ_CST);
+ __atomic_store_n((volatile int32*)dst, value, __ATOMIC_RELAXED);
}
FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value)
{
- __atomic_store(dst, &value, __ATOMIC_SEQ_CST);
+ __atomic_store_n((volatile int64*)dst, value, __ATOMIC_RELAXED);
}
FORCE_INLINE static void Prefetch(void const* ptr)
{
diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h
index 7c6640843..35cfd8d17 100644
--- a/Source/Engine/Platform/Base/PlatformBase.h
+++ b/Source/Engine/Platform/Base/PlatformBase.h
@@ -270,14 +270,14 @@ public:
///
/// A pointer to the destination value.
/// The function returns the value of the destination parameter.
- static int32 AtomicRead(int32 volatile* dst) = delete;
+ static int32 AtomicRead(int32 const volatile* dst) = delete;
///
/// Performs an atomic 64-bit variable read operation on the specified values.
///
/// A pointer to the destination value.
/// The function returns the value of the destination parameter.
- static int64 AtomicRead(int64 volatile* dst) = delete;
+ static int64 AtomicRead(int64 const volatile* dst) = delete;
///
/// Sets a 32-bit variable to the specified value as an atomic operation.
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h
index 73cf327a8..10c81f436 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.h
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.h
@@ -69,13 +69,13 @@ public:
{
return __sync_fetch_and_add(dst, value);
}
- FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
+ FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
{
int32 result;
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
return result;
}
- FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
+ FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{
int64 result;
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
diff --git a/Source/Engine/Platform/Unix/UnixCriticalSection.h b/Source/Engine/Platform/Unix/UnixCriticalSection.h
index 3aa5840b8..1e55b8343 100644
--- a/Source/Engine/Platform/Unix/UnixCriticalSection.h
+++ b/Source/Engine/Platform/Unix/UnixCriticalSection.h
@@ -57,7 +57,7 @@ public:
///
/// Locks the critical section.
///
- void Lock() const
+ NO_SANITIZE_THREAD void Lock() const
{
pthread_mutex_lock(_mutexPtr);
#if BUILD_DEBUG
@@ -69,7 +69,7 @@ public:
/// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
///
/// True if calling thread took ownership of the critical section.
- bool TryLock() const
+ NO_SANITIZE_THREAD bool TryLock() const
{
return pthread_mutex_trylock(_mutexPtr) == 0;
}
@@ -77,7 +77,7 @@ public:
///
/// Releases the lock on the critical section.
///
- void Unlock() const
+ NO_SANITIZE_THREAD void Unlock() const
{
#if BUILD_DEBUG
((UnixCriticalSection*)this)->_owningThreadId = 0;
diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h
index 4e66de99d..5d020a0fd 100644
--- a/Source/Engine/Platform/Win32/Win32Platform.h
+++ b/Source/Engine/Platform/Win32/Win32Platform.h
@@ -63,13 +63,13 @@ public:
return _interlockedexchangeadd64(dst, value);
#endif
}
- static int32 AtomicRead(int32 volatile* dst)
+ static int32 AtomicRead(int32 const volatile* dst)
{
return (int32)_InterlockedCompareExchange((long volatile*)dst, 0, 0);
}
- static int64 AtomicRead(int64 volatile* dst)
+ static int64 AtomicRead(int64 const volatile* dst)
{
- return _InterlockedCompareExchange64(dst, 0, 0);
+ return _InterlockedCompareExchange64((int64 volatile*)dst, 0, 0);
}
static void AtomicStore(int32 volatile* dst, int32 value)
{
diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp
index a3a99a3ee..09e7f9b62 100644
--- a/Source/Engine/Renderer/RenderList.cpp
+++ b/Source/Engine/Renderer/RenderList.cpp
@@ -27,6 +27,7 @@ namespace
// Cached data for the draw calls sorting
Array SortingKeys[2];
Array SortingIndices;
+ Array SortingBatches;
Array FreeRenderList;
struct MemPoolEntry
@@ -594,12 +595,13 @@ namespace
}
}
-void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer& drawCalls, DrawPass pass)
+void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer& drawCalls, DrawPass pass, bool stable)
{
PROFILE_CPU();
const auto* drawCallsData = drawCalls.Get();
const auto* listData = list.Indices.Get();
const int32 listSize = list.Indices.Count();
+ ZoneValue(listSize);
// Peek shared memory
#define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize)
@@ -656,6 +658,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
}
DrawBatch batch;
+ static_assert(sizeof(DrawBatch) == sizeof(uint64) * 2, "Fix the size of draw batch to optimize memory access.");
batch.SortKey = sortedKeys[i];
batch.StartIndex = i;
batch.BatchSize = batchSize;
@@ -665,8 +668,12 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
i += batchSize;
}
- // Sort draw calls batches by depth
- Sorting::QuickSort(list.Batches);
+ // When using depth buffer draw calls are already almost ideally sorted by Radix Sort but transparency needs more stability to prevent flickering
+ if (stable)
+ {
+ // Sort draw calls batches by depth
+ Sorting::MergeSort(list.Batches, &SortingBatches);
+ }
}
FORCE_INLINE bool CanUseInstancing(DrawPass pass)
diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h
index 0fe3bc960..8f15175be 100644
--- a/Source/Engine/Renderer/RenderList.h
+++ b/Source/Engine/Renderer/RenderList.h
@@ -217,17 +217,17 @@ struct DrawBatch
///
/// The first draw call index.
///
- int32 StartIndex;
+ uint16 StartIndex;
///
/// A number of draw calls to be submitted at once.
///
- int32 BatchSize;
+ uint16 BatchSize;
///
/// The total amount of instances (sum from all draw calls in this batch).
///
- int32 InstanceCount;
+ uint32 InstanceCount;
bool operator<(const DrawBatch& other) const
{
@@ -525,7 +525,8 @@ public:
/// The draw pass (optional).
API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType, DrawPass pass = DrawPass::All)
{
- SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass);
+ const bool stable = listType == DrawCallsListType::Forward;
+ SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass, stable);
}
///
@@ -536,7 +537,8 @@ public:
/// The collected draw calls indices list.
/// The collected draw calls list.
/// The draw pass (optional).
- void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer& drawCalls, DrawPass pass = DrawPass::All);
+ /// If set to true draw batches will be additionally sorted to prevent any flickering, otherwise Depth Buffer will smooth out any non-stability in sorting.
+ void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer& drawCalls, DrawPass pass = DrawPass::All, bool stable = false);
///
/// Executes the collected draw calls.
diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp
index bd3df861e..ac0d6da2d 100644
--- a/Source/Engine/Scripting/BinaryModule.cpp
+++ b/Source/Engine/Scripting/BinaryModule.cpp
@@ -3,6 +3,7 @@
#include "BinaryModule.h"
#include "ScriptingObject.h"
#include "Engine/Core/Log.h"
+#include "Engine/Core/Utilities.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "ManagedCLR/MAssembly.h"
@@ -436,6 +437,7 @@ void ScriptingType::SetupScriptVTable(ScriptingTypeHandle baseTypeHandle)
}
}
+NO_SANITIZE_ADDRESS
void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
{
// Analyze vtable size
@@ -475,7 +477,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
// Duplicate vtable
Script.VTable = (void**)((byte*)Platform::Allocate(totalSize, 16) + prefixSize);
- Platform::MemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
+ Utilities::UnsafeMemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
// Override vtable entries
if (interfacesCount)
@@ -508,7 +510,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
const int32 interfaceSize = interfaceCount * sizeof(void*);
// Duplicate interface vtable
- Platform::MemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
+ Utilities::UnsafeMemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
// Override interface vtable entries
const auto scriptOffset = interfaces->ScriptVTableOffset;
diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp
index 5bf3447b5..18060b62f 100644
--- a/Source/Engine/Serialization/Stream.cpp
+++ b/Source/Engine/Serialization/Stream.cpp
@@ -497,7 +497,9 @@ void ReadStream::Read(Variant& data)
break;
}
default:
- CRASH;
+ _hasError = true;
+ LOG(Error, "Invalid Variant type. Corrupted data.");
+ break;
}
}
@@ -946,6 +948,13 @@ void WriteStream::WriteJson(ISerializable* obj, const void* otherObj)
WriteInt32(0);
}
+void WriteStream::WriteJson(const StringAnsiView& json)
+{
+ WriteInt32(FLAXENGINE_VERSION_BUILD);
+ WriteInt32((int32)json.Length());
+ WriteBytes((byte*)json.Get(), (int32)json.Length());
+}
+
void WriteStream::WriteString(const StringView& data)
{
Write(data);
diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h
index e9edc44b0..028588ac4 100644
--- a/Source/Engine/Serialization/WriteStream.h
+++ b/Source/Engine/Serialization/WriteStream.h
@@ -233,6 +233,7 @@ public:
/// The object to serialize.
/// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
+ void WriteJson(const StringAnsiView& json);
public:
// Writes String to the stream
diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp
index d5824c51e..a0b3ddb6d 100644
--- a/Source/Engine/Threading/Task.cpp
+++ b/Source/Engine/Threading/Task.cpp
@@ -11,13 +11,13 @@
void Task::Start()
{
- if (_state != TaskState::Created)
+ if (GetState() != TaskState::Created)
return;
OnStart();
// Change state
- _state = TaskState::Queued;
+ SetState(TaskState::Queued);
// Add task to the execution queue
Enqueue();
@@ -110,7 +110,6 @@ Task* Task::ContinueWith(const Function& action, Object* target)
Task* Task::StartNew(Task* task)
{
ASSERT(task);
-
task->Start();
return task;
}
@@ -137,11 +136,10 @@ Task* Task::StartNew(Function::Signature& action, Object* target)
void Task::Execute()
{
- // Begin
if (IsCanceled())
return;
ASSERT(IsQueued());
- _state = TaskState::Running;
+ SetState(TaskState::Running);
// Perform an operation
bool failed = Run();
@@ -149,7 +147,7 @@ void Task::Execute()
// Process result
if (IsCancelRequested())
{
- _state = TaskState::Canceled;
+ SetState(TaskState::Canceled);
}
else if (failed)
{
@@ -167,10 +165,8 @@ void Task::OnStart()
void Task::OnFinish()
{
- ASSERT(IsRunning());
- ASSERT(!IsCancelRequested());
-
- _state = TaskState::Finished;
+ ASSERT(IsRunning() && !IsCancelRequested());
+ SetState(TaskState::Finished);
// Send event further
if (_continueWith)
@@ -181,7 +177,7 @@ void Task::OnFinish()
void Task::OnFail()
{
- _state = TaskState::Failed;
+ SetState(TaskState::Failed);
// Send event further
if (_continueWith)
@@ -209,8 +205,7 @@ void Task::OnCancel()
const auto state = GetState();
if (state != TaskState::Finished && state != TaskState::Failed)
{
- _state = TaskState::Canceled;
-
+ SetState(TaskState::Canceled);
OnEnd();
}
}
diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h
index ca4655aca..d2d77a2e9 100644
--- a/Source/Engine/Threading/Task.h
+++ b/Source/Engine/Threading/Task.h
@@ -49,7 +49,6 @@ class FLAXENGINE_API Task : public Object, public NonCopyable
//
protected:
-
///
/// The cancel flag used to indicate that there is request to cancel task operation.
///
@@ -65,14 +64,18 @@ protected:
///
Task* _continueWith = nullptr;
-public:
+ void SetState(TaskState state)
+ {
+ Platform::AtomicStore((int64 volatile*)&_state, (uint64)state);
+ }
+public:
///
/// Gets the task state.
///
FORCE_INLINE TaskState GetState() const
{
- return static_cast(Platform::AtomicRead((int64 volatile*)&_state));
+ return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state);
}
///
@@ -94,7 +97,6 @@ public:
}
public:
-
///
/// Checks if operation failed.
///
@@ -153,7 +155,6 @@ public:
}
public:
-
///
/// Starts this task execution (and will continue with all children).
///
@@ -199,7 +200,6 @@ public:
}
public:
-
///
/// Continues that task execution with a given task (will call Start on given task after finishing that one).
///
@@ -232,7 +232,6 @@ public:
Task* ContinueWith(const Function& action, Object* target = nullptr);
public:
-
///
/// Starts the new task.
///
@@ -312,7 +311,6 @@ public:
}
protected:
-
///
/// Executes this task.
/// It should be called by the task consumer (thread pool or other executor of this task type).
@@ -328,7 +326,6 @@ protected:
virtual bool Run() = 0;
protected:
-
virtual void Enqueue() = 0;
virtual void OnStart();
virtual void OnFinish();
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
index 91b1de48f..1b04092be 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
@@ -9,6 +9,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
+#include "Engine/Utilities/AnsiPathTempFile.h"
// Import Assimp library
// Source: https://github.com/assimp/assimp
@@ -157,7 +158,7 @@ struct AssimpImporterData
Array Bones;
Dictionary> MeshIndexToNodeIndex;
- AssimpImporterData(const char* path, const ModelTool::Options& options)
+ AssimpImporterData(const StringView& path, const ModelTool::Options& options)
: Path(path)
, Options(options)
{
@@ -735,7 +736,7 @@ void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerD
}
}
-bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg)
+bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg)
{
static bool AssimpInited = false;
if (!AssimpInited)
@@ -784,7 +785,10 @@ bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& opt
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
// Import file
- context.Scene = context.AssimpImporter.ReadFile(path, flags);
+ {
+ AnsiPathTempFile tempFile(path);
+ context.Scene = context.AssimpImporter.ReadFile(tempFile.Path.Get(), flags);
+ }
if (context.Scene == nullptr)
{
LOG_STR(Warning, String(context.AssimpImporter.GetErrorString()));
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp b/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp
index 29b49952b..1ea6a22ad 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.AutodeskFbxSdk.cpp
@@ -816,7 +816,7 @@ void BakeTransforms(FbxScene* scene)
scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate, false);
}
-bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
+bool ModelTool::ImportDataAutodeskFbxSdk(const String& path, ImportedModelData& data, Options& options, String& errorMsg)
{
ScopeLock lock(FbxSdkManager::Locker);
@@ -836,7 +836,7 @@ bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& da
auto ios = FbxSdkManager::Manager->GetIOSettings();
ios->SetBoolProp(IMP_FBX_MODEL, importMeshes);
ios->SetBoolProp(IMP_FBX_ANIMATION, importAnimations);
- if (!importer->Initialize(path, -1, ios))
+ if (!importer->Initialize(StringAnsi(path), -1, ios))
{
errorMsg = String::Format(TEXT("Failed to initialize FBX importer. {0}"), String(importer->GetStatus().GetErrorString()));
return false;
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
index c8f8f8102..5bf9ef7d8 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
@@ -103,7 +103,7 @@ struct OpenFbxImporterData
Array Materials;
Array ImportedMaterials;
- OpenFbxImporterData(const char* path, const ModelTool::Options& options, ofbx::IScene* scene)
+ OpenFbxImporterData(const String& path, const ModelTool::Options& options, ofbx::IScene* scene)
: Scene(scene)
, ScenePtr(scene)
, Path(path)
@@ -1114,11 +1114,11 @@ static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
return { 0.f, 0.f, 0.f };
}
-bool ModelTool::ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg)
+bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg)
{
// Import file
Array fileData;
- if (File::ReadAllBytes(String(path), fileData))
+ if (File::ReadAllBytes(path, fileData))
{
errorMsg = TEXT("Cannot load file.");
return true;
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index a71777658..fc2298993 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -471,64 +471,37 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
options.MergeMeshes = false; // Meshes merging doesn't make sense when we want to import each mesh individually
// TODO: maybe we could update meshes merger to collapse meshes within the same name if splitting is enabled?
- // Validate path
- // Note: Assimp/Autodesk supports only ANSI characters in imported file path
- StringAnsi importPath;
- String tmpPath;
- if (path.IsANSI() == false)
- {
- // Use temporary file
- LOG(Warning, "Model Tool doesn't support importing files from paths using non ASNI characters. Using temporary file.");
- FileSystem::GetTempFilePath(tmpPath);
- if (tmpPath.IsANSI() == false || FileSystem::CopyFile(tmpPath, path))
- {
- errorMsg = TEXT("Path with non ANSI characters is invalid.");
- return true;
- }
- importPath = tmpPath.ToStringAnsi();
- }
- else
- {
- importPath = path.ToStringAnsi();
- }
-
// Call importing backend
#if (USE_AUTODESK_FBX_SDK || USE_OPEN_FBX) && USE_ASSIMP
if (path.EndsWith(TEXT(".fbx"), StringSearchCase::IgnoreCase))
{
#if USE_AUTODESK_FBX_SDK
- if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
+ if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
return true;
#elif USE_OPEN_FBX
- if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
+ if (ImportDataOpenFBX(path, data, options, errorMsg))
return true;
#endif
}
else
{
- if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
+ if (ImportDataAssimp(path, data, options, errorMsg))
return true;
}
#elif USE_ASSIMP
- if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
+ if (ImportDataAssimp(path, data, options, errorMsg))
return true;
#elif USE_AUTODESK_FBX_SDK
- if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
+ if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
return true;
#elif USE_OPEN_FBX
- if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
+ if (ImportDataOpenFBX(path, data, options, errorMsg))
return true;
#else
LOG(Error, "Compiled without model importing backend.");
return true;
#endif
- // Remove temporary file
- if (tmpPath.HasChars() && FileSystem::FileExists(tmpPath))
- {
- FileSystem::DeleteFile(tmpPath);
- }
-
// Remove namespace prefixes from the nodes names
{
for (auto& node : data.Nodes)
@@ -1248,7 +1221,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
// Apply the import transformation
- if (!importTransform.IsIdentity())
+ if (!importTransform.IsIdentity() && data.Nodes.HasItems())
{
if (options.Type == ModelType::SkinnedModel)
{
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h
index 96d1ae6c0..ed1736214 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.h
+++ b/Source/Engine/Tools/ModelTool/ModelTool.h
@@ -386,13 +386,13 @@ public:
private:
static void CalculateBoneOffsetMatrix(const Array& nodes, Matrix& offsetMatrix, int32 nodeIndex);
#if USE_ASSIMP
- static bool ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg);
+ static bool ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg);
#endif
#if USE_AUTODESK_FBX_SDK
- static bool ImportDataAutodeskFbxSdk(const char* path, ModelData& data, Options& options, String& errorMsg);
+ static bool ImportDataAutodeskFbxSdk(const String& path, ModelData& data, Options& options, String& errorMsg);
#endif
#if USE_OPEN_FBX
- static bool ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg);
+ static bool ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg);
#endif
#endif
};
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
index b14f197d7..05edf2087 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
+++ b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
@@ -21,6 +21,7 @@ public class TextureTool : EngineModule
bool useDirectXTex = false;
bool useStb = false;
+ bool useExr = options.Target.IsEditor;
switch (options.Platform.Target)
{
@@ -58,6 +59,10 @@ public class TextureTool : EngineModule
options.PrivateDependencies.Add("bc7enc16");
}
}
+ if (useExr)
+ {
+ options.PrivateDependencies.Add("tinyexr");
+ }
if (options.Target.IsEditor && astc.IsSupported(options))
{
// ASTC for mobile (iOS and Android)
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
index 485d4ec6f..cec543d05 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
@@ -15,6 +15,7 @@
#if USE_EDITOR
#include "Engine/Graphics/GPUDevice.h"
#endif
+#include "Engine/Utilities/AnsiPathTempFile.h"
// Import DirectXTex library
// Source: https://github.com/Microsoft/DirectXTex
@@ -24,6 +25,19 @@ DECLARE_HANDLE(HMONITOR);
#endif
#include
+#if USE_EDITOR
+// Import tinyexr library
+// Source: https://github.com/syoyo/tinyexr
+#define TINYEXR_IMPLEMENTATION
+#define TINYEXR_USE_MINIZ 1
+#define TINYEXR_USE_STB_ZLIB 0
+#define TINYEXR_USE_THREAD 0
+#define TINYEXR_USE_OPENMP 0
+#undef min
+#undef max
+#include
+#endif
+
namespace
{
FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format)
@@ -36,7 +50,7 @@ namespace
return static_cast(format);
}
- HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, float threshold, DirectX::ScratchImage& cImages)
+ HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, float threshold, DirectX::ScratchImage& cImages)
{
#if USE_EDITOR
if ((format == DXGI_FORMAT_BC7_UNORM || format == DXGI_FORMAT_BC7_UNORM_SRGB || format == DXGI_FORMAT_BC6H_UF16 || format == DXGI_FORMAT_BC6H_SF16) &&
@@ -60,12 +74,12 @@ namespace
size_t _nimages;
const DirectX::TexMetadata& _metadata;
DXGI_FORMAT _format;
- DWORD _compress;
+ DirectX::TEX_COMPRESS_FLAGS _compress;
DirectX::ScratchImage& _cImages;
public:
HRESULT CompressResult = E_FAIL;
- GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, DirectX::ScratchImage& cImages)
+ GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, DirectX::ScratchImage& cImages)
: GPUTask(Type::Custom)
, _signal(&signal)
, _srcImages(srcImages)
@@ -276,6 +290,46 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
return image.InitializeFromImage(img);
}
+HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
+{
+#if USE_EDITOR
+ // Load exr file
+ AnsiPathTempFile tempFile(path);
+ float* pixels;
+ int width, height;
+ const char* err = nullptr;
+ int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
+ if (ret != TINYEXR_SUCCESS)
+ {
+ if (err)
+ {
+ LOG_STR(Warning, String(err));
+ FreeEXRErrorMessage(err);
+ }
+ return S_FALSE;
+ }
+
+ // Setup image
+ DirectX::Image img;
+ img.format = DXGI_FORMAT_R32G32B32A32_FLOAT;
+ img.width = width;
+ img.height = height;
+ img.rowPitch = width * sizeof(Float4);
+ img.slicePitch = img.rowPitch * height;
+
+ // Link data
+ img.pixels = (uint8_t*)pixels;
+
+ // Init
+ HRESULT result = image.InitializeFromImage(img);
+ free(pixels);
+ return result;
+#else
+ LOG(Warning, "EXR format is not supported.");
+ return S_FALSE;
+#endif
+}
+
bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
{
// Load image data
@@ -302,6 +356,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::RAW:
result = LoadFromRAWFile(path, image);
break;
+ case ImageType::EXR:
+ result = LoadFromEXRFile(path, image);
+ break;
default:
result = DXGI_ERROR_INVALID_CALL;
break;
@@ -518,6 +575,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::RAW:
result = LoadFromRAWFile(path, image1);
break;
+ case ImageType::EXR:
+ result = LoadFromEXRFile(path, image1);
+ break;
case ImageType::Internal:
{
if (options.InternalLoad.IsBinded())
@@ -688,7 +748,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (!keepAsIs && options.FlipY)
{
auto& tmpImg = GET_TMP_IMG();
- DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL;
+ DirectX::TEX_FR_FLAGS flags = DirectX::TEX_FR_FLIP_VERTICAL;
result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
if (FAILED(result))
{
@@ -698,7 +758,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg);
}
- // Check if it invert green channel
+ // Check if invert green channel
if (!keepAsIs && options.InvertGreenChannel)
{
auto& timage = GET_TMP_IMG();
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp
index ca580ca7e..ff0a66177 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp
@@ -767,6 +767,10 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type)
{
type = ImageType::RAW;
}
+ else if (extension == TEXT("exr"))
+ {
+ type = ImageType::EXR;
+ }
else
{
LOG(Warning, "Unknown file type.");
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h
index 8aec3eb32..85d8b2d20 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.h
+++ b/Source/Engine/Tools/TextureTool/TextureTool.h
@@ -252,6 +252,7 @@ private:
JPEG,
HDR,
RAW,
+ EXR,
Internal,
};
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp
index 427521f2f..1301327ef 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp
@@ -10,6 +10,7 @@
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
+#include "Engine/Utilities/AnsiPathTempFile.h"
#include "Engine/Platform/File.h"
#define STBI_ASSERT(x) ASSERT(x)
@@ -48,6 +49,18 @@
// Compression libs for Editor
#include
#include
+
+// Import tinyexr library
+// Source: https://github.com/syoyo/tinyexr
+#define TINYEXR_IMPLEMENTATION
+#define TINYEXR_USE_MINIZ 1
+#define TINYEXR_USE_STB_ZLIB 0
+#define TINYEXR_USE_THREAD 0
+#define TINYEXR_USE_OPENMP 0
+#undef min
+#undef max
+#include
+
#endif
static void stbWrite(void* context, void* data, int size)
@@ -173,7 +186,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
{
if (textureData.GetArraySize() != 1)
{
- LOG(Warning, "Exporting texture arrays and cubemaps is not supported by stb library.");
+ LOG(Warning, "Exporting texture arrays and cubemaps is not supported.");
}
TextureData const* texture = &textureData;
@@ -189,7 +202,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
const auto sampler = GetSampler(texture->Format);
if (sampler == nullptr)
{
- LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format);
+ LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format);
return true;
}
const auto srcData = texture->GetData(0, 0);
@@ -272,16 +285,19 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
break;
}
case ImageType::GIF:
- LOG(Warning, "GIF format is not supported by stb library.");
+ LOG(Warning, "GIF format is not supported.");
break;
case ImageType::TIFF:
- LOG(Warning, "GIF format is not supported by stb library.");
+ LOG(Warning, "GIF format is not supported.");
break;
case ImageType::DDS:
- LOG(Warning, "DDS format is not supported by stb library.");
+ LOG(Warning, "DDS format is not supported.");
break;
case ImageType::RAW:
- LOG(Warning, "RAW format is not supported by stb library.");
+ LOG(Warning, "RAW format is not supported.");
+ break;
+ case ImageType::EXR:
+ LOG(Warning, "EXR format is not supported.");
break;
default:
LOG(Warning, "Unknown format.");
@@ -383,11 +399,49 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
break;
}
+ case ImageType::EXR:
+ {
+#if USE_EDITOR
+ // Load exr file
+ AnsiPathTempFile tempFile(path);
+ float* pixels;
+ int width, height;
+ const char* err = nullptr;
+ int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
+ if (ret != TINYEXR_SUCCESS)
+ {
+ if (err)
+ {
+ LOG_STR(Warning, String(err));
+ FreeEXRErrorMessage(err);
+ }
+ return true;
+ }
+
+ // Setup texture data
+ textureData.Width = width;
+ textureData.Height = height;
+ textureData.Depth = 1;
+ textureData.Format = PixelFormat::R32G32B32A32_Float;
+ textureData.Items.Resize(1);
+ textureData.Items[0].Mips.Resize(1);
+ auto& mip = textureData.Items[0].Mips[0];
+ mip.RowPitch = width * sizeof(Float4);
+ mip.DepthPitch = mip.RowPitch * height;
+ mip.Lines = height;
+ mip.Data.Copy((const byte*)pixels, mip.DepthPitch);
+
+ free(pixels);
+#else
+ LOG(Warning, "EXR format is not supported.");
+#endif
+ break;
+ }
case ImageType::DDS:
- LOG(Warning, "DDS format is not supported by stb library.");
+ LOG(Warning, "DDS format is not supported.");
break;
case ImageType::TIFF:
- LOG(Warning, "TIFF format is not supported by stb library.");
+ LOG(Warning, "TIFF format is not supported.");
break;
default:
LOG(Warning, "Unknown format.");
diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
index 30e67e41a..985ee193b 100644
--- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
@@ -290,7 +290,7 @@ namespace FlaxEngine.GUI
{
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
- float height = font.Height / DpiScale;
+ float height = font.Height;
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha;
Color selectionColor = Color.White * alpha;
@@ -340,7 +340,7 @@ namespace FlaxEngine.GUI
if (textBlock.Style.UnderlineBrush != null)
{
var underLineHeight = 2.0f;
- var height = font.Height / DpiScale;
+ var height = font.Height;
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
}
diff --git a/Source/Engine/Utilities/AnsiPathTempFile.h b/Source/Engine/Utilities/AnsiPathTempFile.h
new file mode 100644
index 000000000..b8165fe1e
--- /dev/null
+++ b/Source/Engine/Utilities/AnsiPathTempFile.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Types/String.h"
+#include "Engine/Platform/FileSystem.h"
+
+// Small utility that uses temporary file to properly handle non-ANSI paths for 3rd party libs.
+struct AnsiPathTempFile
+{
+ StringAnsi Path;
+ String TempPath;
+ bool Temp;
+
+ AnsiPathTempFile(const String& path)
+ {
+ if (path.IsANSI() == false)
+ {
+ // Use temporary file
+ FileSystem::GetTempFilePath(TempPath);
+ if (TempPath.IsANSI() && !FileSystem::CopyFile(TempPath, path))
+ {
+ Path = TempPath.ToStringAnsi();
+ return;
+ }
+ TempPath.Clear();
+ }
+ Path = path.ToStringAnsi();
+ }
+
+ ~AnsiPathTempFile()
+ {
+ // Cleanup temporary file after use
+ if (TempPath.HasChars())
+ FileSystem::DeleteFile(TempPath);
+ }
+};
diff --git a/Source/Engine/Utilities/HtmlParser.cs b/Source/Engine/Utilities/HtmlParser.cs
index 0475f5a9e..a60484dd6 100644
--- a/Source/Engine/Utilities/HtmlParser.cs
+++ b/Source/Engine/Utilities/HtmlParser.cs
@@ -47,6 +47,12 @@ namespace FlaxEngine.Utilities
/// True if this tag contained a leading or trailing forward slash.
///
public bool IsSlash => IsLeadingSlash || IsEndingSlash;
+
+ ///
+ public override string ToString()
+ {
+ return Name;
+ }
};
///
@@ -231,6 +237,9 @@ namespace FlaxEngine.Utilities
tag.Attributes[s] = value;
}
}
+
+ if (EOF)
+ return false;
}
// Skip over closing '>'
@@ -264,8 +273,13 @@ namespace FlaxEngine.Utilities
private string ParseAttributeName()
{
int start = _pos;
- while (!EOF && char.IsLetterOrDigit(Peek()))
+ while (!EOF)
+ {
+ var c = Peek();
+ if (!char.IsLetterOrDigit(c) && c != '-')
+ break;
Move();
+ }
return _html.Substring(start, _pos - start);
}
diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.lib
index c3b0c4b5d..069e6e78c 100644
--- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.lib
+++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.lib
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4ffcf31cb5a5582a75ef1cd9466fd2f71310c123c5aebc709b59480b7230f34f
-size 2878094
+oid sha256:2ece9e97efd754246256676994eed54f54e920a25584d00fb9a3fa37c1f263e7
+size 3256698
diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.pdb b/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.pdb
index a7677fc2e..6e277d305 100644
--- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.pdb
+++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/DirectXTex.pdb
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:855794cc2c5db0f6060ee0da2d1bc9f1d27b4939f8b7647616e0ed537c2342e1
-size 2117632
+oid sha256:3ba34f8e37d2fab479cacb62b0b7969de89a19e777919e754a78868402907b34
+size 2142208
diff --git a/Source/ThirdParty/DirectXTex/DirectXTex.h b/Source/ThirdParty/DirectXTex/DirectXTex.h
index 7ffa7dbfa..eee44db8b 100644
--- a/Source/ThirdParty/DirectXTex/DirectXTex.h
+++ b/Source/ThirdParty/DirectXTex/DirectXTex.h
@@ -1,9 +1,9 @@
//-------------------------------------------------------------------------------------
// DirectXTex.h
-//
+//
// DirectX Texture Library
//
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
@@ -11,28 +11,43 @@
#pragma once
-#include
-
-#include
+#include
+#include
#include
+#include
#include
-#if !defined(__d3d11_h__) && !defined(__d3d11_x_h__) && !defined(__d3d12_h__) && !defined(__d3d12_x_h__)
-#if defined(_XBOX_ONE) && defined(_TITLE)
+#ifdef _WIN32
+#if !defined(__d3d11_h__) && !defined(__d3d11_x_h__) && !defined(__d3d12_h__) && !defined(__d3d12_x_h__) && !defined(__XBOX_D3D12_X__)
+#ifdef _GAMING_XBOX_SCARLETT
+#include
+#elif defined(_GAMING_XBOX)
+#include
+#elif defined(_XBOX_ONE) && defined(_TITLE)
#include
#else
#include
#endif
#endif
+#else // !WIN32
+#include
+#include
+#endif
#include
+#ifdef _WIN32
+#if defined(NTDDI_WIN10_FE) || defined(__MINGW32__)
+#include
+#else
#include
-
-#define DIRECTX_TEX_VERSION 162
+#endif
struct IWICImagingFactory;
struct IWICMetadataQueryReader;
+#endif
+
+#define DIRECTX_TEX_VERSION 203
namespace DirectX
@@ -40,64 +55,100 @@ namespace DirectX
//---------------------------------------------------------------------------------
// DXGI Format Utilities
- bool __cdecl IsValid(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsCompressed(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsPacked(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsVideo(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsPlanar(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsPalettized(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsDepthStencil(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsSRGB(_In_ DXGI_FORMAT fmt);
- bool __cdecl IsTypeless(_In_ DXGI_FORMAT fmt, _In_ bool partialTypeless = true);
+ constexpr bool __cdecl IsValid(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsCompressed(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsPacked(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsVideo(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsPlanar(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsPalettized(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsDepthStencil(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsSRGB(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsBGR(_In_ DXGI_FORMAT fmt) noexcept;
+ bool __cdecl IsTypeless(_In_ DXGI_FORMAT fmt, _In_ bool partialTypeless = true) noexcept;
- bool __cdecl HasAlpha(_In_ DXGI_FORMAT fmt);
+ bool __cdecl HasAlpha(_In_ DXGI_FORMAT fmt) noexcept;
- size_t __cdecl BitsPerPixel(_In_ DXGI_FORMAT fmt);
+ size_t __cdecl BitsPerPixel(_In_ DXGI_FORMAT fmt) noexcept;
- size_t __cdecl BitsPerColor(_In_ DXGI_FORMAT fmt);
+ size_t __cdecl BitsPerColor(_In_ DXGI_FORMAT fmt) noexcept;
- enum CP_FLAGS
+ enum FORMAT_TYPE
{
- CP_FLAGS_NONE = 0x0, // Normal operation
- CP_FLAGS_LEGACY_DWORD = 0x1, // Assume pitch is DWORD aligned instead of BYTE aligned
- CP_FLAGS_PARAGRAPH = 0x2, // Assume pitch is 16-byte aligned instead of BYTE aligned
- CP_FLAGS_YMM = 0x4, // Assume pitch is 32-byte aligned instead of BYTE aligned
- CP_FLAGS_ZMM = 0x8, // Assume pitch is 64-byte aligned instead of BYTE aligned
- CP_FLAGS_PAGE4K = 0x200, // Assume pitch is 4096-byte aligned instead of BYTE aligned
- CP_FLAGS_BAD_DXTN_TAILS = 0x1000, // BC formats with malformed mipchain blocks smaller than 4x4
- CP_FLAGS_24BPP = 0x10000, // Override with a legacy 24 bits-per-pixel format size
- CP_FLAGS_16BPP = 0x20000, // Override with a legacy 16 bits-per-pixel format size
- CP_FLAGS_8BPP = 0x40000, // Override with a legacy 8 bits-per-pixel format size
+ FORMAT_TYPE_TYPELESS,
+ FORMAT_TYPE_FLOAT,
+ FORMAT_TYPE_UNORM,
+ FORMAT_TYPE_SNORM,
+ FORMAT_TYPE_UINT,
+ FORMAT_TYPE_SINT,
+ };
+
+ FORMAT_TYPE __cdecl FormatDataType(_In_ DXGI_FORMAT fmt) noexcept;
+
+ enum CP_FLAGS : unsigned long
+ {
+ CP_FLAGS_NONE = 0x0,
+ // Normal operation
+
+ CP_FLAGS_LEGACY_DWORD = 0x1,
+ // Assume pitch is DWORD aligned instead of BYTE aligned
+
+ CP_FLAGS_PARAGRAPH = 0x2,
+ // Assume pitch is 16-byte aligned instead of BYTE aligned
+
+ CP_FLAGS_YMM = 0x4,
+ // Assume pitch is 32-byte aligned instead of BYTE aligned
+
+ CP_FLAGS_ZMM = 0x8,
+ // Assume pitch is 64-byte aligned instead of BYTE aligned
+
+ CP_FLAGS_PAGE4K = 0x200,
+ // Assume pitch is 4096-byte aligned instead of BYTE aligned
+
+ CP_FLAGS_BAD_DXTN_TAILS = 0x1000,
+ // BC formats with malformed mipchain blocks smaller than 4x4
+
+ CP_FLAGS_24BPP = 0x10000,
+ // Override with a legacy 24 bits-per-pixel format size
+
+ CP_FLAGS_16BPP = 0x20000,
+ // Override with a legacy 16 bits-per-pixel format size
+
+ CP_FLAGS_8BPP = 0x40000,
+ // Override with a legacy 8 bits-per-pixel format size
+
+ CP_FLAGS_LIMIT_4GB = 0x10000000,
+ // Don't allow pixel allocations in excess of 4GB (always true for 32-bit)
};
HRESULT __cdecl ComputePitch(
_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height,
- _Out_ size_t& rowPitch, _Out_ size_t& slicePitch, _In_ DWORD flags = CP_FLAGS_NONE);
+ _Out_ size_t& rowPitch, _Out_ size_t& slicePitch, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
- size_t __cdecl ComputeScanlines(_In_ DXGI_FORMAT fmt, _In_ size_t height);
+ size_t __cdecl ComputeScanlines(_In_ DXGI_FORMAT fmt, _In_ size_t height) noexcept;
- DXGI_FORMAT __cdecl MakeSRGB(_In_ DXGI_FORMAT fmt);
- DXGI_FORMAT __cdecl MakeTypeless(_In_ DXGI_FORMAT fmt);
- DXGI_FORMAT __cdecl MakeTypelessUNORM(_In_ DXGI_FORMAT fmt);
- DXGI_FORMAT __cdecl MakeTypelessFLOAT(_In_ DXGI_FORMAT fmt);
+ DXGI_FORMAT __cdecl MakeSRGB(_In_ DXGI_FORMAT fmt) noexcept;
+ DXGI_FORMAT __cdecl MakeLinear(_In_ DXGI_FORMAT fmt) noexcept;
+ DXGI_FORMAT __cdecl MakeTypeless(_In_ DXGI_FORMAT fmt) noexcept;
+ DXGI_FORMAT __cdecl MakeTypelessUNORM(_In_ DXGI_FORMAT fmt) noexcept;
+ DXGI_FORMAT __cdecl MakeTypelessFLOAT(_In_ DXGI_FORMAT fmt) noexcept;
//---------------------------------------------------------------------------------
// Texture metadata
enum TEX_DIMENSION
// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION
{
- TEX_DIMENSION_TEXTURE1D = 2,
- TEX_DIMENSION_TEXTURE2D = 3,
- TEX_DIMENSION_TEXTURE3D = 4,
+ TEX_DIMENSION_TEXTURE1D = 2,
+ TEX_DIMENSION_TEXTURE2D = 3,
+ TEX_DIMENSION_TEXTURE3D = 4,
};
- enum TEX_MISC_FLAG
+ enum TEX_MISC_FLAG : unsigned long
// Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG
{
TEX_MISC_TEXTURECUBE = 0x4L,
};
- enum TEX_MISC_FLAG2
+ enum TEX_MISC_FLAG2 : unsigned long
{
TEX_MISC2_ALPHA_MODE_MASK = 0x7L,
};
@@ -105,11 +156,11 @@ namespace DirectX
enum TEX_ALPHA_MODE
// Matches DDS_ALPHA_MODE, encoded in MISC_FLAGS2
{
- TEX_ALPHA_MODE_UNKNOWN = 0,
- TEX_ALPHA_MODE_STRAIGHT = 1,
+ TEX_ALPHA_MODE_UNKNOWN = 0,
+ TEX_ALPHA_MODE_STRAIGHT = 1,
TEX_ALPHA_MODE_PREMULTIPLIED = 2,
- TEX_ALPHA_MODE_OPAQUE = 3,
- TEX_ALPHA_MODE_CUSTOM = 4,
+ TEX_ALPHA_MODE_OPAQUE = 3,
+ TEX_ALPHA_MODE_CUSTOM = 4,
};
struct TexMetadata
@@ -124,128 +175,207 @@ namespace DirectX
DXGI_FORMAT format;
TEX_DIMENSION dimension;
- size_t __cdecl ComputeIndex(_In_ size_t mip, _In_ size_t item, _In_ size_t slice) const;
+ size_t __cdecl ComputeIndex(size_t mip, size_t item, size_t slice) const noexcept;
// Returns size_t(-1) to indicate an out-of-range error
- bool __cdecl IsCubemap() const { return (miscFlags & TEX_MISC_TEXTURECUBE) != 0; }
+ bool __cdecl IsCubemap() const noexcept { return (miscFlags & TEX_MISC_TEXTURECUBE) != 0; }
// Helper for miscFlags
- bool __cdecl IsPMAlpha() const { return ((miscFlags2 & TEX_MISC2_ALPHA_MODE_MASK) == TEX_ALPHA_MODE_PREMULTIPLIED) != 0; }
- void __cdecl SetAlphaMode(TEX_ALPHA_MODE mode) { miscFlags2 = (miscFlags2 & ~static_cast(TEX_MISC2_ALPHA_MODE_MASK)) | static_cast(mode); }
- TEX_ALPHA_MODE __cdecl GetAlphaMode() const { return static_cast(miscFlags2 & TEX_MISC2_ALPHA_MODE_MASK); }
+ bool __cdecl IsPMAlpha() const noexcept { return ((miscFlags2 & TEX_MISC2_ALPHA_MODE_MASK) == TEX_ALPHA_MODE_PREMULTIPLIED) != 0; }
+ void __cdecl SetAlphaMode(TEX_ALPHA_MODE mode) noexcept { miscFlags2 = (miscFlags2 & ~static_cast(TEX_MISC2_ALPHA_MODE_MASK)) | static_cast(mode); }
+ TEX_ALPHA_MODE __cdecl GetAlphaMode() const noexcept { return static_cast(miscFlags2 & TEX_MISC2_ALPHA_MODE_MASK); }
// Helpers for miscFlags2
- bool __cdecl IsVolumemap() const { return (dimension == TEX_DIMENSION_TEXTURE3D); }
+ bool __cdecl IsVolumemap() const noexcept { return (dimension == TEX_DIMENSION_TEXTURE3D); }
// Helper for dimension
+
+ uint32_t __cdecl CalculateSubresource(size_t mip, size_t item) const noexcept;
+ uint32_t __cdecl CalculateSubresource(size_t mip, size_t item, size_t plane) const noexcept;
+ // Returns size_t(-1) to indicate an out-of-range error
};
- enum DDS_FLAGS
+ struct DDSMetaData
{
- DDS_FLAGS_NONE = 0x0,
+ uint32_t size; // DDPIXELFORMAT.dwSize
+ uint32_t flags; // DDPIXELFORMAT.dwFlags
+ uint32_t fourCC; // DDPIXELFORMAT.dwFourCC
+ uint32_t RGBBitCount; // DDPIXELFORMAT.dwRGBBitCount/dwYUVBitCount/dwAlphaBitDepth/dwLuminanceBitCount/dwBumpBitCount
+ uint32_t RBitMask; // DDPIXELFORMAT.dwRBitMask/dwYBitMask/dwLuminanceBitMask/dwBumpDuBitMask
+ uint32_t GBitMask; // DDPIXELFORMAT.dwGBitMask/dwUBitMask/dwBumpDvBitMask
+ uint32_t BBitMask; // DDPIXELFORMAT.dwBBitMask/dwVBitMask/dwBumpLuminanceBitMask
+ uint32_t ABitMask; // DDPIXELFORMAT.dwRGBAlphaBitMask/dwYUVAlphaBitMask/dwLuminanceAlphaBitMask
- DDS_FLAGS_LEGACY_DWORD = 0x1,
- // Assume pitch is DWORD aligned instead of BYTE aligned (used by some legacy DDS files)
-
- DDS_FLAGS_NO_LEGACY_EXPANSION = 0x2,
- // Do not implicitly convert legacy formats that result in larger pixel sizes (24 bpp, 3:3:2, A8L8, A4L4, P8, A8P8)
-
- DDS_FLAGS_NO_R10B10G10A2_FIXUP = 0x4,
- // Do not use work-around for long-standing D3DX DDS file format issue which reversed the 10:10:10:2 color order masks
-
- DDS_FLAGS_FORCE_RGB = 0x8,
- // Convert DXGI 1.1 BGR formats to DXGI_FORMAT_R8G8B8A8_UNORM to avoid use of optional WDDM 1.1 formats
-
- DDS_FLAGS_NO_16BPP = 0x10,
- // Conversions avoid use of 565, 5551, and 4444 formats and instead expand to 8888 to avoid use of optional WDDM 1.2 formats
-
- DDS_FLAGS_EXPAND_LUMINANCE = 0x20,
- // When loading legacy luminance formats expand replicating the color channels rather than leaving them packed (L8, L16, A8L8)
-
- DDS_FLAGS_BAD_DXTN_TAILS = 0x40,
- // Some older DXTn DDS files incorrectly handle mipchain tails for blocks smaller than 4x4
-
- DDS_FLAGS_FORCE_DX10_EXT = 0x10000,
- // Always use the 'DX10' header extension for DDS writer (i.e. don't try to write DX9 compatible DDS files)
-
- DDS_FLAGS_FORCE_DX10_EXT_MISC2 = 0x20000,
- // DDS_FLAGS_FORCE_DX10_EXT including miscFlags2 information (result may not be compatible with D3DX10 or D3DX11)
+ bool __cdecl IsDX10() const noexcept { return (fourCC == 0x30315844); }
};
- enum WIC_FLAGS
+ enum DDS_FLAGS : unsigned long
{
- WIC_FLAGS_NONE = 0x0,
+ DDS_FLAGS_NONE = 0x0,
- WIC_FLAGS_FORCE_RGB = 0x1,
- // Loads DXGI 1.1 BGR formats as DXGI_FORMAT_R8G8B8A8_UNORM to avoid use of optional WDDM 1.1 formats
+ DDS_FLAGS_LEGACY_DWORD = 0x1,
+ // Assume pitch is DWORD aligned instead of BYTE aligned (used by some legacy DDS files)
- WIC_FLAGS_NO_X2_BIAS = 0x2,
- // Loads DXGI 1.1 X2 10:10:10:2 format as DXGI_FORMAT_R10G10B10A2_UNORM
+ DDS_FLAGS_NO_LEGACY_EXPANSION = 0x2,
+ // Do not implicitly convert legacy formats that result in larger pixel sizes (24 bpp, 3:3:2, A8L8, A4L4, P8, A8P8)
- WIC_FLAGS_NO_16BPP = 0x4,
- // Loads 565, 5551, and 4444 formats as 8888 to avoid use of optional WDDM 1.2 formats
+ DDS_FLAGS_NO_R10B10G10A2_FIXUP = 0x4,
+ // Do not use work-around for long-standing D3DX DDS file format issue which reversed the 10:10:10:2 color order masks
- WIC_FLAGS_ALLOW_MONO = 0x8,
- // Loads 1-bit monochrome (black & white) as R1_UNORM rather than 8-bit grayscale
+ DDS_FLAGS_FORCE_RGB = 0x8,
+ // Convert DXGI 1.1 BGR formats to DXGI_FORMAT_R8G8B8A8_UNORM to avoid use of optional WDDM 1.1 formats
- WIC_FLAGS_ALL_FRAMES = 0x10,
- // Loads all images in a multi-frame file, converting/resizing to match the first frame as needed, defaults to 0th frame otherwise
+ DDS_FLAGS_NO_16BPP = 0x10,
+ // Conversions avoid use of 565, 5551, and 4444 formats and instead expand to 8888 to avoid use of optional WDDM 1.2 formats
- WIC_FLAGS_IGNORE_SRGB = 0x20,
- // Ignores sRGB metadata if present in the file
+ DDS_FLAGS_EXPAND_LUMINANCE = 0x20,
+ // When loading legacy luminance formats expand replicating the color channels rather than leaving them packed (L8, L16, A8L8)
- WIC_FLAGS_FORCE_SRGB = 0x40,
- // Writes sRGB metadata into the file reguardless of format
+ DDS_FLAGS_BAD_DXTN_TAILS = 0x40,
+ // Some older DXTn DDS files incorrectly handle mipchain tails for blocks smaller than 4x4
- WIC_FLAGS_FORCE_LINEAR = 0x80,
- // Writes linear gamma metadata into the file reguardless of format
+ DDS_FLAGS_PERMISSIVE = 0x80,
+ // Allow some file variants due to common bugs in the header written by various leagcy DDS writers
- WIC_FLAGS_DITHER = 0x10000,
- // Use ordered 4x4 dithering for any required conversions
+ DDS_FLAGS_FORCE_DX10_EXT = 0x10000,
+ // Always use the 'DX10' header extension for DDS writer (i.e. don't try to write DX9 compatible DDS files)
- WIC_FLAGS_DITHER_DIFFUSION = 0x20000,
- // Use error-diffusion dithering for any required conversions
+ DDS_FLAGS_FORCE_DX10_EXT_MISC2 = 0x20000,
+ // DDS_FLAGS_FORCE_DX10_EXT including miscFlags2 information (result may not be compatible with D3DX10 or D3DX11)
- WIC_FLAGS_FILTER_POINT = 0x100000,
- WIC_FLAGS_FILTER_LINEAR = 0x200000,
- WIC_FLAGS_FILTER_CUBIC = 0x300000,
- WIC_FLAGS_FILTER_FANT = 0x400000, // Combination of Linear and Box filter
+ DDS_FLAGS_FORCE_DX9_LEGACY = 0x40000,
+ // Force use of legacy header for DDS writer (will fail if unable to write as such)
+
+ DDS_FLAGS_FORCE_DXT5_RXGB = 0x80000,
+ // Force use of 'RXGB' instead of 'DXT5' for DDS write of BC3_UNORM data
+
+ DDS_FLAGS_ALLOW_LARGE_FILES = 0x1000000,
+ // Enables the loader to read large dimension .dds files (i.e. greater than known hardware requirements)
+ };
+
+ enum TGA_FLAGS : unsigned long
+ {
+ TGA_FLAGS_NONE = 0x0,
+
+ TGA_FLAGS_BGR = 0x1,
+ // 24bpp files are returned as BGRX; 32bpp files are returned as BGRA
+
+ TGA_FLAGS_ALLOW_ALL_ZERO_ALPHA = 0x2,
+ // If the loaded image has an all zero alpha channel, normally we assume it should be opaque. This flag leaves it alone.
+
+ TGA_FLAGS_IGNORE_SRGB = 0x10,
+ // Ignores sRGB TGA 2.0 metadata if present in the file
+
+ TGA_FLAGS_FORCE_SRGB = 0x20,
+ // Writes sRGB metadata into the file reguardless of format (TGA 2.0 only)
+
+ TGA_FLAGS_FORCE_LINEAR = 0x40,
+ // Writes linear gamma metadata into the file reguardless of format (TGA 2.0 only)
+
+ TGA_FLAGS_DEFAULT_SRGB = 0x80,
+ // If no colorspace is specified in TGA 2.0 metadata, assume sRGB
+ };
+
+ enum WIC_FLAGS : unsigned long
+ {
+ WIC_FLAGS_NONE = 0x0,
+
+ WIC_FLAGS_FORCE_RGB = 0x1,
+ // Loads DXGI 1.1 BGR formats as DXGI_FORMAT_R8G8B8A8_UNORM to avoid use of optional WDDM 1.1 formats
+
+ WIC_FLAGS_NO_X2_BIAS = 0x2,
+ // Loads DXGI 1.1 X2 10:10:10:2 format as DXGI_FORMAT_R10G10B10A2_UNORM
+
+ WIC_FLAGS_NO_16BPP = 0x4,
+ // Loads 565, 5551, and 4444 formats as 8888 to avoid use of optional WDDM 1.2 formats
+
+ WIC_FLAGS_ALLOW_MONO = 0x8,
+ // Loads 1-bit monochrome (black & white) as R1_UNORM rather than 8-bit grayscale
+
+ WIC_FLAGS_ALL_FRAMES = 0x10,
+ // Loads all images in a multi-frame file, converting/resizing to match the first frame as needed, defaults to 0th frame otherwise
+
+ WIC_FLAGS_IGNORE_SRGB = 0x20,
+ // Ignores sRGB metadata if present in the file
+
+ WIC_FLAGS_FORCE_SRGB = 0x40,
+ // Writes sRGB metadata into the file reguardless of format
+
+ WIC_FLAGS_FORCE_LINEAR = 0x80,
+ // Writes linear gamma metadata into the file reguardless of format
+
+ WIC_FLAGS_DEFAULT_SRGB = 0x100,
+ // If no colorspace is specified, assume sRGB
+
+ WIC_FLAGS_DITHER = 0x10000,
+ // Use ordered 4x4 dithering for any required conversions
+
+ WIC_FLAGS_DITHER_DIFFUSION = 0x20000,
+ // Use error-diffusion dithering for any required conversions
+
+ WIC_FLAGS_FILTER_POINT = 0x100000,
+ WIC_FLAGS_FILTER_LINEAR = 0x200000,
+ WIC_FLAGS_FILTER_CUBIC = 0x300000,
+ WIC_FLAGS_FILTER_FANT = 0x400000, // Combination of Linear and Box filter
// Filtering mode to use for any required image resizing (only needed when loading arrays of differently sized images; defaults to Fant)
};
HRESULT __cdecl GetMetadataFromDDSMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _In_ DWORD flags,
- _Out_ TexMetadata& metadata);
+ _In_ DDS_FLAGS flags,
+ _Out_ TexMetadata& metadata) noexcept;
HRESULT __cdecl GetMetadataFromDDSFile(
_In_z_ const wchar_t* szFile,
- _In_ DWORD flags,
- _Out_ TexMetadata& metadata);
+ _In_ DDS_FLAGS flags,
+ _Out_ TexMetadata& metadata) noexcept;
+
+ HRESULT __cdecl GetMetadataFromDDSMemoryEx(
+ _In_reads_bytes_(size) const void* pSource, _In_ size_t size,
+ _In_ DDS_FLAGS flags,
+ _Out_ TexMetadata& metadata,
+ _Out_opt_ DDSMetaData* ddPixelFormat) noexcept;
+ HRESULT __cdecl GetMetadataFromDDSFileEx(
+ _In_z_ const wchar_t* szFile,
+ _In_ DDS_FLAGS flags,
+ _Out_ TexMetadata& metadata,
+ _Out_opt_ DDSMetaData* ddPixelFormat) noexcept;
HRESULT __cdecl GetMetadataFromHDRMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _Out_ TexMetadata& metadata);
+ _Out_ TexMetadata& metadata) noexcept;
HRESULT __cdecl GetMetadataFromHDRFile(
_In_z_ const wchar_t* szFile,
- _Out_ TexMetadata& metadata);
+ _Out_ TexMetadata& metadata) noexcept;
HRESULT __cdecl GetMetadataFromTGAMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _Out_ TexMetadata& metadata);
+ _In_ TGA_FLAGS flags,
+ _Out_ TexMetadata& metadata) noexcept;
HRESULT __cdecl GetMetadataFromTGAFile(
_In_z_ const wchar_t* szFile,
- _Out_ TexMetadata& metadata);
+ _In_ TGA_FLAGS flags,
+ _Out_ TexMetadata& metadata) noexcept;
+#ifdef _WIN32
HRESULT __cdecl GetMetadataFromWICMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _In_ DWORD flags,
+ _In_ WIC_FLAGS flags,
_Out_ TexMetadata& metadata,
- _In_opt_ std::function getMQR = nullptr);
+ _In_ std::function getMQR = nullptr);
HRESULT __cdecl GetMetadataFromWICFile(
_In_z_ const wchar_t* szFile,
- _In_ DWORD flags,
+ _In_ WIC_FLAGS flags,
_Out_ TexMetadata& metadata,
- _In_opt_ std::function getMQR = nullptr);
+ _In_ std::function getMQR = nullptr);
+#endif
+
+ // Compatability helpers
+ HRESULT __cdecl GetMetadataFromTGAMemory(
+ _In_reads_bytes_(size) const void* pSource, _In_ size_t size,
+ _Out_ TexMetadata& metadata) noexcept;
+ HRESULT __cdecl GetMetadataFromTGAFile(
+ _In_z_ const wchar_t* szFile,
+ _Out_ TexMetadata& metadata) noexcept;
//---------------------------------------------------------------------------------
// Bitmap image container
@@ -273,32 +403,32 @@ namespace DirectX
ScratchImage(const ScratchImage&) = delete;
ScratchImage& operator=(const ScratchImage&) = delete;
- HRESULT __cdecl Initialize(_In_ const TexMetadata& mdata, _In_ DWORD flags = CP_FLAGS_NONE);
+ HRESULT __cdecl Initialize(_In_ const TexMetadata& mdata, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
- HRESULT __cdecl Initialize1D(_In_ DXGI_FORMAT fmt, _In_ size_t length, _In_ size_t arraySize, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE);
- HRESULT __cdecl Initialize2D(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t arraySize, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE);
- HRESULT __cdecl Initialize3D(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t depth, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE);
- HRESULT __cdecl InitializeCube(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t nCubes, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE);
+ HRESULT __cdecl Initialize1D(_In_ DXGI_FORMAT fmt, _In_ size_t length, _In_ size_t arraySize, _In_ size_t mipLevels, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
+ HRESULT __cdecl Initialize2D(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t arraySize, _In_ size_t mipLevels, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
+ HRESULT __cdecl Initialize3D(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t depth, _In_ size_t mipLevels, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
+ HRESULT __cdecl InitializeCube(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t nCubes, _In_ size_t mipLevels, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
- HRESULT __cdecl InitializeFromImage(_In_ const Image& srcImage, _In_ bool allow1D = false, _In_ DWORD flags = CP_FLAGS_NONE);
- HRESULT __cdecl InitializeArrayFromImages(_In_reads_(nImages) const Image* images, _In_ size_t nImages, _In_ bool allow1D = false, _In_ DWORD flags = CP_FLAGS_NONE);
- HRESULT __cdecl InitializeCubeFromImages(_In_reads_(nImages) const Image* images, _In_ size_t nImages, _In_ DWORD flags = CP_FLAGS_NONE);
- HRESULT __cdecl Initialize3DFromImages(_In_reads_(depth) const Image* images, _In_ size_t depth, _In_ DWORD flags = CP_FLAGS_NONE);
+ HRESULT __cdecl InitializeFromImage(_In_ const Image& srcImage, _In_ bool allow1D = false, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
+ HRESULT __cdecl InitializeArrayFromImages(_In_reads_(nImages) const Image* images, _In_ size_t nImages, _In_ bool allow1D = false, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
+ HRESULT __cdecl InitializeCubeFromImages(_In_reads_(nImages) const Image* images, _In_ size_t nImages, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
+ HRESULT __cdecl Initialize3DFromImages(_In_reads_(depth) const Image* images, _In_ size_t depth, _In_ CP_FLAGS flags = CP_FLAGS_NONE) noexcept;
- void __cdecl Release();
+ void __cdecl Release() noexcept;
- bool __cdecl OverrideFormat(_In_ DXGI_FORMAT f);
+ bool __cdecl OverrideFormat(_In_ DXGI_FORMAT f) noexcept;
- const TexMetadata& __cdecl GetMetadata() const { return m_metadata; }
- const Image* __cdecl GetImage(_In_ size_t mip, _In_ size_t item, _In_ size_t slice) const;
+ const TexMetadata& __cdecl GetMetadata() const noexcept { return m_metadata; }
+ const Image* __cdecl GetImage(_In_ size_t mip, _In_ size_t item, _In_ size_t slice) const noexcept;
- const Image* __cdecl GetImages() const { return m_image; }
- size_t __cdecl GetImageCount() const { return m_nimages; }
+ const Image* __cdecl GetImages() const noexcept { return m_image; }
+ size_t __cdecl GetImageCount() const noexcept { return m_nimages; }
- uint8_t* __cdecl GetPixels() const { return m_memory; }
- size_t __cdecl GetPixelsSize() const { return m_size; }
+ uint8_t* __cdecl GetPixels() const noexcept { return m_memory; }
+ size_t __cdecl GetPixelsSize() const noexcept { return m_size; }
- bool __cdecl IsAlphaAllOpaque() const;
+ bool __cdecl IsAlphaAllOpaque() const noexcept;
private:
size_t m_nimages;
@@ -322,14 +452,18 @@ namespace DirectX
Blob(const Blob&) = delete;
Blob& operator=(const Blob&) = delete;
- HRESULT __cdecl Initialize(_In_ size_t size);
+ HRESULT __cdecl Initialize(_In_ size_t size) noexcept;
- void __cdecl Release();
+ void __cdecl Release() noexcept;
- void *__cdecl GetBufferPointer() const { return m_buffer; }
- size_t __cdecl GetBufferSize() const { return m_size; }
+ void *__cdecl GetBufferPointer() const noexcept { return m_buffer; }
+ size_t __cdecl GetBufferSize() const noexcept { return m_size; }
- HRESULT __cdecl Trim(size_t size);
+ HRESULT __cdecl Resize(size_t size) noexcept;
+ // Reallocate for a new size
+
+ HRESULT __cdecl Trim(size_t size) noexcept;
+ // Shorten size without reallocation
private:
void* m_buffer;
@@ -342,313 +476,395 @@ namespace DirectX
// DDS operations
HRESULT __cdecl LoadFromDDSMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _In_ DWORD flags,
- _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
+ _In_ DDS_FLAGS flags,
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl LoadFromDDSFile(
_In_z_ const wchar_t* szFile,
- _In_ DWORD flags,
- _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
+ _In_ DDS_FLAGS flags,
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
+
+ HRESULT __cdecl LoadFromDDSMemoryEx(
+ _In_reads_bytes_(size) const void* pSource, _In_ size_t size,
+ _In_ DDS_FLAGS flags,
+ _Out_opt_ TexMetadata* metadata,
+ _Out_opt_ DDSMetaData* ddPixelFormat,
+ _Out_ ScratchImage& image) noexcept;
+ HRESULT __cdecl LoadFromDDSFileEx(
+ _In_z_ const wchar_t* szFile,
+ _In_ DDS_FLAGS flags,
+ _Out_opt_ TexMetadata* metadata,
+ _Out_opt_ DDSMetaData* ddPixelFormat,
+ _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl SaveToDDSMemory(
_In_ const Image& image,
- _In_ DWORD flags,
- _Out_ Blob& blob);
+ _In_ DDS_FLAGS flags,
+ _Out_ Blob& blob) noexcept;
HRESULT __cdecl SaveToDDSMemory(
_In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD flags,
- _Out_ Blob& blob);
+ _In_ DDS_FLAGS flags,
+ _Out_ Blob& blob) noexcept;
- HRESULT __cdecl SaveToDDSFile(_In_ const Image& image, _In_ DWORD flags, _In_z_ const wchar_t* szFile);
+ HRESULT __cdecl SaveToDDSFile(_In_ const Image& image, _In_ DDS_FLAGS flags, _In_z_ const wchar_t* szFile) noexcept;
HRESULT __cdecl SaveToDDSFile(
_In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD flags, _In_z_ const wchar_t* szFile);
+ _In_ DDS_FLAGS flags, _In_z_ const wchar_t* szFile) noexcept;
// HDR operations
HRESULT __cdecl LoadFromHDRMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl LoadFromHDRFile(
_In_z_ const wchar_t* szFile,
- _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
- HRESULT __cdecl SaveToHDRMemory(_In_ const Image& image, _Out_ Blob& blob);
- HRESULT __cdecl SaveToHDRFile(_In_ const Image& image, _In_z_ const wchar_t* szFile);
+ HRESULT __cdecl SaveToHDRMemory(_In_ const Image& image, _Out_ Blob& blob) noexcept;
+ HRESULT __cdecl SaveToHDRFile(_In_ const Image& image, _In_z_ const wchar_t* szFile) noexcept;
// TGA operations
HRESULT __cdecl LoadFromTGAMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
+ _In_ TGA_FLAGS flags,
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl LoadFromTGAFile(
_In_z_ const wchar_t* szFile,
- _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
+ _In_ TGA_FLAGS flags,
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
- HRESULT __cdecl SaveToTGAMemory(_In_ const Image& image, _Out_ Blob& blob, _In_opt_ const TexMetadata* metadata = nullptr);
- HRESULT __cdecl SaveToTGAFile(_In_ const Image& image, _In_z_ const wchar_t* szFile, _In_opt_ const TexMetadata* metadata = nullptr);
+ HRESULT __cdecl SaveToTGAMemory(_In_ const Image& image,
+ _In_ TGA_FLAGS flags,
+ _Out_ Blob& blob, _In_opt_ const TexMetadata* metadata = nullptr) noexcept;
+ HRESULT __cdecl SaveToTGAFile(_In_ const Image& image,
+ _In_ TGA_FLAGS flags,
+ _In_z_ const wchar_t* szFile, _In_opt_ const TexMetadata* metadata = nullptr) noexcept;
// WIC operations
+#ifdef _WIN32
HRESULT __cdecl LoadFromWICMemory(
_In_reads_bytes_(size) const void* pSource, _In_ size_t size,
- _In_ DWORD flags,
+ _In_ WIC_FLAGS flags,
_Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image,
- _In_opt_ std::function getMQR = nullptr);
+ _In_ std::function getMQR = nullptr);
HRESULT __cdecl LoadFromWICFile(
- _In_z_ const wchar_t* szFile, _In_ DWORD flags,
+ _In_z_ const wchar_t* szFile, _In_ WIC_FLAGS flags,
_Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image,
- _In_opt_ std::function getMQR = nullptr);
+ _In_ std::function getMQR = nullptr);
HRESULT __cdecl SaveToWICMemory(
- _In_ const Image& image, _In_ DWORD flags, _In_ REFGUID guidContainerFormat,
+ _In_ const Image& image, _In_ WIC_FLAGS flags, _In_ REFGUID guidContainerFormat,
_Out_ Blob& blob, _In_opt_ const GUID* targetFormat = nullptr,
- _In_opt_ std::function setCustomProps = nullptr);
+ _In_ std::function setCustomProps = nullptr);
HRESULT __cdecl SaveToWICMemory(
_In_count_(nimages) const Image* images, _In_ size_t nimages,
- _In_ DWORD flags, _In_ REFGUID guidContainerFormat,
+ _In_ WIC_FLAGS flags, _In_ REFGUID guidContainerFormat,
_Out_ Blob& blob, _In_opt_ const GUID* targetFormat = nullptr,
- _In_opt_ std::function setCustomProps = nullptr);
+ _In_ std::function setCustomProps = nullptr);
HRESULT __cdecl SaveToWICFile(
- _In_ const Image& image, _In_ DWORD flags, _In_ REFGUID guidContainerFormat,
+ _In_ const Image& image, _In_ WIC_FLAGS flags, _In_ REFGUID guidContainerFormat,
_In_z_ const wchar_t* szFile, _In_opt_ const GUID* targetFormat = nullptr,
- _In_opt_ std::function setCustomProps = nullptr);
+ _In_ std::function setCustomProps = nullptr);
HRESULT __cdecl SaveToWICFile(
_In_count_(nimages) const Image* images, _In_ size_t nimages,
- _In_ DWORD flags, _In_ REFGUID guidContainerFormat,
+ _In_ WIC_FLAGS flags, _In_ REFGUID guidContainerFormat,
_In_z_ const wchar_t* szFile, _In_opt_ const GUID* targetFormat = nullptr,
- _In_opt_ std::function setCustomProps = nullptr);
+ _In_ std::function setCustomProps = nullptr);
+#endif // WIN32
+
+ // Compatability helpers
+ HRESULT __cdecl LoadFromTGAMemory(
+ _In_reads_bytes_(size) const void* pSource, _In_ size_t size,
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
+ HRESULT __cdecl LoadFromTGAFile(
+ _In_z_ const wchar_t* szFile,
+ _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept;
+
+ HRESULT __cdecl SaveToTGAMemory(_In_ const Image& image, _Out_ Blob& blob, _In_opt_ const TexMetadata* metadata = nullptr) noexcept;
+ HRESULT __cdecl SaveToTGAFile(_In_ const Image& image, _In_z_ const wchar_t* szFile, _In_opt_ const TexMetadata* metadata = nullptr) noexcept;
//---------------------------------------------------------------------------------
// Texture conversion, resizing, mipmap generation, and block compression
- enum TEX_FR_FLAGS
+ enum TEX_FR_FLAGS : unsigned long
{
- TEX_FR_ROTATE0 = 0x0,
- TEX_FR_ROTATE90 = 0x1,
- TEX_FR_ROTATE180 = 0x2,
- TEX_FR_ROTATE270 = 0x3,
- TEX_FR_FLIP_HORIZONTAL = 0x08,
- TEX_FR_FLIP_VERTICAL = 0x10,
+ TEX_FR_ROTATE0 = 0x0,
+ TEX_FR_ROTATE90 = 0x1,
+ TEX_FR_ROTATE180 = 0x2,
+ TEX_FR_ROTATE270 = 0x3,
+ TEX_FR_FLIP_HORIZONTAL = 0x08,
+ TEX_FR_FLIP_VERTICAL = 0x10,
};
- HRESULT __cdecl FlipRotate(_In_ const Image& srcImage, _In_ DWORD flags, _Out_ ScratchImage& image);
+#ifdef _WIN32
+ HRESULT __cdecl FlipRotate(_In_ const Image& srcImage, _In_ TEX_FR_FLAGS flags, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl FlipRotate(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD flags, _Out_ ScratchImage& result);
+ _In_ TEX_FR_FLAGS flags, _Out_ ScratchImage& result) noexcept;
// Flip and/or rotate image
+#endif
- enum TEX_FILTER_FLAGS
+ enum TEX_FILTER_FLAGS : unsigned long
{
- TEX_FILTER_DEFAULT = 0,
+ TEX_FILTER_DEFAULT = 0,
- TEX_FILTER_WRAP_U = 0x1,
- TEX_FILTER_WRAP_V = 0x2,
- TEX_FILTER_WRAP_W = 0x4,
- TEX_FILTER_WRAP = (TEX_FILTER_WRAP_U | TEX_FILTER_WRAP_V | TEX_FILTER_WRAP_W),
- TEX_FILTER_MIRROR_U = 0x10,
- TEX_FILTER_MIRROR_V = 0x20,
- TEX_FILTER_MIRROR_W = 0x40,
- TEX_FILTER_MIRROR = (TEX_FILTER_MIRROR_U | TEX_FILTER_MIRROR_V | TEX_FILTER_MIRROR_W),
- // Wrap vs. Mirror vs. Clamp filtering options
+ TEX_FILTER_WRAP_U = 0x1,
+ TEX_FILTER_WRAP_V = 0x2,
+ TEX_FILTER_WRAP_W = 0x4,
+ TEX_FILTER_WRAP = (TEX_FILTER_WRAP_U | TEX_FILTER_WRAP_V | TEX_FILTER_WRAP_W),
+ TEX_FILTER_MIRROR_U = 0x10,
+ TEX_FILTER_MIRROR_V = 0x20,
+ TEX_FILTER_MIRROR_W = 0x40,
+ TEX_FILTER_MIRROR = (TEX_FILTER_MIRROR_U | TEX_FILTER_MIRROR_V | TEX_FILTER_MIRROR_W),
+ // Wrap vs. Mirror vs. Clamp filtering options
- TEX_FILTER_SEPARATE_ALPHA = 0x100,
- // Resize color and alpha channel independently
+ TEX_FILTER_SEPARATE_ALPHA = 0x100,
+ // Resize color and alpha channel independently
- TEX_FILTER_FLOAT_X2BIAS = 0x200,
- // Enable *2 - 1 conversion cases for unorm<->float and positive-only float formats
+ TEX_FILTER_FLOAT_X2BIAS = 0x200,
+ // Enable *2 - 1 conversion cases for unorm<->float and positive-only float formats
- TEX_FILTER_RGB_COPY_RED = 0x1000,
- TEX_FILTER_RGB_COPY_GREEN = 0x2000,
- TEX_FILTER_RGB_COPY_BLUE = 0x4000,
- // When converting RGB to R, defaults to using grayscale. These flags indicate copying a specific channel instead
- // When converting RGB to RG, defaults to copying RED | GREEN. These flags control which channels are selected instead.
+ TEX_FILTER_RGB_COPY_RED = 0x1000,
+ TEX_FILTER_RGB_COPY_GREEN = 0x2000,
+ TEX_FILTER_RGB_COPY_BLUE = 0x4000,
+ TEX_FILTER_RGB_COPY_ALPHA = 0x8000,
+ // When converting RGB(A) to R, defaults to using grayscale. These flags indicate copying a specific channel instead
+ // When converting RGB(A) to RG, defaults to copying RED | GREEN. These flags control which channels are selected instead.
- TEX_FILTER_DITHER = 0x10000,
- // Use ordered 4x4 dithering for any required conversions
+ TEX_FILTER_DITHER = 0x10000,
+ // Use ordered 4x4 dithering for any required conversions
TEX_FILTER_DITHER_DIFFUSION = 0x20000,
- // Use error-diffusion dithering for any required conversions
+ // Use error-diffusion dithering for any required conversions
- TEX_FILTER_POINT = 0x100000,
- TEX_FILTER_LINEAR = 0x200000,
- TEX_FILTER_CUBIC = 0x300000,
- TEX_FILTER_BOX = 0x400000,
- TEX_FILTER_FANT = 0x400000, // Equiv to Box filtering for mipmap generation
- TEX_FILTER_TRIANGLE = 0x500000,
- // Filtering mode to use for any required image resizing
+ TEX_FILTER_POINT = 0x100000,
+ TEX_FILTER_LINEAR = 0x200000,
+ TEX_FILTER_CUBIC = 0x300000,
+ TEX_FILTER_BOX = 0x400000,
+ TEX_FILTER_FANT = 0x400000, // Equiv to Box filtering for mipmap generation
+ TEX_FILTER_TRIANGLE = 0x500000,
+ // Filtering mode to use for any required image resizing
- TEX_FILTER_SRGB_IN = 0x1000000,
- TEX_FILTER_SRGB_OUT = 0x2000000,
- TEX_FILTER_SRGB = (TEX_FILTER_SRGB_IN | TEX_FILTER_SRGB_OUT),
- // sRGB <-> RGB for use in conversion operations
- // if the input format type is IsSRGB(), then SRGB_IN is on by default
- // if the output format type is IsSRGB(), then SRGB_OUT is on by default
+ TEX_FILTER_SRGB_IN = 0x1000000,
+ TEX_FILTER_SRGB_OUT = 0x2000000,
+ TEX_FILTER_SRGB = (TEX_FILTER_SRGB_IN | TEX_FILTER_SRGB_OUT),
+ // sRGB <-> RGB for use in conversion operations
+ // if the input format type is IsSRGB(), then SRGB_IN is on by default
+ // if the output format type is IsSRGB(), then SRGB_OUT is on by default
- TEX_FILTER_FORCE_NON_WIC = 0x10000000,
- // Forces use of the non-WIC path when both are an option
+ TEX_FILTER_FORCE_NON_WIC = 0x10000000,
+ // Forces use of the non-WIC path when both are an option
- TEX_FILTER_FORCE_WIC = 0x20000000,
- // Forces use of the WIC path even when logic would have picked a non-WIC path when both are an option
+ TEX_FILTER_FORCE_WIC = 0x20000000,
+ // Forces use of the WIC path even when logic would have picked a non-WIC path when both are an option
};
+ constexpr unsigned long TEX_FILTER_DITHER_MASK = 0xF0000;
+ constexpr unsigned long TEX_FILTER_MODE_MASK = 0xF00000;
+ constexpr unsigned long TEX_FILTER_SRGB_MASK = 0xF000000;
+
HRESULT __cdecl Resize(
_In_ const Image& srcImage, _In_ size_t width, _In_ size_t height,
- _In_ DWORD filter,
- _Out_ ScratchImage& image);
+ _In_ TEX_FILTER_FLAGS filter,
+ _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl Resize(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ size_t width, _In_ size_t height, _In_ DWORD filter, _Out_ ScratchImage& result);
+ _In_ size_t width, _In_ size_t height, _In_ TEX_FILTER_FLAGS filter, _Out_ ScratchImage& result) noexcept;
// Resize the image to width x height. Defaults to Fant filtering.
// Note for a complex resize, the result will always have mipLevels == 1
- const float TEX_THRESHOLD_DEFAULT = 0.5f;
+ constexpr float TEX_THRESHOLD_DEFAULT = 0.5f;
// Default value for alpha threshold used when converting to 1-bit alpha
+ struct ConvertOptions
+ {
+ TEX_FILTER_FLAGS filter;
+ float threshold;
+ };
+
HRESULT __cdecl Convert(
- _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ DWORD filter, _In_ float threshold,
- _Out_ ScratchImage& image);
+ _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ TEX_FILTER_FLAGS filter, _In_ float threshold,
+ _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl Convert(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DXGI_FORMAT format, _In_ DWORD filter, _In_ float threshold, _Out_ ScratchImage& result);
+ _In_ DXGI_FORMAT format, _In_ TEX_FILTER_FLAGS filter, _In_ float threshold, _Out_ ScratchImage& result) noexcept;
+
+ HRESULT __cdecl ConvertEx(
+ _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ const ConvertOptions& options,
+ _Out_ ScratchImage& image,
+ _In_ std::function statusCallBack = nullptr);
+ HRESULT __cdecl ConvertEx(
+ _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _In_ DXGI_FORMAT format, _In_ const ConvertOptions& options, _Out_ ScratchImage& result,
+ _In_ std::function statusCallBack = nullptr);
// Convert the image to a new format
- HRESULT __cdecl ConvertToSinglePlane(_In_ const Image& srcImage, _Out_ ScratchImage& image);
+ HRESULT __cdecl ConvertToSinglePlane(_In_ const Image& srcImage, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl ConvertToSinglePlane(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _Out_ ScratchImage& image);
+ _Out_ ScratchImage& image) noexcept;
// Converts the image from a planar format to an equivalent non-planar format
HRESULT __cdecl GenerateMipMaps(
- _In_ const Image& baseImage, _In_ DWORD filter, _In_ size_t levels,
- _Inout_ ScratchImage& mipChain, _In_ bool allow1D = false);
+ _In_ const Image& baseImage, _In_ TEX_FILTER_FLAGS filter, _In_ size_t levels,
+ _Inout_ ScratchImage& mipChain, _In_ bool allow1D = false) noexcept;
HRESULT __cdecl GenerateMipMaps(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD filter, _In_ size_t levels, _Inout_ ScratchImage& mipChain);
+ _In_ TEX_FILTER_FLAGS filter, _In_ size_t levels, _Inout_ ScratchImage& mipChain);
// levels of '0' indicates a full mipchain, otherwise is generates that number of total levels (including the source base image)
// Defaults to Fant filtering which is equivalent to a box filter
HRESULT __cdecl GenerateMipMaps3D(
- _In_reads_(depth) const Image* baseImages, _In_ size_t depth, _In_ DWORD filter, _In_ size_t levels,
- _Out_ ScratchImage& mipChain);
+ _In_reads_(depth) const Image* baseImages, _In_ size_t depth, _In_ TEX_FILTER_FLAGS filter, _In_ size_t levels,
+ _Out_ ScratchImage& mipChain) noexcept;
HRESULT __cdecl GenerateMipMaps3D(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD filter, _In_ size_t levels, _Out_ ScratchImage& mipChain);
+ _In_ TEX_FILTER_FLAGS filter, _In_ size_t levels, _Out_ ScratchImage& mipChain);
// levels of '0' indicates a full mipchain, otherwise is generates that number of total levels (including the source base image)
// Defaults to Fant filtering which is equivalent to a box filter
HRESULT __cdecl ScaleMipMapsAlphaForCoverage(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, _In_ size_t item,
- _In_ float alphaReference, _Inout_ ScratchImage& mipChain);
+ _In_ float alphaReference, _Inout_ ScratchImage& mipChain) noexcept;
- enum TEX_PMALPHA_FLAGS
+ enum TEX_PMALPHA_FLAGS : unsigned long
{
- TEX_PMALPHA_DEFAULT = 0,
+ TEX_PMALPHA_DEFAULT = 0,
- TEX_PMALPHA_IGNORE_SRGB = 0x1,
- // ignores sRGB colorspace conversions
+ TEX_PMALPHA_IGNORE_SRGB = 0x1,
+ // ignores sRGB colorspace conversions
- TEX_PMALPHA_REVERSE = 0x2,
- // converts from premultiplied alpha back to straight alpha
+ TEX_PMALPHA_REVERSE = 0x2,
+ // converts from premultiplied alpha back to straight alpha
- TEX_PMALPHA_SRGB_IN = 0x1000000,
- TEX_PMALPHA_SRGB_OUT = 0x2000000,
- TEX_PMALPHA_SRGB = (TEX_PMALPHA_SRGB_IN | TEX_PMALPHA_SRGB_OUT),
- // if the input format type is IsSRGB(), then SRGB_IN is on by default
- // if the output format type is IsSRGB(), then SRGB_OUT is on by default
+ TEX_PMALPHA_SRGB_IN = 0x1000000,
+ TEX_PMALPHA_SRGB_OUT = 0x2000000,
+ TEX_PMALPHA_SRGB = (TEX_PMALPHA_SRGB_IN | TEX_PMALPHA_SRGB_OUT),
+ // if the input format type is IsSRGB(), then SRGB_IN is on by default
+ // if the output format type is IsSRGB(), then SRGB_OUT is on by default
};
- HRESULT __cdecl PremultiplyAlpha(_In_ const Image& srcImage, _In_ DWORD flags, _Out_ ScratchImage& image);
+ HRESULT __cdecl PremultiplyAlpha(_In_ const Image& srcImage, _In_ TEX_PMALPHA_FLAGS flags, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl PremultiplyAlpha(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD flags, _Out_ ScratchImage& result);
+ _In_ TEX_PMALPHA_FLAGS flags, _Out_ ScratchImage& result) noexcept;
// Converts to/from a premultiplied alpha version of the texture
- enum TEX_COMPRESS_FLAGS
+ enum TEX_COMPRESS_FLAGS : unsigned long
{
- TEX_COMPRESS_DEFAULT = 0,
+ TEX_COMPRESS_DEFAULT = 0,
- TEX_COMPRESS_RGB_DITHER = 0x10000,
- // Enables dithering RGB colors for BC1-3 compression
+ TEX_COMPRESS_RGB_DITHER = 0x10000,
+ // Enables dithering RGB colors for BC1-3 compression
- TEX_COMPRESS_A_DITHER = 0x20000,
- // Enables dithering alpha for BC1-3 compression
+ TEX_COMPRESS_A_DITHER = 0x20000,
+ // Enables dithering alpha for BC1-3 compression
- TEX_COMPRESS_DITHER = 0x30000,
- // Enables both RGB and alpha dithering for BC1-3 compression
+ TEX_COMPRESS_DITHER = 0x30000,
+ // Enables both RGB and alpha dithering for BC1-3 compression
- TEX_COMPRESS_UNIFORM = 0x40000,
- // Uniform color weighting for BC1-3 compression; by default uses perceptual weighting
+ TEX_COMPRESS_UNIFORM = 0x40000,
+ // Uniform color weighting for BC1-3 compression; by default uses perceptual weighting
- TEX_COMPRESS_BC7_USE_3SUBSETS = 0x80000,
- // Enables exhaustive search for BC7 compress for mode 0 and 2; by default skips trying these modes
+ TEX_COMPRESS_BC7_USE_3SUBSETS = 0x80000,
+ // Enables exhaustive search for BC7 compress for mode 0 and 2; by default skips trying these modes
- TEX_COMPRESS_BC7_QUICK = 0x100000,
- // Minimal modes (usually mode 6) for BC7 compression
+ TEX_COMPRESS_BC7_QUICK = 0x100000,
+ // Minimal modes (usually mode 6) for BC7 compression
- TEX_COMPRESS_SRGB_IN = 0x1000000,
- TEX_COMPRESS_SRGB_OUT = 0x2000000,
- TEX_COMPRESS_SRGB = (TEX_COMPRESS_SRGB_IN | TEX_COMPRESS_SRGB_OUT),
- // if the input format type is IsSRGB(), then SRGB_IN is on by default
- // if the output format type is IsSRGB(), then SRGB_OUT is on by default
+ TEX_COMPRESS_SRGB_IN = 0x1000000,
+ TEX_COMPRESS_SRGB_OUT = 0x2000000,
+ TEX_COMPRESS_SRGB = (TEX_COMPRESS_SRGB_IN | TEX_COMPRESS_SRGB_OUT),
+ // if the input format type is IsSRGB(), then SRGB_IN is on by default
+ // if the output format type is IsSRGB(), then SRGB_OUT is on by default
- TEX_COMPRESS_PARALLEL = 0x10000000,
- // Compress is free to use multithreading to improve performance (by default it does not use multithreading)
+ TEX_COMPRESS_PARALLEL = 0x10000000,
+ // Compress is free to use multithreading to improve performance (by default it does not use multithreading)
+ };
+
+ constexpr float TEX_ALPHA_WEIGHT_DEFAULT = 1.0f;
+ // Default value for alpha weight used for GPU BC7 compression
+
+ struct CompressOptions
+ {
+ TEX_COMPRESS_FLAGS flags;
+ float threshold;
+ float alphaWeight;
};
HRESULT __cdecl Compress(
- _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float threshold,
- _Out_ ScratchImage& cImage);
+ _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ TEX_COMPRESS_FLAGS compress, _In_ float threshold,
+ _Out_ ScratchImage& cImage) noexcept;
HRESULT __cdecl Compress(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float threshold, _Out_ ScratchImage& cImages);
+ _In_ DXGI_FORMAT format, _In_ TEX_COMPRESS_FLAGS compress, _In_ float threshold, _Out_ ScratchImage& cImages) noexcept;
// Note that threshold is only used by BC1. TEX_THRESHOLD_DEFAULT is a typical value to use
+ HRESULT __cdecl CompressEx(
+ _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ const CompressOptions& options,
+ _Out_ ScratchImage& cImage,
+ _In_ std::function statusCallBack = nullptr);
+ HRESULT __cdecl CompressEx(
+ _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _In_ DXGI_FORMAT format, _In_ const CompressOptions& options, _Out_ ScratchImage& cImages,
+ _In_ std::function statusCallBack = nullptr);
+
#if defined(__d3d11_h__) || defined(__d3d11_x_h__)
HRESULT __cdecl Compress(
- _In_ ID3D11Device* pDevice, _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ DWORD compress,
- _In_ float alphaWeight, _Out_ ScratchImage& image);
+ _In_ ID3D11Device* pDevice, _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ TEX_COMPRESS_FLAGS compress,
+ _In_ float alphaWeight, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl Compress(
_In_ ID3D11Device* pDevice, _In_ const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float alphaWeight, _Out_ ScratchImage& cImages);
+ _In_ DXGI_FORMAT format, _In_ TEX_COMPRESS_FLAGS compress, _In_ float alphaWeight, _Out_ ScratchImage& cImages) noexcept;
// DirectCompute-based compression (alphaWeight is only used by BC7. 1.0 is the typical value to use)
+
+ HRESULT __cdecl CompressEx(
+ _In_ ID3D11Device* pDevice, _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ const CompressOptions& options,
+ _Out_ ScratchImage& image,
+ _In_ std::function statusCallBack = nullptr);
+ HRESULT __cdecl CompressEx(
+ _In_ ID3D11Device* pDevice, _In_ const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _In_ DXGI_FORMAT format, _In_ const CompressOptions& options, _Out_ ScratchImage& cImages,
+ _In_ std::function statusCallBack = nullptr);
#endif
- HRESULT __cdecl Decompress(_In_ const Image& cImage, _In_ DXGI_FORMAT format, _Out_ ScratchImage& image);
+ HRESULT __cdecl Decompress(_In_ const Image& cImage, _In_ DXGI_FORMAT format, _Out_ ScratchImage& image) noexcept;
HRESULT __cdecl Decompress(
_In_reads_(nimages) const Image* cImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DXGI_FORMAT format, _Out_ ScratchImage& images);
+ _In_ DXGI_FORMAT format, _Out_ ScratchImage& images) noexcept;
//---------------------------------------------------------------------------------
// Normal map operations
- enum CNMAP_FLAGS
+ enum CNMAP_FLAGS : unsigned long
{
- CNMAP_DEFAULT = 0,
+ CNMAP_DEFAULT = 0,
- CNMAP_CHANNEL_RED = 0x1,
- CNMAP_CHANNEL_GREEN = 0x2,
- CNMAP_CHANNEL_BLUE = 0x3,
- CNMAP_CHANNEL_ALPHA = 0x4,
+ CNMAP_CHANNEL_RED = 0x1,
+ CNMAP_CHANNEL_GREEN = 0x2,
+ CNMAP_CHANNEL_BLUE = 0x3,
+ CNMAP_CHANNEL_ALPHA = 0x4,
CNMAP_CHANNEL_LUMINANCE = 0x5,
- // Channel selection when evaluting color value for height
- // Luminance is a combination of red, green, and blue
+ // Channel selection when evaluting color value for height
+ // Luminance is a combination of red, green, and blue
- CNMAP_MIRROR_U = 0x1000,
- CNMAP_MIRROR_V = 0x2000,
- CNMAP_MIRROR = 0x3000,
- // Use mirror semantics for scanline references (defaults to wrap)
+ CNMAP_MIRROR_U = 0x1000,
+ CNMAP_MIRROR_V = 0x2000,
+ CNMAP_MIRROR = 0x3000,
+ // Use mirror semantics for scanline references (defaults to wrap)
- CNMAP_INVERT_SIGN = 0x4000,
- // Inverts normal sign
+ CNMAP_INVERT_SIGN = 0x4000,
+ // Inverts normal sign
CNMAP_COMPUTE_OCCLUSION = 0x8000,
- // Computes a crude occlusion term stored in the alpha channel
+ // Computes a crude occlusion term stored in the alpha channel
};
HRESULT __cdecl ComputeNormalMap(
- _In_ const Image& srcImage, _In_ DWORD flags, _In_ float amplitude,
- _In_ DXGI_FORMAT format, _Out_ ScratchImage& normalMap);
+ _In_ const Image& srcImage, _In_ CNMAP_FLAGS flags, _In_ float amplitude,
+ _In_ DXGI_FORMAT format, _Out_ ScratchImage& normalMap) noexcept;
HRESULT __cdecl ComputeNormalMap(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ DWORD flags, _In_ float amplitude, _In_ DXGI_FORMAT format, _Out_ ScratchImage& normalMaps);
+ _In_ CNMAP_FLAGS flags, _In_ float amplitude, _In_ DXGI_FORMAT format, _Out_ ScratchImage& normalMaps) noexcept;
//---------------------------------------------------------------------------------
// Misc image operations
@@ -661,33 +877,33 @@ namespace DirectX
size_t h;
Rect() = default;
- Rect(size_t _x, size_t _y, size_t _w, size_t _h) : x(_x), y(_y), w(_w), h(_h) {}
+ Rect(size_t _x, size_t _y, size_t _w, size_t _h) noexcept : x(_x), y(_y), w(_w), h(_h) {}
};
HRESULT __cdecl CopyRectangle(
_In_ const Image& srcImage, _In_ const Rect& srcRect, _In_ const Image& dstImage,
- _In_ DWORD filter, _In_ size_t xOffset, _In_ size_t yOffset);
+ _In_ TEX_FILTER_FLAGS filter, _In_ size_t xOffset, _In_ size_t yOffset) noexcept;
- enum CMSE_FLAGS
+ enum CMSE_FLAGS : unsigned long
{
- CMSE_DEFAULT = 0,
+ CMSE_DEFAULT = 0,
- CMSE_IMAGE1_SRGB = 0x1,
- CMSE_IMAGE2_SRGB = 0x2,
- // Indicates that image needs gamma correction before comparision
+ CMSE_IMAGE1_SRGB = 0x1,
+ CMSE_IMAGE2_SRGB = 0x2,
+ // Indicates that image needs gamma correction before comparision
- CMSE_IGNORE_RED = 0x10,
- CMSE_IGNORE_GREEN = 0x20,
- CMSE_IGNORE_BLUE = 0x40,
- CMSE_IGNORE_ALPHA = 0x80,
- // Ignore the channel when computing MSE
+ CMSE_IGNORE_RED = 0x10,
+ CMSE_IGNORE_GREEN = 0x20,
+ CMSE_IGNORE_BLUE = 0x40,
+ CMSE_IGNORE_ALPHA = 0x80,
+ // Ignore the channel when computing MSE
- CMSE_IMAGE1_X2_BIAS = 0x100,
- CMSE_IMAGE2_X2_BIAS = 0x200,
- // Indicates that image should be scaled and biased before comparison (i.e. UNORM -> SNORM)
+ CMSE_IMAGE1_X2_BIAS = 0x100,
+ CMSE_IMAGE2_X2_BIAS = 0x200,
+ // Indicates that image should be scaled and biased before comparison (i.e. UNORM -> SNORM)
};
- HRESULT __cdecl ComputeMSE(_In_ const Image& image1, _In_ const Image& image2, _Out_ float& mse, _Out_writes_opt_(4) float* mseV, _In_ DWORD flags = 0);
+ HRESULT __cdecl ComputeMSE(_In_ const Image& image1, _In_ const Image& image2, _Out_ float& mse, _Out_writes_opt_(4) float* mseV, _In_ CMSE_FLAGS flags = CMSE_DEFAULT) noexcept;
HRESULT __cdecl EvaluateImage(
_In_ const Image& image,
@@ -699,72 +915,89 @@ namespace DirectX
HRESULT __cdecl TransformImage(
_In_ const Image& image,
_In_ std::function pixelFunc,
+ _In_reads_(width) const XMVECTOR* inPixels, size_t width, size_t y)> pixelFunc,
ScratchImage& result);
HRESULT __cdecl TransformImage(
_In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
_In_ std::function pixelFunc,
+ _In_reads_(width) const XMVECTOR* inPixels, size_t width, size_t y)> pixelFunc,
ScratchImage& result);
//---------------------------------------------------------------------------------
// WIC utility code
-
+#ifdef _WIN32
enum WICCodecs
{
- WIC_CODEC_BMP = 1, // Windows Bitmap (.bmp)
+ WIC_CODEC_BMP = 1, // Windows Bitmap (.bmp)
WIC_CODEC_JPEG, // Joint Photographic Experts Group (.jpg, .jpeg)
WIC_CODEC_PNG, // Portable Network Graphics (.png)
WIC_CODEC_TIFF, // Tagged Image File Format (.tif, .tiff)
WIC_CODEC_GIF, // Graphics Interchange Format (.gif)
WIC_CODEC_WMP, // Windows Media Photo / HD Photo / JPEG XR (.hdp, .jxr, .wdp)
WIC_CODEC_ICO, // Windows Icon (.ico)
+ WIC_CODEC_HEIF, // High Efficiency Image File (.heif, .heic)
};
- REFGUID __cdecl GetWICCodec(_In_ WICCodecs codec);
+ REFGUID __cdecl GetWICCodec(_In_ WICCodecs codec) noexcept;
- IWICImagingFactory* __cdecl GetWICFactory(bool& iswic2);
- void __cdecl SetWICFactory(_In_opt_ IWICImagingFactory* pWIC);
-
- //---------------------------------------------------------------------------------
- // Direct3D 11 functions
-#if defined(__d3d11_h__) || defined(__d3d11_x_h__)
- bool __cdecl IsSupportedTexture(_In_ ID3D11Device* pDevice, _In_ const TexMetadata& metadata);
-
- HRESULT __cdecl CreateTexture(
- _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _Outptr_ ID3D11Resource** ppResource);
-
- HRESULT __cdecl CreateShaderResourceView(
- _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _Outptr_ ID3D11ShaderResourceView** ppSRV);
-
- HRESULT __cdecl CreateTextureEx(
- _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags, _In_ bool forceSRGB,
- _Outptr_ ID3D11Resource** ppResource);
-
- HRESULT __cdecl CreateShaderResourceViewEx(
- _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
- _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags, _In_ bool forceSRGB,
- _Outptr_ ID3D11ShaderResourceView** ppSRV);
-
- HRESULT __cdecl CaptureTexture(_In_ ID3D11Device* pDevice, _In_ ID3D11DeviceContext* pContext, _In_ ID3D11Resource* pSource, _Out_ ScratchImage& result);
+ IWICImagingFactory* __cdecl GetWICFactory(bool& iswic2) noexcept;
+ void __cdecl SetWICFactory(_In_opt_ IWICImagingFactory* pWIC) noexcept;
#endif
//---------------------------------------------------------------------------------
+ // DDS helper functions
+ HRESULT __cdecl EncodeDDSHeader(
+ _In_ const TexMetadata& metadata, DDS_FLAGS flags,
+ _Out_writes_bytes_to_opt_(maxsize, required) void* pDestination, _In_ size_t maxsize,
+ _Out_ size_t& required) noexcept;
+
+ //---------------------------------------------------------------------------------
+ // Direct3D interop
+
+ enum CREATETEX_FLAGS : uint32_t
+ {
+ CREATETEX_DEFAULT = 0,
+ CREATETEX_FORCE_SRGB = 0x1,
+ CREATETEX_IGNORE_SRGB = 0x2,
+ };
+
+ // Direct3D 11 functions
+#if defined(__d3d11_h__) || defined(__d3d11_x_h__)
+ bool __cdecl IsSupportedTexture(_In_ ID3D11Device* pDevice, _In_ const TexMetadata& metadata) noexcept;
+
+ HRESULT __cdecl CreateTexture(
+ _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _Outptr_ ID3D11Resource** ppResource) noexcept;
+
+ HRESULT __cdecl CreateShaderResourceView(
+ _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _Outptr_ ID3D11ShaderResourceView** ppSRV) noexcept;
+
+ HRESULT __cdecl CreateTextureEx(
+ _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags, _In_ CREATETEX_FLAGS flags,
+ _Outptr_ ID3D11Resource** ppResource) noexcept;
+
+ HRESULT __cdecl CreateShaderResourceViewEx(
+ _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata,
+ _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags, _In_ CREATETEX_FLAGS flags,
+ _Outptr_ ID3D11ShaderResourceView** ppSRV) noexcept;
+
+ HRESULT __cdecl CaptureTexture(_In_ ID3D11Device* pDevice, _In_ ID3D11DeviceContext* pContext, _In_ ID3D11Resource* pSource, _Out_ ScratchImage& result) noexcept;
+#endif
+
// Direct3D 12 functions
-#if defined(__d3d12_h__) || defined(__d3d12_x_h__)
- bool __cdecl IsSupportedTexture(_In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata);
+#if defined(__d3d12_h__) || defined(__d3d12_x_h__) || defined(__XBOX_D3D12_X__)
+ bool __cdecl IsSupportedTexture(_In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata) noexcept;
HRESULT __cdecl CreateTexture(
_In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata,
- _Outptr_ ID3D12Resource** ppResource);
+ _Outptr_ ID3D12Resource** ppResource) noexcept;
HRESULT __cdecl CreateTextureEx(
_In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata,
- _In_ D3D12_RESOURCE_FLAGS resFlags, _In_ bool forceSRGB,
- _Outptr_ ID3D12Resource** ppResource);
+ _In_ D3D12_RESOURCE_FLAGS resFlags, _In_ CREATETEX_FLAGS flags,
+ _Outptr_ ID3D12Resource** ppResource) noexcept;
HRESULT __cdecl PrepareUpload(
_In_ ID3D12Device* pDevice,
@@ -775,9 +1008,29 @@ namespace DirectX
_In_ ID3D12CommandQueue* pCommandQueue, _In_ ID3D12Resource* pSource, _In_ bool isCubeMap,
_Out_ ScratchImage& result,
_In_ D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_RENDER_TARGET,
- _In_ D3D12_RESOURCE_STATES afterState = D3D12_RESOURCE_STATE_RENDER_TARGET);
+ _In_ D3D12_RESOURCE_STATES afterState = D3D12_RESOURCE_STATE_RENDER_TARGET) noexcept;
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcovered-switch-default"
+#pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
+#pragma clang diagnostic ignored "-Wswitch-enum"
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4619 4616 4061)
#endif
#include "DirectXTex.inl"
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
} // namespace
diff --git a/Source/ThirdParty/DirectXTex/DirectXTex.inl b/Source/ThirdParty/DirectXTex/DirectXTex.inl
index a45abcd9e..0b8be4c48 100644
--- a/Source/ThirdParty/DirectXTex/DirectXTex.inl
+++ b/Source/ThirdParty/DirectXTex/DirectXTex.inl
@@ -1,9 +1,9 @@
//-------------------------------------------------------------------------------------
// DirectXTex.inl
-//
+//
// DirectX Texture Library
//
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
@@ -11,81 +11,109 @@
#pragma once
+//=====================================================================================
+// Bitmask flags enumerator operators
+//=====================================================================================
+DEFINE_ENUM_FLAG_OPERATORS(CP_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(DDS_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(TGA_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(WIC_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(TEX_FR_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(TEX_FILTER_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(TEX_PMALPHA_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(TEX_COMPRESS_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(CNMAP_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(CMSE_FLAGS);
+DEFINE_ENUM_FLAG_OPERATORS(CREATETEX_FLAGS);
+
+// WIC_FILTER modes match TEX_FILTER modes
+constexpr WIC_FLAGS operator|(WIC_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast(static_cast(a) | static_cast(b & TEX_FILTER_MODE_MASK)); }
+constexpr WIC_FLAGS operator|(TEX_FILTER_FLAGS a, WIC_FLAGS b) { return static_cast(static_cast(a & TEX_FILTER_MODE_MASK) | static_cast(b)); }
+
+// TEX_PMALPHA_SRGB match TEX_FILTER_SRGB
+constexpr TEX_PMALPHA_FLAGS operator|(TEX_PMALPHA_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast(static_cast(a) | static_cast(b & TEX_FILTER_SRGB_MASK)); }
+constexpr TEX_PMALPHA_FLAGS operator|(TEX_FILTER_FLAGS a, TEX_PMALPHA_FLAGS b) { return static_cast(static_cast(a & TEX_FILTER_SRGB_MASK) | static_cast(b)); }
+
+// TEX_COMPRESS_SRGB match TEX_FILTER_SRGB
+constexpr TEX_COMPRESS_FLAGS operator|(TEX_COMPRESS_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast(static_cast(a) | static_cast(b & TEX_FILTER_SRGB_MASK)); }
+constexpr TEX_COMPRESS_FLAGS operator|(TEX_FILTER_FLAGS a, TEX_COMPRESS_FLAGS b) { return static_cast(static_cast(a & TEX_FILTER_SRGB_MASK) | static_cast(b)); }
+
+
//=====================================================================================
// DXGI Format Utilities
//=====================================================================================
_Use_decl_annotations_
-inline bool __cdecl IsValid(DXGI_FORMAT fmt)
+constexpr bool __cdecl IsValid(DXGI_FORMAT fmt) noexcept
{
- return (static_cast(fmt) >= 1 && static_cast(fmt) <= 190);
+ return (static_cast(fmt) >= 1 && static_cast(fmt) <= 191);
}
_Use_decl_annotations_
-inline bool __cdecl IsCompressed(DXGI_FORMAT fmt)
+inline bool __cdecl IsCompressed(DXGI_FORMAT fmt) noexcept
{
switch (fmt)
{
- case DXGI_FORMAT_BC1_TYPELESS:
- case DXGI_FORMAT_BC1_UNORM:
- case DXGI_FORMAT_BC1_UNORM_SRGB:
- case DXGI_FORMAT_BC2_TYPELESS:
- case DXGI_FORMAT_BC2_UNORM:
- case DXGI_FORMAT_BC2_UNORM_SRGB:
- case DXGI_FORMAT_BC3_TYPELESS:
- case DXGI_FORMAT_BC3_UNORM:
- case DXGI_FORMAT_BC3_UNORM_SRGB:
- case DXGI_FORMAT_BC4_TYPELESS:
- case DXGI_FORMAT_BC4_UNORM:
- case DXGI_FORMAT_BC4_SNORM:
- case DXGI_FORMAT_BC5_TYPELESS:
- case DXGI_FORMAT_BC5_UNORM:
- case DXGI_FORMAT_BC5_SNORM:
- case DXGI_FORMAT_BC6H_TYPELESS:
- case DXGI_FORMAT_BC6H_UF16:
- case DXGI_FORMAT_BC6H_SF16:
- case DXGI_FORMAT_BC7_TYPELESS:
- case DXGI_FORMAT_BC7_UNORM:
- case DXGI_FORMAT_BC7_UNORM_SRGB:
- return true;
+ case DXGI_FORMAT_BC1_TYPELESS:
+ case DXGI_FORMAT_BC1_UNORM:
+ case DXGI_FORMAT_BC1_UNORM_SRGB:
+ case DXGI_FORMAT_BC2_TYPELESS:
+ case DXGI_FORMAT_BC2_UNORM:
+ case DXGI_FORMAT_BC2_UNORM_SRGB:
+ case DXGI_FORMAT_BC3_TYPELESS:
+ case DXGI_FORMAT_BC3_UNORM:
+ case DXGI_FORMAT_BC3_UNORM_SRGB:
+ case DXGI_FORMAT_BC4_TYPELESS:
+ case DXGI_FORMAT_BC4_UNORM:
+ case DXGI_FORMAT_BC4_SNORM:
+ case DXGI_FORMAT_BC5_TYPELESS:
+ case DXGI_FORMAT_BC5_UNORM:
+ case DXGI_FORMAT_BC5_SNORM:
+ case DXGI_FORMAT_BC6H_TYPELESS:
+ case DXGI_FORMAT_BC6H_UF16:
+ case DXGI_FORMAT_BC6H_SF16:
+ case DXGI_FORMAT_BC7_TYPELESS:
+ case DXGI_FORMAT_BC7_UNORM:
+ case DXGI_FORMAT_BC7_UNORM_SRGB:
+ return true;
- default:
- return false;
+ default:
+ return false;
}
}
_Use_decl_annotations_
-inline bool __cdecl IsPalettized(DXGI_FORMAT fmt)
+inline bool __cdecl IsPalettized(DXGI_FORMAT fmt) noexcept
{
switch (fmt)
{
- case DXGI_FORMAT_AI44:
- case DXGI_FORMAT_IA44:
- case DXGI_FORMAT_P8:
- case DXGI_FORMAT_A8P8:
- return true;
+ case DXGI_FORMAT_AI44:
+ case DXGI_FORMAT_IA44:
+ case DXGI_FORMAT_P8:
+ case DXGI_FORMAT_A8P8:
+ return true;
- default:
- return false;
+ default:
+ return false;
}
}
_Use_decl_annotations_
-inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
+inline bool __cdecl IsSRGB(DXGI_FORMAT fmt) noexcept
{
switch (fmt)
{
- case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
- case DXGI_FORMAT_BC1_UNORM_SRGB:
- case DXGI_FORMAT_BC2_UNORM_SRGB:
- case DXGI_FORMAT_BC3_UNORM_SRGB:
- case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
- case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
- case DXGI_FORMAT_BC7_UNORM_SRGB:
- return true;
+ case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ case DXGI_FORMAT_BC1_UNORM_SRGB:
+ case DXGI_FORMAT_BC2_UNORM_SRGB:
+ case DXGI_FORMAT_BC3_UNORM_SRGB:
+ case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+ case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
+ case DXGI_FORMAT_BC7_UNORM_SRGB:
+ return true;
- default:
- return false;
+ default:
+ return false;
}
}
@@ -94,7 +122,7 @@ inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
// Image I/O
//=====================================================================================
_Use_decl_annotations_
-inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& blob)
+inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DDS_FLAGS flags, Blob& blob) noexcept
{
TexMetadata mdata = {};
mdata.width = image.width;
@@ -109,7 +137,7 @@ inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& bl
}
_Use_decl_annotations_
-inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wchar_t* szFile)
+inline HRESULT __cdecl SaveToDDSFile(const Image& image, DDS_FLAGS flags, const wchar_t* szFile) noexcept
{
TexMetadata mdata = {};
mdata.width = image.width;
@@ -122,3 +150,43 @@ inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wcha
return SaveToDDSFile(&image, 1, mdata, flags, szFile);
}
+
+
+//=====================================================================================
+// Compatability helpers
+//=====================================================================================
+_Use_decl_annotations_
+inline HRESULT __cdecl GetMetadataFromTGAMemory(const void* pSource, size_t size, TexMetadata& metadata) noexcept
+{
+ return GetMetadataFromTGAMemory(pSource, size, TGA_FLAGS_NONE, metadata);
+}
+
+_Use_decl_annotations_
+inline HRESULT __cdecl GetMetadataFromTGAFile(const wchar_t* szFile, TexMetadata& metadata) noexcept
+{
+ return GetMetadataFromTGAFile(szFile, TGA_FLAGS_NONE, metadata);
+}
+
+_Use_decl_annotations_
+inline HRESULT __cdecl LoadFromTGAMemory(const void* pSource, size_t size, TexMetadata* metadata, ScratchImage& image) noexcept
+{
+ return LoadFromTGAMemory(pSource, size, TGA_FLAGS_NONE, metadata, image);
+}
+
+_Use_decl_annotations_
+inline HRESULT __cdecl LoadFromTGAFile(const wchar_t* szFile, TexMetadata* metadata, ScratchImage& image) noexcept
+{
+ return LoadFromTGAFile(szFile, TGA_FLAGS_NONE, metadata, image);
+}
+
+_Use_decl_annotations_
+inline HRESULT __cdecl SaveToTGAMemory(const Image& image, Blob& blob, const TexMetadata* metadata) noexcept
+{
+ return SaveToTGAMemory(image, TGA_FLAGS_NONE, blob, metadata);
+}
+
+_Use_decl_annotations_
+inline HRESULT __cdecl SaveToTGAFile(const Image& image, const wchar_t* szFile, const TexMetadata* metadata) noexcept
+{
+ return SaveToTGAFile(image, TGA_FLAGS_NONE, szFile, metadata);
+}
diff --git a/Source/ThirdParty/DirectXTex/LICENSE b/Source/ThirdParty/DirectXTex/LICENSE
index 48bda89b2..9e841e7a2 100644
--- a/Source/ThirdParty/DirectXTex/LICENSE
+++ b/Source/ThirdParty/DirectXTex/LICENSE
@@ -1,21 +1,21 @@
- The MIT License (MIT)
+ MIT License
-Copyright (c) 2011-2019 Microsoft Corp
+ Copyright (c) Microsoft Corporation.
-Permission is hereby granted, free of charge, to any person obtaining a copy of this
-software and associated documentation files (the "Software"), to deal in the Software
-without restriction, including without limitation the rights to use, copy, modify,
-merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to the following
-conditions:
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies
-or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
diff --git a/Source/ThirdParty/tinyexr/LICENSE b/Source/ThirdParty/tinyexr/LICENSE
new file mode 100644
index 000000000..85e20299a
--- /dev/null
+++ b/Source/ThirdParty/tinyexr/LICENSE
@@ -0,0 +1,5 @@
+3-clause BSD
+
+tinyexr uses miniz, which is developed by Rich Geldreich richgel99@gmail.com, and licensed under public domain.
+
+tinyexr tools uses stb, which is licensed under public domain: https://github.com/nothings/stb tinyexr uses some code from OpenEXR, which is licensed under 3-clause BSD license. tinyexr uses nanozlib and wuffs, whose are licensed unnder Apache 2.0 license.
\ No newline at end of file
diff --git a/Source/ThirdParty/tinyexr/tinyexr.Build.cs b/Source/ThirdParty/tinyexr/tinyexr.Build.cs
new file mode 100644
index 000000000..3499f6ed0
--- /dev/null
+++ b/Source/ThirdParty/tinyexr/tinyexr.Build.cs
@@ -0,0 +1,22 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
+using Flax.Build;
+using Flax.Build.NativeCpp;
+
+///
+/// https://github.com/syoyo/tinyexr
+///
+public class tinyexr : HeaderOnlyModule
+{
+ ///
+ public override void Init()
+ {
+ base.Init();
+
+ LicenseType = LicenseTypes.BSD3Clause;
+ LicenseFilePath = "LICENSE";
+
+ // Merge third-party modules into engine binary
+ BinaryModuleName = "FlaxEngine";
+ }
+}
diff --git a/Source/ThirdParty/tinyexr/tinyexr.h b/Source/ThirdParty/tinyexr/tinyexr.h
new file mode 100644
index 000000000..929c3f253
--- /dev/null
+++ b/Source/ThirdParty/tinyexr/tinyexr.h
@@ -0,0 +1,9307 @@
+#ifndef TINYEXR_H_
+#define TINYEXR_H_
+/*
+Copyright (c) 2014 - 2021, Syoyo Fujita and many contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the Syoyo Fujita nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// TinyEXR contains some OpenEXR code, which is licensed under ------------
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
+// Digital Ltd. LLC
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Industrial Light & Magic nor the names of
+// its contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+///////////////////////////////////////////////////////////////////////////
+
+// End of OpenEXR license -------------------------------------------------
+
+
+//
+//
+// Do this:
+// #define TINYEXR_IMPLEMENTATION
+// before you include this file in *one* C or C++ file to create the
+// implementation.
+//
+// // i.e. it should look like this:
+// #include ...
+// #include ...
+// #include ...
+// #define TINYEXR_IMPLEMENTATION
+// #include "tinyexr.h"
+//
+//
+
+#include // for size_t
+#include // guess stdint.h is available(C99)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \
+ defined(__i386) || defined(__i486__) || defined(__i486) || \
+ defined(i386) || defined(__ia64__) || defined(__x86_64__)
+#define TINYEXR_X86_OR_X64_CPU 1
+#else
+#define TINYEXR_X86_OR_X64_CPU 0
+#endif
+
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || TINYEXR_X86_OR_X64_CPU
+#define TINYEXR_LITTLE_ENDIAN 1
+#else
+#define TINYEXR_LITTLE_ENDIAN 0
+#endif
+
+// Use miniz or not to decode ZIP format pixel. Linking with zlib
+// required if this flag is 0 and TINYEXR_USE_STB_ZLIB is 0.
+#ifndef TINYEXR_USE_MINIZ
+#define TINYEXR_USE_MINIZ (1)
+#endif
+
+// Use the ZIP implementation of stb_image.h and stb_image_write.h.
+#ifndef TINYEXR_USE_STB_ZLIB
+#define TINYEXR_USE_STB_ZLIB (0)
+#endif
+
+// Use nanozlib.
+#ifndef TINYEXR_USE_NANOZLIB
+#define TINYEXR_USE_NANOZLIB (0)
+#endif
+
+// Disable PIZ compression when applying cpplint.
+#ifndef TINYEXR_USE_PIZ
+#define TINYEXR_USE_PIZ (1)
+#endif
+
+#ifndef TINYEXR_USE_ZFP
+#define TINYEXR_USE_ZFP (0) // TinyEXR extension.
+// http://computation.llnl.gov/projects/floating-point-compression
+#endif
+
+#ifndef TINYEXR_USE_THREAD
+#define TINYEXR_USE_THREAD (0) // No threaded loading.
+// http://computation.llnl.gov/projects/floating-point-compression
+#endif
+
+#ifndef TINYEXR_USE_OPENMP
+#ifdef _OPENMP
+#define TINYEXR_USE_OPENMP (1)
+#else
+#define TINYEXR_USE_OPENMP (0)
+#endif
+#endif
+
+#define TINYEXR_SUCCESS (0)
+#define TINYEXR_ERROR_INVALID_MAGIC_NUMBER (-1)
+#define TINYEXR_ERROR_INVALID_EXR_VERSION (-2)
+#define TINYEXR_ERROR_INVALID_ARGUMENT (-3)
+#define TINYEXR_ERROR_INVALID_DATA (-4)
+#define TINYEXR_ERROR_INVALID_FILE (-5)
+#define TINYEXR_ERROR_INVALID_PARAMETER (-6)
+#define TINYEXR_ERROR_CANT_OPEN_FILE (-7)
+#define TINYEXR_ERROR_UNSUPPORTED_FORMAT (-8)
+#define TINYEXR_ERROR_INVALID_HEADER (-9)
+#define TINYEXR_ERROR_UNSUPPORTED_FEATURE (-10)
+#define TINYEXR_ERROR_CANT_WRITE_FILE (-11)
+#define TINYEXR_ERROR_SERIALIZATION_FAILED (-12)
+#define TINYEXR_ERROR_LAYER_NOT_FOUND (-13)
+#define TINYEXR_ERROR_DATA_TOO_LARGE (-14)
+
+// @note { OpenEXR file format: http://www.openexr.com/openexrfilelayout.pdf }
+
+// pixel type: possible values are: UINT = 0 HALF = 1 FLOAT = 2
+#define TINYEXR_PIXELTYPE_UINT (0)
+#define TINYEXR_PIXELTYPE_HALF (1)
+#define TINYEXR_PIXELTYPE_FLOAT (2)
+
+#define TINYEXR_MAX_HEADER_ATTRIBUTES (1024)
+#define TINYEXR_MAX_CUSTOM_ATTRIBUTES (128)
+
+#define TINYEXR_COMPRESSIONTYPE_NONE (0)
+#define TINYEXR_COMPRESSIONTYPE_RLE (1)
+#define TINYEXR_COMPRESSIONTYPE_ZIPS (2)
+#define TINYEXR_COMPRESSIONTYPE_ZIP (3)
+#define TINYEXR_COMPRESSIONTYPE_PIZ (4)
+#define TINYEXR_COMPRESSIONTYPE_ZFP (128) // TinyEXR extension
+
+#define TINYEXR_ZFP_COMPRESSIONTYPE_RATE (0)
+#define TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION (1)
+#define TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY (2)
+
+#define TINYEXR_TILE_ONE_LEVEL (0)
+#define TINYEXR_TILE_MIPMAP_LEVELS (1)
+#define TINYEXR_TILE_RIPMAP_LEVELS (2)
+
+#define TINYEXR_TILE_ROUND_DOWN (0)
+#define TINYEXR_TILE_ROUND_UP (1)
+
+typedef struct TEXRVersion {
+ int version; // this must be 2
+ // tile format image;
+ // not zero for only a single-part "normal" tiled file (according to spec.)
+ int tiled;
+ int long_name; // long name attribute
+ // deep image(EXR 2.0);
+ // for a multi-part file, indicates that at least one part is of type deep* (according to spec.)
+ int non_image;
+ int multipart; // multi-part(EXR 2.0)
+} EXRVersion;
+
+typedef struct TEXRAttribute {
+ char name[256]; // name and type are up to 255 chars long.
+ char type[256];
+ unsigned char *value; // uint8_t*
+ int size;
+ int pad0;
+} EXRAttribute;
+
+typedef struct TEXRChannelInfo {
+ char name[256]; // less than 255 bytes long
+ int pixel_type;
+ int x_sampling;
+ int y_sampling;
+ unsigned char p_linear;
+ unsigned char pad[3];
+} EXRChannelInfo;
+
+typedef struct TEXRTile {
+ int offset_x;
+ int offset_y;
+ int level_x;
+ int level_y;
+
+ int width; // actual width in a tile.
+ int height; // actual height int a tile.
+
+ unsigned char **images; // image[channels][pixels]
+} EXRTile;
+
+typedef struct TEXRBox2i {
+ int min_x;
+ int min_y;
+ int max_x;
+ int max_y;
+} EXRBox2i;
+
+typedef struct TEXRHeader {
+ float pixel_aspect_ratio;
+ int line_order;
+ EXRBox2i data_window;
+ EXRBox2i display_window;
+ float screen_window_center[2];
+ float screen_window_width;
+
+ int chunk_count;
+
+ // Properties for tiled format(`tiledesc`).
+ int tiled;
+ int tile_size_x;
+ int tile_size_y;
+ int tile_level_mode;
+ int tile_rounding_mode;
+
+ int long_name;
+ // for a single-part file, agree with the version field bit 11
+ // for a multi-part file, it is consistent with the type of part
+ int non_image;
+ int multipart;
+ unsigned int header_len;
+
+ // Custom attributes(exludes required attributes(e.g. `channels`,
+ // `compression`, etc)
+ int num_custom_attributes;
+ EXRAttribute *custom_attributes; // array of EXRAttribute. size =
+ // `num_custom_attributes`.
+
+ EXRChannelInfo *channels; // [num_channels]
+
+ int *pixel_types; // Loaded pixel type(TINYEXR_PIXELTYPE_*) of `images` for
+ // each channel. This is overwritten with `requested_pixel_types` when
+ // loading.
+ int num_channels;
+
+ int compression_type; // compression type(TINYEXR_COMPRESSIONTYPE_*)
+ int *requested_pixel_types; // Filled initially by
+ // ParseEXRHeaderFrom(Meomory|File), then users
+ // can edit it(only valid for HALF pixel type
+ // channel)
+ // name attribute required for multipart files;
+ // must be unique and non empty (according to spec.);
+ // use EXRSetNameAttr for setting value;
+ // max 255 character allowed - excluding terminating zero
+ char name[256];
+} EXRHeader;
+
+typedef struct TEXRMultiPartHeader {
+ int num_headers;
+ EXRHeader *headers;
+
+} EXRMultiPartHeader;
+
+typedef struct TEXRImage {
+ EXRTile *tiles; // Tiled pixel data. The application must reconstruct image
+ // from tiles manually. NULL if scanline format.
+ struct TEXRImage* next_level; // NULL if scanline format or image is the last level.
+ int level_x; // x level index
+ int level_y; // y level index
+
+ unsigned char **images; // image[channels][pixels]. NULL if tiled format.
+
+ int width;
+ int height;
+ int num_channels;
+
+ // Properties for tile format.
+ int num_tiles;
+
+} EXRImage;
+
+typedef struct TEXRMultiPartImage {
+ int num_images;
+ EXRImage *images;
+
+} EXRMultiPartImage;
+
+typedef struct TDeepImage {
+ const char **channel_names;
+ float ***image; // image[channels][scanlines][samples]
+ int **offset_table; // offset_table[scanline][offsets]
+ int num_channels;
+ int width;
+ int height;
+ int pad0;
+} DeepImage;
+
+// @deprecated { For backward compatibility. Not recommended to use. }
+// Loads single-frame OpenEXR image. Assume EXR image contains A(single channel
+// alpha) or RGB(A) channels.
+// Application must free image data as returned by `out_rgba`
+// Result image format is: float x RGBA x width x hight
+// Returns negative value and may set error string in `err` when there's an
+// error
+extern int LoadEXR(float **out_rgba, int *width, int *height,
+ const char *filename, const char **err);
+
+// Loads single-frame OpenEXR image by specifying layer name. Assume EXR image
+// contains A(single channel alpha) or RGB(A) channels. Application must free
+// image data as returned by `out_rgba` Result image format is: float x RGBA x
+// width x hight Returns negative value and may set error string in `err` when
+// there's an error When the specified layer name is not found in the EXR file,
+// the function will return `TINYEXR_ERROR_LAYER_NOT_FOUND`.
+extern int LoadEXRWithLayer(float **out_rgba, int *width, int *height,
+ const char *filename, const char *layer_name,
+ const char **err);
+
+//
+// Get layer infos from EXR file.
+//
+// @param[out] layer_names List of layer names. Application must free memory
+// after using this.
+// @param[out] num_layers The number of layers
+// @param[out] err Error string(will be filled when the function returns error
+// code). Free it using FreeEXRErrorMessage after using this value.
+//
+// @return TINYEXR_SUCCEES upon success.
+//
+extern int EXRLayers(const char *filename, const char **layer_names[],
+ int *num_layers, const char **err);
+
+// @deprecated
+// Simple wrapper API for ParseEXRHeaderFromFile.
+// checking given file is a EXR file(by just look up header)
+// @return TINYEXR_SUCCEES for EXR image, TINYEXR_ERROR_INVALID_HEADER for
+// others
+extern int IsEXR(const char *filename);
+
+// Simple wrapper API for ParseEXRHeaderFromMemory.
+// Check if given data is a EXR image(by just looking up a header section)
+// @return TINYEXR_SUCCEES for EXR image, TINYEXR_ERROR_INVALID_HEADER for
+// others
+extern int IsEXRFromMemory(const unsigned char *memory, size_t size);
+
+// @deprecated
+// Saves single-frame OpenEXR image to a buffer. Assume EXR image contains RGB(A) channels.
+// components must be 1(Grayscale), 3(RGB) or 4(RGBA).
+// Input image format is: `float x width x height`, or `float x RGB(A) x width x
+// hight`
+// Save image as fp16(HALF) format when `save_as_fp16` is positive non-zero
+// value.
+// Save image as fp32(FLOAT) format when `save_as_fp16` is 0.
+// Use ZIP compression by default.
+// `buffer` is the pointer to write EXR data.
+// Memory for `buffer` is allocated internally in SaveEXRToMemory.
+// Returns the data size of EXR file when the value is positive(up to 2GB EXR data).
+// Returns negative value and may set error string in `err` when there's an
+// error
+extern int SaveEXRToMemory(const float *data, const int width, const int height,
+ const int components, const int save_as_fp16,
+ unsigned char **buffer, const char **err);
+
+// @deprecated { Not recommended, but handy to use. }
+// Saves single-frame OpenEXR image to a buffer. Assume EXR image contains RGB(A) channels.
+// components must be 1(Grayscale), 3(RGB) or 4(RGBA).
+// Input image format is: `float x width x height`, or `float x RGB(A) x width x
+// hight`
+// Save image as fp16(HALF) format when `save_as_fp16` is positive non-zero
+// value.
+// Save image as fp32(FLOAT) format when `save_as_fp16` is 0.
+// Use ZIP compression by default.
+// Returns TINYEXR_SUCCEES(0) when success.
+// Returns negative value and may set error string in `err` when there's an
+// error
+extern int SaveEXR(const float *data, const int width, const int height,
+ const int components, const int save_as_fp16,
+ const char *filename, const char **err);
+
+// Returns the number of resolution levels of the image (including the base)
+extern int EXRNumLevels(const EXRImage* exr_image);
+
+// Initialize EXRHeader struct
+extern void InitEXRHeader(EXRHeader *exr_header);
+
+// Set name attribute of EXRHeader struct (it makes a copy)
+extern void EXRSetNameAttr(EXRHeader *exr_header, const char* name);
+
+// Initialize EXRImage struct
+extern void InitEXRImage(EXRImage *exr_image);
+
+// Frees internal data of EXRHeader struct
+extern int FreeEXRHeader(EXRHeader *exr_header);
+
+// Frees internal data of EXRImage struct
+extern int FreeEXRImage(EXRImage *exr_image);
+
+// Frees error message
+extern void FreeEXRErrorMessage(const char *msg);
+
+// Parse EXR version header of a file.
+extern int ParseEXRVersionFromFile(EXRVersion *version, const char *filename);
+
+// Parse EXR version header from memory-mapped EXR data.
+extern int ParseEXRVersionFromMemory(EXRVersion *version,
+ const unsigned char *memory, size_t size);
+
+// Parse single-part OpenEXR header from a file and initialize `EXRHeader`.
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int ParseEXRHeaderFromFile(EXRHeader *header, const EXRVersion *version,
+ const char *filename, const char **err);
+
+// Parse single-part OpenEXR header from a memory and initialize `EXRHeader`.
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int ParseEXRHeaderFromMemory(EXRHeader *header,
+ const EXRVersion *version,
+ const unsigned char *memory, size_t size,
+ const char **err);
+
+// Parse multi-part OpenEXR headers from a file and initialize `EXRHeader*`
+// array.
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int ParseEXRMultipartHeaderFromFile(EXRHeader ***headers,
+ int *num_headers,
+ const EXRVersion *version,
+ const char *filename,
+ const char **err);
+
+// Parse multi-part OpenEXR headers from a memory and initialize `EXRHeader*`
+// array
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int ParseEXRMultipartHeaderFromMemory(EXRHeader ***headers,
+ int *num_headers,
+ const EXRVersion *version,
+ const unsigned char *memory,
+ size_t size, const char **err);
+
+// Loads single-part OpenEXR image from a file.
+// Application must setup `ParseEXRHeaderFromFile` before calling this function.
+// Application can free EXRImage using `FreeEXRImage`
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header,
+ const char *filename, const char **err);
+
+// Loads single-part OpenEXR image from a memory.
+// Application must setup `EXRHeader` with
+// `ParseEXRHeaderFromMemory` before calling this function.
+// Application can free EXRImage using `FreeEXRImage`
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header,
+ const unsigned char *memory,
+ const size_t size, const char **err);
+
+// Loads multi-part OpenEXR image from a file.
+// Application must setup `ParseEXRMultipartHeaderFromFile` before calling this
+// function.
+// Application can free EXRImage using `FreeEXRImage`
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int LoadEXRMultipartImageFromFile(EXRImage *images,
+ const EXRHeader **headers,
+ unsigned int num_parts,
+ const char *filename,
+ const char **err);
+
+// Loads multi-part OpenEXR image from a memory.
+// Application must setup `EXRHeader*` array with
+// `ParseEXRMultipartHeaderFromMemory` before calling this function.
+// Application can free EXRImage using `FreeEXRImage`
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int LoadEXRMultipartImageFromMemory(EXRImage *images,
+ const EXRHeader **headers,
+ unsigned int num_parts,
+ const unsigned char *memory,
+ const size_t size, const char **err);
+
+// Saves multi-channel, single-frame OpenEXR image to a file.
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int SaveEXRImageToFile(const EXRImage *image,
+ const EXRHeader *exr_header, const char *filename,
+ const char **err);
+
+// Saves multi-channel, single-frame OpenEXR image to a memory.
+// Image is compressed using EXRImage.compression value.
+// Return the number of bytes if success.
+// Return zero and will set error string in `err` when there's an
+// error.
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern size_t SaveEXRImageToMemory(const EXRImage *image,
+ const EXRHeader *exr_header,
+ unsigned char **memory, const char **err);
+
+// Saves multi-channel, multi-frame OpenEXR image to a memory.
+// Image is compressed using EXRImage.compression value.
+// File global attributes (eg. display_window) must be set in the first header.
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int SaveEXRMultipartImageToFile(const EXRImage *images,
+ const EXRHeader **exr_headers,
+ unsigned int num_parts,
+ const char *filename, const char **err);
+
+// Saves multi-channel, multi-frame OpenEXR image to a memory.
+// Image is compressed using EXRImage.compression value.
+// File global attributes (eg. display_window) must be set in the first header.
+// Return the number of bytes if success.
+// Return zero and will set error string in `err` when there's an
+// error.
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern size_t SaveEXRMultipartImageToMemory(const EXRImage *images,
+ const EXRHeader **exr_headers,
+ unsigned int num_parts,
+ unsigned char **memory, const char **err);
+// Loads single-frame OpenEXR deep image.
+// Application must free memory of variables in DeepImage(image, offset_table)
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int LoadDeepEXR(DeepImage *out_image, const char *filename,
+ const char **err);
+
+// NOT YET IMPLEMENTED:
+// Saves single-frame OpenEXR deep image.
+// Returns negative value and may set error string in `err` when there's an
+// error
+// extern int SaveDeepEXR(const DeepImage *in_image, const char *filename,
+// const char **err);
+
+// NOT YET IMPLEMENTED:
+// Loads multi-part OpenEXR deep image.
+// Application must free memory of variables in DeepImage(image, offset_table)
+// extern int LoadMultiPartDeepEXR(DeepImage **out_image, int num_parts, const
+// char *filename,
+// const char **err);
+
+// For emscripten.
+// Loads single-frame OpenEXR image from memory. Assume EXR image contains
+// RGB(A) channels.
+// Returns negative value and may set error string in `err` when there's an
+// error
+// When there was an error message, Application must free `err` with
+// FreeEXRErrorMessage()
+extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height,
+ const unsigned char *memory, size_t size,
+ const char **err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TINYEXR_H_
+
+#ifdef TINYEXR_IMPLEMENTATION
+#ifndef TINYEXR_IMPLEMENTATION_DEFINED
+#define TINYEXR_IMPLEMENTATION_DEFINED
+
+#ifdef _WIN32
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include // for UTF-8 and memory-mapping
+
+#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
+#define TINYEXR_USE_WIN32_MMAP (1)
+#endif
+
+#elif defined(__linux__) || defined(__unix__)
+#include // for open()
+#include // for memory-mapping
+#include // for stat
+#include // for close()
+#define TINYEXR_USE_POSIX_MMAP (1)
+#endif
+
+#include
+#include
+#include
+#include
+#include
+
+//#include // debug
+
+#include
+#include
+#include
+#include
+
+// https://stackoverflow.com/questions/5047971/how-do-i-check-for-c11-support
+#if __cplusplus > 199711L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+#define TINYEXR_HAS_CXX11 (1)
+// C++11
+#include
+
+#if TINYEXR_USE_THREAD
+#include
+#include
+#endif
+
+#else // __cplusplus > 199711L
+#define TINYEXR_HAS_CXX11 (0)
+#endif // __cplusplus > 199711L
+
+#if TINYEXR_USE_OPENMP
+#include
+#endif
+
+#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
+#include
+#else
+// Issue #46. Please include your own zlib-compatible API header before
+// including `tinyexr.h`
+//#include "zlib.h"
+#endif
+
+#if defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB==1)
+#define NANOZLIB_IMPLEMENTATION
+#include "nanozlib.h"
+#endif
+
+#if TINYEXR_USE_STB_ZLIB
+// Since we don't know where a project has stb_image.h and stb_image_write.h
+// and whether they are in the include path, we don't include them here, and
+// instead declare the two relevant functions manually.
+// from stb_image.h:
+extern "C" int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+// from stb_image_write.h:
+extern "C" unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality);
+#endif
+
+
+#if TINYEXR_USE_ZFP
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Weverything"
+#endif
+
+#include "zfp.h"
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif
+
+// cond: conditional expression
+// msg: std::string
+// err: std::string*
+#define TINYEXR_CHECK_AND_RETURN_MSG(cond, msg, err) do { \
+ if (!(cond)) { \
+ if (!err) { \
+ std::ostringstream ss_e; \
+ ss_e << __func__ << "():" << __LINE__ << msg << "\n"; \
+ (*err) += ss_e.str(); \
+ } \
+ return false;\
+ } \
+ } while(0)
+
+// no error message.
+#define TINYEXR_CHECK_AND_RETURN_C(cond, retcode) do { \
+ if (!(cond)) { \
+ return retcode; \
+ } \
+ } while(0)
+
+namespace tinyexr {
+
+#if __cplusplus > 199711L
+// C++11
+typedef uint64_t tinyexr_uint64;
+typedef int64_t tinyexr_int64;
+#else
+// Although `long long` is not a standard type pre C++11, assume it is defined
+// as a compiler's extension.
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++11-long-long"
+#endif
+typedef unsigned long long tinyexr_uint64;
+typedef long long tinyexr_int64;
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+#endif
+
+// static bool IsBigEndian(void) {
+// union {
+// unsigned int i;
+// char c[4];
+// } bint = {0x01020304};
+//
+// return bint.c[0] == 1;
+//}
+
+static void SetErrorMessage(const std::string &msg, const char **err) {
+ if (err) {
+#ifdef _WIN32
+ (*err) = _strdup(msg.c_str());
+#else
+ (*err) = strdup(msg.c_str());
+#endif
+ }
+}
+
+#if 0
+static void SetWarningMessage(const std::string &msg, const char **warn) {
+ if (warn) {
+#ifdef _WIN32
+ (*warn) = _strdup(msg.c_str());
+#else
+ (*warn) = strdup(msg.c_str());
+#endif
+ }
+}
+#endif
+
+static const int kEXRVersionSize = 8;
+
+static void cpy2(unsigned short *dst_val, const unsigned short *src_val) {
+ unsigned char *dst = reinterpret_cast(dst_val);
+ const unsigned char *src = reinterpret_cast(src_val);
+
+ dst[0] = src[0];
+ dst[1] = src[1];
+}
+
+static void swap2(unsigned short *val) {
+#if TINYEXR_LITTLE_ENDIAN
+ (void)val;
+#else
+ unsigned short tmp = *val;
+ unsigned char *dst = reinterpret_cast(val);
+ unsigned char *src = reinterpret_cast(&tmp);
+
+ dst[0] = src[1];
+ dst[1] = src[0];
+#endif
+}
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+#endif
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+static void cpy4(int *dst_val, const int *src_val) {
+ unsigned char *dst = reinterpret_cast(dst_val);
+ const unsigned char *src = reinterpret_cast(src_val);
+
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+}
+
+static void cpy4(unsigned int *dst_val, const unsigned int *src_val) {
+ unsigned char *dst = reinterpret_cast(dst_val);
+ const unsigned char *src = reinterpret_cast(src_val);
+
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+}
+
+static void cpy4(float *dst_val, const float *src_val) {
+ unsigned char *dst = reinterpret_cast(dst_val);
+ const unsigned char *src = reinterpret_cast(src_val);
+
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+}
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static void swap4(unsigned int *val) {
+#if TINYEXR_LITTLE_ENDIAN
+ (void)val;
+#else
+ unsigned int tmp = *val;
+ unsigned char *dst = reinterpret_cast(val);
+ unsigned char *src = reinterpret_cast(&tmp);
+
+ dst[0] = src[3];
+ dst[1] = src[2];
+ dst[2] = src[1];
+ dst[3] = src[0];
+#endif
+}
+
+static void swap4(int *val) {
+#if TINYEXR_LITTLE_ENDIAN
+ (void)val;
+#else
+ int tmp = *val;
+ unsigned char *dst = reinterpret_cast(val);
+ unsigned char *src = reinterpret_cast(&tmp);
+
+ dst[0] = src[3];
+ dst[1] = src[2];
+ dst[2] = src[1];
+ dst[3] = src[0];
+#endif
+}
+
+static void swap4(float *val) {
+#if TINYEXR_LITTLE_ENDIAN
+ (void)val;
+#else
+ float tmp = *val;
+ unsigned char *dst = reinterpret_cast(val);
+ unsigned char *src = reinterpret_cast(&tmp);
+
+ dst[0] = src[3];
+ dst[1] = src[2];
+ dst[2] = src[1];
+ dst[3] = src[0];
+#endif
+}
+
+#if 0
+static void cpy8(tinyexr::tinyexr_uint64 *dst_val, const tinyexr::tinyexr_uint64 *src_val) {
+ unsigned char *dst = reinterpret_cast(dst_val);
+ const unsigned char *src = reinterpret_cast(src_val);
+
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+ dst[4] = src[4];
+ dst[5] = src[5];
+ dst[6] = src[6];
+ dst[7] = src[7];
+}
+#endif
+
+static void swap8(tinyexr::tinyexr_uint64 *val) {
+#if TINYEXR_LITTLE_ENDIAN
+ (void)val;
+#else
+ tinyexr::tinyexr_uint64 tmp = (*val);
+ unsigned char *dst = reinterpret_cast(val);
+ unsigned char *src = reinterpret_cast(&tmp);
+
+ dst[0] = src[7];
+ dst[1] = src[6];
+ dst[2] = src[5];
+ dst[3] = src[4];
+ dst[4] = src[3];
+ dst[5] = src[2];
+ dst[6] = src[1];
+ dst[7] = src[0];
+#endif
+}
+
+// https://gist.github.com/rygorous/2156668
+union FP32 {
+ unsigned int u;
+ float f;
+ struct {
+#if TINYEXR_LITTLE_ENDIAN
+ unsigned int Mantissa : 23;
+ unsigned int Exponent : 8;
+ unsigned int Sign : 1;
+#else
+ unsigned int Sign : 1;
+ unsigned int Exponent : 8;
+ unsigned int Mantissa : 23;
+#endif
+ } s;
+};
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+union FP16 {
+ unsigned short u;
+ struct {
+#if TINYEXR_LITTLE_ENDIAN
+ unsigned int Mantissa : 10;
+ unsigned int Exponent : 5;
+ unsigned int Sign : 1;
+#else
+ unsigned int Sign : 1;
+ unsigned int Exponent : 5;
+ unsigned int Mantissa : 10;
+#endif
+ } s;
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+static FP32 half_to_float(FP16 h) {
+ static const FP32 magic = {113 << 23};
+ static const unsigned int shifted_exp = 0x7c00
+ << 13; // exponent mask after shift
+ FP32 o;
+
+ o.u = (h.u & 0x7fffU) << 13U; // exponent/mantissa bits
+ unsigned int exp_ = shifted_exp & o.u; // just the exponent
+ o.u += (127 - 15) << 23; // exponent adjust
+
+ // handle exponent special cases
+ if (exp_ == shifted_exp) // Inf/NaN?
+ o.u += (128 - 16) << 23; // extra exp adjust
+ else if (exp_ == 0) // Zero/Denormal?
+ {
+ o.u += 1 << 23; // extra exp adjust
+ o.f -= magic.f; // renormalize
+ }
+
+ o.u |= (h.u & 0x8000U) << 16U; // sign bit
+ return o;
+}
+
+static FP16 float_to_half_full(FP32 f) {
+ FP16 o = {0};
+
+ // Based on ISPC reference code (with minor modifications)
+ if (f.s.Exponent == 0) // Signed zero/denormal (which will underflow)
+ o.s.Exponent = 0;
+ else if (f.s.Exponent == 255) // Inf or NaN (all exponent bits set)
+ {
+ o.s.Exponent = 31;
+ o.s.Mantissa = f.s.Mantissa ? 0x200 : 0; // NaN->qNaN and Inf->Inf
+ } else // Normalized number
+ {
+ // Exponent unbias the single, then bias the halfp
+ int newexp = f.s.Exponent - 127 + 15;
+ if (newexp >= 31) // Overflow, return signed infinity
+ o.s.Exponent = 31;
+ else if (newexp <= 0) // Underflow
+ {
+ if ((14 - newexp) <= 24) // Mantissa might be non-zero
+ {
+ unsigned int mant = f.s.Mantissa | 0x800000; // Hidden 1 bit
+ o.s.Mantissa = mant >> (14 - newexp);
+ if ((mant >> (13 - newexp)) & 1) // Check for rounding
+ o.u++; // Round, might overflow into exp bit, but this is OK
+ }
+ } else {
+ o.s.Exponent = static_cast(newexp);
+ o.s.Mantissa = f.s.Mantissa >> 13;
+ if (f.s.Mantissa & 0x1000) // Check for rounding
+ o.u++; // Round, might overflow to inf, this is OK
+ }
+ }
+
+ o.s.Sign = f.s.Sign;
+ return o;
+}
+
+// NOTE: From OpenEXR code
+// #define IMF_INCREASING_Y 0
+// #define IMF_DECREASING_Y 1
+// #define IMF_RAMDOM_Y 2
+//
+// #define IMF_NO_COMPRESSION 0
+// #define IMF_RLE_COMPRESSION 1
+// #define IMF_ZIPS_COMPRESSION 2
+// #define IMF_ZIP_COMPRESSION 3
+// #define IMF_PIZ_COMPRESSION 4
+// #define IMF_PXR24_COMPRESSION 5
+// #define IMF_B44_COMPRESSION 6
+// #define IMF_B44A_COMPRESSION 7
+
+#ifdef __clang__
+#pragma clang diagnostic push
+
+#if __has_warning("-Wzero-as-null-pointer-constant")
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
+#endif
+
+#endif
+
+static const char *ReadString(std::string *s, const char *ptr, size_t len) {
+ // Read untile NULL(\0).
+ const char *p = ptr;
+ const char *q = ptr;
+ while ((size_t(q - ptr) < len) && (*q) != 0) {
+ q++;
+ }
+
+ if (size_t(q - ptr) >= len) {
+ (*s).clear();
+ return NULL;
+ }
+
+ (*s) = std::string(p, q);
+
+ return q + 1; // skip '\0'
+}
+
+static bool ReadAttribute(std::string *name, std::string *type,
+ std::vector *data, size_t *marker_size,
+ const char *marker, size_t size) {
+ size_t name_len = strnlen(marker, size);
+ if (name_len == size) {
+ // String does not have a terminating character.
+ return false;
+ }
+ *name = std::string(marker, name_len);
+
+ marker += name_len + 1;
+ size -= name_len + 1;
+
+ size_t type_len = strnlen(marker, size);
+ if (type_len == size) {
+ return false;
+ }
+ *type = std::string(marker, type_len);
+
+ marker += type_len + 1;
+ size -= type_len + 1;
+
+ if (size < sizeof(uint32_t)) {
+ return false;
+ }
+
+ uint32_t data_len;
+ memcpy(&data_len, marker, sizeof(uint32_t));
+ tinyexr::swap4(reinterpret_cast(&data_len));
+
+ if (data_len == 0) {
+ if ((*type).compare("string") == 0) {
+ // Accept empty string attribute.
+
+ marker += sizeof(uint32_t);
+ size -= sizeof(uint32_t);
+
+ *marker_size = name_len + 1 + type_len + 1 + sizeof(uint32_t);
+
+ data->resize(1);
+ (*data)[0] = '\0';
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ marker += sizeof(uint32_t);
+ size -= sizeof(uint32_t);
+
+ if (size < data_len) {
+ return false;
+ }
+
+ data->resize(static_cast(data_len));
+ memcpy(&data->at(0), marker, static_cast(data_len));
+
+ *marker_size = name_len + 1 + type_len + 1 + sizeof(uint32_t) + data_len;
+ return true;
+}
+
+static void WriteAttributeToMemory(std::vector *out,
+ const char *name, const char *type,
+ const unsigned char *data, int len) {
+ out->insert(out->end(), name, name + strlen(name) + 1);
+ out->insert(out->end(), type, type + strlen(type) + 1);
+
+ int outLen = len;
+ tinyexr::swap4(&outLen);
+ out->insert(out->end(), reinterpret_cast(&outLen),
+ reinterpret_cast(&outLen) + sizeof(int));
+ out->insert(out->end(), data, data + len);
+}
+
+typedef struct TChannelInfo {
+ std::string name; // less than 255 bytes long
+ int pixel_type;
+ int requested_pixel_type;
+ int x_sampling;
+ int y_sampling;
+ unsigned char p_linear;
+ unsigned char pad[3];
+} ChannelInfo;
+
+typedef struct {
+ int min_x;
+ int min_y;
+ int max_x;
+ int max_y;
+} Box2iInfo;
+
+struct HeaderInfo {
+ std::vector channels;
+ std::vector attributes;
+
+ Box2iInfo data_window;
+ int line_order;
+ Box2iInfo display_window;
+ float screen_window_center[2];
+ float screen_window_width;
+ float pixel_aspect_ratio;
+
+ int chunk_count;
+
+ // Tiled format
+ int tiled; // Non-zero if the part is tiled.
+ int tile_size_x;
+ int tile_size_y;
+ int tile_level_mode;
+ int tile_rounding_mode;
+
+ unsigned int header_len;
+
+ int compression_type;
+
+ // required for multi-part or non-image files
+ std::string name;
+ // required for multi-part or non-image files
+ std::string type;
+
+ void clear() {
+ channels.clear();
+ attributes.clear();
+
+ data_window.min_x = 0;
+ data_window.min_y = 0;
+ data_window.max_x = 0;
+ data_window.max_y = 0;
+ line_order = 0;
+ display_window.min_x = 0;
+ display_window.min_y = 0;
+ display_window.max_x = 0;
+ display_window.max_y = 0;
+ screen_window_center[0] = 0.0f;
+ screen_window_center[1] = 0.0f;
+ screen_window_width = 0.0f;
+ pixel_aspect_ratio = 0.0f;
+
+ chunk_count = 0;
+
+ // Tiled format
+ tiled = 0;
+ tile_size_x = 0;
+ tile_size_y = 0;
+ tile_level_mode = 0;
+ tile_rounding_mode = 0;
+
+ header_len = 0;
+ compression_type = 0;
+
+ name.clear();
+ type.clear();
+ }
+};
+
+static bool ReadChannelInfo(std::vector &channels,
+ const std::vector &data) {
+ const char *p = reinterpret_cast(&data.at(0));
+
+ for (;;) {
+ if ((*p) == 0) {
+ break;
+ }
+ ChannelInfo info;
+ info.requested_pixel_type = 0;
+
+ tinyexr_int64 data_len = static_cast(data.size()) -
+ (p - reinterpret_cast(data.data()));
+ if (data_len < 0) {
+ return false;
+ }
+
+ p = ReadString(&info.name, p, size_t(data_len));
+ if ((p == NULL) && (info.name.empty())) {
+ // Buffer overrun. Issue #51.
+ return false;
+ }
+
+ const unsigned char *data_end =
+ reinterpret_cast(p) + 16;
+ if (data_end >= (data.data() + data.size())) {
+ return false;
+ }
+
+ memcpy(&info.pixel_type, p, sizeof(int));
+ p += 4;
+ info.p_linear = static_cast(p[0]); // uchar
+ p += 1 + 3; // reserved: uchar[3]
+ memcpy(&info.x_sampling, p, sizeof(int)); // int
+ p += 4;
+ memcpy(&info.y_sampling, p, sizeof(int)); // int
+ p += 4;
+
+ tinyexr::swap4(&info.pixel_type);
+ tinyexr::swap4(&info.x_sampling);
+ tinyexr::swap4(&info.y_sampling);
+
+ channels.push_back(info);
+ }
+
+ return true;
+}
+
+static void WriteChannelInfo(std::vector &data,
+ const std::vector &channels) {
+ size_t sz = 0;
+
+ // Calculate total size.
+ for (size_t c = 0; c < channels.size(); c++) {
+ sz += channels[c].name.length() + 1; // +1 for \0
+ sz += 16; // 4 * int
+ }
+ data.resize(sz + 1);
+
+ unsigned char *p = &data.at(0);
+
+ for (size_t c = 0; c < channels.size(); c++) {
+ memcpy(p, channels[c].name.c_str(), channels[c].name.length());
+ p += channels[c].name.length();
+ (*p) = '\0';
+ p++;
+
+ int pixel_type = channels[c].requested_pixel_type;
+ int x_sampling = channels[c].x_sampling;
+ int y_sampling = channels[c].y_sampling;
+ tinyexr::swap4(&pixel_type);
+ tinyexr::swap4(&x_sampling);
+ tinyexr::swap4(&y_sampling);
+
+ memcpy(p, &pixel_type, sizeof(int));
+ p += sizeof(int);
+
+ (*p) = channels[c].p_linear;
+ p += 4;
+
+ memcpy(p, &x_sampling, sizeof(int));
+ p += sizeof(int);
+
+ memcpy(p, &y_sampling, sizeof(int));
+ p += sizeof(int);
+ }
+
+ (*p) = '\0';
+}
+
+static bool CompressZip(unsigned char *dst,
+ tinyexr::tinyexr_uint64 &compressedSize,
+ const unsigned char *src, unsigned long src_size) {
+ std::vector tmpBuf(src_size);
+
+ //
+ // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
+ // ImfZipCompressor.cpp
+ //
+
+ //
+ // Reorder the pixel data.
+ //
+
+ const char *srcPtr = reinterpret_cast(src);
+
+ {
+ char *t1 = reinterpret_cast(&tmpBuf.at(0));
+ char *t2 = reinterpret_cast(&tmpBuf.at(0)) + (src_size + 1) / 2;
+ const char *stop = srcPtr + src_size;
+
+ for (;;) {
+ if (srcPtr < stop)
+ *(t1++) = *(srcPtr++);
+ else
+ break;
+
+ if (srcPtr < stop)
+ *(t2++) = *(srcPtr++);
+ else
+ break;
+ }
+ }
+
+ //
+ // Predictor.
+ //
+
+ {
+ unsigned char *t = &tmpBuf.at(0) + 1;
+ unsigned char *stop = &tmpBuf.at(0) + src_size;
+ int p = t[-1];
+
+ while (t < stop) {
+ int d = int(t[0]) - p + (128 + 256);
+ p = t[0];
+ t[0] = static_cast(d);
+ ++t;
+ }
+ }
+
+#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
+ //
+ // Compress the data using miniz
+ //
+
+ mz_ulong outSize = mz_compressBound(src_size);
+ int ret = mz_compress(
+ dst, &outSize, static_cast(&tmpBuf.at(0)),
+ src_size);
+ if (ret != MZ_OK) {
+ return false;
+ }
+
+ compressedSize = outSize;
+#elif defined(TINYEXR_USE_STB_ZLIB) && (TINYEXR_USE_STB_ZLIB==1)
+ int outSize;
+ unsigned char* ret = stbi_zlib_compress(const_cast(&tmpBuf.at(0)), src_size, &outSize, 8);
+ if (!ret) {
+ return false;
+ }
+ memcpy(dst, ret, outSize);
+ free(ret);
+
+ compressedSize = outSize;
+#elif defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB==1)
+ uint64_t dstSize = nanoz_compressBound(static_cast(src_size));
+ int outSize{0};
+ unsigned char *ret = nanoz_compress(&tmpBuf.at(0), src_size, &outSize, /* quality */8);
+ if (!ret) {
+ return false;
+ }
+
+ memcpy(dst, ret, outSize);
+ free(ret);
+
+ compressedSize = outSize;
+#else
+ uLong outSize = compressBound(static_cast(src_size));
+ int ret = compress(dst, &outSize, static_cast(&tmpBuf.at(0)),
+ src_size);
+ if (ret != Z_OK) {
+ return false;
+ }
+
+ compressedSize = outSize;
+#endif
+
+ // Use uncompressed data when compressed data is larger than uncompressed.
+ // (Issue 40)
+ if (compressedSize >= src_size) {
+ compressedSize = src_size;
+ memcpy(dst, src, src_size);
+ }
+
+ return true;
+}
+
+static bool DecompressZip(unsigned char *dst,
+ unsigned long *uncompressed_size /* inout */,
+ const unsigned char *src, unsigned long src_size) {
+ if ((*uncompressed_size) == src_size) {
+ // Data is not compressed(Issue 40).
+ memcpy(dst, src, src_size);
+ return true;
+ }
+ std::vector tmpBuf(*uncompressed_size);
+
+#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
+ int ret =
+ mz_uncompress(&tmpBuf.at(0), uncompressed_size, src, src_size);
+ if (MZ_OK != ret) {
+ return false;
+ }
+#elif TINYEXR_USE_STB_ZLIB
+ int ret = stbi_zlib_decode_buffer(reinterpret_cast(&tmpBuf.at(0)),
+ *uncompressed_size, reinterpret_cast(src), src_size);
+ if (ret < 0) {
+ return false;
+ }
+#elif defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB==1)
+ uint64_t dest_size = (*uncompressed_size);
+ uint64_t uncomp_size{0};
+ nanoz_status_t ret =
+ nanoz_uncompress(src, src_size, dest_size, &tmpBuf.at(0), &uncomp_size);
+ if (NANOZ_SUCCESS != ret) {
+ return false;
+ }
+ if ((*uncompressed_size) != uncomp_size) {
+ return false;
+ }
+#else
+ int ret = uncompress(&tmpBuf.at(0), uncompressed_size, src, src_size);
+ if (Z_OK != ret) {
+ return false;
+ }
+#endif
+
+ //
+ // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
+ // ImfZipCompressor.cpp
+ //
+
+ // Predictor.
+ {
+ unsigned char *t = &tmpBuf.at(0) + 1;
+ unsigned char *stop = &tmpBuf.at(0) + (*uncompressed_size);
+
+ while (t < stop) {
+ int d = int(t[-1]) + int(t[0]) - 128;
+ t[0] = static_cast(d);
+ ++t;
+ }
+ }
+
+ // Reorder the pixel data.
+ {
+ const char *t1 = reinterpret_cast(&tmpBuf.at(0));
+ const char *t2 = reinterpret_cast(&tmpBuf.at(0)) +
+ (*uncompressed_size + 1) / 2;
+ char *s = reinterpret_cast(dst);
+ char *stop = s + (*uncompressed_size);
+
+ for (;;) {
+ if (s < stop)
+ *(s++) = *(t1++);
+ else
+ break;
+
+ if (s < stop)
+ *(s++) = *(t2++);
+ else
+ break;
+ }
+ }
+
+ return true;
+}
+
+// RLE code from OpenEXR --------------------------------------
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#if __has_warning("-Wextra-semi-stmt")
+#pragma clang diagnostic ignored "-Wextra-semi-stmt"
+#endif
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4204) // nonstandard extension used : non-constant
+ // aggregate initializer (also supported by GNU
+ // C and C99, so no big deal)
+#pragma warning(disable : 4244) // 'initializing': conversion from '__int64' to
+ // 'int', possible loss of data
+#pragma warning(disable : 4267) // 'argument': conversion from '__int64' to
+ // 'int', possible loss of data
+#pragma warning(disable : 4996) // 'strdup': The POSIX name for this item is
+ // deprecated. Instead, use the ISO C and C++
+ // conformant name: _strdup.
+#endif
+
+const int MIN_RUN_LENGTH = 3;
+const int MAX_RUN_LENGTH = 127;
+
+//
+// Compress an array of bytes, using run-length encoding,
+// and return the length of the compressed data.
+//
+
+static int rleCompress(int inLength, const char in[], signed char out[]) {
+ const char *inEnd = in + inLength;
+ const char *runStart = in;
+ const char *runEnd = in + 1;
+ signed char *outWrite = out;
+
+ while (runStart < inEnd) {
+ while (runEnd < inEnd && *runStart == *runEnd &&
+ runEnd - runStart - 1 < MAX_RUN_LENGTH) {
+ ++runEnd;
+ }
+
+ if (runEnd - runStart >= MIN_RUN_LENGTH) {
+ //
+ // Compressible run
+ //
+
+ *outWrite++ = static_cast(runEnd - runStart) - 1;
+ *outWrite++ = *(reinterpret_cast(runStart));
+ runStart = runEnd;
+ } else {
+ //
+ // Uncompressable run
+ //
+
+ while (runEnd < inEnd &&
+ ((runEnd + 1 >= inEnd || *runEnd != *(runEnd + 1)) ||
+ (runEnd + 2 >= inEnd || *(runEnd + 1) != *(runEnd + 2))) &&
+ runEnd - runStart < MAX_RUN_LENGTH) {
+ ++runEnd;
+ }
+
+ *outWrite++ = static_cast(runStart - runEnd);
+
+ while (runStart < runEnd) {
+ *outWrite++ = *(reinterpret_cast(runStart++));
+ }
+ }
+
+ ++runEnd;
+ }
+
+ return static_cast(outWrite - out);
+}
+
+//
+// Uncompress an array of bytes compressed with rleCompress().
+// Returns the length of the uncompressed data, or 0 if the
+// length of the uncompressed data would be more than maxLength.
+//
+
+static int rleUncompress(int inLength, int maxLength, const signed char in[],
+ char out[]) {
+ char *outStart = out;
+
+ while (inLength > 0) {
+ if (*in < 0) {
+ int count = -(static_cast(*in++));
+ inLength -= count + 1;
+
+ // Fixes #116: Add bounds check to in buffer.
+ if ((0 > (maxLength -= count)) || (inLength < 0)) return 0;
+
+ memcpy(out, in, count);
+ out += count;
+ in += count;
+ } else {
+ int count = *in++;
+ inLength -= 2;
+
+ if ((0 > (maxLength -= count + 1)) || (inLength < 0)) return 0;
+
+ memset(out, *reinterpret_cast(in), count + 1);
+ out += count + 1;
+
+ in++;
+ }
+ }
+
+ return static_cast(out - outStart);
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// End of RLE code from OpenEXR -----------------------------------
+
+static bool CompressRle(unsigned char *dst,
+ tinyexr::tinyexr_uint64 &compressedSize,
+ const unsigned char *src, unsigned long src_size) {
+ std::vector tmpBuf(src_size);
+
+ //
+ // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
+ // ImfRleCompressor.cpp
+ //
+
+ //
+ // Reorder the pixel data.
+ //
+
+ const char *srcPtr = reinterpret_cast(src);
+
+ {
+ char *t1 = reinterpret_cast(&tmpBuf.at(0));
+ char *t2 = reinterpret_cast(&tmpBuf.at(0)) + (src_size + 1) / 2;
+ const char *stop = srcPtr + src_size;
+
+ for (;;) {
+ if (srcPtr < stop)
+ *(t1++) = *(srcPtr++);
+ else
+ break;
+
+ if (srcPtr < stop)
+ *(t2++) = *(srcPtr++);
+ else
+ break;
+ }
+ }
+
+ //
+ // Predictor.
+ //
+
+ {
+ unsigned char *t = &tmpBuf.at(0) + 1;
+ unsigned char *stop = &tmpBuf.at(0) + src_size;
+ int p = t[-1];
+
+ while (t < stop) {
+ int d = int(t[0]) - p + (128 + 256);
+ p = t[0];
+ t[0] = static_cast(d);
+ ++t;
+ }
+ }
+
+ // outSize will be (srcSiz * 3) / 2 at max.
+ int outSize = rleCompress(static_cast(src_size),
+ reinterpret_cast(&tmpBuf.at(0)),
+ reinterpret_cast(dst));
+ TINYEXR_CHECK_AND_RETURN_C(outSize > 0, false);
+
+ compressedSize = static_cast(outSize);
+
+ // Use uncompressed data when compressed data is larger than uncompressed.
+ // (Issue 40)
+ if (compressedSize >= src_size) {
+ compressedSize = src_size;
+ memcpy(dst, src, src_size);
+ }
+
+ return true;
+}
+
+static bool DecompressRle(unsigned char *dst,
+ const unsigned long uncompressed_size,
+ const unsigned char *src, unsigned long src_size) {
+ if (uncompressed_size == src_size) {
+ // Data is not compressed(Issue 40).
+ memcpy(dst, src, src_size);
+ return true;
+ }
+
+ // Workaround for issue #112.
+ // TODO(syoyo): Add more robust out-of-bounds check in `rleUncompress`.
+ if (src_size <= 2) {
+ return false;
+ }
+
+ std::vector tmpBuf(uncompressed_size);
+
+ int ret = rleUncompress(static_cast(src_size),
+ static_cast(uncompressed_size),
+ reinterpret_cast(src),
+ reinterpret_cast(&tmpBuf.at(0)));
+ if (ret != static_cast(uncompressed_size)) {
+ return false;
+ }
+
+ //
+ // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
+ // ImfRleCompressor.cpp
+ //
+
+ // Predictor.
+ {
+ unsigned char *t = &tmpBuf.at(0) + 1;
+ unsigned char *stop = &tmpBuf.at(0) + uncompressed_size;
+
+ while (t < stop) {
+ int d = int(t[-1]) + int(t[0]) - 128;
+ t[0] = static_cast(d);
+ ++t;
+ }
+ }
+
+ // Reorder the pixel data.
+ {
+ const char *t1 = reinterpret_cast(&tmpBuf.at(0));
+ const char *t2 = reinterpret_cast(&tmpBuf.at(0)) +
+ (uncompressed_size + 1) / 2;
+ char *s = reinterpret_cast(dst);
+ char *stop = s + uncompressed_size;
+
+ for (;;) {
+ if (s < stop)
+ *(s++) = *(t1++);
+ else
+ break;
+
+ if (s < stop)
+ *(s++) = *(t2++);
+ else
+ break;
+ }
+ }
+
+ return true;
+}
+
+#if TINYEXR_USE_PIZ
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++11-long-long"
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#pragma clang diagnostic ignored "-Wpadded"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wc++11-extensions"
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+
+#if __has_warning("-Wcast-qual")
+#pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+
+#if __has_warning("-Wextra-semi-stmt")
+#pragma clang diagnostic ignored "-Wextra-semi-stmt"
+#endif
+
+#endif
+
+//
+// PIZ compress/uncompress, based on OpenEXR's ImfPizCompressor.cpp
+//
+// -----------------------------------------------------------------
+// Copyright (c) 2004, Industrial Light & Magic, a division of Lucas
+// Digital Ltd. LLC)
+// (3 clause BSD license)
+//
+
+struct PIZChannelData {
+ unsigned short *start;
+ unsigned short *end;
+ int nx;
+ int ny;
+ int ys;
+ int size;
+};
+
+//-----------------------------------------------------------------------------
+//
+// 16-bit Haar Wavelet encoding and decoding
+//
+// The source code in this file is derived from the encoding
+// and decoding routines written by Christian Rouet for his
+// PIZ image file format.
+//
+//-----------------------------------------------------------------------------
+
+//
+// Wavelet basis functions without modulo arithmetic; they produce
+// the best compression ratios when the wavelet-transformed data are
+// Huffman-encoded, but the wavelet transform works only for 14-bit
+// data (untransformed data values must be less than (1 << 14)).
+//
+
+inline void wenc14(unsigned short a, unsigned short b, unsigned short &l,
+ unsigned short &h) {
+ short as = static_cast(a);
+ short bs = static_cast(b);
+
+ short ms = (as + bs) >> 1;
+ short ds = as - bs;
+
+ l = static_cast(ms);
+ h = static_cast(ds);
+}
+
+inline void wdec14(unsigned short l, unsigned short h, unsigned short &a,
+ unsigned short &b) {
+ short ls = static_cast(l);
+ short hs = static_cast(h);
+
+ int hi = hs;
+ int ai = ls + (hi & 1) + (hi >> 1);
+
+ short as = static_cast(ai);
+ short bs = static_cast(ai - hi);
+
+ a = static_cast(as);
+ b = static_cast(bs);
+}
+
+//
+// Wavelet basis functions with modulo arithmetic; they work with full
+// 16-bit data, but Huffman-encoding the wavelet-transformed data doesn't
+// compress the data quite as well.
+//
+
+const int NBITS = 16;
+const int A_OFFSET = 1 << (NBITS - 1);
+const int M_OFFSET = 1 << (NBITS - 1);
+const int MOD_MASK = (1 << NBITS) - 1;
+
+inline void wenc16(unsigned short a, unsigned short b, unsigned short &l,
+ unsigned short &h) {
+ int ao = (a + A_OFFSET) & MOD_MASK;
+ int m = ((ao + b) >> 1);
+ int d = ao - b;
+
+ if (d < 0) m = (m + M_OFFSET) & MOD_MASK;
+
+ d &= MOD_MASK;
+
+ l = static_cast(m);
+ h = static_cast(d);
+}
+
+inline void wdec16(unsigned short l, unsigned short h, unsigned short &a,
+ unsigned short &b) {
+ int m = l;
+ int d = h;
+ int bb = (m - (d >> 1)) & MOD_MASK;
+ int aa = (d + bb - A_OFFSET) & MOD_MASK;
+ b = static_cast(bb);
+ a = static_cast(aa);
+}
+
+//
+// 2D Wavelet encoding:
+//
+
+static void wav2Encode(
+ unsigned short *in, // io: values are transformed in place
+ int nx, // i : x size
+ int ox, // i : x offset
+ int ny, // i : y size
+ int oy, // i : y offset
+ unsigned short mx) // i : maximum in[x][y] value
+{
+ bool w14 = (mx < (1 << 14));
+ int n = (nx > ny) ? ny : nx;
+ int p = 1; // == 1 << level
+ int p2 = 2; // == 1 << (level+1)
+
+ //
+ // Hierarchical loop on smaller dimension n
+ //
+
+ while (p2 <= n) {
+ unsigned short *py = in;
+ unsigned short *ey = in + oy * (ny - p2);
+ int oy1 = oy * p;
+ int oy2 = oy * p2;
+ int ox1 = ox * p;
+ int ox2 = ox * p2;
+ unsigned short i00, i01, i10, i11;
+
+ //
+ // Y loop
+ //
+
+ for (; py <= ey; py += oy2) {
+ unsigned short *px = py;
+ unsigned short *ex = py + ox * (nx - p2);
+
+ //
+ // X loop
+ //
+
+ for (; px <= ex; px += ox2) {
+ unsigned short *p01 = px + ox1;
+ unsigned short *p10 = px + oy1;
+ unsigned short *p11 = p10 + ox1;
+
+ //
+ // 2D wavelet encoding
+ //
+
+ if (w14) {
+ wenc14(*px, *p01, i00, i01);
+ wenc14(*p10, *p11, i10, i11);
+ wenc14(i00, i10, *px, *p10);
+ wenc14(i01, i11, *p01, *p11);
+ } else {
+ wenc16(*px, *p01, i00, i01);
+ wenc16(*p10, *p11, i10, i11);
+ wenc16(i00, i10, *px, *p10);
+ wenc16(i01, i11, *p01, *p11);
+ }
+ }
+
+ //
+ // Encode (1D) odd column (still in Y loop)
+ //
+
+ if (nx & p) {
+ unsigned short *p10 = px + oy1;
+
+ if (w14)
+ wenc14(*px, *p10, i00, *p10);
+ else
+ wenc16(*px, *p10, i00, *p10);
+
+ *px = i00;
+ }
+ }
+
+ //
+ // Encode (1D) odd line (must loop in X)
+ //
+
+ if (ny & p) {
+ unsigned short *px = py;
+ unsigned short *ex = py + ox * (nx - p2);
+
+ for (; px <= ex; px += ox2) {
+ unsigned short *p01 = px + ox1;
+
+ if (w14)
+ wenc14(*px, *p01, i00, *p01);
+ else
+ wenc16(*px, *p01, i00, *p01);
+
+ *px = i00;
+ }
+ }
+
+ //
+ // Next level
+ //
+
+ p = p2;
+ p2 <<= 1;
+ }
+}
+
+//
+// 2D Wavelet decoding:
+//
+
+static void wav2Decode(
+ unsigned short *in, // io: values are transformed in place
+ int nx, // i : x size
+ int ox, // i : x offset
+ int ny, // i : y size
+ int oy, // i : y offset
+ unsigned short mx) // i : maximum in[x][y] value
+{
+ bool w14 = (mx < (1 << 14));
+ int n = (nx > ny) ? ny : nx;
+ int p = 1;
+ int p2;
+
+ //
+ // Search max level
+ //
+
+ while (p <= n) p <<= 1;
+
+ p >>= 1;
+ p2 = p;
+ p >>= 1;
+
+ //
+ // Hierarchical loop on smaller dimension n
+ //
+
+ while (p >= 1) {
+ unsigned short *py = in;
+ unsigned short *ey = in + oy * (ny - p2);
+ int oy1 = oy * p;
+ int oy2 = oy * p2;
+ int ox1 = ox * p;
+ int ox2 = ox * p2;
+ unsigned short i00, i01, i10, i11;
+
+ //
+ // Y loop
+ //
+
+ for (; py <= ey; py += oy2) {
+ unsigned short *px = py;
+ unsigned short *ex = py + ox * (nx - p2);
+
+ //
+ // X loop
+ //
+
+ for (; px <= ex; px += ox2) {
+ unsigned short *p01 = px + ox1;
+ unsigned short *p10 = px + oy1;
+ unsigned short *p11 = p10 + ox1;
+
+ //
+ // 2D wavelet decoding
+ //
+
+ if (w14) {
+ wdec14(*px, *p10, i00, i10);
+ wdec14(*p01, *p11, i01, i11);
+ wdec14(i00, i01, *px, *p01);
+ wdec14(i10, i11, *p10, *p11);
+ } else {
+ wdec16(*px, *p10, i00, i10);
+ wdec16(*p01, *p11, i01, i11);
+ wdec16(i00, i01, *px, *p01);
+ wdec16(i10, i11, *p10, *p11);
+ }
+ }
+
+ //
+ // Decode (1D) odd column (still in Y loop)
+ //
+
+ if (nx & p) {
+ unsigned short *p10 = px + oy1;
+
+ if (w14)
+ wdec14(*px, *p10, i00, *p10);
+ else
+ wdec16(*px, *p10, i00, *p10);
+
+ *px = i00;
+ }
+ }
+
+ //
+ // Decode (1D) odd line (must loop in X)
+ //
+
+ if (ny & p) {
+ unsigned short *px = py;
+ unsigned short *ex = py + ox * (nx - p2);
+
+ for (; px <= ex; px += ox2) {
+ unsigned short *p01 = px + ox1;
+
+ if (w14)
+ wdec14(*px, *p01, i00, *p01);
+ else
+ wdec16(*px, *p01, i00, *p01);
+
+ *px = i00;
+ }
+ }
+
+ //
+ // Next level
+ //
+
+ p2 = p;
+ p >>= 1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// 16-bit Huffman compression and decompression.
+//
+// The source code in this file is derived from the 8-bit
+// Huffman compression and decompression routines written
+// by Christian Rouet for his PIZ image file format.
+//
+//-----------------------------------------------------------------------------
+
+// Adds some modification for tinyexr.
+
+const int HUF_ENCBITS = 16; // literal (value) bit length
+const int HUF_DECBITS = 14; // decoding bit size (>= 8)
+
+const int HUF_ENCSIZE = (1 << HUF_ENCBITS) + 1; // encoding table size
+const int HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
+const int HUF_DECMASK = HUF_DECSIZE - 1;
+
+struct HufDec { // short code long code
+ //-------------------------------
+ unsigned int len : 8; // code length 0
+ unsigned int lit : 24; // lit p size
+ unsigned int *p; // 0 lits
+};
+
+inline long long hufLength(long long code) { return code & 63; }
+
+inline long long hufCode(long long code) { return code >> 6; }
+
+inline void outputBits(int nBits, long long bits, long long &c, int &lc,
+ char *&out) {
+ c <<= nBits;
+ lc += nBits;
+
+ c |= bits;
+
+ while (lc >= 8) *out++ = static_cast((c >> (lc -= 8)));
+}
+
+inline long long getBits(int nBits, long long &c, int &lc, const char *&in) {
+ while (lc < nBits) {
+ c = (c << 8) | *(reinterpret_cast(in++));
+ lc += 8;
+ }
+
+ lc -= nBits;
+ return (c >> lc) & ((1 << nBits) - 1);
+}
+
+//
+// ENCODING TABLE BUILDING & (UN)PACKING
+//
+
+//
+// Build a "canonical" Huffman code table:
+// - for each (uncompressed) symbol, hcode contains the length
+// of the corresponding code (in the compressed data)
+// - canonical codes are computed and stored in hcode
+// - the rules for constructing canonical codes are as follows:
+// * shorter codes (if filled with zeroes to the right)
+// have a numerically higher value than longer codes
+// * for codes with the same length, numerical values
+// increase with numerical symbol values
+// - because the canonical code table can be constructed from
+// symbol lengths alone, the code table can be transmitted
+// without sending the actual code values
+// - see http://www.compressconsult.com/huffman/
+//
+
+static void hufCanonicalCodeTable(long long hcode[HUF_ENCSIZE]) {
+ long long n[59];
+
+ //
+ // For each i from 0 through 58, count the
+ // number of different codes of length i, and
+ // store the count in n[i].
+ //
+
+ for (int i = 0; i <= 58; ++i) n[i] = 0;
+
+ for (int i = 0; i < HUF_ENCSIZE; ++i) n[hcode[i]] += 1;
+
+ //
+ // For each i from 58 through 1, compute the
+ // numerically lowest code with length i, and
+ // store that code in n[i].
+ //
+
+ long long c = 0;
+
+ for (int i = 58; i > 0; --i) {
+ long long nc = ((c + n[i]) >> 1);
+ n[i] = c;
+ c = nc;
+ }
+
+ //
+ // hcode[i] contains the length, l, of the
+ // code for symbol i. Assign the next available
+ // code of length l to the symbol and store both
+ // l and the code in hcode[i].
+ //
+
+ for (int i = 0; i < HUF_ENCSIZE; ++i) {
+ int l = static_cast(hcode[i]);
+
+ if (l > 0) hcode[i] = l | (n[l]++ << 6);
+ }
+}
+
+//
+// Compute Huffman codes (based on frq input) and store them in frq:
+// - code structure is : [63:lsb - 6:msb] | [5-0: bit length];
+// - max code length is 58 bits;
+// - codes outside the range [im-iM] have a null length (unused values);
+// - original frequencies are destroyed;
+// - encoding tables are used by hufEncode() and hufBuildDecTable();
+//
+
+struct FHeapCompare {
+ bool operator()(long long *a, long long *b) { return *a > *b; }
+};
+
+static bool hufBuildEncTable(
+ long long *frq, // io: input frequencies [HUF_ENCSIZE], output table
+ int *im, // o: min frq index
+ int *iM) // o: max frq index
+{
+ //
+ // This function assumes that when it is called, array frq
+ // indicates the frequency of all possible symbols in the data
+ // that are to be Huffman-encoded. (frq[i] contains the number
+ // of occurrences of symbol i in the data.)
+ //
+ // The loop below does three things:
+ //
+ // 1) Finds the minimum and maximum indices that point
+ // to non-zero entries in frq:
+ //
+ // frq[im] != 0, and frq[i] == 0 for all i < im
+ // frq[iM] != 0, and frq[i] == 0 for all i > iM
+ //
+ // 2) Fills array fHeap with pointers to all non-zero
+ // entries in frq.
+ //
+ // 3) Initializes array hlink such that hlink[i] == i
+ // for all array entries.
+ //
+
+ std::vector hlink(HUF_ENCSIZE);
+ std::vector fHeap(HUF_ENCSIZE);
+
+ *im = 0;
+
+ while (!frq[*im]) (*im)++;
+
+ int nf = 0;
+
+ for (int i = *im; i < HUF_ENCSIZE; i++) {
+ hlink[i] = i;
+
+ if (frq[i]) {
+ fHeap[nf] = &frq[i];
+ nf++;
+ *iM = i;
+ }
+ }
+
+ //
+ // Add a pseudo-symbol, with a frequency count of 1, to frq;
+ // adjust the fHeap and hlink array accordingly. Function
+ // hufEncode() uses the pseudo-symbol for run-length encoding.
+ //
+
+ (*iM)++;
+ frq[*iM] = 1;
+ fHeap[nf] = &frq[*iM];
+ nf++;
+
+ //
+ // Build an array, scode, such that scode[i] contains the number
+ // of bits assigned to symbol i. Conceptually this is done by
+ // constructing a tree whose leaves are the symbols with non-zero
+ // frequency:
+ //
+ // Make a heap that contains all symbols with a non-zero frequency,
+ // with the least frequent symbol on top.
+ //
+ // Repeat until only one symbol is left on the heap:
+ //
+ // Take the two least frequent symbols off the top of the heap.
+ // Create a new node that has first two nodes as children, and
+ // whose frequency is the sum of the frequencies of the first
+ // two nodes. Put the new node back into the heap.
+ //
+ // The last node left on the heap is the root of the tree. For each
+ // leaf node, the distance between the root and the leaf is the length
+ // of the code for the corresponding symbol.
+ //
+ // The loop below doesn't actually build the tree; instead we compute
+ // the distances of the leaves from the root on the fly. When a new
+ // node is added to the heap, then that node's descendants are linked
+ // into a single linear list that starts at the new node, and the code
+ // lengths of the descendants (that is, their distance from the root
+ // of the tree) are incremented by one.
+ //
+
+ std::make_heap(&fHeap[0], &fHeap[nf], FHeapCompare());
+
+ std::vector scode(HUF_ENCSIZE);
+ memset(scode.data(), 0, sizeof(long long) * HUF_ENCSIZE);
+
+ while (nf > 1) {
+ //
+ // Find the indices, mm and m, of the two smallest non-zero frq
+ // values in fHeap, add the smallest frq to the second-smallest
+ // frq, and remove the smallest frq value from fHeap.
+ //
+
+ int mm = fHeap[0] - frq;
+ std::pop_heap(&fHeap[0], &fHeap[nf], FHeapCompare());
+ --nf;
+
+ int m = fHeap[0] - frq;
+ std::pop_heap(&fHeap[0], &fHeap[nf], FHeapCompare());
+
+ frq[m] += frq[mm];
+ std::push_heap(&fHeap[0], &fHeap[nf], FHeapCompare());
+
+ //
+ // The entries in scode are linked into lists with the
+ // entries in hlink serving as "next" pointers and with
+ // the end of a list marked by hlink[j] == j.
+ //
+ // Traverse the lists that start at scode[m] and scode[mm].
+ // For each element visited, increment the length of the
+ // corresponding code by one bit. (If we visit scode[j]
+ // during the traversal, then the code for symbol j becomes
+ // one bit longer.)
+ //
+ // Merge the lists that start at scode[m] and scode[mm]
+ // into a single list that starts at scode[m].
+ //
+
+ //
+ // Add a bit to all codes in the first list.
+ //
+
+ for (int j = m;; j = hlink[j]) {
+ scode[j]++;
+
+ TINYEXR_CHECK_AND_RETURN_C(scode[j] <= 58, false);
+
+ if (hlink[j] == j) {
+ //
+ // Merge the two lists.
+ //
+
+ hlink[j] = mm;
+ break;
+ }
+ }
+
+ //
+ // Add a bit to all codes in the second list
+ //
+
+ for (int j = mm;; j = hlink[j]) {
+ scode[j]++;
+
+ TINYEXR_CHECK_AND_RETURN_C(scode[j] <= 58, false);
+
+ if (hlink[j] == j) break;
+ }
+ }
+
+ //
+ // Build a canonical Huffman code table, replacing the code
+ // lengths in scode with (code, code length) pairs. Copy the
+ // code table from scode into frq.
+ //
+
+ hufCanonicalCodeTable(scode.data());
+ memcpy(frq, scode.data(), sizeof(long long) * HUF_ENCSIZE);
+
+ return true;
+}
+
+//
+// Pack an encoding table:
+// - only code lengths, not actual codes, are stored
+// - runs of zeroes are compressed as follows:
+//
+// unpacked packed
+// --------------------------------
+// 1 zero 0 (6 bits)
+// 2 zeroes 59
+// 3 zeroes 60
+// 4 zeroes 61
+// 5 zeroes 62
+// n zeroes (6 or more) 63 n-6 (6 + 8 bits)
+//
+
+const int SHORT_ZEROCODE_RUN = 59;
+const int LONG_ZEROCODE_RUN = 63;
+const int SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
+const int LONGEST_LONG_RUN = 255 + SHORTEST_LONG_RUN;
+
+static void hufPackEncTable(
+ const long long *hcode, // i : encoding table [HUF_ENCSIZE]
+ int im, // i : min hcode index
+ int iM, // i : max hcode index
+ char **pcode) // o: ptr to packed table (updated)
+{
+ char *p = *pcode;
+ long long c = 0;
+ int lc = 0;
+
+ for (; im <= iM; im++) {
+ int l = hufLength(hcode[im]);
+
+ if (l == 0) {
+ int zerun = 1;
+
+ while ((im < iM) && (zerun < LONGEST_LONG_RUN)) {
+ if (hufLength(hcode[im + 1]) > 0) break;
+ im++;
+ zerun++;
+ }
+
+ if (zerun >= 2) {
+ if (zerun >= SHORTEST_LONG_RUN) {
+ outputBits(6, LONG_ZEROCODE_RUN, c, lc, p);
+ outputBits(8, zerun - SHORTEST_LONG_RUN, c, lc, p);
+ } else {
+ outputBits(6, SHORT_ZEROCODE_RUN + zerun - 2, c, lc, p);
+ }
+ continue;
+ }
+ }
+
+ outputBits(6, l, c, lc, p);
+ }
+
+ if (lc > 0) *p++ = (unsigned char)(c << (8 - lc));
+
+ *pcode = p;
+}
+
+//
+// Unpack an encoding table packed by hufPackEncTable():
+//
+
+static bool hufUnpackEncTable(
+ const char **pcode, // io: ptr to packed table (updated)
+ int ni, // i : input size (in bytes)
+ int im, // i : min hcode index
+ int iM, // i : max hcode index
+ long long *hcode) // o: encoding table [HUF_ENCSIZE]
+{
+ memset(hcode, 0, sizeof(long long) * HUF_ENCSIZE);
+
+ const char *p = *pcode;
+ long long c = 0;
+ int lc = 0;
+
+ for (; im <= iM; im++) {
+ if (p - *pcode >= ni) {
+ return false;
+ }
+
+ long long l = hcode[im] = getBits(6, c, lc, p); // code length
+
+ if (l == (long long)LONG_ZEROCODE_RUN) {
+ if (p - *pcode > ni) {
+ return false;
+ }
+
+ int zerun = getBits(8, c, lc, p) + SHORTEST_LONG_RUN;
+
+ if (im + zerun > iM + 1) {
+ return false;
+ }
+
+ while (zerun--) hcode[im++] = 0;
+
+ im--;
+ } else if (l >= (long long)SHORT_ZEROCODE_RUN) {
+ int zerun = l - SHORT_ZEROCODE_RUN + 2;
+
+ if (im + zerun > iM + 1) {
+ return false;
+ }
+
+ while (zerun--) hcode[im++] = 0;
+
+ im--;
+ }
+ }
+
+ *pcode = const_cast(p);
+
+ hufCanonicalCodeTable(hcode);
+
+ return true;
+}
+
+//
+// DECODING TABLE BUILDING
+//
+
+//
+// Clear a newly allocated decoding table so that it contains only zeroes.
+//
+
+static void hufClearDecTable(HufDec *hdecod) // io: (allocated by caller)
+// decoding table [HUF_DECSIZE]
+{
+ for (int i = 0; i < HUF_DECSIZE; i++) {
+ hdecod[i].len = 0;
+ hdecod[i].lit = 0;
+ hdecod[i].p = NULL;
+ }
+ // memset(hdecod, 0, sizeof(HufDec) * HUF_DECSIZE);
+}
+
+//
+// Build a decoding hash table based on the encoding table hcode:
+// - short codes (<= HUF_DECBITS) are resolved with a single table access;
+// - long code entry allocations are not optimized, because long codes are
+// unfrequent;
+// - decoding tables are used by hufDecode();
+//
+
+static bool hufBuildDecTable(const long long *hcode, // i : encoding table
+ int im, // i : min index in hcode
+ int iM, // i : max index in hcode
+ HufDec *hdecod) // o: (allocated by caller)
+// decoding table [HUF_DECSIZE]
+{
+ //
+ // Init hashtable & loop on all codes.
+ // Assumes that hufClearDecTable(hdecod) has already been called.
+ //
+
+ for (; im <= iM; im++) {
+ long long c = hufCode(hcode[im]);
+ int l = hufLength(hcode[im]);
+
+ if (c >> l) {
+ //
+ // Error: c is supposed to be an l-bit code,
+ // but c contains a value that is greater
+ // than the largest l-bit number.
+ //
+
+ // invalidTableEntry();
+ return false;
+ }
+
+ if (l > HUF_DECBITS) {
+ //
+ // Long code: add a secondary entry
+ //
+
+ HufDec *pl = hdecod + (c >> (l - HUF_DECBITS));
+
+ if (pl->len) {
+ //
+ // Error: a short code has already
+ // been stored in table entry *pl.
+ //
+
+ // invalidTableEntry();
+ return false;
+ }
+
+ pl->lit++;
+
+ if (pl->p) {
+ unsigned int *p = pl->p;
+ pl->p = new unsigned int[pl->lit];
+
+ for (unsigned int i = 0; i < pl->lit - 1u; ++i) pl->p[i] = p[i];
+
+ delete[] p;
+ } else {
+ pl->p = new unsigned int[1];
+ }
+
+ pl->p[pl->lit - 1] = im;
+ } else if (l) {
+ //
+ // Short code: init all primary entries
+ //
+
+ HufDec *pl = hdecod + (c << (HUF_DECBITS - l));
+
+ for (long long i = 1ULL << (HUF_DECBITS - l); i > 0; i--, pl++) {
+ if (pl->len || pl->p) {
+ //
+ // Error: a short code or a long code has
+ // already been stored in table entry *pl.
+ //
+
+ // invalidTableEntry();
+ return false;
+ }
+
+ pl->len = l;
+ pl->lit = im;
+ }
+ }
+ }
+
+ return true;
+}
+
+//
+// Free the long code entries of a decoding table built by hufBuildDecTable()
+//
+
+static void hufFreeDecTable(HufDec *hdecod) // io: Decoding table
+{
+ for (int i = 0; i < HUF_DECSIZE; i++) {
+ if (hdecod[i].p) {
+ delete[] hdecod[i].p;
+ hdecod[i].p = 0;
+ }
+ }
+}
+
+//
+// ENCODING
+//
+
+inline void outputCode(long long code, long long &c, int &lc, char *&out) {
+ outputBits(hufLength(code), hufCode(code), c, lc, out);
+}
+
+inline void sendCode(long long sCode, int runCount, long long runCode,
+ long long &c, int &lc, char *&out) {
+ //
+ // Output a run of runCount instances of the symbol sCount.
+ // Output the symbols explicitly, or if that is shorter, output
+ // the sCode symbol once followed by a runCode symbol and runCount
+ // expressed as an 8-bit number.
+ //
+
+ if (hufLength(sCode) + hufLength(runCode) + 8 < hufLength(sCode) * runCount) {
+ outputCode(sCode, c, lc, out);
+ outputCode(runCode, c, lc, out);
+ outputBits(8, runCount, c, lc, out);
+ } else {
+ while (runCount-- >= 0) outputCode(sCode, c, lc, out);
+ }
+}
+
+//
+// Encode (compress) ni values based on the Huffman encoding table hcode:
+//
+
+static int hufEncode // return: output size (in bits)
+ (const long long *hcode, // i : encoding table
+ const unsigned short *in, // i : uncompressed input buffer
+ const int ni, // i : input buffer size (in bytes)
+ int rlc, // i : rl code
+ char *out) // o: compressed output buffer
+{
+ char *outStart = out;
+ long long c = 0; // bits not yet written to out
+ int lc = 0; // number of valid bits in c (LSB)
+ int s = in[0];
+ int cs = 0;
+
+ //
+ // Loop on input values
+ //
+
+ for (int i = 1; i < ni; i++) {
+ //
+ // Count same values or send code
+ //
+
+ if (s == in[i] && cs < 255) {
+ cs++;
+ } else {
+ sendCode(hcode[s], cs, hcode[rlc], c, lc, out);
+ cs = 0;
+ }
+
+ s = in[i];
+ }
+
+ //
+ // Send remaining code
+ //
+
+ sendCode(hcode[s], cs, hcode[rlc], c, lc, out);
+
+ if (lc) *out = (c << (8 - lc)) & 0xff;
+
+ return (out - outStart) * 8 + lc;
+}
+
+//
+// DECODING
+//
+
+//
+// In order to force the compiler to inline them,
+// getChar() and getCode() are implemented as macros
+// instead of "inline" functions.
+//
+
+#define getChar(c, lc, in) \
+ { \
+ c = (c << 8) | *(unsigned char *)(in++); \
+ lc += 8; \
+ }
+
+#if 0
+#define getCode(po, rlc, c, lc, in, out, ob, oe) \
+ { \
+ if (po == rlc) { \
+ if (lc < 8) getChar(c, lc, in); \
+ \
+ lc -= 8; \
+ \
+ unsigned char cs = (c >> lc); \
+ \
+ if (out + cs > oe) return false; \
+ \
+ /* TinyEXR issue 78 */ \
+ unsigned short s = out[-1]; \
+ \
+ while (cs-- > 0) *out++ = s; \
+ } else if (out < oe) { \
+ *out++ = po; \
+ } else { \
+ return false; \
+ } \
+ }
+#else
+static bool getCode(int po, int rlc, long long &c, int &lc, const char *&in,
+ const char *in_end, unsigned short *&out,
+ const unsigned short *ob, const unsigned short *oe) {
+ (void)ob;
+ if (po == rlc) {
+ if (lc < 8) {
+ /* TinyEXR issue 78 */
+ /* TinyEXR issue 160. in + 1 -> in */
+ if (in >= in_end) {
+ return false;
+ }
+
+ getChar(c, lc, in);
+ }
+
+ lc -= 8;
+
+ unsigned char cs = (c >> lc);
+
+ if (out + cs > oe) return false;
+
+ // Bounds check for safety
+ // Issue 100.
+ if ((out - 1) < ob) return false;
+ unsigned short s = out[-1];
+
+ while (cs-- > 0) *out++ = s;
+ } else if (out < oe) {
+ *out++ = po;
+ } else {
+ return false;
+ }
+ return true;
+}
+#endif
+
+//
+// Decode (uncompress) ni bits based on encoding & decoding tables:
+//
+
+static bool hufDecode(const long long *hcode, // i : encoding table
+ const HufDec *hdecod, // i : decoding table
+ const char *in, // i : compressed input buffer
+ int ni, // i : input size (in bits)
+ int rlc, // i : run-length code
+ int no, // i : expected output size (in bytes)
+ unsigned short *out) // o: uncompressed output buffer
+{
+ long long c = 0;
+ int lc = 0;
+ unsigned short *outb = out; // begin
+ unsigned short *oe = out + no; // end
+ const char *ie = in + (ni + 7) / 8; // input byte size
+
+ //
+ // Loop on input bytes
+ //
+
+ while (in < ie) {
+ getChar(c, lc, in);
+
+ //
+ // Access decoding table
+ //
+
+ while (lc >= HUF_DECBITS) {
+ const HufDec pl = hdecod[(c >> (lc - HUF_DECBITS)) & HUF_DECMASK];
+
+ if (pl.len) {
+ //
+ // Get short code
+ //
+
+ lc -= pl.len;
+ // std::cout << "lit = " << pl.lit << std::endl;
+ // std::cout << "rlc = " << rlc << std::endl;
+ // std::cout << "c = " << c << std::endl;
+ // std::cout << "lc = " << lc << std::endl;
+ // std::cout << "in = " << in << std::endl;
+ // std::cout << "out = " << out << std::endl;
+ // std::cout << "oe = " << oe << std::endl;
+ if (!getCode(pl.lit, rlc, c, lc, in, ie, out, outb, oe)) {
+ return false;
+ }
+ } else {
+ if (!pl.p) {
+ return false;
+ }
+ // invalidCode(); // wrong code
+
+ //
+ // Search long code
+ //
+
+ unsigned int j;
+
+ for (j = 0; j < pl.lit; j++) {
+ int l = hufLength(hcode[pl.p[j]]);
+
+ while (lc < l && in < ie) // get more bits
+ getChar(c, lc, in);
+
+ if (lc >= l) {
+ if (hufCode(hcode[pl.p[j]]) ==
+ ((c >> (lc - l)) & (((long long)(1) << l) - 1))) {
+ //
+ // Found : get long code
+ //
+
+ lc -= l;
+ if (!getCode(pl.p[j], rlc, c, lc, in, ie, out, outb, oe)) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ if (j == pl.lit) {
+ return false;
+ // invalidCode(); // Not found
+ }
+ }
+ }
+ }
+
+ //
+ // Get remaining (short) codes
+ //
+
+ int i = (8 - ni) & 7;
+ c >>= i;
+ lc -= i;
+
+ while (lc > 0) {
+ const HufDec pl = hdecod[(c << (HUF_DECBITS - lc)) & HUF_DECMASK];
+
+ if (pl.len) {
+ lc -= pl.len;
+ if (!getCode(pl.lit, rlc, c, lc, in, ie, out, outb, oe)) {
+ return false;
+ }
+ } else {
+ return false;
+ // invalidCode(); // wrong (long) code
+ }
+ }
+
+ if (out - outb != no) {
+ return false;
+ }
+ // notEnoughData ();
+
+ return true;
+}
+
+static void countFrequencies(std::vector &freq,
+ const unsigned short data[/*n*/], int n) {
+ for (int i = 0; i < HUF_ENCSIZE; ++i) freq[i] = 0;
+
+ for (int i = 0; i < n; ++i) ++freq[data[i]];
+}
+
+static void writeUInt(char buf[4], unsigned int i) {
+ unsigned char *b = (unsigned char *)buf;
+
+ b[0] = i;
+ b[1] = i >> 8;
+ b[2] = i >> 16;
+ b[3] = i >> 24;
+}
+
+static unsigned int readUInt(const char buf[4]) {
+ const unsigned char *b = (const unsigned char *)buf;
+
+ return (b[0] & 0x000000ff) | ((b[1] << 8) & 0x0000ff00) |
+ ((b[2] << 16) & 0x00ff0000) | ((b[3] << 24) & 0xff000000);
+}
+
+//
+// EXTERNAL INTERFACE
+//
+
+static int hufCompress(const unsigned short raw[], int nRaw,
+ char compressed[]) {
+ if (nRaw == 0) return 0;
+
+ std::vector freq(HUF_ENCSIZE);
+
+ countFrequencies(freq, raw, nRaw);
+
+ int im = 0;
+ int iM = 0;
+ hufBuildEncTable(freq.data(), &im, &iM);
+
+ char *tableStart = compressed + 20;
+ char *tableEnd = tableStart;
+ hufPackEncTable(freq.data(), im, iM, &tableEnd);
+ int tableLength = tableEnd - tableStart;
+
+ char *dataStart = tableEnd;
+ int nBits = hufEncode(freq.data(), raw, nRaw, iM, dataStart);
+ int data_length = (nBits + 7) / 8;
+
+ writeUInt(compressed, im);
+ writeUInt(compressed + 4, iM);
+ writeUInt(compressed + 8, tableLength);
+ writeUInt(compressed + 12, nBits);
+ writeUInt(compressed + 16, 0); // room for future extensions
+
+ return dataStart + data_length - compressed;
+}
+
+static bool hufUncompress(const char compressed[], int nCompressed,
+ std::vector *raw) {
+ if (nCompressed == 0) {
+ if (raw->size() != 0) return false;
+
+ return false;
+ }
+
+ int im = readUInt(compressed);
+ int iM = readUInt(compressed + 4);
+ // int tableLength = readUInt (compressed + 8);
+ int nBits = readUInt(compressed + 12);
+
+ if (im < 0 || im >= HUF_ENCSIZE || iM < 0 || iM >= HUF_ENCSIZE) return false;
+
+ const char *ptr = compressed + 20;
+
+ //
+ // Fast decoder needs at least 2x64-bits of compressed data, and
+ // needs to be run-able on this platform. Otherwise, fall back
+ // to the original decoder
+ //
+
+ // if (FastHufDecoder::enabled() && nBits > 128)
+ //{
+ // FastHufDecoder fhd (ptr, nCompressed - (ptr - compressed), im, iM, iM);
+ // fhd.decode ((unsigned char*)ptr, nBits, raw, nRaw);
+ //}
+ // else
+ {
+ std::vector freq(HUF_ENCSIZE);
+ std::vector hdec(HUF_DECSIZE);
+
+ hufClearDecTable(&hdec.at(0));
+
+ hufUnpackEncTable(&ptr, nCompressed - (ptr - compressed), im, iM,
+ &freq.at(0));
+
+ {
+ if (nBits > 8 * (nCompressed - (ptr - compressed))) {
+ return false;
+ }
+
+ hufBuildDecTable(&freq.at(0), im, iM, &hdec.at(0));
+ hufDecode(&freq.at(0), &hdec.at(0), ptr, nBits, iM, raw->size(),
+ raw->data());
+ }
+ // catch (...)
+ //{
+ // hufFreeDecTable (hdec);
+ // throw;
+ //}
+
+ hufFreeDecTable(&hdec.at(0));
+ }
+
+ return true;
+}
+
+//
+// Functions to compress the range of values in the pixel data
+//
+
+const int USHORT_RANGE = (1 << 16);
+const int BITMAP_SIZE = (USHORT_RANGE >> 3);
+
+static void bitmapFromData(const unsigned short data[/*nData*/], int nData,
+ unsigned char bitmap[BITMAP_SIZE],
+ unsigned short &minNonZero,
+ unsigned short &maxNonZero) {
+ for (int i = 0; i < BITMAP_SIZE; ++i) bitmap[i] = 0;
+
+ for (int i = 0; i < nData; ++i) bitmap[data[i] >> 3] |= (1 << (data[i] & 7));
+
+ bitmap[0] &= ~1; // zero is not explicitly stored in
+ // the bitmap; we assume that the
+ // data always contain zeroes
+ minNonZero = BITMAP_SIZE - 1;
+ maxNonZero = 0;
+
+ for (int i = 0; i < BITMAP_SIZE; ++i) {
+ if (bitmap[i]) {
+ if (minNonZero > i) minNonZero = i;
+ if (maxNonZero < i) maxNonZero = i;
+ }
+ }
+}
+
+static unsigned short forwardLutFromBitmap(
+ const unsigned char bitmap[BITMAP_SIZE], unsigned short lut[USHORT_RANGE]) {
+ int k = 0;
+
+ for (int i = 0; i < USHORT_RANGE; ++i) {
+ if ((i == 0) || (bitmap[i >> 3] & (1 << (i & 7))))
+ lut[i] = k++;
+ else
+ lut[i] = 0;
+ }
+
+ return k - 1; // maximum value stored in lut[],
+} // i.e. number of ones in bitmap minus 1
+
+static unsigned short reverseLutFromBitmap(
+ const unsigned char bitmap[BITMAP_SIZE], unsigned short lut[USHORT_RANGE]) {
+ int k = 0;
+
+ for (int i = 0; i < USHORT_RANGE; ++i) {
+ if ((i == 0) || (bitmap[i >> 3] & (1 << (i & 7)))) lut[k++] = i;
+ }
+
+ int n = k - 1;
+
+ while (k < USHORT_RANGE) lut[k++] = 0;
+
+ return n; // maximum k where lut[k] is non-zero,
+} // i.e. number of ones in bitmap minus 1
+
+static void applyLut(const unsigned short lut[USHORT_RANGE],
+ unsigned short data[/*nData*/], int nData) {
+ for (int i = 0; i < nData; ++i) data[i] = lut[data[i]];
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif // __clang__
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+static bool CompressPiz(unsigned char *outPtr, unsigned int *outSize,
+ const unsigned char *inPtr, size_t inSize,
+ const std::vector &channelInfo,
+ int data_width, int num_lines) {
+ std::vector bitmap(BITMAP_SIZE);
+ unsigned short minNonZero;
+ unsigned short maxNonZero;
+
+#if !TINYEXR_LITTLE_ENDIAN
+ // @todo { PIZ compression on BigEndian architecture. }
+ return false;
+#endif
+
+ // Assume `inSize` is multiple of 2 or 4.
+ std::vector tmpBuffer(inSize / sizeof(unsigned short));
+
+ std::vector channelData(channelInfo.size());
+ unsigned short *tmpBufferEnd = &tmpBuffer.at(0);
+
+ for (size_t c = 0; c < channelData.size(); c++) {
+ PIZChannelData &cd = channelData[c];
+
+ cd.start = tmpBufferEnd;
+ cd.end = cd.start;
+
+ cd.nx = data_width;
+ cd.ny = num_lines;
+ // cd.ys = c.channel().ySampling;
+
+ size_t pixelSize = sizeof(int); // UINT and FLOAT
+ if (channelInfo[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) {
+ pixelSize = sizeof(short);
+ }
+
+ cd.size = static_cast(pixelSize / sizeof(short));
+
+ tmpBufferEnd += cd.nx * cd.ny * cd.size;
+ }
+
+ const unsigned char *ptr = inPtr;
+ for (int y = 0; y < num_lines; ++y) {
+ for (size_t i = 0; i < channelData.size(); ++i) {
+ PIZChannelData &cd = channelData[i];
+
+ // if (modp (y, cd.ys) != 0)
+ // continue;
+
+ size_t n = static_cast(cd.nx * cd.size);
+ memcpy(cd.end, ptr, n * sizeof(unsigned short));
+ ptr += n * sizeof(unsigned short);
+ cd.end += n;
+ }
+ }
+
+ bitmapFromData(&tmpBuffer.at(0), static_cast(tmpBuffer.size()),
+ bitmap.data(), minNonZero, maxNonZero);
+
+ std::vector lut(USHORT_RANGE);
+ unsigned short maxValue = forwardLutFromBitmap(bitmap.data(), lut.data());
+ applyLut(lut.data(), &tmpBuffer.at(0), static_cast(tmpBuffer.size()));
+
+ //
+ // Store range compression info in _outBuffer
+ //
+
+ char *buf = reinterpret_cast(outPtr);
+
+ memcpy(buf, &minNonZero, sizeof(unsigned short));
+ buf += sizeof(unsigned short);
+ memcpy(buf, &maxNonZero, sizeof(unsigned short));
+ buf += sizeof(unsigned short);
+
+ if (minNonZero <= maxNonZero) {
+ memcpy(buf, reinterpret_cast(&bitmap[0] + minNonZero),
+ maxNonZero - minNonZero + 1);
+ buf += maxNonZero - minNonZero + 1;
+ }
+
+ //
+ // Apply wavelet encoding
+ //
+
+ for (size_t i = 0; i < channelData.size(); ++i) {
+ PIZChannelData &cd = channelData[i];
+
+ for (int j = 0; j < cd.size; ++j) {
+ wav2Encode(cd.start + j, cd.nx, cd.size, cd.ny, cd.nx * cd.size,
+ maxValue);
+ }
+ }
+
+ //
+ // Apply Huffman encoding; append the result to _outBuffer
+ //
+
+ // length header(4byte), then huff data. Initialize length header with zero,
+ // then later fill it by `length`.
+ char *lengthPtr = buf;
+ int zero = 0;
+ memcpy(buf, &zero, sizeof(int));
+ buf += sizeof(int);
+
+ int length =
+ hufCompress(&tmpBuffer.at(0), static_cast