diff --git a/Penumbra.GameData b/Penumbra.GameData index 2ff50e68..3d4d8510 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2ff50e68f7c951f0f8b25957a400a2e32ed9d6dc +Subproject commit 3d4d8510f832dfd95d7069b86e6b3da4ec612558 diff --git a/Penumbra/Api/Api/ModSettingsApi.cs b/Penumbra/Api/Api/ModSettingsApi.cs index 3ba17cf4..d49c2904 100644 --- a/Penumbra/Api/Api/ModSettingsApi.cs +++ b/Penumbra/Api/Api/ModSettingsApi.cs @@ -73,6 +73,27 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable return (ret.Item1, (ret.Item2.Value.Item1, ret.Item2.Value.Item2, ret.Item2.Value.Item3, ret.Item2.Value.Item4)); } + public PenumbraApiEc GetSettingsInAllCollections(string modDirectory, string modName, + out Dictionary>, bool, bool)> settings, + bool ignoreTemporaryCollections = false) + { + settings = []; + if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) + return PenumbraApiEc.ModMissing; + + var collections = ignoreTemporaryCollections + ? _collectionManager.Storage.Where(c => c != ModCollection.Empty) + : _collectionManager.Storage.Where(c => c != ModCollection.Empty).Concat(_collectionManager.Temp.Values); + settings = []; + foreach (var collection in collections) + { + if (GetCurrentSettings(collection, mod, false, false, 0) is { } s) + settings.Add(collection.Identity.Id, s); + } + + return PenumbraApiEc.Success; + } + public (PenumbraApiEc, (bool, int, Dictionary>, bool, bool)?) GetCurrentModSettingsWithTemp(Guid collectionId, string modDirectory, string modName, bool ignoreInheritance, bool ignoreTemporary, int key) { diff --git a/Penumbra/Api/Api/ResolveApi.cs b/Penumbra/Api/Api/ResolveApi.cs index 481ea7ad..00a0c86f 100644 --- a/Penumbra/Api/Api/ResolveApi.cs +++ b/Penumbra/Api/Api/ResolveApi.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using OtterGui.Services; +using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; @@ -41,6 +42,19 @@ public class ResolveApi( return ret.Select(r => r.ToString()).ToArray(); } + public PenumbraApiEc ResolvePath(Guid collectionId, string gamePath, out string resolvedPath) + { + resolvedPath = gamePath; + if (!collectionManager.Storage.ById(collectionId, out var collection)) + return PenumbraApiEc.CollectionMissing; + + if (!collection.HasCache) + return PenumbraApiEc.CollectionInactive; + + resolvedPath = ResolvePath(gamePath, modManager, collection); + return PenumbraApiEc.Success; + } + public string[] ReverseResolvePlayerPath(string moddedPath) { if (!config.EnableMods) @@ -64,6 +78,26 @@ public class ResolveApi( return (resolved, reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray()); } + public PenumbraApiEc ResolvePaths(Guid collectionId, string[] forward, string[] reverse, out string[] resolvedForward, + out string[][] resolvedReverse) + { + resolvedForward = forward; + resolvedReverse = []; + if (!config.EnableMods) + return PenumbraApiEc.Success; + + if (!collectionManager.Storage.ById(collectionId, out var collection)) + return PenumbraApiEc.CollectionMissing; + + if (!collection.HasCache) + return PenumbraApiEc.CollectionInactive; + + resolvedForward = forward.Select(p => ResolvePath(p, modManager, collection)).ToArray(); + var reverseResolved = collection.ReverseResolvePaths(reverse); + resolvedReverse = reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray(); + return PenumbraApiEc.Success; + } + public async Task<(string[], string[][])> ResolvePlayerPathsAsync(string[] forward, string[] reverse) { if (!config.EnableMods) diff --git a/Penumbra/Api/Api/UiApi.cs b/Penumbra/Api/Api/UiApi.cs index b14f67ae..70f018bb 100644 --- a/Penumbra/Api/Api/UiApi.cs +++ b/Penumbra/Api/Api/UiApi.cs @@ -81,6 +81,12 @@ public class UiApi : IPenumbraApiUi, IApiService, IDisposable public void CloseMainWindow() => _configWindow.IsOpen = false; + public PenumbraApiEc RegisterSettingsSection(Action draw) + => throw new NotImplementedException(); + + public PenumbraApiEc UnregisterSettingsSection(Action draw) + => throw new NotImplementedException(); + private void OnChangedItemClick(MouseButton button, IIdentifiedObjectData data) { if (ChangedItemClicked == null) diff --git a/Penumbra/Api/IpcProviders.cs b/Penumbra/Api/IpcProviders.cs index 5f04540f..fdacc73b 100644 --- a/Penumbra/Api/IpcProviders.cs +++ b/Penumbra/Api/IpcProviders.cs @@ -66,6 +66,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetCurrentModSettings.Provider(pi, api.ModSettings), IpcSubscribers.GetCurrentModSettingsWithTemp.Provider(pi, api.ModSettings), IpcSubscribers.GetAllModSettings.Provider(pi, api.ModSettings), + IpcSubscribers.GetSettingsInAllCollections.Provider(pi, api.ModSettings), IpcSubscribers.TryInheritMod.Provider(pi, api.ModSettings), IpcSubscribers.TrySetMod.Provider(pi, api.ModSettings), IpcSubscribers.TrySetModPriority.Provider(pi, api.ModSettings), @@ -98,6 +99,8 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.ReverseResolvePlayerPath.Provider(pi, api.Resolve), IpcSubscribers.ResolvePlayerPaths.Provider(pi, api.Resolve), IpcSubscribers.ResolvePlayerPathsAsync.Provider(pi, api.Resolve), + IpcSubscribers.ResolvePath.Provider(pi, api.Resolve), + IpcSubscribers.ResolvePaths.Provider(pi, api.Resolve), IpcSubscribers.GetGameObjectResourcePaths.Provider(pi, api.ResourceTree), IpcSubscribers.GetPlayerResourcePaths.Provider(pi, api.ResourceTree), diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 073fef2f..177722ec 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -7,12 +7,12 @@ using OtterGui.Log; using OtterGui.Services; using OtterGui.Tasks; using OtterTex; -using SharpDX.Direct3D11; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; -using DxgiDevice = SharpDX.DXGI.Device; +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; using Image = SixLabors.ImageSharp.Image; namespace Penumbra.Import.Textures; @@ -125,11 +125,11 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur switch (_type) { case TextureType.Png: - data?.SaveAsync(_outputPath, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }, cancel) + data?.SaveAsync(_outputPath, new PngEncoder { CompressionLevel = PngCompressionLevel.NoCompression }, cancel) .Wait(cancel); return; case TextureType.Targa: - data?.SaveAsync(_outputPath, new TgaEncoder() + data?.SaveAsync(_outputPath, new TgaEncoder { Compression = TgaCompression.None, BitsPerPixel = TgaBitsPerPixel.Pixel32, @@ -204,11 +204,16 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur rgba, width, height), CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps), CombinedTexture.TextureSaveType.Bitmap => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC1 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC1UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC3 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC3UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC4 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC4UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC5 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC5UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC7 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC7UNorm, cancel, rgba, width, height), + CombinedTexture.TextureSaveType.BC1 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC1UNorm, cancel, rgba, + width, height), + CombinedTexture.TextureSaveType.BC3 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC3UNorm, cancel, rgba, + width, height), + CombinedTexture.TextureSaveType.BC4 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC4UNorm, cancel, rgba, + width, height), + CombinedTexture.TextureSaveType.BC5 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC5UNorm, cancel, rgba, + width, height), + CombinedTexture.TextureSaveType.BC7 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC7UNorm, cancel, rgba, + width, height), _ => throw new Exception("Wrong save type."), }; @@ -390,7 +395,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } /// Create a BC3 or BC7 block-compressed .dds from the input (optionally with mipmaps). Returns input (+ mipmaps) if it is already the correct format. - public ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, DXGIFormat format, CancellationToken cancel) + public unsafe ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, DXGIFormat format, CancellationToken cancel) { if (input.Meta.Format == format) return input; @@ -406,11 +411,58 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur // See https://github.com/microsoft/DirectXTex/wiki/Compress#parameters for the format condition. if (format is DXGIFormat.BC6HUF16 or DXGIFormat.BC6HSF16 or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB) { - var device = new Device(uiBuilder.DeviceHandle); - var dxgiDevice = device.QueryInterface(); + ref var device = ref *(ID3D11Device*)uiBuilder.DeviceHandle; + IDXGIDevice* dxgiDevice; + Marshal.ThrowExceptionForHR(device.QueryInterface(TerraFX.Interop.Windows.Windows.__uuidof(), (void**)&dxgiDevice)); - using var deviceClone = new Device(dxgiDevice.Adapter, device.CreationFlags, device.FeatureLevel); - return input.Compress(deviceClone.NativePointer, format, CompressFlags.Parallel); + try + { + IDXGIAdapter* adapter = null; + Marshal.ThrowExceptionForHR(dxgiDevice->GetAdapter(&adapter)); + try + { + dxgiDevice->Release(); + dxgiDevice = null; + + ID3D11Device* deviceClone = null; + ID3D11DeviceContext* contextClone = null; + var featureLevel = device.GetFeatureLevel(); + Marshal.ThrowExceptionForHR(DirectX.D3D11CreateDevice( + adapter, + D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_UNKNOWN, + HMODULE.NULL, + device.GetCreationFlags(), + &featureLevel, + 1, + D3D11.D3D11_SDK_VERSION, + &deviceClone, + null, + &contextClone)); + try + { + adapter->Release(); + adapter = null; + return input.Compress((nint)deviceClone, format, CompressFlags.Parallel); + } + finally + { + if (contextClone is not null) + contextClone->Release(); + if (deviceClone is not null) + deviceClone->Release(); + } + } + finally + { + if (adapter is not null) + adapter->Release(); + } + } + finally + { + if (dxgiDevice is not null) + dxgiDevice->Release(); + } } return input.Compress(format, CompressFlags.BC7Quick | CompressFlags.Parallel); @@ -456,7 +508,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur GC.KeepAlive(input); } - private readonly struct ImageInputData + private readonly struct ImageInputData : IEquatable { private readonly string? _inputPath; @@ -524,5 +576,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public override int GetHashCode() => _inputPath != null ? _inputPath.ToLowerInvariant().GetHashCode() : HashCode.Combine(_width, _height); + + public override bool Equals(object? obj) + => obj is ImageInputData o && Equals(o); } } diff --git a/Penumbra/Interop/Services/TextureArraySlicer.cs b/Penumbra/Interop/Services/TextureArraySlicer.cs index 11498878..7b873f26 100644 --- a/Penumbra/Interop/Services/TextureArraySlicer.cs +++ b/Penumbra/Interop/Services/TextureArraySlicer.cs @@ -1,8 +1,7 @@ using Dalamud.Bindings.ImGui; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using OtterGui.Services; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; +using TerraFX.Interop.DirectX; namespace Penumbra.Interop.Services; @@ -22,46 +21,78 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable 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})"); + 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 new ImTextureID((nint)state.ShaderResourceView); } - var srv = (ShaderResourceView)(nint)texture->D3D11ShaderResourceView; - var description = srv.Description; - switch (description.Dimension) + + ref var srv = ref *(ID3D11ShaderResourceView*)(nint)texture->D3D11ShaderResourceView; + srv.AddRef(); + try { - 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}"); + D3D11_SHADER_RESOURCE_VIEW_DESC description; + srv.GetDesc(&description); + switch (description.ViewDimension) + { + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE1D: + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D: + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2DMS: + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE3D: + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_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 D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE1DARRAY: + description.Texture1DArray.FirstArraySlice = sliceIndex; + description.Texture2DArray.ArraySize = 1; + break; + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2DARRAY: + description.Texture2DArray.FirstArraySlice = sliceIndex; + description.Texture2DArray.ArraySize = 1; + break; + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY: + description.Texture2DMSArray.FirstArraySlice = sliceIndex; + description.Texture2DMSArray.ArraySize = 1; + break; + case D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURECUBEARRAY: + description.TextureCubeArray.First2DArrayFace = sliceIndex * 6u; + description.TextureCubeArray.NumCubes = 1; + break; + default: + throw new NotSupportedException($"{nameof(TextureArraySlicer)} does not support dimension {description.ViewDimension}"); + } + + ID3D11Device* device = null; + srv.GetDevice(&device); + ID3D11Resource* resource = null; + srv.GetResource(&resource); + try + { + ID3D11ShaderResourceView* slicedSrv = null; + Marshal.ThrowExceptionForHR(device->CreateShaderResourceView(resource, &description, &slicedSrv)); + resource->Release(); + device->Release(); + + state = new SliceState(slicedSrv); + _activeSlices.Add(((nint)texture, sliceIndex), state); + return new ImTextureID((nint)state.ShaderResourceView); + } + finally + { + if (resource is not null) + resource->Release(); + if (device is not null) + device->Release(); + } + } + finally + { + srv.Release(); } - state = new SliceState(new ShaderResourceView(srv.Device, srv.Resource, description)); - _activeSlices.Add(((nint)texture, sliceIndex), state); - return new ImTextureID((nint)state.ShaderResourceView); } public void Tick() @@ -73,10 +104,9 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable if (!slice.Tick()) _expiredKeys.Add(key); } + foreach (var key in _expiredKeys) - { _activeSlices.Remove(key); - } } finally { @@ -87,14 +117,12 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable public void Dispose() { foreach (var slice in _activeSlices.Values) - { slice.Dispose(); - } } - private sealed class SliceState(ShaderResourceView shaderResourceView) : IDisposable + private sealed class SliceState(ID3D11ShaderResourceView* shaderResourceView) : IDisposable { - public readonly ShaderResourceView ShaderResourceView = shaderResourceView; + public readonly ID3D11ShaderResourceView* ShaderResourceView = shaderResourceView; private uint _timeToLive = InitialTimeToLive; @@ -108,13 +136,15 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable if (unchecked(_timeToLive--) > 0) return true; - ShaderResourceView.Dispose(); + if (ShaderResourceView is not null) + ShaderResourceView->Release(); return false; } public void Dispose() { - ShaderResourceView.Dispose(); + if (ShaderResourceView is not null) + ShaderResourceView->Release(); } } } diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index f04928a5..43f853f3 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -38,16 +38,8 @@ $(DalamudLibPath)Iced.dll False - - $(DalamudLibPath)SharpDX.dll - False - - - $(DalamudLibPath)SharpDX.Direct3D11.dll - False - - - $(DalamudLibPath)SharpDX.DXGI.dll + + $(DalamudLibPath)TerraFX.Interop.Windows.dll False diff --git a/Penumbra/Services/StaticServiceManager.cs b/Penumbra/Services/StaticServiceManager.cs index 27582395..be482d1d 100644 --- a/Penumbra/Services/StaticServiceManager.cs +++ b/Penumbra/Services/StaticServiceManager.cs @@ -48,6 +48,7 @@ public static class StaticServiceManager .AddDalamudService(pi) .AddDalamudService(pi) .AddDalamudService(pi) + .AddDalamudService(pi) .AddDalamudService(pi) .AddDalamudService(pi) .AddDalamudService(pi)