diff --git a/Meddle/Meddle.Plugin/Lib/DirectXTexC.dll b/Meddle/Meddle.Plugin/Lib/DirectXTexC.dll
new file mode 100644
index 0000000..2cab1dc
Binary files /dev/null and b/Meddle/Meddle.Plugin/Lib/DirectXTexC.dll differ
diff --git a/Meddle/Meddle.Plugin/Lib/OtterTex.dll b/Meddle/Meddle.Plugin/Lib/OtterTex.dll
new file mode 100644
index 0000000..29912e6
Binary files /dev/null and b/Meddle/Meddle.Plugin/Lib/OtterTex.dll differ
diff --git a/Meddle/Meddle.Plugin/Meddle.Plugin.csproj b/Meddle/Meddle.Plugin/Meddle.Plugin.csproj
index 861083a..eb59d59 100644
--- a/Meddle/Meddle.Plugin/Meddle.Plugin.csproj
+++ b/Meddle/Meddle.Plugin/Meddle.Plugin.csproj
@@ -22,9 +22,10 @@
-
-
-
+
+ PreserveNewest
+ DirectXTexC.dll
+
@@ -49,6 +50,9 @@
$(DalamudLibPath)Newtonsoft.Json.dll
False
+
+ Lib\OtterTex.dll
+
diff --git a/Meddle/Meddle.Plugin/Service.cs b/Meddle/Meddle.Plugin/Service.cs
index 6d6edc1..6658932 100644
--- a/Meddle/Meddle.Plugin/Service.cs
+++ b/Meddle/Meddle.Plugin/Service.cs
@@ -11,6 +11,7 @@ public class Service
public Service(DalamudPluginInterface pluginInterface)
{
pluginInterface.Inject(this);
+ Log = PrivLog;
}
public void AddServices(IServiceCollection services)
@@ -18,7 +19,7 @@ public void AddServices(IServiceCollection services)
services.AddSingleton(PluginInterface);
services.AddSingleton(Framework);
services.AddSingleton(CommandManager);
- services.AddSingleton(Log);
+ services.AddSingleton(PrivLog);
services.AddSingleton(ObjectTable);
services.AddSingleton(ClientState);
services.AddSingleton(SigScanner);
@@ -32,7 +33,9 @@ public void AddServices(IServiceCollection services)
[PluginService] private ICommandManager CommandManager { get; set; } = null!;
- [PluginService] private IPluginLog Log { get; set; } = null!;
+ public static IPluginLog Log { get; private set; } = null!;
+
+ [PluginService] private IPluginLog PrivLog { get; set; } = null!;
[PluginService] private IObjectTable ObjectTable { get; set; } = null!;
[PluginService] private IClientState ClientState { get; set; } = null!;
[PluginService] private ISigScanner SigScanner { get; set; } = null!;
diff --git a/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs b/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs
index 7ff3c3f..2ed57af 100644
--- a/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs
+++ b/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs
@@ -364,9 +364,10 @@ public static void DrawMaterial(Material material)
ImGui.BulletText($"Shader: {material.ShaderPackage.Name}");
// Display Material Textures in the same table
- using (var textable = ImRaii.Table("TextureTable", 2, ImGuiTableFlags.Borders))
+ using (var textable = ImRaii.Table("TextureTable", 3, ImGuiTableFlags.Borders))
{
ImGui.TableSetupColumn("Texture Usage", ImGuiTableColumnFlags.WidthFixed, 120);
+ ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, 120);
ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableHeadersRow();
@@ -376,6 +377,8 @@ public static void DrawMaterial(Material material)
ImGui.TableNextColumn();
ImGui.Text($"{texture.Usage}");
ImGui.TableNextColumn();
+ ImGui.Text($"{texture.Resource.Format}");
+ ImGui.TableNextColumn();
ImGui.Text($"{texture.HandlePath}");
}
}
diff --git a/Meddle/Meddle.Plugin/Utility/DXHelper.cs b/Meddle/Meddle.Plugin/Utility/DXHelper.cs
index dada94f..2bf5e64 100644
--- a/Meddle/Meddle.Plugin/Utility/DXHelper.cs
+++ b/Meddle/Meddle.Plugin/Utility/DXHelper.cs
@@ -1,9 +1,10 @@
using System.Runtime.InteropServices;
-using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
+using OtterTex;
using SharpGen.Runtime;
using Vortice.Direct3D11;
using Format = Vortice.DXGI.Format;
+using MapFlags = Vortice.Direct3D11.MapFlags;
namespace Meddle.Plugin.Utility;
@@ -18,37 +19,55 @@ public static TextureHelper.TextureResource ExportTextureResource(Texture* kerne
tex.AddRef();
var ret = GetResourceData(tex,
- r =>
- {
- var desc = r.Description1 with
- {
- Usage = ResourceUsage.Staging,
- BindFlags = 0,
- CPUAccessFlags = CpuAccessFlags.Read,
- MiscFlags = 0,
- };
-
- return r.Device.As().CreateTexture2D1(desc);
- },
- (r, map) =>
- {
- var desc = r.Description1;
-
- if (desc.Format is Format.BC1_UNorm or Format.BC2_UNorm or Format.BC3_UNorm or Format.BC4_UNorm or Format.BC5_UNorm)
- {
- if (map.RowPitch * Math.Max(1, (desc.Height + 3) / 4) != map.DepthPitch)
- throw new InvalidDataException($"Invalid/unknown texture size for {desc.Format}: RowPitch = {map.RowPitch}; Height = {desc.Height}; Block Height = {Math.Max(1, (desc.Height + 3) / 4)}; DepthPitch = {map.DepthPitch}");
- }
- else
- {
- if (map.RowPitch * desc.Height != map.DepthPitch)
- throw new InvalidDataException($"Invalid/unknown texture size for {desc.Format}: RowPitch = {map.RowPitch}; Height = {desc.Height}; DepthPitch = {map.DepthPitch}");
- }
-
- var buf = new byte[map.DepthPitch];
- Marshal.Copy(map.DataPointer, buf, 0, buf.Length);
- return new TextureHelper.TextureResource(desc.Format, desc.Width, desc.Height, map.RowPitch, buf);
- });
+ r =>
+ {
+ var desc = r.Description1 with
+ {
+ Usage = ResourceUsage.Staging,
+ BindFlags = 0,
+ CPUAccessFlags = CpuAccessFlags.Read,
+ MiscFlags = 0,
+ };
+
+ return r.Device.As().CreateTexture2D1(desc);
+ },
+ (r, map) =>
+ {
+ var desc = r.Description1;
+ if (desc.Format is
+ Format.BC1_UNorm or
+ Format.BC2_UNorm or
+ Format.BC3_UNorm or
+ Format.BC4_UNorm or
+ Format.BC5_UNorm or
+ Format.BC7_UNorm)
+ {
+ var blockHeight = Math.Max(1, (desc.Height + 3) / 4);
+ if (map.RowPitch * blockHeight != map.DepthPitch)
+ throw new InvalidDataException($"Invalid/unknown texture size for {desc.Format}: RowPitch = {map.RowPitch}; Height = {desc.Height}; Block Height = {blockHeight}; DepthPitch = {map.DepthPitch}");
+ }
+ else
+ {
+ if (map.RowPitch * desc.Height != map.DepthPitch)
+ throw new InvalidDataException($"Invalid/unknown texture size for {desc.Format}: RowPitch = {map.RowPitch}; Height = {desc.Height}; DepthPitch = {map.DepthPitch}");
+ }
+
+ var buf = new byte[map.DepthPitch];
+ Marshal.Copy(map.DataPointer, buf, 0, buf.Length);
+ var resource = new TextureHelper.TextureResource(
+ // Vortice and OtterTex use different enums but the values *should* be the same so just cast
+ (DXGIFormat)desc.Format,
+ desc.Width,
+ desc.Height,
+ map.RowPitch,
+ desc.MipLevels,
+ desc.ArraySize,
+ (TexDimension)r.Dimension,
+ (D3DResourceMiscFlags)desc.MiscFlags,
+ buf);
+
+ return resource;
+ });
return ret;
}
diff --git a/Meddle/Meddle.Plugin/Utility/TextureHelper.cs b/Meddle/Meddle.Plugin/Utility/TextureHelper.cs
index 5b103ff..c4be003 100644
--- a/Meddle/Meddle.Plugin/Utility/TextureHelper.cs
+++ b/Meddle/Meddle.Plugin/Utility/TextureHelper.cs
@@ -1,32 +1,23 @@
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Dalamud.Logging;
-using Lumina.Data.Parsing.Tex;
using Meddle.Plugin.Models;
using SkiaSharp;
-using Vortice.DXGI;
-using LuminaFormat = Lumina.Data.Files.TexFile.TextureFormat;
+using OtterTex;
namespace Meddle.Plugin.Utility;
public static class TextureHelper
{
- public readonly struct TextureResource
+ public readonly struct TextureResource(DXGIFormat format, int width, int height, int stride, int mipLevels, int arraySize, TexDimension dimension, D3DResourceMiscFlags miscFlags, byte[] data)
{
- public TextureResource(Format format, int width, int height, int stride, byte[] data)
- {
- this.Format = format;
- this.Width = width;
- this.Height = height;
- this.Stride = stride;
- this.Data = data;
- }
- public Format Format { get; init; }
- public int Width { get; init; }
- public int Height { get; init; }
- public int Stride { get; init; }
- public byte[] Data { get; init; }
-
+ public DXGIFormat Format { get; init; } = format;
+ public int Width { get; init; } = width;
+ public int Height { get; init; } = height;
+ public int Stride { get; init; } = stride;
+ public int MipLevels { get; init; } = mipLevels;
+ public int ArraySize { get; init; } = arraySize;
+ public TexDimension Dimension { get; init; } = dimension;
+ public D3DResourceMiscFlags MiscFlags { get; init; } = miscFlags;
+ public byte[] Data { get; init; } = data;
+
public SKTexture ToTexture((int width, int height)? resize = null)
{
var bitmap = ToBitmap(this);
@@ -38,8 +29,85 @@ public SKTexture ToTexture((int width, int height)? resize = null)
return new SKTexture(bitmap);
}
}
+
+ private static readonly object LockObj = new();
+ public static unsafe SKBitmap ToBitmap(TextureResource resource)
+ {
+ lock (LockObj)
+ {
+ var meta = FromResource(resource);
+ var image = ScratchImage.Initialize(meta);
+
+ // copy data - ensure destination not too short
+ fixed (byte* data = image.Pixels)
+ {
+ var span = new Span(data, image.Pixels.Length);
+ if (resource.Data.Length > span.Length)
+ {
+ // As far as I can tell this only happens when placeholder textures are used anyways
+ Service.Log.Warning($"Data too large for scratch image. " +
+ $"{resource.Data.Length} > {span.Length} " +
+ $"{resource.Width}x{resource.Height} {resource.Format}");
+ }
+ else
+ {
+ resource.Data.AsSpan().CopyTo(span);
+ }
+ }
+
+ image.GetRGBA(out var rgba);
+
+ var bitmap = new SKBitmap(rgba.Meta.Width, rgba.Meta.Height, SKColorType.Rgba8888, SKAlphaType.Unpremul);
+ bitmap.Erase(new SKColor(0));
+ fixed (byte* data = rgba.Pixels)
+ {
+ bitmap.InstallPixels(bitmap.Info, (nint)data, rgba.Meta.Width * 4);
+ }
+
+ // I have trust issues
+ var copy = new SKBitmap(bitmap.Info);
+ bitmap.CopyTo(copy, bitmap.Info.ColorType);
+
+ return copy;
+ }
+ }
+
+ private static TexMeta FromResource(TextureResource resource)
+ {
+ var meta = new TexMeta
+ {
+ Height = resource.Height,
+ Width = resource.Width,
+ Depth = 1, // 3D textures would have other values, but we're only handling kernelTexture->D3D11Texture2D
+ MipLevels = resource.MipLevels,
+ ArraySize = resource.ArraySize,
+ Format = resource.Format,
+ Dimension = resource.Dimension,
+ MiscFlags = resource.MiscFlags.HasFlag(D3DResourceMiscFlags.TextureCube) ? D3DResourceMiscFlags.TextureCube : 0,
+ MiscFlags2 = 0,
+ };
+
+ return meta;
+ }
- public static TextureResource FromTexFile(Lumina.Data.Files.TexFile file)
+ public static byte[] AdjustStride(int oldStride, int newStride, int height, byte[] data)
+ {
+ if (data.Length != oldStride * height)
+ throw new ArgumentException("Data length must match stride * height.", nameof(data));
+
+ if (oldStride == newStride)
+ return data;
+ if (oldStride < newStride)
+ throw new ArgumentException("New stride must be smaller than old stride.", nameof(newStride));
+
+ var newData = new byte[newStride * height];
+ for (var y = 0; y < height; ++y)
+ Buffer.BlockCopy(data, y * oldStride, newData, y * newStride, newStride);
+ return newData;
+ }
+
+ /*
+ public static TextureResource FromTexFile(TexFile file)
{
var format = file.Header.Format switch
{
@@ -85,23 +153,7 @@ public static TextureResource FromTexFile(Lumina.Data.Files.TexFile file)
return new TextureResource(format, file.Header.Width, file.Header.Height, stride, file.Data);
}
-
- public static byte[] AdjustStride(int oldStride, int newStride, int height, byte[] data)
- {
- if (data.Length != oldStride * height)
- throw new ArgumentException("Data length must match stride * height.", nameof(data));
-
- if (oldStride == newStride)
- return data;
- if (oldStride < newStride)
- throw new ArgumentException("New stride must be smaller than old stride.", nameof(newStride));
-
- var newData = new byte[newStride * height];
- for (var y = 0; y < height; ++y)
- Buffer.BlockCopy(data, y * oldStride, newData, y * newStride, newStride);
- return newData;
- }
-
+
public static byte[] ToBGRA8(TextureResource resource)
{
var ret = new byte[resource.Width * resource.Height * 4];
@@ -173,7 +225,7 @@ public static unsafe SKBitmap ToBitmap(TextureResource resource)
bitmap.InstallPixels(format.WithSize(resource.Width, resource.Height), (nint)data, resource.Width * 4);
}
s.Stop();
- //PluginLog.Log($"ToBitmap ({(direct ? "SkiaSharp" : "Software")}) took {s.Elapsed.TotalMilliseconds}ms for {resource.Width}x{resource.Height} {resource.Format}");
+ Service.Log.Debug($"ToBitmap ({(direct ? "SkiaSharp" : "Software")}) took {s.Elapsed.TotalMilliseconds}ms for {resource.Width}x{resource.Height} {resource.Format}");
// make copy so we can dispose the original
var copy = new SKBitmap(bitmap.Info);
@@ -354,4 +406,5 @@ private static void FromBC(TextureResource resource, Span output)
};
Squish.DecompressImage(resource.Data, resource.Width, resource.Height, flags).CopyTo(MemoryMarshal.Cast(output));
}
+ */
}