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 7118f8af..0c85f5be 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;
@@ -201,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 => ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height),
- CombinedTexture.TextureSaveType.BC7 => 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."),
};
@@ -320,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 static 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())
@@ -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, 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();
}
@@ -384,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 static 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;
@@ -398,6 +403,16 @@ 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 (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);
+ 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..4664372e 100644
--- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs
+++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs
@@ -25,11 +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."),
- ("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)",
- "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 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.\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.\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.\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.\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.\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)
@@ -134,7 +140,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 +165,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 +175,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 +186,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 +241,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 +251,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 29912e62..c137aee1 100644
Binary files a/Penumbra/lib/OtterTex.dll and b/Penumbra/lib/OtterTex.dll differ