diff --git a/Penumbra/Interop/Services/TextureArraySlicer.cs b/Penumbra/Interop/Services/TextureArraySlicer.cs new file mode 100644 index 00000000..c934ac2b --- /dev/null +++ b/Penumbra/Interop/Services/TextureArraySlicer.cs @@ -0,0 +1,119 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using OtterGui.Services; +using SharpDX.Direct3D; +using SharpDX.Direct3D11; + +namespace Penumbra.Interop.Services; + +/// +/// Creates ImGui handles over slices of array textures, and manages their lifetime. +/// +public sealed unsafe class TextureArraySlicer : IUiService, IDisposable +{ + private const uint InitialTimeToLive = 2; + + private readonly Dictionary<(nint XivTexture, byte SliceIndex), SliceState> _activeSlices = []; + private readonly HashSet<(nint XivTexture, byte SliceIndex)> _expiredKeys = []; + + /// Caching this across frames will cause a crash to desktop. + public nint GetImGuiHandle(Texture* texture, byte sliceIndex) + { + if (texture == null) + throw new ArgumentNullException(nameof(texture)); + if (sliceIndex >= texture->ArraySize) + throw new ArgumentOutOfRangeException(nameof(sliceIndex), $"Slice index ({sliceIndex}) is greater than or equal to the texture array size ({texture->ArraySize})"); + if (_activeSlices.TryGetValue(((nint)texture, sliceIndex), out var state)) + { + state.Refresh(); + return (nint)state.ShaderResourceView; + } + var srv = (ShaderResourceView)(nint)texture->D3D11ShaderResourceView; + var description = srv.Description; + switch (description.Dimension) + { + case ShaderResourceViewDimension.Texture1D: + case ShaderResourceViewDimension.Texture2D: + case ShaderResourceViewDimension.Texture2DMultisampled: + case ShaderResourceViewDimension.Texture3D: + case ShaderResourceViewDimension.TextureCube: + // This function treats these as single-slice arrays. + // As per the range check above, the only valid slice (i. e. 0) has been requested, therefore there is nothing to do. + break; + case ShaderResourceViewDimension.Texture1DArray: + description.Texture1DArray.FirstArraySlice = sliceIndex; + description.Texture2DArray.ArraySize = 1; + break; + case ShaderResourceViewDimension.Texture2DArray: + description.Texture2DArray.FirstArraySlice = sliceIndex; + description.Texture2DArray.ArraySize = 1; + break; + case ShaderResourceViewDimension.Texture2DMultisampledArray: + description.Texture2DMSArray.FirstArraySlice = sliceIndex; + description.Texture2DMSArray.ArraySize = 1; + break; + case ShaderResourceViewDimension.TextureCubeArray: + description.TextureCubeArray.First2DArrayFace = sliceIndex * 6; + description.TextureCubeArray.CubeCount = 1; + break; + default: + throw new NotSupportedException($"{nameof(TextureArraySlicer)} does not support dimension {description.Dimension}"); + } + state = new SliceState(new ShaderResourceView(srv.Device, srv.Resource, description)); + _activeSlices.Add(((nint)texture, sliceIndex), state); + return (nint)state.ShaderResourceView; + } + + public void Tick() + { + try + { + foreach (var (key, slice) in _activeSlices) + { + if (!slice.Tick()) + _expiredKeys.Add(key); + } + foreach (var key in _expiredKeys) + { + _activeSlices.Remove(key); + } + } + finally + { + _expiredKeys.Clear(); + } + } + + public void Dispose() + { + foreach (var slice in _activeSlices.Values) + { + slice.Dispose(); + } + } + + private sealed class SliceState(ShaderResourceView shaderResourceView) : IDisposable + { + public readonly ShaderResourceView ShaderResourceView = shaderResourceView; + + private uint _timeToLive = InitialTimeToLive; + + public void Refresh() + { + _timeToLive = InitialTimeToLive; + } + + public bool Tick() + { + if (unchecked(_timeToLive--) > 0) + return true; + + ShaderResourceView.Dispose(); + return false; + } + + public void Dispose() + { + ShaderResourceView.Dispose(); + } + } +} diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 24ffe469..8e143e3c 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -72,6 +72,14 @@ $(DalamudLibPath)Iced.dll False + + $(DalamudLibPath)SharpDX.dll + False + + + $(DalamudLibPath)SharpDX.Direct3D11.dll + False + lib\OtterTex.dll diff --git a/Penumbra/UI/WindowSystem.cs b/Penumbra/UI/WindowSystem.cs index 6d382ad4..575a381f 100644 --- a/Penumbra/UI/WindowSystem.cs +++ b/Penumbra/UI/WindowSystem.cs @@ -2,6 +2,7 @@ using Dalamud.Interface; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using OtterGui.Services; +using Penumbra.Interop.Services; using Penumbra.UI.AdvancedWindow; using Penumbra.UI.Knowledge; using Penumbra.UI.Tabs.Debug; @@ -10,23 +11,25 @@ namespace Penumbra.UI; public class PenumbraWindowSystem : IDisposable, IUiService { - private readonly IUiBuilder _uiBuilder; - private readonly WindowSystem _windowSystem; - private readonly FileDialogService _fileDialog; - public readonly ConfigWindow Window; - public readonly PenumbraChangelog Changelog; - public readonly KnowledgeWindow KnowledgeWindow; + private readonly IUiBuilder _uiBuilder; + private readonly WindowSystem _windowSystem; + private readonly FileDialogService _fileDialog; + private readonly TextureArraySlicer _textureArraySlicer; + public readonly ConfigWindow Window; + public readonly PenumbraChangelog Changelog; + public readonly KnowledgeWindow KnowledgeWindow; public PenumbraWindowSystem(IDalamudPluginInterface pi, Configuration config, PenumbraChangelog changelog, ConfigWindow window, LaunchButton _, ModEditWindow editWindow, FileDialogService fileDialog, ImportPopup importPopup, DebugTab debugTab, - KnowledgeWindow knowledgeWindow) + KnowledgeWindow knowledgeWindow, TextureArraySlicer textureArraySlicer) { - _uiBuilder = pi.UiBuilder; - _fileDialog = fileDialog; - KnowledgeWindow = knowledgeWindow; - Changelog = changelog; - Window = window; - _windowSystem = new WindowSystem("Penumbra"); + _uiBuilder = pi.UiBuilder; + _fileDialog = fileDialog; + _textureArraySlicer = textureArraySlicer; + KnowledgeWindow = knowledgeWindow; + Changelog = changelog; + Window = window; + _windowSystem = new WindowSystem("Penumbra"); _windowSystem.AddWindow(changelog.Changelog); _windowSystem.AddWindow(window); _windowSystem.AddWindow(editWindow); @@ -37,6 +40,7 @@ public class PenumbraWindowSystem : IDisposable, IUiService _uiBuilder.OpenConfigUi += Window.OpenSettings; _uiBuilder.Draw += _windowSystem.Draw; _uiBuilder.Draw += _fileDialog.Draw; + _uiBuilder.Draw += _textureArraySlicer.Tick; _uiBuilder.DisableGposeUiHide = !config.HideUiInGPose; _uiBuilder.DisableCutsceneUiHide = !config.HideUiInCutscenes; _uiBuilder.DisableUserUiHide = !config.HideUiWhenUiHidden; @@ -51,5 +55,6 @@ public class PenumbraWindowSystem : IDisposable, IUiService _uiBuilder.OpenConfigUi -= Window.OpenSettings; _uiBuilder.Draw -= _windowSystem.Draw; _uiBuilder.Draw -= _fileDialog.Draw; + _uiBuilder.Draw -= _textureArraySlicer.Tick; } }