From e5620e17e0f468ab6331e9dbf347e881c592cb5b Mon Sep 17 00:00:00 2001 From: Exter-N Date: Wed, 12 Mar 2025 01:20:36 +0100 Subject: [PATCH 1/4] Improve texture saving --- Penumbra/Import/Textures/TextureManager.cs | 43 ++++++++++++------ Penumbra/Penumbra.csproj | 4 ++ .../Materials/MtrlTab.LivePreview.cs | 2 +- .../AdvancedWindow/ModEditWindow.Textures.cs | 21 +++++++-- Penumbra/lib/OtterTex.dll | Bin 41984 -> 42496 bytes 5 files changed, 50 insertions(+), 20 deletions(-) diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 7118f8af..6adf5861 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -1,3 +1,4 @@ +using Dalamud.Interface; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; @@ -6,15 +7,17 @@ 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 Image = SixLabors.ImageSharp.Image; namespace Penumbra.Import.Textures; -public sealed class TextureManager(IDataManager gameData, Logger logger, ITextureProvider textureProvider) +public sealed class TextureManager(IDataManager gameData, Logger logger, ITextureProvider textureProvider, IUiBuilder uiBuilder) : SingleTaskQueue, IDisposable, IService { private readonly Logger _logger = logger; @@ -47,11 +50,11 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, string output) - => Enqueue(new SaveAsAction(this, type, mipMaps, asTex, input, output)); + => Enqueue(new SaveAsAction(this, type, uiBuilder.Device, mipMaps, asTex, input, output)); public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) - => Enqueue(new SaveAsAction(this, type, mipMaps, asTex, image, path, rgba, width, height)); + => Enqueue(new SaveAsAction(this, type, uiBuilder.Device, mipMaps, asTex, image, path, rgba, width, height)); private Task Enqueue(IAction action) { @@ -156,27 +159,30 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur private readonly string _outputPath; private readonly ImageInputData _input; private readonly CombinedTexture.TextureSaveType _type; + private readonly Device? _device; private readonly bool _mipMaps; private readonly bool _asTex; - public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, - string output) + public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, Device? device, bool mipMaps, bool asTex, + string input, string output) { _textures = textures; _input = new ImageInputData(input); _outputPath = output; _type = type; + _device = device; _mipMaps = mipMaps; _asTex = asTex; } - public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, - string path, byte[]? rgba = null, int width = 0, int height = 0) + public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, Device? device, bool mipMaps, bool asTex, + BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) { _textures = textures; _input = new ImageInputData(image, rgba, width, height); _outputPath = path; _type = type; + _device = device; _mipMaps = mipMaps; _asTex = asTex; } @@ -201,8 +207,8 @@ 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.BC3 => ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC7 => ConvertToCompressedDds(image, _mipMaps, true, cancel, rgba, width, height), + CombinedTexture.TextureSaveType.BC3 => ConvertToCompressedDds(image, _mipMaps, false, _device, cancel, rgba, width, height), + CombinedTexture.TextureSaveType.BC7 => ConvertToCompressedDds(image, _mipMaps, true, _device, cancel, rgba, width, height), _ => throw new Exception("Wrong save type."), }; @@ -320,8 +326,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } /// Convert an existing image to a block compressed .dds. Does not create a deep copy of an existing dds of the correct format and just returns the existing one. - public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, CancellationToken cancel, byte[]? rgba = null, - int width = 0, int height = 0) + public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, Device? device, CancellationToken cancel, + byte[]? rgba = null, int width = 0, int height = 0) { switch (input.Type.ReduceToBehaviour()) { @@ -331,12 +337,12 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur cancel.ThrowIfCancellationRequested(); var dds = ConvertToDds(rgba, width, height).AsDds!; cancel.ThrowIfCancellationRequested(); - return CreateCompressed(dds, mipMaps, bc7, cancel); + return CreateCompressed(dds, mipMaps, bc7, device, cancel); } case TextureType.Dds: { var scratch = input.AsDds!; - return CreateCompressed(scratch, mipMaps, bc7, cancel); + return CreateCompressed(scratch, mipMaps, bc7, device, cancel); } default: return new BaseImage(); } @@ -384,7 +390,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 static ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, bool bc7, CancellationToken cancel) + public static ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, bool bc7, Device? device, CancellationToken cancel) { var format = bc7 ? DXGIFormat.BC7UNorm : DXGIFormat.BC3UNorm; if (input.Meta.Format == format) @@ -398,6 +404,15 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur input = AddMipMaps(input, mipMaps); cancel.ThrowIfCancellationRequested(); + // See https://github.com/microsoft/DirectXTex/wiki/Compress#parameters for the format condition. + if (device is not null && format is DXGIFormat.BC6HUF16 or DXGIFormat.BC6HSF16 or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB) + { + var dxgiDevice = device.QueryInterface(); + + using var deviceClone = new Device(dxgiDevice.Adapter, device.CreationFlags, device.FeatureLevel); + return input.Compress(deviceClone.NativePointer, format, CompressFlags.Parallel); + } + return input.Compress(format, CompressFlags.BC7Quick | CompressFlags.Parallel); } diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index b4266aeb..870865da 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -80,6 +80,10 @@ $(DalamudLibPath)SharpDX.Direct3D11.dll False + + $(DalamudLibPath)SharpDX.DXGI.dll + False + lib\OtterTex.dll diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs index 01a40980..5025bafd 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs @@ -138,7 +138,7 @@ public partial class MtrlTab foreach (var constant in Mtrl.ShaderPackage.Constants) { var values = Mtrl.GetConstantValue(constant); - if (values != null) + if (values != []) SetMaterialParameter(constant.Id, 0, values); } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index c08e8a8e..d0764808 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -134,7 +134,7 @@ public partial class ModEditWindow tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) { _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - InvokeChange(Mod, _left.Path); + AddChangeTask(_left.Path); AddReloadTask(_left.Path, false); } @@ -159,7 +159,7 @@ public partial class ModEditWindow !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) { _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); - InvokeChange(Mod, _left.Path); + AddChangeTask(_left.Path); AddReloadTask(_left.Path, false); } @@ -169,7 +169,7 @@ public partial class ModEditWindow !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) { _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); - InvokeChange(Mod, _left.Path); + AddChangeTask(_left.Path); AddReloadTask(_left.Path, false); } @@ -180,7 +180,7 @@ public partial class ModEditWindow || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) { _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); - InvokeChange(Mod, _left.Path); + AddChangeTask(_left.Path); AddReloadTask(_left.Path, false); } } @@ -235,7 +235,7 @@ public partial class ModEditWindow if (a) { _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - InvokeChange(Mod, b); + AddChangeTask(b); if (b == _left.Path) AddReloadTask(_left.Path, false); else if (b == _right.Path) @@ -245,6 +245,17 @@ public partial class ModEditWindow _forceTextureStartPath = false; } + private void AddChangeTask(string path) + { + _center.SaveTask.ContinueWith(t => + { + if (!t.IsCompletedSuccessfully) + return; + + _framework.RunOnFrameworkThread(() => InvokeChange(Mod, path)); + }, TaskScheduler.Default); + } + private void AddReloadTask(string path, bool right) { _center.SaveTask.ContinueWith(t => diff --git a/Penumbra/lib/OtterTex.dll b/Penumbra/lib/OtterTex.dll index 29912e6215ff28a7959b73aa1e00cc9ebb919d76..c137aee184d789102648a05ecc24c0437df54ba1 100644 GIT binary patch delta 16738 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 delta 16281 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 Date: Wed, 12 Mar 2025 15:18:20 +0100 Subject: [PATCH 2/4] Simplify passing of the device (suggested by @rootdarkarchon) --- Penumbra/Import/Textures/TextureManager.cs | 32 ++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 6adf5861..8fab097e 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -50,11 +50,11 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, string output) - => Enqueue(new SaveAsAction(this, type, uiBuilder.Device, mipMaps, asTex, input, output)); + => Enqueue(new SaveAsAction(this, type, mipMaps, asTex, input, output)); public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) - => Enqueue(new SaveAsAction(this, type, uiBuilder.Device, mipMaps, asTex, image, path, rgba, width, height)); + => Enqueue(new SaveAsAction(this, type, mipMaps, asTex, image, path, rgba, width, height)); private Task Enqueue(IAction action) { @@ -159,30 +159,27 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur private readonly string _outputPath; private readonly ImageInputData _input; private readonly CombinedTexture.TextureSaveType _type; - private readonly Device? _device; private readonly bool _mipMaps; private readonly bool _asTex; - public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, Device? device, bool mipMaps, bool asTex, - string input, string output) + public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, + string output) { _textures = textures; _input = new ImageInputData(input); _outputPath = output; _type = type; - _device = device; _mipMaps = mipMaps; _asTex = asTex; } - public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, Device? device, bool mipMaps, bool asTex, - BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) + public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, + string path, byte[]? rgba = null, int width = 0, int height = 0) { _textures = textures; _input = new ImageInputData(image, rgba, width, height); _outputPath = path; _type = type; - _device = device; _mipMaps = mipMaps; _asTex = asTex; } @@ -207,8 +204,8 @@ 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.BC3 => ConvertToCompressedDds(image, _mipMaps, false, _device, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC7 => ConvertToCompressedDds(image, _mipMaps, true, _device, cancel, rgba, width, height), + CombinedTexture.TextureSaveType.BC3 => _textures.ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), + CombinedTexture.TextureSaveType.BC7 => _textures.ConvertToCompressedDds(image, _mipMaps, true, cancel, rgba, width, height), _ => throw new Exception("Wrong save type."), }; @@ -326,8 +323,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } /// Convert an existing image to a block compressed .dds. Does not create a deep copy of an existing dds of the correct format and just returns the existing one. - public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, Device? device, CancellationToken cancel, - byte[]? rgba = null, int width = 0, int height = 0) + public BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, CancellationToken cancel, byte[]? rgba = null, + int width = 0, int height = 0) { switch (input.Type.ReduceToBehaviour()) { @@ -337,12 +334,12 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur cancel.ThrowIfCancellationRequested(); var dds = ConvertToDds(rgba, width, height).AsDds!; cancel.ThrowIfCancellationRequested(); - return CreateCompressed(dds, mipMaps, bc7, device, cancel); + return CreateCompressed(dds, mipMaps, bc7, cancel); } case TextureType.Dds: { var scratch = input.AsDds!; - return CreateCompressed(scratch, mipMaps, bc7, device, cancel); + return CreateCompressed(scratch, mipMaps, bc7, cancel); } default: return new BaseImage(); } @@ -390,7 +387,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 static ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, bool bc7, Device? device, CancellationToken cancel) + public ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, bool bc7, CancellationToken cancel) { var format = bc7 ? DXGIFormat.BC7UNorm : DXGIFormat.BC3UNorm; if (input.Meta.Format == format) @@ -405,8 +402,9 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur input = AddMipMaps(input, mipMaps); cancel.ThrowIfCancellationRequested(); // See https://github.com/microsoft/DirectXTex/wiki/Compress#parameters for the format condition. - if (device is not null && format is DXGIFormat.BC6HUF16 or DXGIFormat.BC6HSF16 or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB) + if (format is DXGIFormat.BC6HUF16 or DXGIFormat.BC6HSF16 or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB) { + var device = uiBuilder.Device; var dxgiDevice = device.QueryInterface(); using var deviceClone = new Device(dxgiDevice.Adapter, device.CreationFlags, device.FeatureLevel); From 442ae960cf429ddf9703252615916acf3fa0d679 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Wed, 12 Mar 2025 20:03:53 +0100 Subject: [PATCH 3/4] Add encoding support for BC1, BC4 and BC5 --- Penumbra/Import/Textures/CombinedTexture.cs | 3 +++ Penumbra/Import/Textures/TextureManager.cs | 16 +++++++++------- .../UI/AdvancedWindow/ModEditWindow.Textures.cs | 12 +++++++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Penumbra/Import/Textures/CombinedTexture.cs b/Penumbra/Import/Textures/CombinedTexture.cs index c1a22088..f5f921be 100644 --- a/Penumbra/Import/Textures/CombinedTexture.cs +++ b/Penumbra/Import/Textures/CombinedTexture.cs @@ -6,7 +6,10 @@ public partial class CombinedTexture : IDisposable { AsIs, Bitmap, + BC1, BC3, + BC4, + BC5, BC7, } diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 8fab097e..0c85f5be 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -204,8 +204,11 @@ 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.BC3 => _textures.ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC7 => _textures.ConvertToCompressedDds(image, _mipMaps, true, 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."), }; @@ -323,7 +326,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } /// Convert an existing image to a block compressed .dds. Does not create a deep copy of an existing dds of the correct format and just returns the existing one. - public BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, CancellationToken cancel, byte[]? rgba = null, + public BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, DXGIFormat format, CancellationToken cancel, byte[]? rgba = null, int width = 0, int height = 0) { switch (input.Type.ReduceToBehaviour()) @@ -334,12 +337,12 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur cancel.ThrowIfCancellationRequested(); var dds = ConvertToDds(rgba, width, height).AsDds!; cancel.ThrowIfCancellationRequested(); - return CreateCompressed(dds, mipMaps, bc7, cancel); + return CreateCompressed(dds, mipMaps, format, cancel); } case TextureType.Dds: { var scratch = input.AsDds!; - return CreateCompressed(scratch, mipMaps, bc7, cancel); + return CreateCompressed(scratch, mipMaps, format, cancel); } default: return new BaseImage(); } @@ -387,9 +390,8 @@ 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, bool bc7, CancellationToken cancel) + public ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, DXGIFormat format, CancellationToken cancel) { - var format = bc7 ? DXGIFormat.BC7UNorm : DXGIFormat.BC3UNorm; if (input.Meta.Format == format) return input; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index d0764808..274c216b 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -26,9 +26,15 @@ public partial class ModEditWindow ("As Is", "Save the current texture with its own format without additional conversion or compression, if possible."), ("RGBA (Uncompressed)", "Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality."), - ("BC3 (Simple Compression)", - "Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality."), - ("BC7 (Complex Compression)", + ("BC1 (Simple Compression for Opaque RGB)", + "Save the current texture compressed via BC1/DXT1 compression. This offers a 8:1 compression ratio and is quick with acceptable quality, but only supports RGB, without Alpha."), + ("BC3 (Simple Compression for RGBA)", + "Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality, and fully supports RGBA."), + ("BC4 (Simple Compression for Opaque Grayscale)", + "Save the current texture compressed via BC4 compression. This offers a 8:1 compression ratio and has almost indistinguishable quality, but only supports Grayscale, without Alpha."), + ("BC5 (Simple Compression for Opaque RG)", + "Save the current texture compressed via BC5 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but only supports RG, without B or Alpha."), + ("BC7 (Complex Compression for RGBA)", "Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while."), }; From 4093228e6150c87cc3968ff0371f6a8f64cfcf36 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Wed, 12 Mar 2025 23:04:57 +0100 Subject: [PATCH 4/4] Improve wording of block compressions (suggested by @Theo-Asterio) --- Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 274c216b..4664372e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -25,17 +25,17 @@ public partial class ModEditWindow { ("As Is", "Save the current texture with its own format without additional conversion or compression, if possible."), ("RGBA (Uncompressed)", - "Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality."), + "Save the current texture as an uncompressed BGRA bitmap.\nThis requires the most space but technically offers the best quality."), ("BC1 (Simple Compression for Opaque RGB)", - "Save the current texture compressed via BC1/DXT1 compression. This offers a 8:1 compression ratio and is quick with acceptable quality, but only supports RGB, without Alpha."), + "Save the current texture compressed via BC1/DXT1 compression.\nThis offers a 8:1 compression ratio and is quick with acceptable quality, but only supports RGB, without Alpha.\n\nCan be used for diffuse maps and equipment textures to save extra space."), ("BC3 (Simple Compression for RGBA)", - "Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality, and fully supports RGBA."), + "Save the current texture compressed via BC3/DXT5 compression.\nThis offers a 4:1 compression ratio and is quick with acceptable quality, and fully supports RGBA.\n\nGeneric format that can be used for most textures."), ("BC4 (Simple Compression for Opaque Grayscale)", - "Save the current texture compressed via BC4 compression. This offers a 8:1 compression ratio and has almost indistinguishable quality, but only supports Grayscale, without Alpha."), + "Save the current texture compressed via BC4 compression.\nThis offers a 8:1 compression ratio and has almost indistinguishable quality, but only supports Grayscale, without Alpha.\n\nCan be used for face paints and legacy marks."), ("BC5 (Simple Compression for Opaque RG)", - "Save the current texture compressed via BC5 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but only supports RG, without B or Alpha."), + "Save the current texture compressed via BC5 compression.\nThis offers a 4:1 compression ratio and has almost indistinguishable quality, but only supports RG, without B or Alpha.\n\nRecommended for index maps, unrecommended for normal maps."), ("BC7 (Complex Compression for RGBA)", - "Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while."), + "Save the current texture compressed via BC7 compression.\nThis offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while.\n\nGeneric format that can be used for most textures."), }; private void DrawInputChild(string label, Texture tex, Vector2 size, Vector2 imageSize)