Improve texture saving

This commit is contained in:
Exter-N 2025-03-12 01:20:36 +01:00
parent 93b0996794
commit e5620e17e0
5 changed files with 50 additions and 20 deletions

View file

@ -1,3 +1,4 @@
using Dalamud.Interface;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@ -6,15 +7,17 @@ using OtterGui.Log;
using OtterGui.Services; using OtterGui.Services;
using OtterGui.Tasks; using OtterGui.Tasks;
using OtterTex; using OtterTex;
using SharpDX.Direct3D11;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using DxgiDevice = SharpDX.DXGI.Device;
using Image = SixLabors.ImageSharp.Image; using Image = SixLabors.ImageSharp.Image;
namespace Penumbra.Import.Textures; 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 : SingleTaskQueue, IDisposable, IService
{ {
private readonly Logger _logger = logger; 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) 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, public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, string path, byte[]? rgba = null,
int width = 0, int height = 0) 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) private Task Enqueue(IAction action)
{ {
@ -156,27 +159,30 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur
private readonly string _outputPath; private readonly string _outputPath;
private readonly ImageInputData _input; private readonly ImageInputData _input;
private readonly CombinedTexture.TextureSaveType _type; private readonly CombinedTexture.TextureSaveType _type;
private readonly Device? _device;
private readonly bool _mipMaps; private readonly bool _mipMaps;
private readonly bool _asTex; private readonly bool _asTex;
public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, Device? device, bool mipMaps, bool asTex,
string output) string input, string output)
{ {
_textures = textures; _textures = textures;
_input = new ImageInputData(input); _input = new ImageInputData(input);
_outputPath = output; _outputPath = output;
_type = type; _type = type;
_device = device;
_mipMaps = mipMaps; _mipMaps = mipMaps;
_asTex = asTex; _asTex = asTex;
} }
public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, BaseImage image, public SaveAsAction(TextureManager textures, CombinedTexture.TextureSaveType type, Device? device, bool mipMaps, bool asTex,
string path, byte[]? rgba = null, int width = 0, int height = 0) BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0)
{ {
_textures = textures; _textures = textures;
_input = new ImageInputData(image, rgba, width, height); _input = new ImageInputData(image, rgba, width, height);
_outputPath = path; _outputPath = path;
_type = type; _type = type;
_device = device;
_mipMaps = mipMaps; _mipMaps = mipMaps;
_asTex = asTex; _asTex = asTex;
} }
@ -201,8 +207,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur
rgba, width, height), rgba, width, height),
CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps), CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps),
CombinedTexture.TextureSaveType.Bitmap => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, width, height), CombinedTexture.TextureSaveType.Bitmap => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, width, height),
CombinedTexture.TextureSaveType.BC3 => ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), CombinedTexture.TextureSaveType.BC3 => ConvertToCompressedDds(image, _mipMaps, false, _device, cancel, rgba, width, height),
CombinedTexture.TextureSaveType.BC7 => ConvertToCompressedDds(image, _mipMaps, true, cancel, rgba, width, height), CombinedTexture.TextureSaveType.BC7 => ConvertToCompressedDds(image, _mipMaps, true, _device, cancel, rgba, width, height),
_ => throw new Exception("Wrong save type."), _ => throw new Exception("Wrong save type."),
}; };
@ -320,8 +326,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur
} }
/// <summary> 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. </summary> /// <summary> 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. </summary>
public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, CancellationToken cancel, byte[]? rgba = null, public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, Device? device, CancellationToken cancel,
int width = 0, int height = 0) byte[]? rgba = null, int width = 0, int height = 0)
{ {
switch (input.Type.ReduceToBehaviour()) switch (input.Type.ReduceToBehaviour())
{ {
@ -331,12 +337,12 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur
cancel.ThrowIfCancellationRequested(); cancel.ThrowIfCancellationRequested();
var dds = ConvertToDds(rgba, width, height).AsDds!; var dds = ConvertToDds(rgba, width, height).AsDds!;
cancel.ThrowIfCancellationRequested(); cancel.ThrowIfCancellationRequested();
return CreateCompressed(dds, mipMaps, bc7, cancel); return CreateCompressed(dds, mipMaps, bc7, device, cancel);
} }
case TextureType.Dds: case TextureType.Dds:
{ {
var scratch = input.AsDds!; var scratch = input.AsDds!;
return CreateCompressed(scratch, mipMaps, bc7, cancel); return CreateCompressed(scratch, mipMaps, bc7, device, cancel);
} }
default: return new BaseImage(); default: return new BaseImage();
} }
@ -384,7 +390,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur
} }
/// <summary> Create a BC3 or BC7 block-compressed .dds from the input (optionally with mipmaps). Returns input (+ mipmaps) if it is already the correct format. </summary> /// <summary> Create a BC3 or BC7 block-compressed .dds from the input (optionally with mipmaps). Returns input (+ mipmaps) if it is already the correct format. </summary>
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; var format = bc7 ? DXGIFormat.BC7UNorm : DXGIFormat.BC3UNorm;
if (input.Meta.Format == format) if (input.Meta.Format == format)
@ -398,6 +404,15 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur
input = AddMipMaps(input, mipMaps); input = AddMipMaps(input, mipMaps);
cancel.ThrowIfCancellationRequested(); 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<DxgiDevice>();
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); return input.Compress(format, CompressFlags.BC7Quick | CompressFlags.Parallel);
} }

