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(tmpBuffer.size()), buf); + memcpy(lengthPtr, &length, sizeof(int)); + + (*outSize) = static_cast( + (reinterpret_cast(buf) - outPtr) + + static_cast(length)); + + // Use uncompressed data when compressed data is larger than uncompressed. + // (Issue 40) + if ((*outSize) >= inSize) { + (*outSize) = static_cast(inSize); + memcpy(outPtr, inPtr, inSize); + } + return true; +} + +static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, + size_t tmpBufSizeInBytes, size_t inLen, int num_channels, + const EXRChannelInfo *channels, int data_width, + int num_lines) { + if (inLen == tmpBufSizeInBytes) { + // Data is not compressed(Issue 40). + memcpy(outPtr, inPtr, inLen); + return true; + } + + std::vector bitmap(BITMAP_SIZE); + unsigned short minNonZero; + unsigned short maxNonZero; + +#if !TINYEXR_LITTLE_ENDIAN + // @todo { PIZ compression on BigEndian architecture. } + return false; +#endif + + memset(bitmap.data(), 0, BITMAP_SIZE); + + if (inLen < 4) { + return false; + } + + size_t readLen = 0; + + const unsigned char *ptr = inPtr; + // minNonZero = *(reinterpret_cast(ptr)); + tinyexr::cpy2(&minNonZero, reinterpret_cast(ptr)); + // maxNonZero = *(reinterpret_cast(ptr + 2)); + tinyexr::cpy2(&maxNonZero, reinterpret_cast(ptr + 2)); + ptr += 4; + readLen += 4; + + if (maxNonZero >= BITMAP_SIZE) { + return false; + } + + //printf("maxNonZero = %d\n", maxNonZero); + //printf("minNonZero = %d\n", minNonZero); + //printf("len = %d\n", (maxNonZero - minNonZero + 1)); + //printf("BITMAPSIZE - min = %d\n", (BITMAP_SIZE - minNonZero)); + + if (minNonZero <= maxNonZero) { + if (((maxNonZero - minNonZero + 1) + readLen) > inLen) { + // Input too short + return false; + } + + memcpy(reinterpret_cast(&bitmap[0] + minNonZero), ptr, + maxNonZero - minNonZero + 1); + ptr += maxNonZero - minNonZero + 1; + readLen += maxNonZero - minNonZero + 1; + } else { + // Issue 194 + if ((minNonZero == (BITMAP_SIZE - 1)) && (maxNonZero == 0)) { + // OK. all pixels are zero. And no need to read `bitmap` data. + } else { + // invalid minNonZero/maxNonZero combination. + return false; + } + } + + std::vector lut(USHORT_RANGE); + memset(lut.data(), 0, sizeof(unsigned short) * USHORT_RANGE); + unsigned short maxValue = reverseLutFromBitmap(bitmap.data(), lut.data()); + + // + // Huffman decoding + // + + if ((readLen + 4) > inLen) { + return false; + } + + int length=0; + + // length = *(reinterpret_cast(ptr)); + tinyexr::cpy4(&length, reinterpret_cast(ptr)); + ptr += sizeof(int); + + if (size_t((ptr - inPtr) + length) > inLen) { + return false; + } + + std::vector tmpBuffer(tmpBufSizeInBytes / sizeof(unsigned short)); + hufUncompress(reinterpret_cast(ptr), length, &tmpBuffer); + + // + // Wavelet decoding + // + + std::vector channelData(static_cast(num_channels)); + + unsigned short *tmpBufferEnd = &tmpBuffer.at(0); + + for (size_t i = 0; i < static_cast(num_channels); ++i) { + const EXRChannelInfo &chan = channels[i]; + + size_t pixelSize = sizeof(int); // UINT and FLOAT + if (chan.pixel_type == TINYEXR_PIXELTYPE_HALF) { + pixelSize = sizeof(short); + } + + channelData[i].start = tmpBufferEnd; + channelData[i].end = channelData[i].start; + channelData[i].nx = data_width; + channelData[i].ny = num_lines; + // channelData[i].ys = 1; + channelData[i].size = static_cast(pixelSize / sizeof(short)); + + tmpBufferEnd += channelData[i].nx * channelData[i].ny * channelData[i].size; + } + + for (size_t i = 0; i < channelData.size(); ++i) { + PIZChannelData &cd = channelData[i]; + + for (int j = 0; j < cd.size; ++j) { + wav2Decode(cd.start + j, cd.nx, cd.size, cd.ny, cd.nx * cd.size, + maxValue); + } + } + + // + // Expand the pixel data to their original range + // + + applyLut(lut.data(), &tmpBuffer.at(0), static_cast(tmpBufSizeInBytes / sizeof(unsigned short))); + + 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(outPtr, cd.end, static_cast(n * sizeof(unsigned short))); + outPtr += n * sizeof(unsigned short); + cd.end += n; + } + } + + return true; +} +#endif // TINYEXR_USE_PIZ + +#if TINYEXR_USE_ZFP + +struct ZFPCompressionParam { + double rate; + unsigned int precision; + unsigned int __pad0; + double tolerance; + int type; // TINYEXR_ZFP_COMPRESSIONTYPE_* + unsigned int __pad1; + + ZFPCompressionParam() { + type = TINYEXR_ZFP_COMPRESSIONTYPE_RATE; + rate = 2.0; + precision = 0; + tolerance = 0.0; + } +}; + +static bool FindZFPCompressionParam(ZFPCompressionParam *param, + const EXRAttribute *attributes, + int num_attributes, std::string *err) { + bool foundType = false; + + for (int i = 0; i < num_attributes; i++) { + if ((strcmp(attributes[i].name, "zfpCompressionType") == 0)) { + if (attributes[i].size == 1) { + param->type = static_cast(attributes[i].value[0]); + foundType = true; + break; + } else { + if (err) { + (*err) += + "zfpCompressionType attribute must be uchar(1 byte) type.\n"; + } + return false; + } + } + } + + if (!foundType) { + if (err) { + (*err) += "`zfpCompressionType` attribute not found.\n"; + } + return false; + } + + if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) { + for (int i = 0; i < num_attributes; i++) { + if ((strcmp(attributes[i].name, "zfpCompressionRate") == 0) && + (attributes[i].size == 8)) { + param->rate = *(reinterpret_cast(attributes[i].value)); + return true; + } + } + + if (err) { + (*err) += "`zfpCompressionRate` attribute not found.\n"; + } + + } else if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) { + for (int i = 0; i < num_attributes; i++) { + if ((strcmp(attributes[i].name, "zfpCompressionPrecision") == 0) && + (attributes[i].size == 4)) { + param->rate = *(reinterpret_cast(attributes[i].value)); + return true; + } + } + + if (err) { + (*err) += "`zfpCompressionPrecision` attribute not found.\n"; + } + + } else if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) { + for (int i = 0; i < num_attributes; i++) { + if ((strcmp(attributes[i].name, "zfpCompressionTolerance") == 0) && + (attributes[i].size == 8)) { + param->tolerance = *(reinterpret_cast(attributes[i].value)); + return true; + } + } + + if (err) { + (*err) += "`zfpCompressionTolerance` attribute not found.\n"; + } + } else { + if (err) { + (*err) += "Unknown value specified for `zfpCompressionType`.\n"; + } + } + + return false; +} + +// Assume pixel format is FLOAT for all channels. +static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines, + size_t num_channels, const unsigned char *src, + unsigned long src_size, + const ZFPCompressionParam ¶m) { + size_t uncompressed_size = + size_t(dst_width) * size_t(dst_num_lines) * num_channels; + + if (uncompressed_size == src_size) { + // Data is not compressed(Issue 40). + memcpy(dst, src, src_size); + } + + zfp_stream *zfp = NULL; + zfp_field *field = NULL; + + TINYEXR_CHECK_AND_RETURN_C((dst_width % 4) == 0, false); + TINYEXR_CHECK_AND_RETURN_C((dst_num_lines % 4) == 0, false); + + if ((size_t(dst_width) & 3U) || (size_t(dst_num_lines) & 3U)) { + return false; + } + + field = + zfp_field_2d(reinterpret_cast(const_cast(src)), + zfp_type_float, static_cast(dst_width), + static_cast(dst_num_lines) * + static_cast(num_channels)); + zfp = zfp_stream_open(NULL); + + if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) { + zfp_stream_set_rate(zfp, param.rate, zfp_type_float, /* dimension */ 2, + /* write random access */ 0); + } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) { + zfp_stream_set_precision(zfp, param.precision); + } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) { + zfp_stream_set_accuracy(zfp, param.tolerance); + } else { + return false; + } + + size_t buf_size = zfp_stream_maximum_size(zfp, field); + std::vector buf(buf_size); + memcpy(&buf.at(0), src, src_size); + + bitstream *stream = stream_open(&buf.at(0), buf_size); + zfp_stream_set_bit_stream(zfp, stream); + zfp_stream_rewind(zfp); + + size_t image_size = size_t(dst_width) * size_t(dst_num_lines); + + for (size_t c = 0; c < size_t(num_channels); c++) { + // decompress 4x4 pixel block. + for (size_t y = 0; y < size_t(dst_num_lines); y += 4) { + for (size_t x = 0; x < size_t(dst_width); x += 4) { + float fblock[16]; + zfp_decode_block_float_2(zfp, fblock); + for (size_t j = 0; j < 4; j++) { + for (size_t i = 0; i < 4; i++) { + dst[c * image_size + ((y + j) * size_t(dst_width) + (x + i))] = + fblock[j * 4 + i]; + } + } + } + } + } + + zfp_field_free(field); + zfp_stream_close(zfp); + stream_close(stream); + + return true; +} + +// Assume pixel format is FLOAT for all channels. +static bool CompressZfp(std::vector *outBuf, + unsigned int *outSize, const float *inPtr, int width, + int num_lines, int num_channels, + const ZFPCompressionParam ¶m) { + zfp_stream *zfp = NULL; + zfp_field *field = NULL; + + TINYEXR_CHECK_AND_RETURN_C((width % 4) == 0, false); + TINYEXR_CHECK_AND_RETURN_C((num_lines % 4) == 0, false); + + if ((size_t(width) & 3U) || (size_t(num_lines) & 3U)) { + return false; + } + + // create input array. + field = zfp_field_2d(reinterpret_cast(const_cast(inPtr)), + zfp_type_float, static_cast(width), + static_cast(num_lines * num_channels)); + + zfp = zfp_stream_open(NULL); + + if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) { + zfp_stream_set_rate(zfp, param.rate, zfp_type_float, 2, 0); + } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) { + zfp_stream_set_precision(zfp, param.precision); + } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) { + zfp_stream_set_accuracy(zfp, param.tolerance); + } else { + return false; + } + + size_t buf_size = zfp_stream_maximum_size(zfp, field); + + outBuf->resize(buf_size); + + bitstream *stream = stream_open(&outBuf->at(0), buf_size); + zfp_stream_set_bit_stream(zfp, stream); + zfp_field_free(field); + + size_t image_size = size_t(width) * size_t(num_lines); + + for (size_t c = 0; c < size_t(num_channels); c++) { + // compress 4x4 pixel block. + for (size_t y = 0; y < size_t(num_lines); y += 4) { + for (size_t x = 0; x < size_t(width); x += 4) { + float fblock[16]; + for (size_t j = 0; j < 4; j++) { + for (size_t i = 0; i < 4; i++) { + fblock[j * 4 + i] = + inPtr[c * image_size + ((y + j) * size_t(width) + (x + i))]; + } + } + zfp_encode_block_float_2(zfp, fblock); + } + } + } + + zfp_stream_flush(zfp); + (*outSize) = static_cast(zfp_stream_compressed_size(zfp)); + + zfp_stream_close(zfp); + + return true; +} + +#endif + +// +// ----------------------------------------------------------------- +// + +// heuristics +#define TINYEXR_DIMENSION_THRESHOLD (1024 * 8192) + +// TODO(syoyo): Refactor function arguments. +static bool DecodePixelData(/* out */ unsigned char **out_images, + const int *requested_pixel_types, + const unsigned char *data_ptr, size_t data_len, + int compression_type, int line_order, int width, + int height, int x_stride, int y, int line_no, + int num_lines, size_t pixel_data_size, + size_t num_attributes, + const EXRAttribute *attributes, size_t num_channels, + const EXRChannelInfo *channels, + const std::vector &channel_offset_list) { + if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { // PIZ +#if TINYEXR_USE_PIZ + if ((width == 0) || (num_lines == 0) || (pixel_data_size == 0)) { + // Invalid input #90 + return false; + } + + // Allocate original data size. + std::vector outBuf(static_cast( + static_cast(width * num_lines) * pixel_data_size)); + size_t tmpBufLen = outBuf.size(); + + bool ret = tinyexr::DecompressPiz( + reinterpret_cast(&outBuf.at(0)), data_ptr, tmpBufLen, + data_len, static_cast(num_channels), channels, width, num_lines); + + if (!ret) { + return false; + } + + // For PIZ_COMPRESSION: + // pixel sample data for channel 0 for scanline 0 + // pixel sample data for channel 1 for scanline 0 + // pixel sample data for channel ... for scanline 0 + // pixel sample data for channel n for scanline 0 + // pixel sample data for channel 0 for scanline 1 + // pixel sample data for channel 1 for scanline 1 + // pixel sample data for channel ... for scanline 1 + // pixel sample data for channel n for scanline 1 + // ... + for (size_t c = 0; c < static_cast(num_channels); c++) { + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + for (size_t v = 0; v < static_cast(num_lines); v++) { + const unsigned short *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + FP16 hf; + + // hf.u = line_ptr[u]; + // use `cpy` to avoid unaligned memory access when compiler's + // optimization is on. + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { + unsigned short *image = + reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += static_cast( + (height - 1 - (line_no + static_cast(v)))) * + static_cast(x_stride) + + u; + } + *image = hf.u; + } else { // HALF -> FLOAT + FP32 f32 = half_to_float(hf); + float *image = reinterpret_cast(out_images)[c]; + size_t offset = 0; + if (line_order == 0) { + offset = (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + offset = static_cast( + (height - 1 - (line_no + static_cast(v)))) * + static_cast(x_stride) + + u; + } + image += offset; + *image = f32.f; + } + } + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false); + + for (size_t v = 0; v < static_cast(num_lines); v++) { + const unsigned int *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + unsigned int val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(&val); + + unsigned int *image = + reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += static_cast( + (height - 1 - (line_no + static_cast(v)))) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false); + for (size_t v = 0; v < static_cast(num_lines); v++) { + const float *line_ptr = reinterpret_cast(&outBuf.at( + v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + float val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(reinterpret_cast(&val)); + + float *image = reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += static_cast( + (height - 1 - (line_no + static_cast(v)))) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else { + return false; + } + } +#else + return false; +#endif + + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS || + compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) { + // Allocate original data size. + std::vector outBuf(static_cast(width) * + static_cast(num_lines) * + pixel_data_size); + + unsigned long dstLen = static_cast(outBuf.size()); + TINYEXR_CHECK_AND_RETURN_C(dstLen > 0, false); + if (!tinyexr::DecompressZip( + reinterpret_cast(&outBuf.at(0)), &dstLen, data_ptr, + static_cast(data_len))) { + return false; + } + + // For ZIP_COMPRESSION: + // pixel sample data for channel 0 for scanline 0 + // pixel sample data for channel 1 for scanline 0 + // pixel sample data for channel ... for scanline 0 + // pixel sample data for channel n for scanline 0 + // pixel sample data for channel 0 for scanline 1 + // pixel sample data for channel 1 for scanline 1 + // pixel sample data for channel ... for scanline 1 + // pixel sample data for channel n for scanline 1 + // ... + for (size_t c = 0; c < static_cast(num_channels); c++) { + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + for (size_t v = 0; v < static_cast(num_lines); v++) { + const unsigned short *line_ptr = reinterpret_cast( + &outBuf.at(v * static_cast(pixel_data_size) * + static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + tinyexr::FP16 hf; + + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { + unsigned short *image = + reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = hf.u; + } else { // HALF -> FLOAT + tinyexr::FP32 f32 = half_to_float(hf); + float *image = reinterpret_cast(out_images)[c]; + size_t offset = 0; + if (line_order == 0) { + offset = (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + offset = (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + image += offset; + + *image = f32.f; + } + } + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false); + + for (size_t v = 0; v < static_cast(num_lines); v++) { + const unsigned int *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + unsigned int val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(&val); + + unsigned int *image = + reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false); + for (size_t v = 0; v < static_cast(num_lines); v++) { + const float *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + float val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(reinterpret_cast(&val)); + + float *image = reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else { + return false; + } + } + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) { + // Allocate original data size. + std::vector outBuf(static_cast(width) * + static_cast(num_lines) * + pixel_data_size); + + unsigned long dstLen = static_cast(outBuf.size()); + if (dstLen == 0) { + return false; + } + + if (!tinyexr::DecompressRle( + reinterpret_cast(&outBuf.at(0)), dstLen, data_ptr, + static_cast(data_len))) { + return false; + } + + // For RLE_COMPRESSION: + // pixel sample data for channel 0 for scanline 0 + // pixel sample data for channel 1 for scanline 0 + // pixel sample data for channel ... for scanline 0 + // pixel sample data for channel n for scanline 0 + // pixel sample data for channel 0 for scanline 1 + // pixel sample data for channel 1 for scanline 1 + // pixel sample data for channel ... for scanline 1 + // pixel sample data for channel n for scanline 1 + // ... + for (size_t c = 0; c < static_cast(num_channels); c++) { + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + for (size_t v = 0; v < static_cast(num_lines); v++) { + const unsigned short *line_ptr = reinterpret_cast( + &outBuf.at(v * static_cast(pixel_data_size) * + static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + tinyexr::FP16 hf; + + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { + unsigned short *image = + reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = hf.u; + } else { // HALF -> FLOAT + tinyexr::FP32 f32 = half_to_float(hf); + float *image = reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = f32.f; + } + } + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false); + + for (size_t v = 0; v < static_cast(num_lines); v++) { + const unsigned int *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + unsigned int val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(&val); + + unsigned int *image = + reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false); + for (size_t v = 0; v < static_cast(num_lines); v++) { + const float *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + float val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(reinterpret_cast(&val)); + + float *image = reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else { + return false; + } + } + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { +#if TINYEXR_USE_ZFP + tinyexr::ZFPCompressionParam zfp_compression_param; + std::string e; + if (!tinyexr::FindZFPCompressionParam(&zfp_compression_param, attributes, + int(num_attributes), &e)) { + // This code path should not be reachable. + return false; + } + + // Allocate original data size. + std::vector outBuf(static_cast(width) * + static_cast(num_lines) * + pixel_data_size); + + unsigned long dstLen = outBuf.size(); + TINYEXR_CHECK_AND_RETURN_C(dstLen > 0, false); + tinyexr::DecompressZfp(reinterpret_cast(&outBuf.at(0)), width, + num_lines, num_channels, data_ptr, + static_cast(data_len), + zfp_compression_param); + + // For ZFP_COMPRESSION: + // pixel sample data for channel 0 for scanline 0 + // pixel sample data for channel 1 for scanline 0 + // pixel sample data for channel ... for scanline 0 + // pixel sample data for channel n for scanline 0 + // pixel sample data for channel 0 for scanline 1 + // pixel sample data for channel 1 for scanline 1 + // pixel sample data for channel ... for scanline 1 + // pixel sample data for channel n for scanline 1 + // ... + for (size_t c = 0; c < static_cast(num_channels); c++) { + TINYEXR_CHECK_AND_RETURN_C(channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT, false); + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false); + for (size_t v = 0; v < static_cast(num_lines); v++) { + const float *line_ptr = reinterpret_cast( + &outBuf.at(v * pixel_data_size * static_cast(width) + + channel_offset_list[c] * static_cast(width))); + for (size_t u = 0; u < static_cast(width); u++) { + float val; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(reinterpret_cast(&val)); + + float *image = reinterpret_cast(out_images)[c]; + if (line_order == 0) { + image += (static_cast(line_no) + v) * + static_cast(x_stride) + + u; + } else { + image += (static_cast(height) - 1U - + (static_cast(line_no) + v)) * + static_cast(x_stride) + + u; + } + *image = val; + } + } + } else { + return false; + } + } +#else + (void)attributes; + (void)num_attributes; + (void)num_channels; + return false; +#endif + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_NONE) { + for (size_t c = 0; c < num_channels; c++) { + for (size_t v = 0; v < static_cast(num_lines); v++) { + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + const unsigned short *line_ptr = + reinterpret_cast( + data_ptr + v * pixel_data_size * size_t(width) + + channel_offset_list[c] * static_cast(width)); + + if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { + unsigned short *outLine = + reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + for (int u = 0; u < width; u++) { + tinyexr::FP16 hf; + + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + outLine[u] = hf.u; + } + } else if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { + float *outLine = reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Insufficient data size + return false; + } + + for (int u = 0; u < width; u++) { + tinyexr::FP16 hf; + + // address may not be aligned. use byte-wise copy for safety.#76 + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + tinyexr::FP32 f32 = half_to_float(hf); + + outLine[u] = f32.f; + } + } else { + return false; + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + const float *line_ptr = reinterpret_cast( + data_ptr + v * pixel_data_size * size_t(width) + + channel_offset_list[c] * static_cast(width)); + + float *outLine = reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Insufficient data size + return false; + } + + for (int u = 0; u < width; u++) { + float val; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(reinterpret_cast(&val)); + + outLine[u] = val; + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + const unsigned int *line_ptr = reinterpret_cast( + data_ptr + v * pixel_data_size * size_t(width) + + channel_offset_list[c] * static_cast(width)); + + unsigned int *outLine = + reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Corrupted data + return false; + } + + for (int u = 0; u < width; u++) { + + unsigned int val; + tinyexr::cpy4(&val, line_ptr + u); + + tinyexr::swap4(reinterpret_cast(&val)); + + outLine[u] = val; + } + } + } + } + } + + return true; +} + +static bool DecodeTiledPixelData( + unsigned char **out_images, int *width, int *height, + const int *requested_pixel_types, const unsigned char *data_ptr, + size_t data_len, int compression_type, int line_order, int data_width, + int data_height, int tile_offset_x, int tile_offset_y, int tile_size_x, + int tile_size_y, size_t pixel_data_size, size_t num_attributes, + const EXRAttribute *attributes, size_t num_channels, + const EXRChannelInfo *channels, + const std::vector &channel_offset_list) { + // Here, data_width and data_height are the dimensions of the current (sub)level. + if (tile_size_x * tile_offset_x > data_width || + tile_size_y * tile_offset_y > data_height) { + return false; + } + + // Compute actual image size in a tile. + if ((tile_offset_x + 1) * tile_size_x >= data_width) { + (*width) = data_width - (tile_offset_x * tile_size_x); + } else { + (*width) = tile_size_x; + } + + if ((tile_offset_y + 1) * tile_size_y >= data_height) { + (*height) = data_height - (tile_offset_y * tile_size_y); + } else { + (*height) = tile_size_y; + } + + // Image size = tile size. + return DecodePixelData(out_images, requested_pixel_types, data_ptr, data_len, + compression_type, line_order, (*width), tile_size_y, + /* stride */ tile_size_x, /* y */ 0, /* line_no */ 0, + (*height), pixel_data_size, num_attributes, attributes, + num_channels, channels, channel_offset_list); +} + +static bool ComputeChannelLayout(std::vector *channel_offset_list, + int *pixel_data_size, size_t *channel_offset, + int num_channels, + const EXRChannelInfo *channels) { + channel_offset_list->resize(static_cast(num_channels)); + + (*pixel_data_size) = 0; + (*channel_offset) = 0; + + for (size_t c = 0; c < static_cast(num_channels); c++) { + (*channel_offset_list)[c] = (*channel_offset); + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + (*pixel_data_size) += sizeof(unsigned short); + (*channel_offset) += sizeof(unsigned short); + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + (*pixel_data_size) += sizeof(float); + (*channel_offset) += sizeof(float); + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + (*pixel_data_size) += sizeof(unsigned int); + (*channel_offset) += sizeof(unsigned int); + } else { + // ??? + return false; + } + } + return true; +} + +// TODO: Simply return nullptr when failed to allocate? +static unsigned char **AllocateImage(int num_channels, + const EXRChannelInfo *channels, + const int *requested_pixel_types, + int data_width, int data_height, bool *success) { + unsigned char **images = + reinterpret_cast(static_cast( + malloc(sizeof(float *) * static_cast(num_channels)))); + + for (size_t c = 0; c < static_cast(num_channels); c++) { + images[c] = NULL; + } + + bool valid = true; + + for (size_t c = 0; c < static_cast(num_channels); c++) { + size_t data_len = + static_cast(data_width) * static_cast(data_height); + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + // pixel_data_size += sizeof(unsigned short); + // channel_offset += sizeof(unsigned short); + // Alloc internal image for half type. + if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { + images[c] = + reinterpret_cast(static_cast( + malloc(sizeof(unsigned short) * data_len))); + } else if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { + images[c] = reinterpret_cast( + static_cast(malloc(sizeof(float) * data_len))); + } else { + images[c] = NULL; // just in case. + valid = false; + break; + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + // pixel_data_size += sizeof(float); + // channel_offset += sizeof(float); + images[c] = reinterpret_cast( + static_cast(malloc(sizeof(float) * data_len))); + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + // pixel_data_size += sizeof(unsigned int); + // channel_offset += sizeof(unsigned int); + images[c] = reinterpret_cast( + static_cast(malloc(sizeof(unsigned int) * data_len))); + } else { + images[c] = NULL; // just in case. + valid = false; + break; + } + } + + if (!valid) { + for (size_t c = 0; c < static_cast(num_channels); c++) { + if (images[c]) { + free(images[c]); + images[c] = NULL; + } + } + + if (success) { + (*success) = false; + } + } else { + if (success) { + (*success) = true; + } + } + + return images; +} + +#ifdef _WIN32 +static inline std::wstring UTF8ToWchar(const std::string &str) { + int wstr_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), NULL, 0); + std::wstring wstr(wstr_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], + (int)wstr.size()); + return wstr; +} +#endif + + +static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, + const EXRVersion *version, std::string *err, + const unsigned char *buf, size_t size) { + const char *marker = reinterpret_cast(&buf[0]); + + if (empty_header) { + (*empty_header) = false; + } + + if (version->multipart) { + if (size > 0 && marker[0] == '\0') { + // End of header list. + if (empty_header) { + (*empty_header) = true; + } + return TINYEXR_SUCCESS; + } + } + + // According to the spec, the header of every OpenEXR file must contain at + // least the following attributes: + // + // channels chlist + // compression compression + // dataWindow box2i + // displayWindow box2i + // lineOrder lineOrder + // pixelAspectRatio float + // screenWindowCenter v2f + // screenWindowWidth float + bool has_channels = false; + bool has_compression = false; + bool has_data_window = false; + bool has_display_window = false; + bool has_line_order = false; + bool has_pixel_aspect_ratio = false; + bool has_screen_window_center = false; + bool has_screen_window_width = false; + bool has_name = false; + bool has_type = false; + + info->name.clear(); + info->type.clear(); + + info->data_window.min_x = 0; + info->data_window.min_y = 0; + info->data_window.max_x = 0; + info->data_window.max_y = 0; + info->line_order = 0; // @fixme + info->display_window.min_x = 0; + info->display_window.min_y = 0; + info->display_window.max_x = 0; + info->display_window.max_y = 0; + info->screen_window_center[0] = 0.0f; + info->screen_window_center[1] = 0.0f; + info->screen_window_width = -1.0f; + info->pixel_aspect_ratio = -1.0f; + + info->tiled = 0; + info->tile_size_x = -1; + info->tile_size_y = -1; + info->tile_level_mode = -1; + info->tile_rounding_mode = -1; + + info->attributes.clear(); + + // Read attributes + size_t orig_size = size; + for (size_t nattr = 0; nattr < TINYEXR_MAX_HEADER_ATTRIBUTES; nattr++) { + if (0 == size) { + if (err) { + (*err) += "Insufficient data size for attributes.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } else if (marker[0] == '\0') { + size--; + break; + } + + std::string attr_name; + std::string attr_type; + std::vector data; + size_t marker_size; + if (!tinyexr::ReadAttribute(&attr_name, &attr_type, &data, &marker_size, + marker, size)) { + if (err) { + (*err) += "Failed to read attribute.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + marker += marker_size; + size -= marker_size; + + // For a multipart file, the version field 9th bit is 0. + if ((version->tiled || version->multipart || version->non_image) && attr_name.compare("tiles") == 0) { + unsigned int x_size, y_size; + unsigned char tile_mode; + if (data.size() != 9) { + if (err) { + (*err) += "(ParseEXRHeader) Invalid attribute data size. Attribute data size must be 9.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + memcpy(&x_size, &data.at(0), sizeof(int)); + memcpy(&y_size, &data.at(4), sizeof(int)); + tile_mode = data[8]; + tinyexr::swap4(&x_size); + tinyexr::swap4(&y_size); + + if (x_size > static_cast(std::numeric_limits::max()) || + y_size > static_cast(std::numeric_limits::max())) { + if (err) { + (*err) = "Tile sizes were invalid."; + } + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; + } + + info->tile_size_x = static_cast(x_size); + info->tile_size_y = static_cast(y_size); + + // mode = levelMode + roundingMode * 16 + info->tile_level_mode = tile_mode & 0x3; + info->tile_rounding_mode = (tile_mode >> 4) & 0x1; + info->tiled = 1; + } else if (attr_name.compare("compression") == 0) { + bool ok = false; + if (data[0] < TINYEXR_COMPRESSIONTYPE_PIZ) { + ok = true; + } + + if (data[0] == TINYEXR_COMPRESSIONTYPE_PIZ) { +#if TINYEXR_USE_PIZ + ok = true; +#else + if (err) { + (*err) = "PIZ compression is not supported."; + } + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; +#endif + } + + if (data[0] == TINYEXR_COMPRESSIONTYPE_ZFP) { +#if TINYEXR_USE_ZFP + ok = true; +#else + if (err) { + (*err) = "ZFP compression is not supported."; + } + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; +#endif + } + + if (!ok) { + if (err) { + (*err) = "Unknown compression type."; + } + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; + } + + info->compression_type = static_cast(data[0]); + has_compression = true; + + } else if (attr_name.compare("channels") == 0) { + // name: zero-terminated string, from 1 to 255 bytes long + // pixel type: int, possible values are: UINT = 0 HALF = 1 FLOAT = 2 + // pLinear: unsigned char, possible values are 0 and 1 + // reserved: three chars, should be zero + // xSampling: int + // ySampling: int + + if (!ReadChannelInfo(info->channels, data)) { + if (err) { + (*err) += "Failed to parse channel info.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + if (info->channels.size() < 1) { + if (err) { + (*err) += "# of channels is zero.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + has_channels = true; + + } else if (attr_name.compare("dataWindow") == 0) { + if (data.size() >= 16) { + memcpy(&info->data_window.min_x, &data.at(0), sizeof(int)); + memcpy(&info->data_window.min_y, &data.at(4), sizeof(int)); + memcpy(&info->data_window.max_x, &data.at(8), sizeof(int)); + memcpy(&info->data_window.max_y, &data.at(12), sizeof(int)); + tinyexr::swap4(&info->data_window.min_x); + tinyexr::swap4(&info->data_window.min_y); + tinyexr::swap4(&info->data_window.max_x); + tinyexr::swap4(&info->data_window.max_y); + has_data_window = true; + } + } else if (attr_name.compare("displayWindow") == 0) { + if (data.size() >= 16) { + memcpy(&info->display_window.min_x, &data.at(0), sizeof(int)); + memcpy(&info->display_window.min_y, &data.at(4), sizeof(int)); + memcpy(&info->display_window.max_x, &data.at(8), sizeof(int)); + memcpy(&info->display_window.max_y, &data.at(12), sizeof(int)); + tinyexr::swap4(&info->display_window.min_x); + tinyexr::swap4(&info->display_window.min_y); + tinyexr::swap4(&info->display_window.max_x); + tinyexr::swap4(&info->display_window.max_y); + + has_display_window = true; + } + } else if (attr_name.compare("lineOrder") == 0) { + if (data.size() >= 1) { + info->line_order = static_cast(data[0]); + has_line_order = true; + } + } else if (attr_name.compare("pixelAspectRatio") == 0) { + if (data.size() >= sizeof(float)) { + memcpy(&info->pixel_aspect_ratio, &data.at(0), sizeof(float)); + tinyexr::swap4(&info->pixel_aspect_ratio); + has_pixel_aspect_ratio = true; + } + } else if (attr_name.compare("screenWindowCenter") == 0) { + if (data.size() >= 8) { + memcpy(&info->screen_window_center[0], &data.at(0), sizeof(float)); + memcpy(&info->screen_window_center[1], &data.at(4), sizeof(float)); + tinyexr::swap4(&info->screen_window_center[0]); + tinyexr::swap4(&info->screen_window_center[1]); + has_screen_window_center = true; + } + } else if (attr_name.compare("screenWindowWidth") == 0) { + if (data.size() >= sizeof(float)) { + memcpy(&info->screen_window_width, &data.at(0), sizeof(float)); + tinyexr::swap4(&info->screen_window_width); + + has_screen_window_width = true; + } + } else if (attr_name.compare("chunkCount") == 0) { + if (data.size() >= sizeof(int)) { + memcpy(&info->chunk_count, &data.at(0), sizeof(int)); + tinyexr::swap4(&info->chunk_count); + } + } else if (attr_name.compare("name") == 0) { + if (!data.empty() && data[0]) { + data.push_back(0); + size_t len = strlen(reinterpret_cast(&data[0])); + info->name.resize(len); + info->name.assign(reinterpret_cast(&data[0]), len); + has_name = true; + } + } else if (attr_name.compare("type") == 0) { + if (!data.empty() && data[0]) { + data.push_back(0); + size_t len = strlen(reinterpret_cast(&data[0])); + info->type.resize(len); + info->type.assign(reinterpret_cast(&data[0]), len); + has_type = true; + } + } else { + // Custom attribute(up to TINYEXR_MAX_CUSTOM_ATTRIBUTES) + if (info->attributes.size() < TINYEXR_MAX_CUSTOM_ATTRIBUTES) { + EXRAttribute attrib; +#ifdef _MSC_VER + strncpy_s(attrib.name, attr_name.c_str(), 255); + strncpy_s(attrib.type, attr_type.c_str(), 255); +#else + strncpy(attrib.name, attr_name.c_str(), 255); + strncpy(attrib.type, attr_type.c_str(), 255); +#endif + attrib.name[255] = '\0'; + attrib.type[255] = '\0'; + //std::cout << "i = " << info->attributes.size() << ", dsize = " << data.size() << "\n"; + attrib.size = static_cast(data.size()); + attrib.value = static_cast(malloc(data.size())); + memcpy(reinterpret_cast(attrib.value), &data.at(0), + data.size()); + info->attributes.push_back(attrib); + } + } + } + + // Check if required attributes exist + { + std::stringstream ss_err; + + if (!has_compression) { + ss_err << "\"compression\" attribute not found in the header." + << std::endl; + } + + if (!has_channels) { + ss_err << "\"channels\" attribute not found in the header." << std::endl; + } + + if (!has_line_order) { + ss_err << "\"lineOrder\" attribute not found in the header." << std::endl; + } + + if (!has_display_window) { + ss_err << "\"displayWindow\" attribute not found in the header." + << std::endl; + } + + if (!has_data_window) { + ss_err << "\"dataWindow\" attribute not found in the header or invalid." + << std::endl; + } + + if (!has_pixel_aspect_ratio) { + ss_err << "\"pixelAspectRatio\" attribute not found in the header." + << std::endl; + } + + if (!has_screen_window_width) { + ss_err << "\"screenWindowWidth\" attribute not found in the header." + << std::endl; + } + + if (!has_screen_window_center) { + ss_err << "\"screenWindowCenter\" attribute not found in the header." + << std::endl; + } + + if (version->multipart || version->non_image) { + if (!has_name) { + ss_err << "\"name\" attribute not found in the header." + << std::endl; + } + if (!has_type) { + ss_err << "\"type\" attribute not found in the header." + << std::endl; + } + } + + if (!(ss_err.str().empty())) { + if (err) { + (*err) += ss_err.str(); + } + + return TINYEXR_ERROR_INVALID_HEADER; + } + } + + info->header_len = static_cast(orig_size - size); + + return TINYEXR_SUCCESS; +} + +// C++ HeaderInfo to C EXRHeader conversion. +static bool ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info, std::string *warn, std::string *err) { + exr_header->pixel_aspect_ratio = info.pixel_aspect_ratio; + exr_header->screen_window_center[0] = info.screen_window_center[0]; + exr_header->screen_window_center[1] = info.screen_window_center[1]; + exr_header->screen_window_width = info.screen_window_width; + exr_header->chunk_count = info.chunk_count; + exr_header->display_window.min_x = info.display_window.min_x; + exr_header->display_window.min_y = info.display_window.min_y; + exr_header->display_window.max_x = info.display_window.max_x; + exr_header->display_window.max_y = info.display_window.max_y; + exr_header->data_window.min_x = info.data_window.min_x; + exr_header->data_window.min_y = info.data_window.min_y; + exr_header->data_window.max_x = info.data_window.max_x; + exr_header->data_window.max_y = info.data_window.max_y; + exr_header->line_order = info.line_order; + exr_header->compression_type = info.compression_type; + exr_header->tiled = info.tiled; + exr_header->tile_size_x = info.tile_size_x; + exr_header->tile_size_y = info.tile_size_y; + exr_header->tile_level_mode = info.tile_level_mode; + exr_header->tile_rounding_mode = info.tile_rounding_mode; + + EXRSetNameAttr(exr_header, info.name.c_str()); + + + if (!info.type.empty()) { + bool valid = true; + if (info.type == "scanlineimage") { + if (exr_header->tiled) { + if (err) { + (*err) += "(ConvertHeader) tiled bit must be off for `scanlineimage` type.\n"; + } + valid = false; + } + } else if (info.type == "tiledimage") { + if (!exr_header->tiled) { + if (err) { + (*err) += "(ConvertHeader) tiled bit must be on for `tiledimage` type.\n"; + } + valid = false; + } + } else if (info.type == "deeptile") { + exr_header->non_image = 1; + if (!exr_header->tiled) { + if (err) { + (*err) += "(ConvertHeader) tiled bit must be on for `deeptile` type.\n"; + } + valid = false; + } + } else if (info.type == "deepscanline") { + exr_header->non_image = 1; + if (exr_header->tiled) { + if (err) { + (*err) += "(ConvertHeader) tiled bit must be off for `deepscanline` type.\n"; + } + //valid = false; + } + } else { + if (warn) { + std::stringstream ss; + ss << "(ConvertHeader) Unsupported or unknown info.type: " << info.type << "\n"; + (*warn) += ss.str(); + } + } + + if (!valid) { + return false; + } + } + + exr_header->num_channels = static_cast(info.channels.size()); + + exr_header->channels = static_cast(malloc( + sizeof(EXRChannelInfo) * static_cast(exr_header->num_channels))); + for (size_t c = 0; c < static_cast(exr_header->num_channels); c++) { +#ifdef _MSC_VER + strncpy_s(exr_header->channels[c].name, info.channels[c].name.c_str(), 255); +#else + strncpy(exr_header->channels[c].name, info.channels[c].name.c_str(), 255); +#endif + // manually add '\0' for safety. + exr_header->channels[c].name[255] = '\0'; + + exr_header->channels[c].pixel_type = info.channels[c].pixel_type; + exr_header->channels[c].p_linear = info.channels[c].p_linear; + exr_header->channels[c].x_sampling = info.channels[c].x_sampling; + exr_header->channels[c].y_sampling = info.channels[c].y_sampling; + } + + exr_header->pixel_types = static_cast( + malloc(sizeof(int) * static_cast(exr_header->num_channels))); + for (size_t c = 0; c < static_cast(exr_header->num_channels); c++) { + exr_header->pixel_types[c] = info.channels[c].pixel_type; + } + + // Initially fill with values of `pixel_types` + exr_header->requested_pixel_types = static_cast( + malloc(sizeof(int) * static_cast(exr_header->num_channels))); + for (size_t c = 0; c < static_cast(exr_header->num_channels); c++) { + exr_header->requested_pixel_types[c] = info.channels[c].pixel_type; + } + + exr_header->num_custom_attributes = static_cast(info.attributes.size()); + + if (exr_header->num_custom_attributes > 0) { + // TODO(syoyo): Report warning when # of attributes exceeds + // `TINYEXR_MAX_CUSTOM_ATTRIBUTES` + if (exr_header->num_custom_attributes > TINYEXR_MAX_CUSTOM_ATTRIBUTES) { + exr_header->num_custom_attributes = TINYEXR_MAX_CUSTOM_ATTRIBUTES; + } + + exr_header->custom_attributes = static_cast(malloc( + sizeof(EXRAttribute) * size_t(exr_header->num_custom_attributes))); + + for (size_t i = 0; i < size_t(exr_header->num_custom_attributes); i++) { + memcpy(exr_header->custom_attributes[i].name, info.attributes[i].name, + 256); + memcpy(exr_header->custom_attributes[i].type, info.attributes[i].type, + 256); + exr_header->custom_attributes[i].size = info.attributes[i].size; + // Just copy pointer + exr_header->custom_attributes[i].value = info.attributes[i].value; + } + + } else { + exr_header->custom_attributes = NULL; + } + + exr_header->header_len = info.header_len; + + return true; +} + +struct OffsetData { + OffsetData() : num_x_levels(0), num_y_levels(0) {} + std::vector > > offsets; + int num_x_levels; + int num_y_levels; +}; + +// -1 = error +static int LevelIndex(int lx, int ly, int tile_level_mode, int num_x_levels) { + switch (tile_level_mode) { + case TINYEXR_TILE_ONE_LEVEL: + return 0; + + case TINYEXR_TILE_MIPMAP_LEVELS: + return lx; + + case TINYEXR_TILE_RIPMAP_LEVELS: + return lx + ly * num_x_levels; + + default: + return -1; + } + return 0; +} + +static int LevelSize(int toplevel_size, int level, int tile_rounding_mode) { + if (level < 0) { + return -1; + } + + int b = static_cast(1u << static_cast(level)); + int level_size = toplevel_size / b; + + if (tile_rounding_mode == TINYEXR_TILE_ROUND_UP && level_size * b < toplevel_size) + level_size += 1; + + return std::max(level_size, 1); +} + +static int DecodeTiledLevel(EXRImage* exr_image, const EXRHeader* exr_header, + const OffsetData& offset_data, + const std::vector& channel_offset_list, + int pixel_data_size, + const unsigned char* head, const size_t size, + std::string* err) { + int num_channels = exr_header->num_channels; + + int level_index = LevelIndex(exr_image->level_x, exr_image->level_y, exr_header->tile_level_mode, offset_data.num_x_levels); + int num_y_tiles = int(offset_data.offsets[size_t(level_index)].size()); + if (num_y_tiles < 1) { + return TINYEXR_ERROR_INVALID_DATA; + } + int num_x_tiles = int(offset_data.offsets[size_t(level_index)][0].size()); + if (num_x_tiles < 1) { + return TINYEXR_ERROR_INVALID_DATA; + } + int num_tiles = num_x_tiles * num_y_tiles; + + int err_code = TINYEXR_SUCCESS; + + enum { + EF_SUCCESS = 0, + EF_INVALID_DATA = 1, + EF_INSUFFICIENT_DATA = 2, + EF_FAILED_TO_DECODE = 4 + }; +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::atomic error_flag(EF_SUCCESS); +#else + unsigned error_flag(EF_SUCCESS); +#endif + + // Although the spec says : "...the data window is subdivided into an array of smaller rectangles...", + // the IlmImf library allows the dimensions of the tile to be larger (or equal) than the dimensions of the data window. +#if 0 + if ((exr_header->tile_size_x > exr_image->width || exr_header->tile_size_y > exr_image->height) && + exr_image->level_x == 0 && exr_image->level_y == 0) { + if (err) { + (*err) += "Failed to decode tile data.\n"; + } + err_code = TINYEXR_ERROR_INVALID_DATA; + } +#endif + exr_image->tiles = static_cast( + calloc(sizeof(EXRTile), static_cast(num_tiles))); + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::vector workers; + std::atomic tile_count(0); + + int num_threads = std::max(1, int(std::thread::hardware_concurrency())); + if (num_threads > int(num_tiles)) { + num_threads = int(num_tiles); + } + + for (int t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&]() + { + int tile_idx = 0; + while ((tile_idx = tile_count++) < num_tiles) { + +#else +#if TINYEXR_USE_OPENMP +#pragma omp parallel for +#endif + for (int tile_idx = 0; tile_idx < num_tiles; tile_idx++) { +#endif + // Allocate memory for each tile. + bool alloc_success = false; + exr_image->tiles[tile_idx].images = tinyexr::AllocateImage( + num_channels, exr_header->channels, + exr_header->requested_pixel_types, exr_header->tile_size_x, + exr_header->tile_size_y, &alloc_success); + + if (!alloc_success) { + error_flag |= EF_INVALID_DATA; + continue; + } + + int x_tile = tile_idx % num_x_tiles; + int y_tile = tile_idx / num_x_tiles; + // 16 byte: tile coordinates + // 4 byte : data size + // ~ : data(uncompressed or compressed) + tinyexr::tinyexr_uint64 offset = offset_data.offsets[size_t(level_index)][size_t(y_tile)][size_t(x_tile)]; + if (offset + sizeof(int) * 5 > size) { + // Insufficient data size. + error_flag |= EF_INSUFFICIENT_DATA; + continue; + } + + size_t data_size = + size_t(size - (offset + sizeof(int) * 5)); + const unsigned char* data_ptr = + reinterpret_cast(head + offset); + + int tile_coordinates[4]; + memcpy(tile_coordinates, data_ptr, sizeof(int) * 4); + tinyexr::swap4(&tile_coordinates[0]); + tinyexr::swap4(&tile_coordinates[1]); + tinyexr::swap4(&tile_coordinates[2]); + tinyexr::swap4(&tile_coordinates[3]); + + if (tile_coordinates[2] != exr_image->level_x) { + // Invalid data. + error_flag |= EF_INVALID_DATA; + continue; + } + if (tile_coordinates[3] != exr_image->level_y) { + // Invalid data. + error_flag |= EF_INVALID_DATA; + continue; + } + + int data_len; + memcpy(&data_len, data_ptr + 16, + sizeof(int)); // 16 = sizeof(tile_coordinates) + tinyexr::swap4(&data_len); + + if (data_len < 2 || size_t(data_len) > data_size) { + // Insufficient data size. + error_flag |= EF_INSUFFICIENT_DATA; + continue; + } + + // Move to data addr: 20 = 16 + 4; + data_ptr += 20; + bool ret = tinyexr::DecodeTiledPixelData( + exr_image->tiles[tile_idx].images, + &(exr_image->tiles[tile_idx].width), + &(exr_image->tiles[tile_idx].height), + exr_header->requested_pixel_types, data_ptr, + static_cast(data_len), exr_header->compression_type, + exr_header->line_order, + exr_image->width, exr_image->height, + tile_coordinates[0], tile_coordinates[1], exr_header->tile_size_x, + exr_header->tile_size_y, static_cast(pixel_data_size), + static_cast(exr_header->num_custom_attributes), + exr_header->custom_attributes, + static_cast(exr_header->num_channels), + exr_header->channels, channel_offset_list); + + if (!ret) { + // Failed to decode tile data. + error_flag |= EF_FAILED_TO_DECODE; + } + + exr_image->tiles[tile_idx].offset_x = tile_coordinates[0]; + exr_image->tiles[tile_idx].offset_y = tile_coordinates[1]; + exr_image->tiles[tile_idx].level_x = tile_coordinates[2]; + exr_image->tiles[tile_idx].level_y = tile_coordinates[3]; + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + } + })); + } // num_thread loop + + for (auto& t : workers) { + t.join(); + } + +#else + } // parallel for +#endif + + // Even in the event of an error, the reserved memory may be freed. + exr_image->num_channels = num_channels; + exr_image->num_tiles = static_cast(num_tiles); + + if (error_flag) err_code = TINYEXR_ERROR_INVALID_DATA; + if (err) { + if (error_flag & EF_INSUFFICIENT_DATA) { + (*err) += "Insufficient data length.\n"; + } + if (error_flag & EF_FAILED_TO_DECODE) { + (*err) += "Failed to decode tile data.\n"; + } + } + return err_code; +} + +static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, + const OffsetData& offset_data, + const unsigned char *head, const size_t size, + std::string *err) { + int num_channels = exr_header->num_channels; + + int num_scanline_blocks = 1; + if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) { + num_scanline_blocks = 16; + } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { + num_scanline_blocks = 32; + } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { + num_scanline_blocks = 16; + +#if TINYEXR_USE_ZFP + tinyexr::ZFPCompressionParam zfp_compression_param; + if (!FindZFPCompressionParam(&zfp_compression_param, + exr_header->custom_attributes, + int(exr_header->num_custom_attributes), err)) { + return TINYEXR_ERROR_INVALID_HEADER; + } +#endif + } + + if (exr_header->data_window.max_x < exr_header->data_window.min_x || + exr_header->data_window.max_y < exr_header->data_window.min_y) { + if (err) { + (*err) += "Invalid data window.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + tinyexr_int64 data_width = + static_cast(exr_header->data_window.max_x) - static_cast(exr_header->data_window.min_x) + static_cast(1); + tinyexr_int64 data_height = + static_cast(exr_header->data_window.max_y) - static_cast(exr_header->data_window.min_y) + static_cast(1); + + if (data_width <= 0) { + if (err) { + (*err) += "Invalid data window width.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + if (data_height <= 0) { + if (err) { + (*err) += "Invalid data window height.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + // Do not allow too large data_width and data_height. header invalid? + { + if ((data_width > TINYEXR_DIMENSION_THRESHOLD) || (data_height > TINYEXR_DIMENSION_THRESHOLD)) { + if (err) { + std::stringstream ss; + ss << "data_with or data_height too large. data_width: " << data_width + << ", " + << "data_height = " << data_height << std::endl; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_DATA; + } + if (exr_header->tiled) { + if ((exr_header->tile_size_x > TINYEXR_DIMENSION_THRESHOLD) || (exr_header->tile_size_y > TINYEXR_DIMENSION_THRESHOLD)) { + if (err) { + std::stringstream ss; + ss << "tile with or tile height too large. tile width: " << exr_header->tile_size_x + << ", " + << "tile height = " << exr_header->tile_size_y << std::endl; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_DATA; + } + } + } + + const std::vector& offsets = offset_data.offsets[0][0]; + size_t num_blocks = offsets.size(); + + std::vector channel_offset_list; + int pixel_data_size = 0; + size_t channel_offset = 0; + if (!tinyexr::ComputeChannelLayout(&channel_offset_list, &pixel_data_size, + &channel_offset, num_channels, + exr_header->channels)) { + if (err) { + (*err) += "Failed to compute channel layout.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::atomic invalid_data(false); +#else + bool invalid_data(false); +#endif + + if (exr_header->tiled) { + // value check + if (exr_header->tile_size_x < 0) { + if (err) { + std::stringstream ss; + ss << "Invalid tile size x : " << exr_header->tile_size_x << "\n"; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_HEADER; + } + + if (exr_header->tile_size_y < 0) { + if (err) { + std::stringstream ss; + ss << "Invalid tile size y : " << exr_header->tile_size_y << "\n"; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_HEADER; + } + if (exr_header->tile_level_mode != TINYEXR_TILE_RIPMAP_LEVELS) { + EXRImage* level_image = NULL; + for (int level = 0; level < offset_data.num_x_levels; ++level) { + if (!level_image) { + level_image = exr_image; + } else { + level_image->next_level = new EXRImage; + InitEXRImage(level_image->next_level); + level_image = level_image->next_level; + } + level_image->width = + LevelSize(exr_header->data_window.max_x - exr_header->data_window.min_x + 1, level, exr_header->tile_rounding_mode); + if (level_image->width < 1) { + return TINYEXR_ERROR_INVALID_DATA; + } + + level_image->height = + LevelSize(exr_header->data_window.max_y - exr_header->data_window.min_y + 1, level, exr_header->tile_rounding_mode); + + if (level_image->height < 1) { + return TINYEXR_ERROR_INVALID_DATA; + } + + level_image->level_x = level; + level_image->level_y = level; + + int ret = DecodeTiledLevel(level_image, exr_header, + offset_data, + channel_offset_list, + pixel_data_size, + head, size, + err); + if (ret != TINYEXR_SUCCESS) return ret; + } + } else { + EXRImage* level_image = NULL; + for (int level_y = 0; level_y < offset_data.num_y_levels; ++level_y) + for (int level_x = 0; level_x < offset_data.num_x_levels; ++level_x) { + if (!level_image) { + level_image = exr_image; + } else { + level_image->next_level = new EXRImage; + InitEXRImage(level_image->next_level); + level_image = level_image->next_level; + } + + level_image->width = + LevelSize(exr_header->data_window.max_x - exr_header->data_window.min_x + 1, level_x, exr_header->tile_rounding_mode); + if (level_image->width < 1) { + return TINYEXR_ERROR_INVALID_DATA; + } + + level_image->height = + LevelSize(exr_header->data_window.max_y - exr_header->data_window.min_y + 1, level_y, exr_header->tile_rounding_mode); + if (level_image->height < 1) { + return TINYEXR_ERROR_INVALID_DATA; + } + + level_image->level_x = level_x; + level_image->level_y = level_y; + + int ret = DecodeTiledLevel(level_image, exr_header, + offset_data, + channel_offset_list, + pixel_data_size, + head, size, + err); + if (ret != TINYEXR_SUCCESS) return ret; + } + } + } else { // scanline format + // Don't allow too large image(256GB * pixel_data_size or more). Workaround + // for #104. + size_t total_data_len = + size_t(data_width) * size_t(data_height) * size_t(num_channels); + const bool total_data_len_overflown = + sizeof(void *) == 8 ? (total_data_len >= 0x4000000000) : false; + if ((total_data_len == 0) || total_data_len_overflown) { + if (err) { + std::stringstream ss; + ss << "Image data size is zero or too large: width = " << data_width + << ", height = " << data_height << ", channels = " << num_channels + << std::endl; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_DATA; + } + + bool alloc_success = false; + exr_image->images = tinyexr::AllocateImage( + num_channels, exr_header->channels, exr_header->requested_pixel_types, + int(data_width), int(data_height), &alloc_success); + + if (!alloc_success) { + if (err) { + std::stringstream ss; + ss << "Failed to allocate memory for Images. Maybe EXR header is corrupted or Image data size is too large: width = " << data_width + << ", height = " << data_height << ", channels = " << num_channels + << std::endl; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_DATA; + } + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::vector workers; + std::atomic y_count(0); + + int num_threads = std::max(1, int(std::thread::hardware_concurrency())); + if (num_threads > int(num_blocks)) { + num_threads = int(num_blocks); + } + + for (int t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&]() { + int y = 0; + while ((y = y_count++) < int(num_blocks)) { + +#else + +#if TINYEXR_USE_OPENMP +#pragma omp parallel for +#endif + for (int y = 0; y < static_cast(num_blocks); y++) { + +#endif + size_t y_idx = static_cast(y); + + if (offsets[y_idx] + sizeof(int) * 2 > size) { + invalid_data = true; + } else { + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(uncompressed or compressed) + size_t data_size = + size_t(size - (offsets[y_idx] + sizeof(int) * 2)); + const unsigned char *data_ptr = + reinterpret_cast(head + offsets[y_idx]); + + int line_no; + memcpy(&line_no, data_ptr, sizeof(int)); + int data_len; + memcpy(&data_len, data_ptr + 4, sizeof(int)); + tinyexr::swap4(&line_no); + tinyexr::swap4(&data_len); + + if (size_t(data_len) > data_size) { + invalid_data = true; + + } else if ((line_no > (2 << 20)) || (line_no < -(2 << 20))) { + // Too large value. Assume this is invalid + // 2**20 = 1048576 = heuristic value. + invalid_data = true; + } else if (data_len == 0) { + // TODO(syoyo): May be ok to raise the threshold for example + // `data_len < 4` + invalid_data = true; + } else { + // line_no may be negative. + int end_line_no = (std::min)(line_no + num_scanline_blocks, + (exr_header->data_window.max_y + 1)); + + int num_lines = end_line_no - line_no; + + if (num_lines <= 0) { + invalid_data = true; + } else { + // Move to data addr: 8 = 4 + 4; + data_ptr += 8; + + // Adjust line_no with data_window.bmin.y + + // overflow check + tinyexr_int64 lno = + static_cast(line_no) - + static_cast(exr_header->data_window.min_y); + if (lno > std::numeric_limits::max()) { + line_no = -1; // invalid + } else if (lno < -std::numeric_limits::max()) { + line_no = -1; // invalid + } else { + line_no -= exr_header->data_window.min_y; + } + + if (line_no < 0) { + invalid_data = true; + } else { + if (!tinyexr::DecodePixelData( + exr_image->images, exr_header->requested_pixel_types, + data_ptr, static_cast(data_len), + exr_header->compression_type, exr_header->line_order, + int(data_width), int(data_height), int(data_width), y, line_no, + num_lines, static_cast(pixel_data_size), + static_cast( + exr_header->num_custom_attributes), + exr_header->custom_attributes, + static_cast(exr_header->num_channels), + exr_header->channels, channel_offset_list)) { + invalid_data = true; + } + } + } + } + } + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + } + })); + } + + for (auto &t : workers) { + t.join(); + } +#else + } // omp parallel +#endif + } + + if (invalid_data) { + if (err) { + (*err) += "Invalid/Corrupted data found when decoding pixels.\n"; + } + + // free alloced image. + for (size_t c = 0; c < static_cast(num_channels); c++) { + if (exr_image->images[c]) { + free(exr_image->images[c]); + exr_image->images[c] = NULL; + } + } + return TINYEXR_ERROR_INVALID_DATA; + } + + // Overwrite `pixel_type` with `requested_pixel_type`. + { + for (int c = 0; c < exr_header->num_channels; c++) { + exr_header->pixel_types[c] = exr_header->requested_pixel_types[c]; + } + } + + { + exr_image->num_channels = num_channels; + + exr_image->width = int(data_width); + exr_image->height = int(data_height); + } + + return TINYEXR_SUCCESS; +} + +static bool ReconstructLineOffsets( + std::vector *offsets, size_t n, + const unsigned char *head, const unsigned char *marker, const size_t size) { + if (head >= marker) { + return false; + } + if (offsets->size() != n) { + return false; + } + + for (size_t i = 0; i < n; i++) { + size_t offset = static_cast(marker - head); + // Offset should not exceed whole EXR file/data size. + if ((offset + sizeof(tinyexr::tinyexr_uint64)) >= size) { + return false; + } + + int y; + unsigned int data_len; + + memcpy(&y, marker, sizeof(int)); + memcpy(&data_len, marker + 4, sizeof(unsigned int)); + + if (data_len >= size) { + return false; + } + + tinyexr::swap4(&y); + tinyexr::swap4(&data_len); + + (*offsets)[i] = offset; + + marker += data_len + 8; // 8 = 4 bytes(y) + 4 bytes(data_len) + } + + return true; +} + + +static int FloorLog2(unsigned x) { + // + // For x > 0, floorLog2(y) returns floor(log(x)/log(2)). + // + int y = 0; + while (x > 1) { + y += 1; + x >>= 1u; + } + return y; +} + + +static int CeilLog2(unsigned x) { + // + // For x > 0, ceilLog2(y) returns ceil(log(x)/log(2)). + // + int y = 0; + int r = 0; + while (x > 1) { + if (x & 1) + r = 1; + + y += 1; + x >>= 1u; + } + return y + r; +} + +static int RoundLog2(int x, int tile_rounding_mode) { + return (tile_rounding_mode == TINYEXR_TILE_ROUND_DOWN) ? FloorLog2(static_cast(x)) : CeilLog2(static_cast(x)); +} + +static int CalculateNumXLevels(const EXRHeader* exr_header) { + int min_x = exr_header->data_window.min_x; + int max_x = exr_header->data_window.max_x; + int min_y = exr_header->data_window.min_y; + int max_y = exr_header->data_window.max_y; + + int num = 0; + switch (exr_header->tile_level_mode) { + case TINYEXR_TILE_ONE_LEVEL: + + num = 1; + break; + + case TINYEXR_TILE_MIPMAP_LEVELS: + + { + int w = max_x - min_x + 1; + int h = max_y - min_y + 1; + num = RoundLog2(std::max(w, h), exr_header->tile_rounding_mode) + 1; + } + break; + + case TINYEXR_TILE_RIPMAP_LEVELS: + + { + int w = max_x - min_x + 1; + num = RoundLog2(w, exr_header->tile_rounding_mode) + 1; + } + break; + + default: + + return -1; + } + + return num; +} + +static int CalculateNumYLevels(const EXRHeader* exr_header) { + int min_x = exr_header->data_window.min_x; + int max_x = exr_header->data_window.max_x; + int min_y = exr_header->data_window.min_y; + int max_y = exr_header->data_window.max_y; + int num = 0; + + switch (exr_header->tile_level_mode) { + case TINYEXR_TILE_ONE_LEVEL: + + num = 1; + break; + + case TINYEXR_TILE_MIPMAP_LEVELS: + + { + int w = max_x - min_x + 1; + int h = max_y - min_y + 1; + num = RoundLog2(std::max(w, h), exr_header->tile_rounding_mode) + 1; + } + break; + + case TINYEXR_TILE_RIPMAP_LEVELS: + + { + int h = max_y - min_y + 1; + num = RoundLog2(h, exr_header->tile_rounding_mode) + 1; + } + break; + + default: + + return -1; + } + + return num; +} + +static bool CalculateNumTiles(std::vector& numTiles, + int toplevel_size, + int size, + int tile_rounding_mode) { + for (unsigned i = 0; i < numTiles.size(); i++) { + int l = LevelSize(toplevel_size, int(i), tile_rounding_mode); + if (l < 0) { + return false; + } + TINYEXR_CHECK_AND_RETURN_C(l <= std::numeric_limits::max() - size + 1, false); + + numTiles[i] = (l + size - 1) / size; + } + return true; +} + +static bool PrecalculateTileInfo(std::vector& num_x_tiles, + std::vector& num_y_tiles, + const EXRHeader* exr_header) { + int min_x = exr_header->data_window.min_x; + int max_x = exr_header->data_window.max_x; + int min_y = exr_header->data_window.min_y; + int max_y = exr_header->data_window.max_y; + + int num_x_levels = CalculateNumXLevels(exr_header); + + if (num_x_levels < 0) { + return false; + } + + int num_y_levels = CalculateNumYLevels(exr_header); + + if (num_y_levels < 0) { + return false; + } + + num_x_tiles.resize(size_t(num_x_levels)); + num_y_tiles.resize(size_t(num_y_levels)); + + if (!CalculateNumTiles(num_x_tiles, + max_x - min_x + 1, + exr_header->tile_size_x, + exr_header->tile_rounding_mode)) { + return false; + } + + if (!CalculateNumTiles(num_y_tiles, + max_y - min_y + 1, + exr_header->tile_size_y, + exr_header->tile_rounding_mode)) { + return false; + } + + return true; +} + +static void InitSingleResolutionOffsets(OffsetData& offset_data, size_t num_blocks) { + offset_data.offsets.resize(1); + offset_data.offsets[0].resize(1); + offset_data.offsets[0][0].resize(num_blocks); + offset_data.num_x_levels = 1; + offset_data.num_y_levels = 1; +} + +// Return sum of tile blocks. +// 0 = error +static int InitTileOffsets(OffsetData& offset_data, + const EXRHeader* exr_header, + const std::vector& num_x_tiles, + const std::vector& num_y_tiles) { + int num_tile_blocks = 0; + offset_data.num_x_levels = static_cast(num_x_tiles.size()); + offset_data.num_y_levels = static_cast(num_y_tiles.size()); + switch (exr_header->tile_level_mode) { + case TINYEXR_TILE_ONE_LEVEL: + case TINYEXR_TILE_MIPMAP_LEVELS: + TINYEXR_CHECK_AND_RETURN_C(offset_data.num_x_levels == offset_data.num_y_levels, 0); + offset_data.offsets.resize(size_t(offset_data.num_x_levels)); + + for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) { + offset_data.offsets[l].resize(size_t(num_y_tiles[l])); + + for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) { + offset_data.offsets[l][dy].resize(size_t(num_x_tiles[l])); + num_tile_blocks += num_x_tiles[l]; + } + } + break; + + case TINYEXR_TILE_RIPMAP_LEVELS: + + offset_data.offsets.resize(static_cast(offset_data.num_x_levels) * static_cast(offset_data.num_y_levels)); + + for (int ly = 0; ly < offset_data.num_y_levels; ++ly) { + for (int lx = 0; lx < offset_data.num_x_levels; ++lx) { + int l = ly * offset_data.num_x_levels + lx; + offset_data.offsets[size_t(l)].resize(size_t(num_y_tiles[size_t(ly)])); + + for (size_t dy = 0; dy < offset_data.offsets[size_t(l)].size(); ++dy) { + offset_data.offsets[size_t(l)][dy].resize(size_t(num_x_tiles[size_t(lx)])); + num_tile_blocks += num_x_tiles[size_t(lx)]; + } + } + } + break; + + default: + return 0; + } + return num_tile_blocks; +} + +static bool IsAnyOffsetsAreInvalid(const OffsetData& offset_data) { + for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) + for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) + for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) + if (reinterpret_cast(offset_data.offsets[l][dy][dx]) <= 0) + return true; + + return false; +} + +static bool isValidTile(const EXRHeader* exr_header, + const OffsetData& offset_data, + int dx, int dy, int lx, int ly) { + if (lx < 0 || ly < 0 || dx < 0 || dy < 0) return false; + int num_x_levels = offset_data.num_x_levels; + int num_y_levels = offset_data.num_y_levels; + switch (exr_header->tile_level_mode) { + case TINYEXR_TILE_ONE_LEVEL: + + if (lx == 0 && + ly == 0 && + offset_data.offsets.size() > 0 && + offset_data.offsets[0].size() > static_cast(dy) && + offset_data.offsets[0][size_t(dy)].size() > static_cast(dx)) { + return true; + } + + break; + + case TINYEXR_TILE_MIPMAP_LEVELS: + + if (lx < num_x_levels && + ly < num_y_levels && + offset_data.offsets.size() > static_cast(lx) && + offset_data.offsets[size_t(lx)].size() > static_cast(dy) && + offset_data.offsets[size_t(lx)][size_t(dy)].size() > static_cast(dx)) { + return true; + } + + break; + + case TINYEXR_TILE_RIPMAP_LEVELS: + { + size_t idx = static_cast(lx) + static_cast(ly)* static_cast(num_x_levels); + if (lx < num_x_levels && + ly < num_y_levels && + (offset_data.offsets.size() > idx) && + offset_data.offsets[idx].size() > static_cast(dy) && + offset_data.offsets[idx][size_t(dy)].size() > static_cast(dx)) { + return true; + } + } + + break; + + default: + + return false; + } + + return false; +} + +static bool ReconstructTileOffsets(OffsetData& offset_data, + const EXRHeader* exr_header, + const unsigned char* head, const unsigned char* marker, const size_t size, + bool isMultiPartFile, + bool isDeep) { + int numXLevels = offset_data.num_x_levels; + for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) { + for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) { + for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) { + tinyexr::tinyexr_uint64 tileOffset = tinyexr::tinyexr_uint64(marker - head); + + + if (isMultiPartFile) { + if ((marker + sizeof(int)) >= (head + size)) { + return false; + } + + //int partNumber; + marker += sizeof(int); + } + + if ((marker + 4 * sizeof(int)) >= (head + size)) { + return false; + } + + int tileX; + memcpy(&tileX, marker, sizeof(int)); + tinyexr::swap4(&tileX); + marker += sizeof(int); + + int tileY; + memcpy(&tileY, marker, sizeof(int)); + tinyexr::swap4(&tileY); + marker += sizeof(int); + + int levelX; + memcpy(&levelX, marker, sizeof(int)); + tinyexr::swap4(&levelX); + marker += sizeof(int); + + int levelY; + memcpy(&levelY, marker, sizeof(int)); + tinyexr::swap4(&levelY); + marker += sizeof(int); + + if (isDeep) { + if ((marker + 2 * sizeof(tinyexr::tinyexr_int64)) >= (head + size)) { + return false; + } + tinyexr::tinyexr_int64 packed_offset_table_size; + memcpy(&packed_offset_table_size, marker, sizeof(tinyexr::tinyexr_int64)); + tinyexr::swap8(reinterpret_cast(&packed_offset_table_size)); + marker += sizeof(tinyexr::tinyexr_int64); + + tinyexr::tinyexr_int64 packed_sample_size; + memcpy(&packed_sample_size, marker, sizeof(tinyexr::tinyexr_int64)); + tinyexr::swap8(reinterpret_cast(&packed_sample_size)); + marker += sizeof(tinyexr::tinyexr_int64); + + // next Int64 is unpacked sample size - skip that too + marker += packed_offset_table_size + packed_sample_size + 8; + + if (marker >= (head + size)) { + return false; + } + + } else { + + if ((marker + sizeof(uint32_t)) >= (head + size)) { + return false; + } + + uint32_t dataSize; + memcpy(&dataSize, marker, sizeof(uint32_t)); + tinyexr::swap4(&dataSize); + marker += sizeof(uint32_t); + + marker += dataSize; + + if (marker >= (head + size)) { + return false; + } + } + + if (!isValidTile(exr_header, offset_data, + tileX, tileY, levelX, levelY)) { + return false; + } + + int level_idx = LevelIndex(levelX, levelY, exr_header->tile_level_mode, numXLevels); + if (level_idx < 0) { + return false; + } + + if (size_t(level_idx) >= offset_data.offsets.size()) { + return false; + } + + if (size_t(tileY) >= offset_data.offsets[size_t(level_idx)].size()) { + return false; + } + + if (size_t(tileX) >= offset_data.offsets[size_t(level_idx)][size_t(tileY)].size()) { + return false; + } + + offset_data.offsets[size_t(level_idx)][size_t(tileY)][size_t(tileX)] = tileOffset; + } + } + } + return true; +} + +// marker output is also +static int ReadOffsets(OffsetData& offset_data, + const unsigned char* head, + const unsigned char*& marker, + const size_t size, + const char** err) { + for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) { + for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) { + for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) { + tinyexr::tinyexr_uint64 offset; + if ((marker + sizeof(tinyexr_uint64)) >= (head + size)) { + tinyexr::SetErrorMessage("Insufficient data size in offset table.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + memcpy(&offset, marker, sizeof(tinyexr::tinyexr_uint64)); + tinyexr::swap8(&offset); + if (offset >= size) { + tinyexr::SetErrorMessage("Invalid offset value in DecodeEXRImage.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + marker += sizeof(tinyexr::tinyexr_uint64); // = 8 + offset_data.offsets[l][dy][dx] = offset; + } + } + } + return TINYEXR_SUCCESS; +} + +static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, + const unsigned char *head, + const unsigned char *marker, const size_t size, + const char **err) { + if (exr_image == NULL || exr_header == NULL || head == NULL || + marker == NULL || (size <= tinyexr::kEXRVersionSize)) { + tinyexr::SetErrorMessage("Invalid argument for DecodeEXRImage().", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + int num_scanline_blocks = 1; + if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) { + num_scanline_blocks = 16; + } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { + num_scanline_blocks = 32; + } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { + num_scanline_blocks = 16; + } + + if (exr_header->data_window.max_x < exr_header->data_window.min_x || + exr_header->data_window.max_x - exr_header->data_window.min_x == + std::numeric_limits::max()) { + // Issue 63 + tinyexr::SetErrorMessage("Invalid data width value", err); + return TINYEXR_ERROR_INVALID_DATA; + } + tinyexr_int64 data_width = + static_cast(exr_header->data_window.max_x) - static_cast(exr_header->data_window.min_x) + static_cast(1); + if (data_width <= 0) { + tinyexr::SetErrorMessage("Invalid data window width value", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + if (exr_header->data_window.max_y < exr_header->data_window.min_y || + exr_header->data_window.max_y - exr_header->data_window.min_y == + std::numeric_limits::max()) { + tinyexr::SetErrorMessage("Invalid data height value", err); + return TINYEXR_ERROR_INVALID_DATA; + } + tinyexr_int64 data_height = + static_cast(exr_header->data_window.max_y) - static_cast(exr_header->data_window.min_y) + static_cast(1); + + if (data_height <= 0) { + tinyexr::SetErrorMessage("Invalid data window height value", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + // Do not allow too large data_width and data_height. header invalid? + { + if (data_width > TINYEXR_DIMENSION_THRESHOLD) { + tinyexr::SetErrorMessage("data width too large.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + if (data_height > TINYEXR_DIMENSION_THRESHOLD) { + tinyexr::SetErrorMessage("data height too large.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + + if (exr_header->tiled) { + if (exr_header->tile_size_x > TINYEXR_DIMENSION_THRESHOLD) { + tinyexr::SetErrorMessage("tile width too large.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + if (exr_header->tile_size_y > TINYEXR_DIMENSION_THRESHOLD) { + tinyexr::SetErrorMessage("tile height too large.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + + // Read offset tables. + OffsetData offset_data; + size_t num_blocks = 0; + // For a multi-resolution image, the size of the offset table will be calculated from the other attributes of the header. + // If chunk_count > 0 then chunk_count must be equal to the calculated tile count. + if (exr_header->tiled) { + { + std::vector num_x_tiles, num_y_tiles; + if (!PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_header)) { + tinyexr::SetErrorMessage("Failed to precalculate tile info.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + num_blocks = size_t(InitTileOffsets(offset_data, exr_header, num_x_tiles, num_y_tiles)); + if (exr_header->chunk_count > 0) { + if (exr_header->chunk_count != static_cast(num_blocks)) { + tinyexr::SetErrorMessage("Invalid offset table size.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + } + + int ret = ReadOffsets(offset_data, head, marker, size, err); + if (ret != TINYEXR_SUCCESS) return ret; + if (IsAnyOffsetsAreInvalid(offset_data)) { + if (!ReconstructTileOffsets(offset_data, exr_header, + head, marker, size, + exr_header->multipart, exr_header->non_image)) { + + tinyexr::SetErrorMessage("Invalid Tile Offsets data.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + } else if (exr_header->chunk_count > 0) { + // Use `chunkCount` attribute. + num_blocks = static_cast(exr_header->chunk_count); + InitSingleResolutionOffsets(offset_data, num_blocks); + } else { + num_blocks = static_cast(data_height) / + static_cast(num_scanline_blocks); + if (num_blocks * static_cast(num_scanline_blocks) < + static_cast(data_height)) { + num_blocks++; + } + + InitSingleResolutionOffsets(offset_data, num_blocks); + } + + if (!exr_header->tiled) { + std::vector& offsets = offset_data.offsets[0][0]; + for (size_t y = 0; y < num_blocks; y++) { + tinyexr::tinyexr_uint64 offset; + // Issue #81 + if ((marker + sizeof(tinyexr_uint64)) >= (head + size)) { + tinyexr::SetErrorMessage("Insufficient data size in offset table.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + memcpy(&offset, marker, sizeof(tinyexr::tinyexr_uint64)); + tinyexr::swap8(&offset); + if (offset >= size) { + tinyexr::SetErrorMessage("Invalid offset value in DecodeEXRImage.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + marker += sizeof(tinyexr::tinyexr_uint64); // = 8 + offsets[y] = offset; + } + + // If line offsets are invalid, we try to reconstruct it. + // See OpenEXR/IlmImf/ImfScanLineInputFile.cpp::readLineOffsets() for details. + for (size_t y = 0; y < num_blocks; y++) { + if (offsets[y] <= 0) { + // TODO(syoyo) Report as warning? + // if (err) { + // stringstream ss; + // ss << "Incomplete lineOffsets." << std::endl; + // (*err) += ss.str(); + //} + bool ret = + ReconstructLineOffsets(&offsets, num_blocks, head, marker, size); + if (ret) { + // OK + break; + } else { + tinyexr::SetErrorMessage( + "Cannot reconstruct lineOffset table in DecodeEXRImage.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + } + } + + { + std::string e; + int ret = DecodeChunk(exr_image, exr_header, offset_data, head, size, &e); + + if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } + +#if 1 + FreeEXRImage(exr_image); +#else + // release memory(if exists) + if ((exr_header->num_channels > 0) && exr_image && exr_image->images) { + for (size_t c = 0; c < size_t(exr_header->num_channels); c++) { + if (exr_image->images[c]) { + free(exr_image->images[c]); + exr_image->images[c] = NULL; + } + } + free(exr_image->images); + exr_image->images = NULL; + } +#endif + } + + return ret; + } +} + +static void GetLayers(const EXRHeader &exr_header, + std::vector &layer_names) { + // Naive implementation + // Group channels by layers + // go over all channel names, split by periods + // collect unique names + layer_names.clear(); + for (int c = 0; c < exr_header.num_channels; c++) { + std::string full_name(exr_header.channels[c].name); + const size_t pos = full_name.find_last_of('.'); + if (pos != std::string::npos && pos != 0 && pos + 1 < full_name.size()) { + full_name.erase(pos); + if (std::find(layer_names.begin(), layer_names.end(), full_name) == + layer_names.end()) + layer_names.push_back(full_name); + } + } +} + +struct LayerChannel { + explicit LayerChannel(size_t i, std::string n) : index(i), name(n) {} + size_t index; + std::string name; +}; + +static void ChannelsInLayer(const EXRHeader &exr_header, + const std::string &layer_name, + std::vector &channels) { + channels.clear(); + //std::cout << "layer_name = " << layer_name << "\n"; + for (int c = 0; c < exr_header.num_channels; c++) { + //std::cout << "chan[" << c << "] = " << exr_header.channels[c].name << "\n"; + std::string ch_name(exr_header.channels[c].name); + if (layer_name.empty()) { + const size_t pos = ch_name.find_last_of('.'); + if (pos != std::string::npos && pos < ch_name.size()) { + if (pos != 0) continue; + ch_name = ch_name.substr(pos + 1); + } + } else { + const size_t pos = ch_name.find(layer_name + '.'); + if (pos == std::string::npos) continue; + if (pos == 0) { + ch_name = ch_name.substr(layer_name.size() + 1); + } + } + LayerChannel ch(size_t(c), ch_name); + channels.push_back(ch); + } +} + +} // namespace tinyexr + +int EXRLayers(const char *filename, const char **layer_names[], int *num_layers, + const char **err) { + EXRVersion exr_version; + EXRHeader exr_header; + InitEXRHeader(&exr_header); + + { + int ret = ParseEXRVersionFromFile(&exr_version, filename); + if (ret != TINYEXR_SUCCESS) { + tinyexr::SetErrorMessage("Invalid EXR header.", err); + return ret; + } + + if (exr_version.multipart || exr_version.non_image) { + tinyexr::SetErrorMessage( + "Loading multipart or DeepImage is not supported in LoadEXR() API", + err); + return TINYEXR_ERROR_INVALID_DATA; // @fixme. + } + } + + int ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename, err); + if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); + return ret; + } + + std::vector layer_vec; + tinyexr::GetLayers(exr_header, layer_vec); + + (*num_layers) = int(layer_vec.size()); + (*layer_names) = static_cast( + malloc(sizeof(const char *) * static_cast(layer_vec.size()))); + for (size_t c = 0; c < static_cast(layer_vec.size()); c++) { +#ifdef _MSC_VER + (*layer_names)[c] = _strdup(layer_vec[c].c_str()); +#else + (*layer_names)[c] = strdup(layer_vec[c].c_str()); +#endif + } + + FreeEXRHeader(&exr_header); + return TINYEXR_SUCCESS; +} + +int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, + const char **err) { + return LoadEXRWithLayer(out_rgba, width, height, filename, + /* layername */ NULL, err); +} + +int LoadEXRWithLayer(float **out_rgba, int *width, int *height, + const char *filename, const char *layername, + const char **err) { + if (out_rgba == NULL) { + tinyexr::SetErrorMessage("Invalid argument for LoadEXR()", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + EXRVersion exr_version; + EXRImage exr_image; + EXRHeader exr_header; + InitEXRHeader(&exr_header); + InitEXRImage(&exr_image); + + { + int ret = ParseEXRVersionFromFile(&exr_version, filename); + if (ret != TINYEXR_SUCCESS) { + std::stringstream ss; + ss << "Failed to open EXR file or read version info from EXR file. code(" + << ret << ")"; + tinyexr::SetErrorMessage(ss.str(), err); + return ret; + } + + if (exr_version.multipart || exr_version.non_image) { + tinyexr::SetErrorMessage( + "Loading multipart or DeepImage is not supported in LoadEXR() API", + err); + return TINYEXR_ERROR_INVALID_DATA; // @fixme. + } + } + + { + int ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename, err); + if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); + return ret; + } + } + + // Read HALF channel as FLOAT. + for (int i = 0; i < exr_header.num_channels; i++) { + if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { + exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; + } + } + + // TODO: Probably limit loading to layers (channels) selected by layer index + { + int ret = LoadEXRImageFromFile(&exr_image, &exr_header, filename, err); + if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); + return ret; + } + } + + // RGBA + int idxR = -1; + int idxG = -1; + int idxB = -1; + int idxA = -1; + + std::vector layer_names; + tinyexr::GetLayers(exr_header, layer_names); + + std::vector channels; + tinyexr::ChannelsInLayer( + exr_header, layername == NULL ? "" : std::string(layername), channels); + + + if (channels.size() < 1) { + if (layername == NULL) { + tinyexr::SetErrorMessage("Layer Not Found. Seems EXR contains channels with layer(e.g. `diffuse.R`). if you are using LoadEXR(), please try LoadEXRWithLayer(). LoadEXR() cannot load EXR having channels with layer.", err); + + } else { + tinyexr::SetErrorMessage("Layer Not Found", err); + } + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_LAYER_NOT_FOUND; + } + + size_t ch_count = channels.size() < 4 ? channels.size() : 4; + for (size_t c = 0; c < ch_count; c++) { + const tinyexr::LayerChannel &ch = channels[c]; + + if (ch.name == "R") { + idxR = int(ch.index); + } else if (ch.name == "G") { + idxG = int(ch.index); + } else if (ch.name == "B") { + idxB = int(ch.index); + } else if (ch.name == "A") { + idxA = int(ch.index); + } + } + + if (channels.size() == 1) { + int chIdx = int(channels.front().index); + // Grayscale channel only. + + (*out_rgba) = reinterpret_cast( + malloc(4 * sizeof(float) * static_cast(exr_image.width) * + static_cast(exr_image.height))); + + if (exr_header.tiled) { + const size_t tile_size_x = static_cast(exr_header.tile_size_x); + const size_t tile_size_y = static_cast(exr_header.tile_size_y); + for (int it = 0; it < exr_image.num_tiles; it++) { + for (size_t j = 0; j < tile_size_y; j++) { + for (size_t i = 0; i < tile_size_x; i++) { + const size_t ii = + static_cast(exr_image.tiles[it].offset_x) * tile_size_x + + i; + const size_t jj = + static_cast(exr_image.tiles[it].offset_y) * tile_size_y + + j; + const size_t idx = ii + jj * static_cast(exr_image.width); + + // out of region check. + if (ii >= static_cast(exr_image.width)) { + continue; + } + if (jj >= static_cast(exr_image.height)) { + continue; + } + const size_t srcIdx = i + j * tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[chIdx][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[chIdx][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[chIdx][srcIdx]; + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[chIdx][srcIdx]; + } + } + } + } else { + const size_t pixel_size = static_cast(exr_image.width) * + static_cast(exr_image.height); + for (size_t i = 0; i < pixel_size; i++) { + const float val = + reinterpret_cast(exr_image.images)[chIdx][i]; + (*out_rgba)[4 * i + 0] = val; + (*out_rgba)[4 * i + 1] = val; + (*out_rgba)[4 * i + 2] = val; + (*out_rgba)[4 * i + 3] = val; + } + } + } else { + // Assume RGB(A) + + if (idxR == -1) { + tinyexr::SetErrorMessage("R channel not found", err); + + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_INVALID_DATA; + } + + if (idxG == -1) { + tinyexr::SetErrorMessage("G channel not found", err); + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_INVALID_DATA; + } + + if (idxB == -1) { + tinyexr::SetErrorMessage("B channel not found", err); + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_INVALID_DATA; + } + + (*out_rgba) = reinterpret_cast( + malloc(4 * sizeof(float) * static_cast(exr_image.width) * + static_cast(exr_image.height))); + if (exr_header.tiled) { + const size_t tile_size_x = static_cast(exr_header.tile_size_x); + const size_t tile_size_y = static_cast(exr_header.tile_size_y); + for (int it = 0; it < exr_image.num_tiles; it++) { + for (size_t j = 0; j < tile_size_y; j++) { + for (size_t i = 0; i < tile_size_x; i++) { + const size_t ii = + static_cast(exr_image.tiles[it].offset_x) * + tile_size_x + + i; + const size_t jj = + static_cast(exr_image.tiles[it].offset_y) * + tile_size_y + + j; + const size_t idx = ii + jj * static_cast(exr_image.width); + + // out of region check. + if (ii >= static_cast(exr_image.width)) { + continue; + } + if (jj >= static_cast(exr_image.height)) { + continue; + } + const size_t srcIdx = i + j * tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[idxR][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[idxG][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[idxB][srcIdx]; + if (idxA != -1) { + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[idxA][srcIdx]; + } else { + (*out_rgba)[4 * idx + 3] = 1.0; + } + } + } + } + } else { + const size_t pixel_size = static_cast(exr_image.width) * + static_cast(exr_image.height); + for (size_t i = 0; i < pixel_size; i++) { + (*out_rgba)[4 * i + 0] = + reinterpret_cast(exr_image.images)[idxR][i]; + (*out_rgba)[4 * i + 1] = + reinterpret_cast(exr_image.images)[idxG][i]; + (*out_rgba)[4 * i + 2] = + reinterpret_cast(exr_image.images)[idxB][i]; + if (idxA != -1) { + (*out_rgba)[4 * i + 3] = + reinterpret_cast(exr_image.images)[idxA][i]; + } else { + (*out_rgba)[4 * i + 3] = 1.0; + } + } + } + } + + (*width) = exr_image.width; + (*height) = exr_image.height; + + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + + return TINYEXR_SUCCESS; +} + +int IsEXR(const char *filename) { + EXRVersion exr_version; + + int ret = ParseEXRVersionFromFile(&exr_version, filename); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + return TINYEXR_SUCCESS; +} + +int IsEXRFromMemory(const unsigned char *memory, size_t size) { + EXRVersion exr_version; + + int ret = ParseEXRVersionFromMemory(&exr_version, memory, size); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + return TINYEXR_SUCCESS; +} + +int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, + const unsigned char *memory, size_t size, + const char **err) { + if (memory == NULL || exr_header == NULL) { + tinyexr::SetErrorMessage( + "Invalid argument. `memory` or `exr_header` argument is null in " + "ParseEXRHeaderFromMemory()", + err); + + // Invalid argument + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (size < tinyexr::kEXRVersionSize) { + tinyexr::SetErrorMessage("Insufficient header/data size.\n", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + const unsigned char *marker = memory + tinyexr::kEXRVersionSize; + size_t marker_size = size - tinyexr::kEXRVersionSize; + + tinyexr::HeaderInfo info; + info.clear(); + + int ret; + { + std::string err_str; + ret = ParseEXRHeader(&info, NULL, version, &err_str, marker, marker_size); + + if (ret != TINYEXR_SUCCESS) { + if (err && !err_str.empty()) { + tinyexr::SetErrorMessage(err_str, err); + } + } + } + + { + std::string warn; + std::string err_str; + + if (!ConvertHeader(exr_header, info, &warn, &err_str)) { + // release mem + for (size_t i = 0; i < info.attributes.size(); i++) { + if (info.attributes[i].value) { + free(info.attributes[i].value); + } + } + if (err && !err_str.empty()) { + tinyexr::SetErrorMessage(err_str, err); + } + ret = TINYEXR_ERROR_INVALID_HEADER; + } + } + + exr_header->multipart = version->multipart ? 1 : 0; + exr_header->non_image = version->non_image ? 1 : 0; + + return ret; +} + +int LoadEXRFromMemory(float **out_rgba, int *width, int *height, + const unsigned char *memory, size_t size, + const char **err) { + if (out_rgba == NULL || memory == NULL) { + tinyexr::SetErrorMessage("Invalid argument for LoadEXRFromMemory", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + EXRVersion exr_version; + EXRImage exr_image; + EXRHeader exr_header; + + InitEXRHeader(&exr_header); + + int ret = ParseEXRVersionFromMemory(&exr_version, memory, size); + if (ret != TINYEXR_SUCCESS) { + std::stringstream ss; + ss << "Failed to parse EXR version. code(" << ret << ")"; + tinyexr::SetErrorMessage(ss.str(), err); + return ret; + } + + ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, memory, size, err); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + // Read HALF channel as FLOAT. + for (int i = 0; i < exr_header.num_channels; i++) { + if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { + exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; + } + } + + InitEXRImage(&exr_image); + ret = LoadEXRImageFromMemory(&exr_image, &exr_header, memory, size, err); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + // RGBA + int idxR = -1; + int idxG = -1; + int idxB = -1; + int idxA = -1; + for (int c = 0; c < exr_header.num_channels; c++) { + if (strcmp(exr_header.channels[c].name, "R") == 0) { + idxR = c; + } else if (strcmp(exr_header.channels[c].name, "G") == 0) { + idxG = c; + } else if (strcmp(exr_header.channels[c].name, "B") == 0) { + idxB = c; + } else if (strcmp(exr_header.channels[c].name, "A") == 0) { + idxA = c; + } + } + + // TODO(syoyo): Refactor removing same code as used in LoadEXR(). + if (exr_header.num_channels == 1) { + // Grayscale channel only. + + (*out_rgba) = reinterpret_cast( + malloc(4 * sizeof(float) * static_cast(exr_image.width) * + static_cast(exr_image.height))); + + if (exr_header.tiled) { + const size_t tile_size_x = static_cast(exr_header.tile_size_x); + const size_t tile_size_y = static_cast(exr_header.tile_size_y); + for (int it = 0; it < exr_image.num_tiles; it++) { + for (size_t j = 0; j < tile_size_y; j++) { + for (size_t i = 0; i < tile_size_x; i++) { + const size_t ii = + static_cast(exr_image.tiles[it].offset_x) * + tile_size_x + + i; + const size_t jj = + static_cast(exr_image.tiles[it].offset_y) * + tile_size_y + + j; + const size_t idx = ii + jj * static_cast(exr_image.width); + + // out of region check. + if (ii >= static_cast(exr_image.width)) { + continue; + } + if (jj >= static_cast(exr_image.height)) { + continue; + } + const size_t srcIdx = i + j * tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[0][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[0][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[0][srcIdx]; + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[0][srcIdx]; + } + } + } + } else { + const size_t pixel_size = static_cast(exr_image.width) * + static_cast(exr_image.height); + for (size_t i = 0; i < pixel_size; i++) { + const float val = reinterpret_cast(exr_image.images)[0][i]; + (*out_rgba)[4 * i + 0] = val; + (*out_rgba)[4 * i + 1] = val; + (*out_rgba)[4 * i + 2] = val; + (*out_rgba)[4 * i + 3] = val; + } + } + + } else { + // TODO(syoyo): Support non RGBA image. + + if (idxR == -1) { + tinyexr::SetErrorMessage("R channel not found", err); + + // @todo { free exr_image } + return TINYEXR_ERROR_INVALID_DATA; + } + + if (idxG == -1) { + tinyexr::SetErrorMessage("G channel not found", err); + // @todo { free exr_image } + return TINYEXR_ERROR_INVALID_DATA; + } + + if (idxB == -1) { + tinyexr::SetErrorMessage("B channel not found", err); + // @todo { free exr_image } + return TINYEXR_ERROR_INVALID_DATA; + } + + (*out_rgba) = reinterpret_cast( + malloc(4 * sizeof(float) * static_cast(exr_image.width) * + static_cast(exr_image.height))); + + if (exr_header.tiled) { + const size_t tile_size_x = static_cast(exr_header.tile_size_x); + const size_t tile_size_y = static_cast(exr_header.tile_size_y); + for (int it = 0; it < exr_image.num_tiles; it++) { + for (size_t j = 0; j < tile_size_y; j++) + for (size_t i = 0; i < tile_size_x; i++) { + const size_t ii = + static_cast(exr_image.tiles[it].offset_x) * + tile_size_x + + i; + const size_t jj = + static_cast(exr_image.tiles[it].offset_y) * + tile_size_y + + j; + const size_t idx = ii + jj * static_cast(exr_image.width); + + // out of region check. + if (ii >= static_cast(exr_image.width)) { + continue; + } + if (jj >= static_cast(exr_image.height)) { + continue; + } + const size_t srcIdx = i + j * tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[idxR][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[idxG][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[idxB][srcIdx]; + if (idxA != -1) { + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[idxA][srcIdx]; + } else { + (*out_rgba)[4 * idx + 3] = 1.0; + } + } + } + } else { + const size_t pixel_size = static_cast(exr_image.width) * + static_cast(exr_image.height); + for (size_t i = 0; i < pixel_size; i++) { + (*out_rgba)[4 * i + 0] = + reinterpret_cast(exr_image.images)[idxR][i]; + (*out_rgba)[4 * i + 1] = + reinterpret_cast(exr_image.images)[idxG][i]; + (*out_rgba)[4 * i + 2] = + reinterpret_cast(exr_image.images)[idxB][i]; + if (idxA != -1) { + (*out_rgba)[4 * i + 3] = + reinterpret_cast(exr_image.images)[idxA][i]; + } else { + (*out_rgba)[4 * i + 3] = 1.0; + } + } + } + } + + (*width) = exr_image.width; + (*height) = exr_image.height; + + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + + return TINYEXR_SUCCESS; +} + +// Represents a read-only file mapped to an address space in memory. +// If no memory-mapping API is available, falls back to allocating a buffer +// with a copy of the file's data. +struct MemoryMappedFile { + unsigned char *data; // To the start of the file's data. + size_t size; // The size of the file in bytes. +#ifdef TINYEXR_USE_WIN32_MMAP + HANDLE windows_file; + HANDLE windows_file_mapping; +#elif defined(TINYEXR_USE_POSIX_MMAP) + int posix_descriptor; +#endif + + // MemoryMappedFile's constructor tries to map memory to a file. + // If this succeeds, valid() will return true and all fields + // are usable; otherwise, valid() will return false. + MemoryMappedFile(const char *filename) { + data = NULL; + size = 0; +#ifdef TINYEXR_USE_WIN32_MMAP + windows_file_mapping = NULL; + windows_file = + CreateFileW(tinyexr::UTF8ToWchar(filename).c_str(), // lpFileName + GENERIC_READ, // dwDesiredAccess + FILE_SHARE_READ, // dwShareMode + NULL, // lpSecurityAttributes + OPEN_EXISTING, // dwCreationDisposition + FILE_ATTRIBUTE_READONLY, // dwFlagsAndAttributes + NULL); // hTemplateFile + if (windows_file == INVALID_HANDLE_VALUE) { + return; + } + + windows_file_mapping = CreateFileMapping(windows_file, // hFile + NULL, // lpFileMappingAttributes + PAGE_READONLY, // flProtect + 0, // dwMaximumSizeHigh + 0, // dwMaximumSizeLow + NULL); // lpName + if (windows_file_mapping == NULL) { + return; + } + + data = reinterpret_cast( + MapViewOfFile(windows_file_mapping, // hFileMappingObject + FILE_MAP_READ, // dwDesiredAccess + 0, // dwFileOffsetHigh + 0, // dwFileOffsetLow + 0)); // dwNumberOfBytesToMap + if (!data) { + return; + } + + LARGE_INTEGER windows_file_size = {}; + if (!GetFileSizeEx(windows_file, &windows_file_size) || + static_cast(windows_file_size.QuadPart) > + std::numeric_limits::max()) { + UnmapViewOfFile(data); + data = NULL; + return; + } + size = static_cast(windows_file_size.QuadPart); +#elif defined(TINYEXR_USE_POSIX_MMAP) + posix_descriptor = open(filename, O_RDONLY); + if (posix_descriptor == -1) { + return; + } + + struct stat info; + if (fstat(posix_descriptor, &info) < 0) { + return; + } + // Make sure st_size is in the valid range for a size_t. The second case + // can only fail if a POSIX implementation defines off_t to be a larger + // type than size_t - for instance, compiling with _FILE_OFFSET_BITS=64 + // on a 32-bit system. On current 64-bit systems, this check can never + // fail, so we turn off clang's Wtautological-type-limit-compare warning + // around this code. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-type-limit-compare" +#endif + if (info.st_size < 0 || + info.st_size > std::numeric_limits::max()) { + return; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + size = static_cast(info.st_size); + + data = reinterpret_cast( + mmap(0, size, PROT_READ, MAP_SHARED, posix_descriptor, 0)); + if (data == MAP_FAILED) { + data = nullptr; + return; + } +#else + FILE *fp = fopen(filename, "rb"); + if (!fp) { + return; + } + + // Calling fseek(fp, 0, SEEK_END) isn't strictly-conforming C code, but + // since neither the WIN32 nor POSIX APIs are available in this branch, this + // is a reasonable fallback option. + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return; + } + const long ftell_result = ftell(fp); + if (ftell_result < 0) { + // Error from ftell + fclose(fp); + return; + } + size = static_cast(ftell_result); + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + size = 0; + return; + } + + data = reinterpret_cast(malloc(size)); + if (!data) { + size = 0; + fclose(fp); + return; + } + size_t read_bytes = fread(data, 1, size, fp); + if (read_bytes != size) { + // TODO: Try to read data until reading `size` bytes. + fclose(fp); + size = 0; + data = nullptr; + return; + } + fclose(fp); +#endif + } + + // MemoryMappedFile's destructor closes all its handles. + ~MemoryMappedFile() { +#ifdef TINYEXR_USE_WIN32_MMAP + if (data) { + (void)UnmapViewOfFile(data); + data = NULL; + } + + if (windows_file_mapping != NULL) { + (void)CloseHandle(windows_file_mapping); + } + + if (windows_file != INVALID_HANDLE_VALUE) { + (void)CloseHandle(windows_file); + } +#elif defined(TINYEXR_USE_POSIX_MMAP) + if (data) { + (void)munmap(data, size); + data = NULL; + } + + if (posix_descriptor != -1) { + (void)close(posix_descriptor); + } +#else + if (data) { + (void)free(data); + } + data = NULL; +#endif + } + + // A MemoryMappedFile cannot be copied or moved. + // Only check for this when compiling with C++11 or higher, since deleted + // function definitions were added then. +#if TINYEXR_HAS_CXX11 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +#endif + MemoryMappedFile(const MemoryMappedFile &) = delete; + MemoryMappedFile &operator=(const MemoryMappedFile &) = delete; + MemoryMappedFile(MemoryMappedFile &&other) noexcept = delete; + MemoryMappedFile &operator=(MemoryMappedFile &&other) noexcept = delete; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#endif + + // Returns whether this was successfully opened. + bool valid() const { return data; } +}; + +int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, + const char *filename, const char **err) { + if (exr_image == NULL) { + tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromFile", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + MemoryMappedFile file(filename); + if (!file.valid()) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } + + if (file.size < 16) { + tinyexr::SetErrorMessage("File size too short : " + std::string(filename), + err); + return TINYEXR_ERROR_INVALID_FILE; + } + + return LoadEXRImageFromMemory(exr_image, exr_header, file.data, file.size, + err); +} + +int LoadEXRImageFromMemory(EXRImage *exr_image, const EXRHeader *exr_header, + const unsigned char *memory, const size_t size, + const char **err) { + if (exr_image == NULL || memory == NULL || + (size < tinyexr::kEXRVersionSize)) { + tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromMemory", + err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (exr_header->header_len == 0) { + tinyexr::SetErrorMessage("EXRHeader variable is not initialized.", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + const unsigned char *head = memory; + const unsigned char *marker = reinterpret_cast( + memory + exr_header->header_len + + 8); // +8 for magic number + version header. + return tinyexr::DecodeEXRImage(exr_image, exr_header, head, marker, size, + err); +} + +namespace tinyexr +{ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + +// out_data must be allocated initially with the block-header size +// of the current image(-part) type +static bool EncodePixelData(/* out */ std::vector& out_data, + const unsigned char* const* images, + int compression_type, + int /*line_order*/, + int width, // for tiled : tile.width + int /*height*/, // for tiled : header.tile_size_y + int x_stride, // for tiled : header.tile_size_x + int line_no, // for tiled : 0 + int num_lines, // for tiled : tile.height + size_t pixel_data_size, + const std::vector& channels, + const std::vector& channel_offset_list, + std::string *err, + const void* compression_param = 0) // zfp compression param +{ + size_t buf_size = static_cast(width) * + static_cast(num_lines) * + static_cast(pixel_data_size); + //int last2bit = (buf_size & 3); + // buf_size must be multiple of four + //if(last2bit) buf_size += 4 - last2bit; + std::vector buf(buf_size); + + size_t start_y = static_cast(line_no); + for (size_t c = 0; c < channels.size(); c++) { + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + for (int y = 0; y < num_lines; y++) { + // Assume increasing Y + float *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * size_t(y) * size_t(width)) + + channel_offset_list[c] * + static_cast(width))); + for (int x = 0; x < width; x++) { + tinyexr::FP16 h16; + h16.u = reinterpret_cast( + images)[c][(y + start_y) * size_t(x_stride) + size_t(x)]; + + tinyexr::FP32 f32 = half_to_float(h16); + + tinyexr::swap4(&f32.f); + + // line_ptr[x] = f32.f; + tinyexr::cpy4(line_ptr + x, &(f32.f)); + } + } + } else if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) { + for (int y = 0; y < num_lines; y++) { + // Assume increasing Y + unsigned short *line_ptr = reinterpret_cast( + &buf.at(static_cast(pixel_data_size * y * + width) + + channel_offset_list[c] * + static_cast(width))); + for (int x = 0; x < width; x++) { + unsigned short val = reinterpret_cast( + images)[c][(y + start_y) * x_stride + x]; + + tinyexr::swap2(&val); + + // line_ptr[x] = val; + tinyexr::cpy2(line_ptr + x, &val); + } + } + } else { + if (err) { + (*err) += "Invalid requested_pixel_type.\n"; + } + return false; + } + + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) { + for (int y = 0; y < num_lines; y++) { + // Assume increasing Y + unsigned short *line_ptr = reinterpret_cast( + &buf.at(static_cast(pixel_data_size * y * + width) + + channel_offset_list[c] * + static_cast(width))); + for (int x = 0; x < width; x++) { + tinyexr::FP32 f32; + f32.f = reinterpret_cast( + images)[c][(y + start_y) * x_stride + x]; + + tinyexr::FP16 h16; + h16 = float_to_half_full(f32); + + tinyexr::swap2(reinterpret_cast(&h16.u)); + + // line_ptr[x] = h16.u; + tinyexr::cpy2(line_ptr + x, &(h16.u)); + } + } + } else if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + for (int y = 0; y < num_lines; y++) { + // Assume increasing Y + float *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * width) + + channel_offset_list[c] * + static_cast(width))); + for (int x = 0; x < width; x++) { + float val = reinterpret_cast( + images)[c][(y + start_y) * x_stride + x]; + + tinyexr::swap4(&val); + + // line_ptr[x] = val; + tinyexr::cpy4(line_ptr + x, &val); + } + } + } else { + if (err) { + (*err) += "Invalid requested_pixel_type.\n"; + } + return false; + } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + for (int y = 0; y < num_lines; y++) { + // Assume increasing Y + unsigned int *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * width) + + channel_offset_list[c] * static_cast(width))); + for (int x = 0; x < width; x++) { + unsigned int val = reinterpret_cast( + images)[c][(y + start_y) * x_stride + x]; + + tinyexr::swap4(&val); + + // line_ptr[x] = val; + tinyexr::cpy4(line_ptr + x, &val); + } + } + } + } + + if (compression_type == TINYEXR_COMPRESSIONTYPE_NONE) { + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(uncompressed) + out_data.insert(out_data.end(), buf.begin(), buf.end()); + + } else if ((compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) || + (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP)) { +#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1) + std::vector block(mz_compressBound( + static_cast(buf.size()))); +#elif TINYEXR_USE_STB_ZLIB + // there is no compressBound() function, so we use a value that + // is grossly overestimated, but should always work + std::vector block(256 + 2 * buf.size()); +#elif defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB == 1) + std::vector block(nanoz_compressBound( + static_cast(buf.size()))); +#else + std::vector block( + compressBound(static_cast(buf.size()))); +#endif + tinyexr::tinyexr_uint64 outSize = block.size(); + + if (!tinyexr::CompressZip(&block.at(0), outSize, + reinterpret_cast(&buf.at(0)), + static_cast(buf.size()))) { + if (err) { + (*err) += "Zip compresssion failed.\n"; + } + return false; + } + + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(compressed) + unsigned int data_len = static_cast(outSize); // truncate + + out_data.insert(out_data.end(), block.begin(), block.begin() + data_len); + + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) { + // (buf.size() * 3) / 2 would be enough. + std::vector block((buf.size() * 3) / 2); + + tinyexr::tinyexr_uint64 outSize = block.size(); + + if (!tinyexr::CompressRle(&block.at(0), outSize, + reinterpret_cast(&buf.at(0)), + static_cast(buf.size()))) { + if (err) { + (*err) += "RLE compresssion failed.\n"; + } + return false; + } + + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(compressed) + unsigned int data_len = static_cast(outSize); // truncate + out_data.insert(out_data.end(), block.begin(), block.begin() + data_len); + + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { +#if TINYEXR_USE_PIZ + unsigned int bufLen = + 8192 + static_cast( + 2 * static_cast( + buf.size())); // @fixme { compute good bound. } + std::vector block(bufLen); + unsigned int outSize = static_cast(block.size()); + + if (!CompressPiz(&block.at(0), &outSize, + reinterpret_cast(&buf.at(0)), + buf.size(), channels, width, num_lines)) { + if (err) { + (*err) += "PIZ compresssion failed.\n"; + } + return false; + } + + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(compressed) + unsigned int data_len = outSize; + out_data.insert(out_data.end(), block.begin(), block.begin() + data_len); + +#else + if (err) { + (*err) += "PIZ compression is disabled in this build.\n"; + } + return false; +#endif + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { +#if TINYEXR_USE_ZFP + const ZFPCompressionParam* zfp_compression_param = reinterpret_cast(compression_param); + std::vector block; + unsigned int outSize; + + tinyexr::CompressZfp( + &block, &outSize, reinterpret_cast(&buf.at(0)), + width, num_lines, static_cast(channels.size()), *zfp_compression_param); + + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(compressed) + unsigned int data_len = outSize; + out_data.insert(out_data.end(), block.begin(), block.begin() + data_len); + +#else + if (err) { + (*err) += "ZFP compression is disabled in this build.\n"; + } + (void)compression_param; + return false; +#endif + } else { + return false; + } + + return true; +} + +static int EncodeTiledLevel(const EXRImage* level_image, const EXRHeader* exr_header, + const std::vector& channels, + std::vector >& data_list, + size_t start_index, // for data_list + int num_x_tiles, int num_y_tiles, + const std::vector& channel_offset_list, + int pixel_data_size, + const void* compression_param, // must be set if zfp compression is enabled + std::string* err) { + int num_tiles = num_x_tiles * num_y_tiles; + if (num_tiles != level_image->num_tiles) { + if (err) { + (*err) += "Invalid number of tiles in argument.\n"; + } + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if ((exr_header->tile_size_x > level_image->width || exr_header->tile_size_y > level_image->height) && + level_image->level_x == 0 && level_image->level_y == 0) { + if (err) { + (*err) += "Failed to encode tile data.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::atomic invalid_data(false); +#else + bool invalid_data(false); +#endif + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::vector workers; + std::atomic tile_count(0); + + int num_threads = std::max(1, int(std::thread::hardware_concurrency())); + if (num_threads > int(num_tiles)) { + num_threads = int(num_tiles); + } + + for (int t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&]() { + int i = 0; + while ((i = tile_count++) < num_tiles) { + +#else + // Use signed int since some OpenMP compiler doesn't allow unsigned type for + // `parallel for` +#if TINYEXR_USE_OPENMP +#pragma omp parallel for +#endif + for (int i = 0; i < num_tiles; i++) { + +#endif + size_t tile_idx = static_cast(i); + size_t data_idx = tile_idx + start_index; + + int x_tile = i % num_x_tiles; + int y_tile = i / num_x_tiles; + + EXRTile& tile = level_image->tiles[tile_idx]; + + const unsigned char* const* images = + static_cast(tile.images); + + data_list[data_idx].resize(5*sizeof(int)); + size_t data_header_size = data_list[data_idx].size(); + bool ret = EncodePixelData(data_list[data_idx], + images, + exr_header->compression_type, + 0, // increasing y + tile.width, + exr_header->tile_size_y, + exr_header->tile_size_x, + 0, + tile.height, + pixel_data_size, + channels, + channel_offset_list, + err, compression_param); + if (!ret) { + invalid_data = true; + continue; + } + if (data_list[data_idx].size() <= data_header_size) { + invalid_data = true; + continue; + } + + int data_len = static_cast(data_list[data_idx].size() - data_header_size); + //tileX, tileY, levelX, levelY // pixel_data_size(int) + memcpy(&data_list[data_idx][0], &x_tile, sizeof(int)); + memcpy(&data_list[data_idx][4], &y_tile, sizeof(int)); + memcpy(&data_list[data_idx][8], &level_image->level_x, sizeof(int)); + memcpy(&data_list[data_idx][12], &level_image->level_y, sizeof(int)); + memcpy(&data_list[data_idx][16], &data_len, sizeof(int)); + + swap4(reinterpret_cast(&data_list[data_idx][0])); + swap4(reinterpret_cast(&data_list[data_idx][4])); + swap4(reinterpret_cast(&data_list[data_idx][8])); + swap4(reinterpret_cast(&data_list[data_idx][12])); + swap4(reinterpret_cast(&data_list[data_idx][16])); + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + } +})); + } + + for (auto &t : workers) { + t.join(); + } +#else + } // omp parallel +#endif + + if (invalid_data) { + if (err) { + (*err) += "Failed to encode tile data.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + return TINYEXR_SUCCESS; +} + +static int NumScanlines(int compression_type) { + int num_scanlines = 1; + if (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) { + num_scanlines = 16; + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { + num_scanlines = 32; + } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { + num_scanlines = 16; + } + return num_scanlines; +} + +static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header, + const std::vector& channels, + int num_blocks, + tinyexr_uint64 chunk_offset, // starting offset of current chunk + bool is_multipart, + OffsetData& offset_data, // output block offsets, must be initialized + std::vector >& data_list, // output + tinyexr_uint64& total_size, // output: ending offset of current chunk + std::string* err) { + int num_scanlines = NumScanlines(exr_header->compression_type); + + data_list.resize(num_blocks); + + std::vector channel_offset_list( + static_cast(exr_header->num_channels)); + + int pixel_data_size = 0; + { + size_t channel_offset = 0; + for (size_t c = 0; c < static_cast(exr_header->num_channels); c++) { + channel_offset_list[c] = channel_offset; + if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) { + pixel_data_size += sizeof(unsigned short); + channel_offset += sizeof(unsigned short); + } else if (channels[c].requested_pixel_type == + TINYEXR_PIXELTYPE_FLOAT) { + pixel_data_size += sizeof(float); + channel_offset += sizeof(float); + } else if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_UINT) { + pixel_data_size += sizeof(unsigned int); + channel_offset += sizeof(unsigned int); + } else { + if (err) { + (*err) += "Invalid requested_pixel_type.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + } + } + + const void* compression_param = 0; +#if TINYEXR_USE_ZFP + tinyexr::ZFPCompressionParam zfp_compression_param; + + // Use ZFP compression parameter from custom attributes(if such a parameter + // exists) + { + std::string e; + bool ret = tinyexr::FindZFPCompressionParam( + &zfp_compression_param, exr_header->custom_attributes, + exr_header->num_custom_attributes, &e); + + if (!ret) { + // Use predefined compression parameter. + zfp_compression_param.type = 0; + zfp_compression_param.rate = 2; + } + compression_param = &zfp_compression_param; + } +#endif + + tinyexr_uint64 offset = chunk_offset; + tinyexr_uint64 doffset = is_multipart ? 4u : 0u; + + if (exr_image->tiles) { + const EXRImage* level_image = exr_image; + size_t block_idx = 0; + //tinyexr::tinyexr_uint64 block_data_size = 0; + int num_levels = (exr_header->tile_level_mode != TINYEXR_TILE_RIPMAP_LEVELS) ? + offset_data.num_x_levels : (offset_data.num_x_levels * offset_data.num_y_levels); + for (int level_index = 0; level_index < num_levels; ++level_index) { + if (!level_image) { + if (err) { + (*err) += "Invalid number of tiled levels for EncodeChunk\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + int level_index_from_image = LevelIndex(level_image->level_x, level_image->level_y, + exr_header->tile_level_mode, offset_data.num_x_levels); + if (level_index_from_image < 0) { + if (err) { + (*err) += "Invalid tile level mode\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + if (level_index_from_image != level_index) { + if (err) { + (*err) += "Incorrect level ordering in tiled image\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + int num_y_tiles = int(offset_data.offsets[level_index].size()); + if (num_y_tiles <= 0) { + if (err) { + (*err) += "Invalid Y tile size\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + int num_x_tiles = int(offset_data.offsets[level_index][0].size()); + if (num_x_tiles <= 0) { + if (err) { + (*err) += "Invalid X tile size\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + std::string e; + int ret = EncodeTiledLevel(level_image, + exr_header, + channels, + data_list, + block_idx, + num_x_tiles, + num_y_tiles, + channel_offset_list, + pixel_data_size, + compression_param, + &e); + if (ret != TINYEXR_SUCCESS) { + if (!e.empty() && err) { + (*err) += e; + } + return ret; + } + + for (size_t j = 0; j < static_cast(num_y_tiles); ++j) + for (size_t i = 0; i < static_cast(num_x_tiles); ++i) { + offset_data.offsets[level_index][j][i] = offset; + swap8(reinterpret_cast(&offset_data.offsets[level_index][j][i])); + offset += data_list[block_idx].size() + doffset; + //block_data_size += data_list[block_idx].size(); + ++block_idx; + } + level_image = level_image->next_level; + } + TINYEXR_CHECK_AND_RETURN_C(static_cast(block_idx) == num_blocks, TINYEXR_ERROR_INVALID_DATA); + total_size = offset; + } else { // scanlines + std::vector& offsets = offset_data.offsets[0][0]; + +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + std::atomic invalid_data(false); + std::vector workers; + std::atomic block_count(0); + + int num_threads = std::min(std::max(1, int(std::thread::hardware_concurrency())), num_blocks); + + for (int t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&]() { + int i = 0; + while ((i = block_count++) < num_blocks) { + +#else + bool invalid_data(false); +#if TINYEXR_USE_OPENMP +#pragma omp parallel for +#endif + for (int i = 0; i < num_blocks; i++) { + +#endif + int start_y = num_scanlines * i; + int end_Y = (std::min)(num_scanlines * (i + 1), exr_image->height); + int num_lines = end_Y - start_y; + + const unsigned char* const* images = + static_cast(exr_image->images); + + data_list[i].resize(2*sizeof(int)); + size_t data_header_size = data_list[i].size(); + + bool ret = EncodePixelData(data_list[i], + images, + exr_header->compression_type, + 0, // increasing y + exr_image->width, + exr_image->height, + exr_image->width, + start_y, + num_lines, + pixel_data_size, + channels, + channel_offset_list, + err, + compression_param); + if (!ret) { + invalid_data = true; + continue; // "break" cannot be used with OpenMP + } + if (data_list[i].size() <= data_header_size) { + invalid_data = true; + continue; // "break" cannot be used with OpenMP + } + int data_len = static_cast(data_list[i].size() - data_header_size); + memcpy(&data_list[i][0], &start_y, sizeof(int)); + memcpy(&data_list[i][4], &data_len, sizeof(int)); + + swap4(reinterpret_cast(&data_list[i][0])); + swap4(reinterpret_cast(&data_list[i][4])); +#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0) + } + })); + } + + for (auto &t : workers) { + t.join(); + } +#else + } // omp parallel +#endif + + if (invalid_data) { + if (err) { + (*err) += "Failed to encode scanline data.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + for (size_t i = 0; i < static_cast(num_blocks); i++) { + offsets[i] = offset; + tinyexr::swap8(reinterpret_cast(&offsets[i])); + offset += data_list[i].size() + doffset; + } + + total_size = static_cast(offset); + } + return TINYEXR_SUCCESS; +} + +// can save a single or multi-part image (no deep* formats) +static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images, + const EXRHeader** exr_headers, + unsigned int num_parts, + unsigned char** memory_out, const char** err) { + if (exr_images == NULL || exr_headers == NULL || num_parts == 0 || + memory_out == NULL) { + SetErrorMessage("Invalid argument for SaveEXRNPartImageToMemory", + err); + return 0; + } + { + for (unsigned int i = 0; i < num_parts; ++i) { + if (exr_headers[i]->compression_type < 0) { + SetErrorMessage("Invalid argument for SaveEXRNPartImageToMemory", + err); + return 0; + } +#if !TINYEXR_USE_PIZ + if (exr_headers[i]->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { + SetErrorMessage("PIZ compression is not supported in this build", + err); + return 0; + } +#endif + if (exr_headers[i]->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { +#if !TINYEXR_USE_ZFP + SetErrorMessage("ZFP compression is not supported in this build", + err); + return 0; +#else + // All channels must be fp32. + // No fp16 support in ZFP atm(as of 2023 June) + // https://github.com/LLNL/fpzip/issues/2 + for (int c = 0; c < exr_headers[i]->num_channels; ++c) { + if (exr_headers[i]->requested_pixel_types[c] != TINYEXR_PIXELTYPE_FLOAT) { + SetErrorMessage("Pixel type must be FLOAT for ZFP compression", + err); + return 0; + } + } +#endif + } + } + } + + std::vector memory; + + // Header + { + const char header[] = { 0x76, 0x2f, 0x31, 0x01 }; + memory.insert(memory.end(), header, header + 4); + } + + // Version + // using value from the first header + int long_name = exr_headers[0]->long_name; + { + char marker[] = { 2, 0, 0, 0 }; + /* @todo + if (exr_header->non_image) { + marker[1] |= 0x8; + } + */ + // tiled + if (num_parts == 1 && exr_images[0].tiles) { + marker[1] |= 0x2; + } + // long_name + if (long_name) { + marker[1] |= 0x4; + } + // multipart + if (num_parts > 1) { + marker[1] |= 0x10; + } + memory.insert(memory.end(), marker, marker + 4); + } + + int total_chunk_count = 0; + std::vector chunk_count(num_parts); + std::vector offset_data(num_parts); + for (unsigned int i = 0; i < num_parts; ++i) { + if (!exr_images[i].tiles) { + int num_scanlines = NumScanlines(exr_headers[i]->compression_type); + chunk_count[i] = + (exr_images[i].height + num_scanlines - 1) / num_scanlines; + InitSingleResolutionOffsets(offset_data[i], chunk_count[i]); + total_chunk_count += chunk_count[i]; + } else { + { + std::vector num_x_tiles, num_y_tiles; + if (!PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_headers[i])) { + SetErrorMessage("Failed to precalculate Tile info", + err); + return TINYEXR_ERROR_INVALID_DATA; + } + int ntiles = InitTileOffsets(offset_data[i], exr_headers[i], num_x_tiles, num_y_tiles); + if (ntiles > 0) { + chunk_count[i] = ntiles; + } else { + SetErrorMessage("Failed to compute Tile offsets", + err); + return TINYEXR_ERROR_INVALID_DATA; + + } + total_chunk_count += chunk_count[i]; + } + } + } + // Write attributes to memory buffer. + std::vector< std::vector > channels(num_parts); + { + std::set partnames; + for (unsigned int i = 0; i < num_parts; ++i) { + //channels + { + std::vector data; + + for (int c = 0; c < exr_headers[i]->num_channels; c++) { + tinyexr::ChannelInfo info; + info.p_linear = 0; + info.pixel_type = exr_headers[i]->pixel_types[c]; + info.requested_pixel_type = exr_headers[i]->requested_pixel_types[c]; + info.x_sampling = 1; + info.y_sampling = 1; + info.name = std::string(exr_headers[i]->channels[c].name); + channels[i].push_back(info); + } + + tinyexr::WriteChannelInfo(data, channels[i]); + + tinyexr::WriteAttributeToMemory(&memory, "channels", "chlist", &data.at(0), + static_cast(data.size())); + } + + { + int comp = exr_headers[i]->compression_type; + swap4(&comp); + WriteAttributeToMemory( + &memory, "compression", "compression", + reinterpret_cast(&comp), 1); + } + + { + int data[4] = { 0, 0, exr_images[i].width - 1, exr_images[i].height - 1 }; + swap4(&data[0]); + swap4(&data[1]); + swap4(&data[2]); + swap4(&data[3]); + WriteAttributeToMemory( + &memory, "dataWindow", "box2i", + reinterpret_cast(data), sizeof(int) * 4); + + int data0[4] = { 0, 0, exr_images[0].width - 1, exr_images[0].height - 1 }; + swap4(&data0[0]); + swap4(&data0[1]); + swap4(&data0[2]); + swap4(&data0[3]); + // Note: must be the same across parts (currently, using value from the first header) + WriteAttributeToMemory( + &memory, "displayWindow", "box2i", + reinterpret_cast(data0), sizeof(int) * 4); + } + + { + unsigned char line_order = 0; // @fixme { read line_order from EXRHeader } + WriteAttributeToMemory(&memory, "lineOrder", "lineOrder", + &line_order, 1); + } + + { + // Note: must be the same across parts + float aspectRatio = 1.0f; + swap4(&aspectRatio); + WriteAttributeToMemory( + &memory, "pixelAspectRatio", "float", + reinterpret_cast(&aspectRatio), sizeof(float)); + } + + { + float center[2] = { 0.0f, 0.0f }; + swap4(¢er[0]); + swap4(¢er[1]); + WriteAttributeToMemory( + &memory, "screenWindowCenter", "v2f", + reinterpret_cast(center), 2 * sizeof(float)); + } + + { + float w = 1.0f; + swap4(&w); + WriteAttributeToMemory(&memory, "screenWindowWidth", "float", + reinterpret_cast(&w), + sizeof(float)); + } + + if (exr_images[i].tiles) { + unsigned char tile_mode = static_cast(exr_headers[i]->tile_level_mode & 0x3); + if (exr_headers[i]->tile_rounding_mode) tile_mode |= (1u << 4u); + //unsigned char data[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + unsigned int datai[3] = { 0, 0, 0 }; + unsigned char* data = reinterpret_cast(&datai[0]); + datai[0] = static_cast(exr_headers[i]->tile_size_x); + datai[1] = static_cast(exr_headers[i]->tile_size_y); + data[8] = tile_mode; + swap4(reinterpret_cast(&data[0])); + swap4(reinterpret_cast(&data[4])); + WriteAttributeToMemory( + &memory, "tiles", "tiledesc", + reinterpret_cast(data), 9); + } + + // must be present for multi-part files - according to spec. + if (num_parts > 1) { + // name + { + size_t len = 0; + if ((len = strlen(exr_headers[i]->name)) > 0) { +#if TINYEXR_HAS_CXX11 + partnames.emplace(exr_headers[i]->name); +#else + partnames.insert(std::string(exr_headers[i]->name)); +#endif + if (partnames.size() != i + 1) { + SetErrorMessage("'name' attributes must be unique for a multi-part file", err); + return 0; + } + WriteAttributeToMemory( + &memory, "name", "string", + reinterpret_cast(exr_headers[i]->name), + static_cast(len)); + } else { + SetErrorMessage("Invalid 'name' attribute for a multi-part file", err); + return 0; + } + } + // type + { + const char* type = "scanlineimage"; + if (exr_images[i].tiles) type = "tiledimage"; + WriteAttributeToMemory( + &memory, "type", "string", + reinterpret_cast(type), + static_cast(strlen(type))); + } + // chunkCount + { + WriteAttributeToMemory( + &memory, "chunkCount", "int", + reinterpret_cast(&chunk_count[i]), + 4); + } + } + + // Custom attributes + if (exr_headers[i]->num_custom_attributes > 0) { + for (int j = 0; j < exr_headers[i]->num_custom_attributes; j++) { + tinyexr::WriteAttributeToMemory( + &memory, exr_headers[i]->custom_attributes[j].name, + exr_headers[i]->custom_attributes[j].type, + reinterpret_cast( + exr_headers[i]->custom_attributes[j].value), + exr_headers[i]->custom_attributes[j].size); + } + } + + { // end of header + memory.push_back(0); + } + } + } + if (num_parts > 1) { + // end of header list + memory.push_back(0); + } + + tinyexr_uint64 chunk_offset = memory.size() + size_t(total_chunk_count) * sizeof(tinyexr_uint64); + + tinyexr_uint64 total_size = 0; + std::vector< std::vector< std::vector > > data_lists(num_parts); + for (unsigned int i = 0; i < num_parts; ++i) { + std::string e; + int ret = EncodeChunk(&exr_images[i], exr_headers[i], + channels[i], + chunk_count[i], + // starting offset of current chunk after part-number + chunk_offset, + num_parts > 1, + offset_data[i], // output: block offsets, must be initialized + data_lists[i], // output + total_size, // output + &e); + if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } + return 0; + } + chunk_offset = total_size; + } + + // Allocating required memory + if (total_size == 0) { // something went wrong + tinyexr::SetErrorMessage("Output memory size is zero", err); + return TINYEXR_ERROR_INVALID_DATA; + } + (*memory_out) = static_cast(malloc(size_t(total_size))); + + // Writing header + memcpy((*memory_out), &memory[0], memory.size()); + unsigned char* memory_ptr = *memory_out + memory.size(); + size_t sum = memory.size(); + + // Writing offset data for chunks + for (unsigned int i = 0; i < num_parts; ++i) { + if (exr_images[i].tiles) { + const EXRImage* level_image = &exr_images[i]; + int num_levels = (exr_headers[i]->tile_level_mode != TINYEXR_TILE_RIPMAP_LEVELS) ? + offset_data[i].num_x_levels : (offset_data[i].num_x_levels * offset_data[i].num_y_levels); + for (int level_index = 0; level_index < num_levels; ++level_index) { + for (size_t j = 0; j < offset_data[i].offsets[level_index].size(); ++j) { + size_t num_bytes = sizeof(tinyexr_uint64) * offset_data[i].offsets[level_index][j].size(); + sum += num_bytes; + if (sum > total_size) { + tinyexr::SetErrorMessage("Invalid offset bytes in Tiled Part image.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + memcpy(memory_ptr, + reinterpret_cast(&offset_data[i].offsets[level_index][j][0]), + num_bytes); + memory_ptr += num_bytes; + } + level_image = level_image->next_level; + } + } else { + size_t num_bytes = sizeof(tinyexr::tinyexr_uint64) * static_cast(chunk_count[i]); + sum += num_bytes; + if (sum > total_size) { + tinyexr::SetErrorMessage("Invalid offset bytes in Part image.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + std::vector& offsets = offset_data[i].offsets[0][0]; + memcpy(memory_ptr, reinterpret_cast(&offsets[0]), num_bytes); + memory_ptr += num_bytes; + } + } + + // Writing chunk data + for (unsigned int i = 0; i < num_parts; ++i) { + for (size_t j = 0; j < static_cast(chunk_count[i]); ++j) { + if (num_parts > 1) { + sum += 4; + if (sum > total_size) { + tinyexr::SetErrorMessage("Buffer overrun in reading Part image chunk data.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + unsigned int part_number = i; + swap4(&part_number); + memcpy(memory_ptr, &part_number, 4); + memory_ptr += 4; + } + sum += data_lists[i][j].size(); + if (sum > total_size) { + tinyexr::SetErrorMessage("Buffer overrun in reading Part image chunk data.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + memcpy(memory_ptr, &data_lists[i][j][0], data_lists[i][j].size()); + memory_ptr += data_lists[i][j].size(); + } + } + + if (sum != total_size) { + tinyexr::SetErrorMessage("Corrupted Part image chunk data.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + return size_t(total_size); // OK +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // tinyexr + +size_t SaveEXRImageToMemory(const EXRImage* exr_image, + const EXRHeader* exr_header, + unsigned char** memory_out, const char** err) { + return tinyexr::SaveEXRNPartImageToMemory(exr_image, &exr_header, 1, memory_out, err); +} + +int SaveEXRImageToFile(const EXRImage *exr_image, const EXRHeader *exr_header, + const char *filename, const char **err) { + if (exr_image == NULL || filename == NULL || + exr_header->compression_type < 0) { + tinyexr::SetErrorMessage("Invalid argument for SaveEXRImageToFile", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + +#if !TINYEXR_USE_PIZ + if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { + tinyexr::SetErrorMessage("PIZ compression is not supported in this build", + err); + return TINYEXR_ERROR_UNSUPPORTED_FEATURE; + } +#endif + +#if !TINYEXR_USE_ZFP + if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { + tinyexr::SetErrorMessage("ZFP compression is not supported in this build", + err); + return TINYEXR_ERROR_UNSUPPORTED_FEATURE; + } +#endif + + FILE *fp = NULL; +#ifdef _WIN32 +#if defined(_MSC_VER) || (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API) // MSVC, MinGW GCC, or Clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"wb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } +#else + // Unknown compiler or MinGW without MINGW_HAS_SECURE_API. + fp = fopen(filename, "wb"); +#endif +#else + fp = fopen(filename, "wb"); +#endif + if (!fp) { + tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } + + unsigned char *mem = NULL; + size_t mem_size = SaveEXRImageToMemory(exr_image, exr_header, &mem, err); + if (mem_size == 0) { + fclose(fp); + return TINYEXR_ERROR_SERIALIZATION_FAILED; + } + + size_t written_size = 0; + if ((mem_size > 0) && mem) { + written_size = fwrite(mem, 1, mem_size, fp); + } + free(mem); + + fclose(fp); + + if (written_size != mem_size) { + tinyexr::SetErrorMessage("Cannot write a file", err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } + + return TINYEXR_SUCCESS; +} + +size_t SaveEXRMultipartImageToMemory(const EXRImage* exr_images, + const EXRHeader** exr_headers, + unsigned int num_parts, + unsigned char** memory_out, const char** err) { + if (exr_images == NULL || exr_headers == NULL || num_parts < 2 || + memory_out == NULL) { + tinyexr::SetErrorMessage("Invalid argument for SaveEXRNPartImageToMemory", + err); + return 0; + } + return tinyexr::SaveEXRNPartImageToMemory(exr_images, exr_headers, num_parts, memory_out, err); +} + +int SaveEXRMultipartImageToFile(const EXRImage* exr_images, + const EXRHeader** exr_headers, + unsigned int num_parts, + const char* filename, + const char** err) { + if (exr_images == NULL || exr_headers == NULL || num_parts < 2) { + tinyexr::SetErrorMessage("Invalid argument for SaveEXRMultipartImageToFile", + err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + FILE *fp = NULL; +#ifdef _WIN32 +#if defined(_MSC_VER) || (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API) // MSVC, MinGW GCC, or Clang. + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"wb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } +#else + // Unknown compiler or MinGW without MINGW_HAS_SECURE_API. + fp = fopen(filename, "wb"); +#endif +#else + fp = fopen(filename, "wb"); +#endif + if (!fp) { + tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } + + unsigned char *mem = NULL; + size_t mem_size = SaveEXRMultipartImageToMemory(exr_images, exr_headers, num_parts, &mem, err); + if (mem_size == 0) { + fclose(fp); + return TINYEXR_ERROR_SERIALIZATION_FAILED; + } + + size_t written_size = 0; + if ((mem_size > 0) && mem) { + written_size = fwrite(mem, 1, mem_size, fp); + } + free(mem); + + fclose(fp); + + if (written_size != mem_size) { + tinyexr::SetErrorMessage("Cannot write a file", err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } + + return TINYEXR_SUCCESS; +} + +int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { + if (deep_image == NULL) { + tinyexr::SetErrorMessage("Invalid argument for LoadDeepEXR", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + MemoryMappedFile file(filename); + if (!file.valid()) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } + + if (file.size == 0) { + tinyexr::SetErrorMessage("File size is zero : " + std::string(filename), + err); + return TINYEXR_ERROR_INVALID_FILE; + } + + const char *head = reinterpret_cast(file.data); + const char *marker = reinterpret_cast(file.data); + + // Header check. + { + const char header[] = {0x76, 0x2f, 0x31, 0x01}; + + if (memcmp(marker, header, 4) != 0) { + tinyexr::SetErrorMessage("Invalid magic number", err); + return TINYEXR_ERROR_INVALID_MAGIC_NUMBER; + } + marker += 4; + } + + // Version, scanline. + { + // ver 2.0, scanline, deep bit on(0x800) + // must be [2, 0, 0, 0] + if (marker[0] != 2 || marker[1] != 8 || marker[2] != 0 || marker[3] != 0) { + tinyexr::SetErrorMessage("Unsupported version or scanline", err); + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; + } + + marker += 4; + } + + int dx = -1; + int dy = -1; + int dw = -1; + int dh = -1; + int num_scanline_blocks = 1; // 16 for ZIP compression. + int compression_type = -1; + int num_channels = -1; + std::vector channels; + + // Read attributes + size_t size = file.size - tinyexr::kEXRVersionSize; + for (;;) { + if (0 == size) { + return TINYEXR_ERROR_INVALID_DATA; + } else if (marker[0] == '\0') { + marker++; + size--; + break; + } + + std::string attr_name; + std::string attr_type; + std::vector data; + size_t marker_size; + if (!tinyexr::ReadAttribute(&attr_name, &attr_type, &data, &marker_size, + marker, size)) { + std::stringstream ss; + ss << "Failed to parse attribute\n"; + tinyexr::SetErrorMessage(ss.str(), err); + return TINYEXR_ERROR_INVALID_DATA; + } + marker += marker_size; + size -= marker_size; + + if (attr_name.compare("compression") == 0) { + compression_type = data[0]; + if (compression_type > TINYEXR_COMPRESSIONTYPE_PIZ) { + std::stringstream ss; + ss << "Unsupported compression type : " << compression_type; + tinyexr::SetErrorMessage(ss.str(), err); + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; + } + + if (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) { + num_scanline_blocks = 16; + } + + } else if (attr_name.compare("channels") == 0) { + // name: zero-terminated string, from 1 to 255 bytes long + // pixel type: int, possible values are: UINT = 0 HALF = 1 FLOAT = 2 + // pLinear: unsigned char, possible values are 0 and 1 + // reserved: three chars, should be zero + // xSampling: int + // ySampling: int + + if (!tinyexr::ReadChannelInfo(channels, data)) { + tinyexr::SetErrorMessage("Failed to parse channel info", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + num_channels = static_cast(channels.size()); + + if (num_channels < 1) { + tinyexr::SetErrorMessage("Invalid channels format", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + } else if (attr_name.compare("dataWindow") == 0) { + memcpy(&dx, &data.at(0), sizeof(int)); + memcpy(&dy, &data.at(4), sizeof(int)); + memcpy(&dw, &data.at(8), sizeof(int)); + memcpy(&dh, &data.at(12), sizeof(int)); + tinyexr::swap4(&dx); + tinyexr::swap4(&dy); + tinyexr::swap4(&dw); + tinyexr::swap4(&dh); + + } else if (attr_name.compare("displayWindow") == 0) { + int x; + int y; + int w; + int h; + memcpy(&x, &data.at(0), sizeof(int)); + memcpy(&y, &data.at(4), sizeof(int)); + memcpy(&w, &data.at(8), sizeof(int)); + memcpy(&h, &data.at(12), sizeof(int)); + tinyexr::swap4(&x); + tinyexr::swap4(&y); + tinyexr::swap4(&w); + tinyexr::swap4(&h); + } + } + + TINYEXR_CHECK_AND_RETURN_C(dx >= 0, TINYEXR_ERROR_INVALID_DATA); + TINYEXR_CHECK_AND_RETURN_C(dy >= 0, TINYEXR_ERROR_INVALID_DATA); + TINYEXR_CHECK_AND_RETURN_C(dw >= 0, TINYEXR_ERROR_INVALID_DATA); + TINYEXR_CHECK_AND_RETURN_C(dh >= 0, TINYEXR_ERROR_INVALID_DATA); + TINYEXR_CHECK_AND_RETURN_C(num_channels >= 1, TINYEXR_ERROR_INVALID_DATA); + + int data_width = dw - dx + 1; + int data_height = dh - dy + 1; + + // Read offset tables. + int num_blocks = data_height / num_scanline_blocks; + if (num_blocks * num_scanline_blocks < data_height) { + num_blocks++; + } + + std::vector offsets(static_cast(num_blocks)); + + for (size_t y = 0; y < static_cast(num_blocks); y++) { + tinyexr::tinyexr_int64 offset; + memcpy(&offset, marker, sizeof(tinyexr::tinyexr_int64)); + tinyexr::swap8(reinterpret_cast(&offset)); + marker += sizeof(tinyexr::tinyexr_int64); // = 8 + offsets[y] = offset; + } + +#if TINYEXR_USE_PIZ + if ((compression_type == TINYEXR_COMPRESSIONTYPE_NONE) || + (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) || + (compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) || + (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) || + (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ)) { +#else + if ((compression_type == TINYEXR_COMPRESSIONTYPE_NONE) || + (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) || + (compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) || + (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP)) { +#endif + // OK + } else { + tinyexr::SetErrorMessage("Unsupported compression format", err); + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; + } + + deep_image->image = static_cast( + malloc(sizeof(float **) * static_cast(num_channels))); + for (int c = 0; c < num_channels; c++) { + deep_image->image[c] = static_cast( + malloc(sizeof(float *) * static_cast(data_height))); + for (int y = 0; y < data_height; y++) { + } + } + + deep_image->offset_table = static_cast( + malloc(sizeof(int *) * static_cast(data_height))); + for (int y = 0; y < data_height; y++) { + deep_image->offset_table[y] = static_cast( + malloc(sizeof(int) * static_cast(data_width))); + } + + for (size_t y = 0; y < static_cast(num_blocks); y++) { + const unsigned char *data_ptr = + reinterpret_cast(head + offsets[y]); + + // int: y coordinate + // int64: packed size of pixel offset table + // int64: packed size of sample data + // int64: unpacked size of sample data + // compressed pixel offset table + // compressed sample data + int line_no; + tinyexr::tinyexr_int64 packedOffsetTableSize; + tinyexr::tinyexr_int64 packedSampleDataSize; + tinyexr::tinyexr_int64 unpackedSampleDataSize; + memcpy(&line_no, data_ptr, sizeof(int)); + memcpy(&packedOffsetTableSize, data_ptr + 4, + sizeof(tinyexr::tinyexr_int64)); + memcpy(&packedSampleDataSize, data_ptr + 12, + sizeof(tinyexr::tinyexr_int64)); + memcpy(&unpackedSampleDataSize, data_ptr + 20, + sizeof(tinyexr::tinyexr_int64)); + + tinyexr::swap4(&line_no); + tinyexr::swap8( + reinterpret_cast(&packedOffsetTableSize)); + tinyexr::swap8( + reinterpret_cast(&packedSampleDataSize)); + tinyexr::swap8( + reinterpret_cast(&unpackedSampleDataSize)); + + std::vector pixelOffsetTable(static_cast(data_width)); + + // decode pixel offset table. + { + unsigned long dstLen = + static_cast(pixelOffsetTable.size() * sizeof(int)); + if (!tinyexr::DecompressZip( + reinterpret_cast(&pixelOffsetTable.at(0)), + &dstLen, data_ptr + 28, + static_cast(packedOffsetTableSize))) { + return false; + } + + TINYEXR_CHECK_AND_RETURN_C(dstLen == pixelOffsetTable.size() * sizeof(int), TINYEXR_ERROR_INVALID_DATA); + for (size_t i = 0; i < static_cast(data_width); i++) { + deep_image->offset_table[y][i] = pixelOffsetTable[i]; + } + } + + std::vector sample_data( + static_cast(unpackedSampleDataSize)); + + // decode sample data. + { + unsigned long dstLen = static_cast(unpackedSampleDataSize); + if (dstLen) { + if (!tinyexr::DecompressZip( + reinterpret_cast(&sample_data.at(0)), &dstLen, + data_ptr + 28 + packedOffsetTableSize, + static_cast(packedSampleDataSize))) { + return false; + } + TINYEXR_CHECK_AND_RETURN_C(dstLen == static_cast(unpackedSampleDataSize), TINYEXR_ERROR_INVALID_DATA); + } + } + + // decode sample + int sampleSize = -1; + std::vector channel_offset_list(static_cast(num_channels)); + { + int channel_offset = 0; + for (size_t i = 0; i < static_cast(num_channels); i++) { + channel_offset_list[i] = channel_offset; + if (channels[i].pixel_type == TINYEXR_PIXELTYPE_UINT) { // UINT + channel_offset += 4; + } else if (channels[i].pixel_type == TINYEXR_PIXELTYPE_HALF) { // half + channel_offset += 2; + } else if (channels[i].pixel_type == + TINYEXR_PIXELTYPE_FLOAT) { // float + channel_offset += 4; + } else { + tinyexr::SetErrorMessage("Invalid pixel_type in chnnels.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + sampleSize = channel_offset; + } + TINYEXR_CHECK_AND_RETURN_C(sampleSize >= 2, TINYEXR_ERROR_INVALID_DATA); + + TINYEXR_CHECK_AND_RETURN_C(static_cast( + pixelOffsetTable[static_cast(data_width - 1)] * + sampleSize) == sample_data.size(), TINYEXR_ERROR_INVALID_DATA); + int samples_per_line = static_cast(sample_data.size()) / sampleSize; + + // + // Alloc memory + // + + // + // pixel data is stored as image[channels][pixel_samples] + // + { + tinyexr::tinyexr_uint64 data_offset = 0; + for (size_t c = 0; c < static_cast(num_channels); c++) { + deep_image->image[c][y] = static_cast( + malloc(sizeof(float) * static_cast(samples_per_line))); + + if (channels[c].pixel_type == 0) { // UINT + for (size_t x = 0; x < static_cast(samples_per_line); x++) { + unsigned int ui; + unsigned int *src_ptr = reinterpret_cast( + &sample_data.at(size_t(data_offset) + x * sizeof(int))); + tinyexr::cpy4(&ui, src_ptr); + deep_image->image[c][y][x] = static_cast(ui); // @fixme + } + data_offset += + sizeof(unsigned int) * static_cast(samples_per_line); + } else if (channels[c].pixel_type == 1) { // half + for (size_t x = 0; x < static_cast(samples_per_line); x++) { + tinyexr::FP16 f16; + const unsigned short *src_ptr = reinterpret_cast( + &sample_data.at(size_t(data_offset) + x * sizeof(short))); + tinyexr::cpy2(&(f16.u), src_ptr); + tinyexr::FP32 f32 = half_to_float(f16); + deep_image->image[c][y][x] = f32.f; + } + data_offset += sizeof(short) * static_cast(samples_per_line); + } else { // float + for (size_t x = 0; x < static_cast(samples_per_line); x++) { + float f; + const float *src_ptr = reinterpret_cast( + &sample_data.at(size_t(data_offset) + x * sizeof(float))); + tinyexr::cpy4(&f, src_ptr); + deep_image->image[c][y][x] = f; + } + data_offset += sizeof(float) * static_cast(samples_per_line); + } + } + } + } // y + + deep_image->width = data_width; + deep_image->height = data_height; + + deep_image->channel_names = static_cast( + malloc(sizeof(const char *) * static_cast(num_channels))); + for (size_t c = 0; c < static_cast(num_channels); c++) { +#ifdef _WIN32 + deep_image->channel_names[c] = _strdup(channels[c].name.c_str()); +#else + deep_image->channel_names[c] = strdup(channels[c].name.c_str()); +#endif + } + deep_image->num_channels = num_channels; + + return TINYEXR_SUCCESS; +} + +void InitEXRImage(EXRImage *exr_image) { + if (exr_image == NULL) { + return; + } + + exr_image->width = 0; + exr_image->height = 0; + exr_image->num_channels = 0; + + exr_image->images = NULL; + exr_image->tiles = NULL; + exr_image->next_level = NULL; + exr_image->level_x = 0; + exr_image->level_y = 0; + + exr_image->num_tiles = 0; +} + +void FreeEXRErrorMessage(const char *msg) { + if (msg) { + free(reinterpret_cast(const_cast(msg))); + } + return; +} + +void InitEXRHeader(EXRHeader *exr_header) { + if (exr_header == NULL) { + return; + } + + memset(exr_header, 0, sizeof(EXRHeader)); +} + +int FreeEXRHeader(EXRHeader *exr_header) { + if (exr_header == NULL) { + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (exr_header->channels) { + free(exr_header->channels); + } + + if (exr_header->pixel_types) { + free(exr_header->pixel_types); + } + + if (exr_header->requested_pixel_types) { + free(exr_header->requested_pixel_types); + } + + for (int i = 0; i < exr_header->num_custom_attributes; i++) { + if (exr_header->custom_attributes[i].value) { + free(exr_header->custom_attributes[i].value); + } + } + + if (exr_header->custom_attributes) { + free(exr_header->custom_attributes); + } + + EXRSetNameAttr(exr_header, NULL); + + return TINYEXR_SUCCESS; +} + +void EXRSetNameAttr(EXRHeader* exr_header, const char* name) { + if (exr_header == NULL) { + return; + } + memset(exr_header->name, 0, 256); + if (name != NULL) { + size_t len = std::min(strlen(name), size_t(255)); + if (len) { + memcpy(exr_header->name, name, len); + } + } +} + +int EXRNumLevels(const EXRImage* exr_image) { + if (exr_image == NULL) return 0; + if(exr_image->images) return 1; // scanlines + int levels = 1; + const EXRImage* level_image = exr_image; + while((level_image = level_image->next_level)) ++levels; + return levels; +} + +int FreeEXRImage(EXRImage *exr_image) { + if (exr_image == NULL) { + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (exr_image->next_level) { + FreeEXRImage(exr_image->next_level); + delete exr_image->next_level; + } + + for (int i = 0; i < exr_image->num_channels; i++) { + if (exr_image->images && exr_image->images[i]) { + free(exr_image->images[i]); + } + } + + if (exr_image->images) { + free(exr_image->images); + } + + if (exr_image->tiles) { + for (int tid = 0; tid < exr_image->num_tiles; tid++) { + for (int i = 0; i < exr_image->num_channels; i++) { + if (exr_image->tiles[tid].images && exr_image->tiles[tid].images[i]) { + free(exr_image->tiles[tid].images[i]); + } + } + if (exr_image->tiles[tid].images) { + free(exr_image->tiles[tid].images); + } + } + free(exr_image->tiles); + } + + return TINYEXR_SUCCESS; +} + +int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version, + const char *filename, const char **err) { + if (exr_header == NULL || exr_version == NULL || filename == NULL) { + tinyexr::SetErrorMessage("Invalid argument for ParseEXRHeaderFromFile", + err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + MemoryMappedFile file(filename); + if (!file.valid()) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } + + return ParseEXRHeaderFromMemory(exr_header, exr_version, file.data, file.size, + err); +} + +int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, + int *num_headers, + const EXRVersion *exr_version, + const unsigned char *memory, size_t size, + const char **err) { + if (memory == NULL || exr_headers == NULL || num_headers == NULL || + exr_version == NULL) { + // Invalid argument + tinyexr::SetErrorMessage( + "Invalid argument for ParseEXRMultipartHeaderFromMemory", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (size < tinyexr::kEXRVersionSize) { + tinyexr::SetErrorMessage("Data size too short", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + const unsigned char *marker = memory + tinyexr::kEXRVersionSize; + size_t marker_size = size - tinyexr::kEXRVersionSize; + + std::vector infos; + + for (;;) { + tinyexr::HeaderInfo info; + info.clear(); + + std::string err_str; + bool empty_header = false; + int ret = ParseEXRHeader(&info, &empty_header, exr_version, &err_str, + marker, marker_size); + + if (ret != TINYEXR_SUCCESS) { + + // Free malloc-allocated memory here. + for (size_t i = 0; i < info.attributes.size(); i++) { + if (info.attributes[i].value) { + free(info.attributes[i].value); + } + } + + tinyexr::SetErrorMessage(err_str, err); + return ret; + } + + if (empty_header) { + marker += 1; // skip '\0' + break; + } + + // `chunkCount` must exist in the header. + if (info.chunk_count == 0) { + + // Free malloc-allocated memory here. + for (size_t i = 0; i < info.attributes.size(); i++) { + if (info.attributes[i].value) { + free(info.attributes[i].value); + } + } + + tinyexr::SetErrorMessage( + "`chunkCount' attribute is not found in the header.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + infos.push_back(info); + + // move to next header. + marker += info.header_len; + size -= info.header_len; + } + + // allocate memory for EXRHeader and create array of EXRHeader pointers. + (*exr_headers) = + static_cast(malloc(sizeof(EXRHeader *) * infos.size())); + + + int retcode = TINYEXR_SUCCESS; + + for (size_t i = 0; i < infos.size(); i++) { + EXRHeader *exr_header = static_cast(malloc(sizeof(EXRHeader))); + memset(exr_header, 0, sizeof(EXRHeader)); + + std::string warn; + std::string _err; + if (!ConvertHeader(exr_header, infos[i], &warn, &_err)) { + + // Free malloc-allocated memory here. + for (size_t k = 0; k < infos[i].attributes.size(); k++) { + if (infos[i].attributes[k].value) { + free(infos[i].attributes[k].value); + } + } + + if (!_err.empty()) { + tinyexr::SetErrorMessage( + _err, err); + } + // continue to converting headers + retcode = TINYEXR_ERROR_INVALID_HEADER; + } + + exr_header->multipart = exr_version->multipart ? 1 : 0; + + (*exr_headers)[i] = exr_header; + } + + (*num_headers) = static_cast(infos.size()); + + return retcode; +} + +int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers, + const EXRVersion *exr_version, + const char *filename, const char **err) { + if (exr_headers == NULL || num_headers == NULL || exr_version == NULL || + filename == NULL) { + tinyexr::SetErrorMessage( + "Invalid argument for ParseEXRMultipartHeaderFromFile()", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + MemoryMappedFile file(filename); + if (!file.valid()) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } + + return ParseEXRMultipartHeaderFromMemory( + exr_headers, num_headers, exr_version, file.data, file.size, err); +} + +int ParseEXRVersionFromMemory(EXRVersion *version, const unsigned char *memory, + size_t size) { + if (version == NULL || memory == NULL) { + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (size < tinyexr::kEXRVersionSize) { + return TINYEXR_ERROR_INVALID_DATA; + } + + const unsigned char *marker = memory; + + // Header check. + { + const char header[] = {0x76, 0x2f, 0x31, 0x01}; + + if (memcmp(marker, header, 4) != 0) { + return TINYEXR_ERROR_INVALID_MAGIC_NUMBER; + } + marker += 4; + } + + version->tiled = false; + version->long_name = false; + version->non_image = false; + version->multipart = false; + + // Parse version header. + { + // must be 2 + if (marker[0] != 2) { + return TINYEXR_ERROR_INVALID_EXR_VERSION; + } + + if (version == NULL) { + return TINYEXR_SUCCESS; // May OK + } + + version->version = 2; + + if (marker[1] & 0x2) { // 9th bit + version->tiled = true; + } + if (marker[1] & 0x4) { // 10th bit + version->long_name = true; + } + if (marker[1] & 0x8) { // 11th bit + version->non_image = true; // (deep image) + } + if (marker[1] & 0x10) { // 12th bit + version->multipart = true; + } + } + + return TINYEXR_SUCCESS; +} + +int ParseEXRVersionFromFile(EXRVersion *version, const char *filename) { + if (filename == NULL) { + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + FILE *fp = NULL; +#ifdef _WIN32 +#if defined(_MSC_VER) || (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API) // MSVC, MinGW GCC, or Clang. + errno_t err = _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (err != 0) { + // TODO(syoyo): return wfopen_s erro code + return TINYEXR_ERROR_CANT_OPEN_FILE; + } +#else + // Unknown compiler or MinGW without MINGW_HAS_SECURE_API. + fp = fopen(filename, "rb"); +#endif +#else + fp = fopen(filename, "rb"); +#endif + if (!fp) { + return TINYEXR_ERROR_CANT_OPEN_FILE; + } + + // Try to read kEXRVersionSize bytes; if the file is shorter than + // kEXRVersionSize, this will produce an error. This avoids a call to + // fseek(fp, 0, SEEK_END), which is not required to be supported by C + // implementations. + unsigned char buf[tinyexr::kEXRVersionSize]; + size_t ret = fread(&buf[0], 1, tinyexr::kEXRVersionSize, fp); + fclose(fp); + + if (ret != tinyexr::kEXRVersionSize) { + return TINYEXR_ERROR_INVALID_FILE; + } + + return ParseEXRVersionFromMemory(version, buf, tinyexr::kEXRVersionSize); +} + +int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, + const EXRHeader **exr_headers, + unsigned int num_parts, + const unsigned char *memory, + const size_t size, const char **err) { + if (exr_images == NULL || exr_headers == NULL || num_parts == 0 || + memory == NULL || (size <= tinyexr::kEXRVersionSize)) { + tinyexr::SetErrorMessage( + "Invalid argument for LoadEXRMultipartImageFromMemory()", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + // compute total header size. + size_t total_header_size = 0; + for (unsigned int i = 0; i < num_parts; i++) { + if (exr_headers[i]->header_len == 0) { + tinyexr::SetErrorMessage("EXRHeader variable is not initialized.", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + total_header_size += exr_headers[i]->header_len; + } + + const char *marker = reinterpret_cast( + memory + total_header_size + 4 + + 4); // +8 for magic number and version header. + + marker += 1; // Skip empty header. + + // NOTE 1: + // In multipart image, There is 'part number' before chunk data. + // 4 byte : part number + // 4+ : chunk + // + // NOTE 2: + // EXR spec says 'part number' is 'unsigned long' but actually this is + // 'unsigned int(4 bytes)' in OpenEXR implementation... + // http://www.openexr.com/openexrfilelayout.pdf + + // Load chunk offset table. + std::vector chunk_offset_table_list; + chunk_offset_table_list.reserve(num_parts); + for (size_t i = 0; i < static_cast(num_parts); i++) { + chunk_offset_table_list.resize(chunk_offset_table_list.size() + 1); + tinyexr::OffsetData& offset_data = chunk_offset_table_list.back(); + if (!exr_headers[i]->tiled || exr_headers[i]->tile_level_mode == TINYEXR_TILE_ONE_LEVEL) { + tinyexr::InitSingleResolutionOffsets(offset_data, size_t(exr_headers[i]->chunk_count)); + std::vector& offset_table = offset_data.offsets[0][0]; + + for (size_t c = 0; c < offset_table.size(); c++) { + tinyexr::tinyexr_uint64 offset; + memcpy(&offset, marker, 8); + tinyexr::swap8(&offset); + + if (offset >= size) { + tinyexr::SetErrorMessage("Invalid offset size in EXR header chunks.", + err); + return TINYEXR_ERROR_INVALID_DATA; + } + + offset_table[c] = offset + 4; // +4 to skip 'part number' + marker += 8; + } + } else { + { + std::vector num_x_tiles, num_y_tiles; + if (!tinyexr::PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_headers[i])) { + tinyexr::SetErrorMessage("Invalid tile info.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + int num_blocks = InitTileOffsets(offset_data, exr_headers[i], num_x_tiles, num_y_tiles); + if (num_blocks != exr_headers[i]->chunk_count) { + tinyexr::SetErrorMessage("Invalid offset table size.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) { + for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) { + for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) { + tinyexr::tinyexr_uint64 offset; + memcpy(&offset, marker, sizeof(tinyexr::tinyexr_uint64)); + tinyexr::swap8(&offset); + if (offset >= size) { + tinyexr::SetErrorMessage("Invalid offset size in EXR header chunks.", + err); + return TINYEXR_ERROR_INVALID_DATA; + } + offset_data.offsets[l][dy][dx] = offset + 4; // +4 to skip 'part number' + marker += sizeof(tinyexr::tinyexr_uint64); // = 8 + } + } + } + } + } + + // Decode image. + for (size_t i = 0; i < static_cast(num_parts); i++) { + tinyexr::OffsetData &offset_data = chunk_offset_table_list[i]; + + // First check 'part number' is identical to 'i' + for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) + for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) + for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) { + + const unsigned char *part_number_addr = + memory + offset_data.offsets[l][dy][dx] - 4; // -4 to move to 'part number' field. + unsigned int part_no; + memcpy(&part_no, part_number_addr, sizeof(unsigned int)); // 4 + tinyexr::swap4(&part_no); + + if (part_no != i) { + tinyexr::SetErrorMessage("Invalid `part number' in EXR header chunks.", + err); + return TINYEXR_ERROR_INVALID_DATA; + } + } + + std::string e; + int ret = tinyexr::DecodeChunk(&exr_images[i], exr_headers[i], offset_data, + memory, size, &e); + if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } + return ret; + } + } + + return TINYEXR_SUCCESS; +} + +int LoadEXRMultipartImageFromFile(EXRImage *exr_images, + const EXRHeader **exr_headers, + unsigned int num_parts, const char *filename, + const char **err) { + if (exr_images == NULL || exr_headers == NULL || num_parts == 0) { + tinyexr::SetErrorMessage( + "Invalid argument for LoadEXRMultipartImageFromFile", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + MemoryMappedFile file(filename); + if (!file.valid()) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } + + return LoadEXRMultipartImageFromMemory(exr_images, exr_headers, num_parts, + file.data, file.size, err); +} + +int SaveEXRToMemory(const float *data, int width, int height, int components, + const int save_as_fp16, unsigned char **outbuf, const char **err) { + + if ((components == 1) || components == 3 || components == 4) { + // OK + } else { + std::stringstream ss; + ss << "Unsupported component value : " << components << std::endl; + + tinyexr::SetErrorMessage(ss.str(), err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + EXRHeader header; + InitEXRHeader(&header); + + if ((width < 16) && (height < 16)) { + // No compression for small image. + header.compression_type = TINYEXR_COMPRESSIONTYPE_NONE; + } else { + header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP; + } + + EXRImage image; + InitEXRImage(&image); + + image.num_channels = components; + + std::vector images[4]; + + if (components == 1) { + images[0].resize(static_cast(width * height)); + memcpy(images[0].data(), data, sizeof(float) * size_t(width * height)); + } else { + images[0].resize(static_cast(width * height)); + images[1].resize(static_cast(width * height)); + images[2].resize(static_cast(width * height)); + images[3].resize(static_cast(width * height)); + + // Split RGB(A)RGB(A)RGB(A)... into R, G and B(and A) layers + for (size_t i = 0; i < static_cast(width * height); i++) { + images[0][i] = data[static_cast(components) * i + 0]; + images[1][i] = data[static_cast(components) * i + 1]; + images[2][i] = data[static_cast(components) * i + 2]; + if (components == 4) { + images[3][i] = data[static_cast(components) * i + 3]; + } + } + } + + float *image_ptr[4] = {0, 0, 0, 0}; + if (components == 4) { + image_ptr[0] = &(images[3].at(0)); // A + image_ptr[1] = &(images[2].at(0)); // B + image_ptr[2] = &(images[1].at(0)); // G + image_ptr[3] = &(images[0].at(0)); // R + } else if (components == 3) { + image_ptr[0] = &(images[2].at(0)); // B + image_ptr[1] = &(images[1].at(0)); // G + image_ptr[2] = &(images[0].at(0)); // R + } else if (components == 1) { + image_ptr[0] = &(images[0].at(0)); // A + } + + image.images = reinterpret_cast(image_ptr); + image.width = width; + image.height = height; + + header.num_channels = components; + header.channels = static_cast(malloc( + sizeof(EXRChannelInfo) * static_cast(header.num_channels))); + // Must be (A)BGR order, since most of EXR viewers expect this channel order. + if (components == 4) { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "A", 255); + strncpy_s(header.channels[1].name, "B", 255); + strncpy_s(header.channels[2].name, "G", 255); + strncpy_s(header.channels[3].name, "R", 255); +#else + strncpy(header.channels[0].name, "A", 255); + strncpy(header.channels[1].name, "B", 255); + strncpy(header.channels[2].name, "G", 255); + strncpy(header.channels[3].name, "R", 255); +#endif + header.channels[0].name[strlen("A")] = '\0'; + header.channels[1].name[strlen("B")] = '\0'; + header.channels[2].name[strlen("G")] = '\0'; + header.channels[3].name[strlen("R")] = '\0'; + } else if (components == 3) { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "B", 255); + strncpy_s(header.channels[1].name, "G", 255); + strncpy_s(header.channels[2].name, "R", 255); +#else + strncpy(header.channels[0].name, "B", 255); + strncpy(header.channels[1].name, "G", 255); + strncpy(header.channels[2].name, "R", 255); +#endif + header.channels[0].name[strlen("B")] = '\0'; + header.channels[1].name[strlen("G")] = '\0'; + header.channels[2].name[strlen("R")] = '\0'; + } else { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "A", 255); +#else + strncpy(header.channels[0].name, "A", 255); +#endif + header.channels[0].name[strlen("A")] = '\0'; + } + + header.pixel_types = static_cast( + malloc(sizeof(int) * static_cast(header.num_channels))); + header.requested_pixel_types = static_cast( + malloc(sizeof(int) * static_cast(header.num_channels))); + for (int i = 0; i < header.num_channels; i++) { + header.pixel_types[i] = + TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image + + if (save_as_fp16 > 0) { + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_HALF; // save with half(fp16) pixel format + } else { + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_FLOAT; // save with float(fp32) pixel format(i.e. + // no precision reduction) + } + } + + + unsigned char *mem_buf; + size_t mem_size = SaveEXRImageToMemory(&image, &header, &mem_buf, err); + + if (mem_size == 0) { + return TINYEXR_ERROR_SERIALIZATION_FAILED; + } + + free(header.channels); + free(header.pixel_types); + free(header.requested_pixel_types); + + if (mem_size > size_t(std::numeric_limits::max())) { + free(mem_buf); + return TINYEXR_ERROR_DATA_TOO_LARGE; + } + + (*outbuf) = mem_buf; + + return int(mem_size); +} + +int SaveEXR(const float *data, int width, int height, int components, + const int save_as_fp16, const char *outfilename, const char **err) { + if ((components == 1) || components == 3 || components == 4) { + // OK + } else { + std::stringstream ss; + ss << "Unsupported component value : " << components << std::endl; + + tinyexr::SetErrorMessage(ss.str(), err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + EXRHeader header; + InitEXRHeader(&header); + + if ((width < 16) && (height < 16)) { + // No compression for small image. + header.compression_type = TINYEXR_COMPRESSIONTYPE_NONE; + } else { + header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP; + } + + EXRImage image; + InitEXRImage(&image); + + image.num_channels = components; + + std::vector images[4]; + const size_t pixel_count = + static_cast(width) * static_cast(height); + + if (components == 1) { + images[0].resize(pixel_count); + memcpy(images[0].data(), data, sizeof(float) * pixel_count); + } else { + images[0].resize(pixel_count); + images[1].resize(pixel_count); + images[2].resize(pixel_count); + images[3].resize(pixel_count); + + // Split RGB(A)RGB(A)RGB(A)... into R, G and B(and A) layers + for (size_t i = 0; i < pixel_count; i++) { + images[0][i] = data[static_cast(components) * i + 0]; + images[1][i] = data[static_cast(components) * i + 1]; + images[2][i] = data[static_cast(components) * i + 2]; + if (components == 4) { + images[3][i] = data[static_cast(components) * i + 3]; + } + } + } + + float *image_ptr[4] = {0, 0, 0, 0}; + if (components == 4) { + image_ptr[0] = &(images[3].at(0)); // A + image_ptr[1] = &(images[2].at(0)); // B + image_ptr[2] = &(images[1].at(0)); // G + image_ptr[3] = &(images[0].at(0)); // R + } else if (components == 3) { + image_ptr[0] = &(images[2].at(0)); // B + image_ptr[1] = &(images[1].at(0)); // G + image_ptr[2] = &(images[0].at(0)); // R + } else if (components == 1) { + image_ptr[0] = &(images[0].at(0)); // A + } + + image.images = reinterpret_cast(image_ptr); + image.width = width; + image.height = height; + + header.num_channels = components; + header.channels = static_cast(malloc( + sizeof(EXRChannelInfo) * static_cast(header.num_channels))); + // Must be (A)BGR order, since most of EXR viewers expect this channel order. + if (components == 4) { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "A", 255); + strncpy_s(header.channels[1].name, "B", 255); + strncpy_s(header.channels[2].name, "G", 255); + strncpy_s(header.channels[3].name, "R", 255); +#else + strncpy(header.channels[0].name, "A", 255); + strncpy(header.channels[1].name, "B", 255); + strncpy(header.channels[2].name, "G", 255); + strncpy(header.channels[3].name, "R", 255); +#endif + header.channels[0].name[strlen("A")] = '\0'; + header.channels[1].name[strlen("B")] = '\0'; + header.channels[2].name[strlen("G")] = '\0'; + header.channels[3].name[strlen("R")] = '\0'; + } else if (components == 3) { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "B", 255); + strncpy_s(header.channels[1].name, "G", 255); + strncpy_s(header.channels[2].name, "R", 255); +#else + strncpy(header.channels[0].name, "B", 255); + strncpy(header.channels[1].name, "G", 255); + strncpy(header.channels[2].name, "R", 255); +#endif + header.channels[0].name[strlen("B")] = '\0'; + header.channels[1].name[strlen("G")] = '\0'; + header.channels[2].name[strlen("R")] = '\0'; + } else { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "A", 255); +#else + strncpy(header.channels[0].name, "A", 255); +#endif + header.channels[0].name[strlen("A")] = '\0'; + } + + header.pixel_types = static_cast( + malloc(sizeof(int) * static_cast(header.num_channels))); + header.requested_pixel_types = static_cast( + malloc(sizeof(int) * static_cast(header.num_channels))); + for (int i = 0; i < header.num_channels; i++) { + header.pixel_types[i] = + TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image + + if (save_as_fp16 > 0) { + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_HALF; // save with half(fp16) pixel format + } else { + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_FLOAT; // save with float(fp32) pixel format(i.e. + // no precision reduction) + } + } + + int ret = SaveEXRImageToFile(&image, &header, outfilename, err); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + free(header.channels); + free(header.pixel_types); + free(header.requested_pixel_types); + + return ret; +} + +#ifdef __clang__ +// zero-as-null-pointer-constant +#pragma clang diagnostic pop +#endif + +#endif // TINYEXR_IMPLEMENTATION_DEFINED +#endif // TINYEXR_IMPLEMENTATION diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index b32529404..0e200ea39 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -269,8 +269,9 @@ namespace Flax.Build dotnetSdkVersions = MergeVersions(dotnetSdkVersions, GetVersions(Path.Combine(dotnetPath, "sdk"))); dotnetRuntimeVersions = MergeVersions(dotnetRuntimeVersions, GetVersions(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App"))); - dotnetSdkVersions = dotnetSdkVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "sdk", x))); - dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x))); + dotnetSdkVersions = dotnetSdkVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "sdk", x, ".version"))); + dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x, ".version"))); + dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => Directory.Exists(Path.Combine(dotnetPath, "packs", "Microsoft.NETCore.App.Ref", x))); dotnetSdkVersions = dotnetSdkVersions.OrderByDescending(ParseVersion); dotnetRuntimeVersions = dotnetRuntimeVersions.OrderByDescending(ParseVersion); @@ -543,11 +544,6 @@ namespace Flax.Build return null; } - private static bool IsValidVersion(string versionPath) - { - return File.Exists(Path.Combine(versionPath, ".version")); - } - private static string SearchForDotnetLocationLinux() { if (File.Exists("/etc/dotnet/install_location")) // Officially recommended dotnet location file diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs index d9271e586..eac85e17e 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs @@ -26,6 +26,38 @@ namespace Flax.Build.NativeCpp SmallCode } + /// + /// The code sanitizers for core errors detection by compiler-supported checks. + /// + [Flags] + public enum Sanitizer + { + /// + /// No sanitizers in use. + /// + None = 0, + + /// + /// Memory errors detector, + /// + Address = 1, + + /// + /// Data races and deadlocks detector. + /// + Thread = 2, + + /// + /// Uninitialized memory reads detector. + /// + Memory = 4, + + /// + /// Undefined behavior (UB) detector. + /// + Undefined = 8, + } + /// /// The compilation optimization hint. /// @@ -67,6 +99,11 @@ namespace Flax.Build.NativeCpp /// public FavorSizeOrSpeed FavorSizeOrSpeed = FavorSizeOrSpeed.Neither; + /// + /// Selects a sanitizers to use (as flags). + /// + public Sanitizer Sanitizers = Sanitizer.None; + /// /// Enables exceptions support. /// @@ -184,6 +221,7 @@ namespace Flax.Build.NativeCpp { CppVersion = CppVersion, FavorSizeOrSpeed = FavorSizeOrSpeed, + Sanitizers = Sanitizers, EnableExceptions = EnableExceptions, RuntimeTypeInfo = RuntimeTypeInfo, Inlining = Inlining, diff --git a/Source/Tools/Flax.Build/Build/Target.cs b/Source/Tools/Flax.Build/Build/Target.cs index 5820d14d6..90523b5ef 100644 --- a/Source/Tools/Flax.Build/Build/Target.cs +++ b/Source/Tools/Flax.Build/Build/Target.cs @@ -249,6 +249,7 @@ namespace Flax.Build } options.CompileEnv.EnableExceptions = true; // TODO: try to disable this! + options.CompileEnv.Sanitizers = Configuration.Sanitizers; switch (options.Configuration) { case TargetConfiguration.Debug: diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 8c12fc207..63d4aa5b3 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -225,6 +225,12 @@ namespace Flax.Build [CommandLine("compiler", "", "Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows.")] public static string Compiler = null; + /// + /// Selects a sanitizers to use (as flags). Options: Address, Thread. + /// + [CommandLine("sanitizers", "", "Selects a sanitizers to use (as flags). Options: Address, Thread.")] + public static Flax.Build.NativeCpp.Sanitizer Sanitizers = Flax.Build.NativeCpp.Sanitizer.None; + /// /// Specifies the dotnet SDK version to use for the build. Eg. set to '7' to use .NET 7 even if .NET 8 is installed. /// @@ -242,6 +248,8 @@ namespace Flax.Build cmdLine += " -compiler=" + Compiler; if (!string.IsNullOrEmpty(Dotnet)) cmdLine += " -dotnet=" + Dotnet; + if (Sanitizers != Flax.Build.NativeCpp.Sanitizer.None) + cmdLine += " -sanitizers=" + Sanitizers.ToString(); } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/DirectXTex.cs b/Source/Tools/Flax.Build/Deps/Dependencies/DirectXTex.cs index aa4cbfbda..318223228 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/DirectXTex.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/DirectXTex.cs @@ -44,7 +44,7 @@ namespace Flax.Deps.Dependencies // Get the source CloneGitRepo(root, "https://github.com/Microsoft/DirectXTex.git"); - GitCheckout(root, "master", "9a417f506c43e087b84c017260ad673abd6c64e1"); + GitCheckout(root, "main", "5cfd711dc5d64cde1e8b27670036535df5c3f922"); foreach (var platform in options.Platforms) { @@ -52,60 +52,39 @@ namespace Flax.Deps.Dependencies { case TargetPlatform.Windows: { - var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2015.sln"); - var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2015"); + var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2022.sln"); + var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2022"); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64"); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in outputFileNames) - { Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file)); - } break; } case TargetPlatform.UWP: { - var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2017.sln"); - var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2017"); + var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2019.sln"); + var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2019"); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64"); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in outputFileNames) - { Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file)); - } break; } case TargetPlatform.XboxOne: - { - var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln"); - File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true); - var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj")); - projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142"); - var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2017.vcxproj"); - File.WriteAllText(projectPath, projectFileContents); - var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017"); - Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.XboxOne.x64"); - var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); - foreach (var file in outputFileNames) - { - Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.XboxOne.x64", configuration, file), Path.Combine(depsFolder, file)); - } - break; - } case TargetPlatform.XboxScarlett: { - var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln"); - File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true); - var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj")); + var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2019.sln"); + File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.sln"), solutionPath, true); + var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.vcxproj")); projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142"); - var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2017.vcxproj"); + var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2019.vcxproj"); File.WriteAllText(projectPath, projectFileContents); - var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017"); - Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.Scarlett.x64"); + var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2019"); + var xboxName = platform == TargetPlatform.XboxOne ? "Gaming.Xbox.XboxOne.x64" : "Gaming.Xbox.Scarlett.x64"; + Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, xboxName); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in outputFileNames) - { - Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.Scarlett.x64", configuration, file), Path.Combine(depsFolder, file)); - } + Utilities.FileCopy(Path.Combine(binFolder, xboxName, configuration, file), Path.Combine(depsFolder, file)); break; } } diff --git a/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs b/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs index a1268ddfc..8461d6aac 100644 --- a/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs @@ -105,6 +105,7 @@ namespace Flax.Build.Platforms commonArgs.Add("objective-c++"); commonArgs.Add("-stdlib=libc++"); AddArgsCommon(options, commonArgs); + AddArgsSanitizer(compileEnvironment.Sanitizers, commonArgs); switch (compileEnvironment.CppVersion) { @@ -155,14 +156,24 @@ namespace Flax.Build.Platforms commonArgs.Add("-pthread"); - if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode) - commonArgs.Add("-Ofast"); - else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode) - commonArgs.Add("-Os"); - if (compileEnvironment.Optimization) - commonArgs.Add("-O3"); + if (compileEnvironment.Sanitizers.HasFlag(Sanitizer.Address)) + { + commonArgs.Add("-fno-optimize-sibling-calls"); + commonArgs.Add("-fno-omit-frame-pointer"); + if (compileEnvironment.Optimization) + commonArgs.Add("-O1"); + } else - commonArgs.Add("-O0"); + { + if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode) + commonArgs.Add("-Ofast"); + else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode) + commonArgs.Add("-Os"); + if (compileEnvironment.Optimization) + commonArgs.Add("-O3"); + else + commonArgs.Add("-O0"); + } if (compileEnvironment.BufferSecurityCheck) commonArgs.Add("-fstack-protector"); @@ -240,6 +251,7 @@ namespace Flax.Build.Platforms { args.Add(string.Format("-o \"{0}\"", outputFilePath)); AddArgsCommon(options, args); + AddArgsSanitizer(options.CompileEnv.Sanitizers, args); if (isArchive) { @@ -426,5 +438,15 @@ namespace Flax.Build.Platforms break; } } + + protected void AddArgsSanitizer(Sanitizer sanitizers, List args) + { + if (sanitizers.HasFlag(Sanitizer.Address)) + args.Add("-fsanitize=address"); + if (sanitizers.HasFlag(Sanitizer.Thread)) + args.Add("-fsanitize=thread"); + if (sanitizers.HasFlag(Sanitizer.Undefined)) + args.Add("-fsanitize=undefined"); + } } }