View file

@ -80,6 +80,10 @@
<HintPath>$(DalamudLibPath)SharpDX.Direct3D11.dll</HintPath> <HintPath>$(DalamudLibPath)SharpDX.Direct3D11.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="SharpDX.DXGI">
<HintPath>$(DalamudLibPath)SharpDX.DXGI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="OtterTex.dll"> <Reference Include="OtterTex.dll">
<HintPath>lib\OtterTex.dll</HintPath> <HintPath>lib\OtterTex.dll</HintPath>
</Reference> </Reference>

View file

@ -138,7 +138,7 @@ public partial class MtrlTab
foreach (var constant in Mtrl.ShaderPackage.Constants) foreach (var constant in Mtrl.ShaderPackage.Constants)
{ {
var values = Mtrl.GetConstantValue<byte>(constant); var values = Mtrl.GetConstantValue<byte>(constant);
if (values != null) if (values != [])
SetMaterialParameter(constant.Id, 0, values); SetMaterialParameter(constant.Id, 0, values);
} }

View file

@ -134,7 +134,7 @@ public partial class ModEditWindow
tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs))
{ {
_center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
InvokeChange(Mod, _left.Path); AddChangeTask(_left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
@ -159,7 +159,7 @@ public partial class ModEditWindow
!canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB))
{ {
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path); AddChangeTask(_left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
@ -169,7 +169,7 @@ public partial class ModEditWindow
!canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB))
{ {
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path); AddChangeTask(_left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
@ -180,7 +180,7 @@ public partial class ModEditWindow
|| _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB))
{ {
_center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1);
InvokeChange(Mod, _left.Path); AddChangeTask(_left.Path);
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
} }
} }
@ -235,7 +235,7 @@ public partial class ModEditWindow
if (a) if (a)
{ {
_center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
InvokeChange(Mod, b); AddChangeTask(b);
if (b == _left.Path) if (b == _left.Path)
AddReloadTask(_left.Path, false); AddReloadTask(_left.Path, false);
else if (b == _right.Path) else if (b == _right.Path)
@ -245,6 +245,17 @@ public partial class ModEditWindow
_forceTextureStartPath = false; _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) private void AddReloadTask(string path, bool right)
{ {
_center.SaveTask.ContinueWith(t => _center.SaveTask.ContinueWith(t =>

Binary file not shown.