Skip to content

Commit

Permalink
Use OtterTex ScratchImage
Browse files Browse the repository at this point in the history
- supports bc7 and reduces need for manual conversion implementation
  • Loading branch information
PassiveModding committed Mar 31, 2024
1 parent bf9017a commit c9e3b6b
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 78 deletions.
Binary file added Meddle/Meddle.Plugin/Lib/DirectXTexC.dll
Binary file not shown.
Binary file added Meddle/Meddle.Plugin/Lib/OtterTex.dll
Binary file not shown.
10 changes: 7 additions & 3 deletions Meddle/Meddle.Plugin/Meddle.Plugin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
</PropertyGroup>

<ItemGroup>
<Compile Remove="UI\Shared\**" />
<EmbeddedResource Remove="UI\Shared\**" />
<None Remove="UI\Shared\**" />
<None Update="Lib\DirectXTexC.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>DirectXTexC.dll</TargetPath>
</None>
</ItemGroup>

<ItemGroup>
Expand All @@ -49,6 +50,9 @@
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="OtterTex">
<HintPath>Lib\OtterTex.dll</HintPath>
</Reference>
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 5 additions & 2 deletions Meddle/Meddle.Plugin/Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ public class Service
public Service(DalamudPluginInterface pluginInterface)
{
pluginInterface.Inject(this);
Log = PrivLog;
}

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);
Expand All @@ -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!;
Expand Down
5 changes: 4 additions & 1 deletion Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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}");
}
}
Expand Down
83 changes: 51 additions & 32 deletions Meddle/Meddle.Plugin/Utility/DXHelper.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<ID3D11Device3>().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<ID3D11Device3>().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;
}
Expand Down
133 changes: 93 additions & 40 deletions Meddle/Meddle.Plugin/Utility/TextureHelper.cs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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<byte>(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
{
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -354,4 +406,5 @@ private static void FromBC(TextureResource resource, Span<uint> output)
};
Squish.DecompressImage(resource.Data, resource.Width, resource.Height, flags).CopyTo(MemoryMarshal.Cast<uint, byte>(output));
}
*/
}

0 comments on commit c9e3b6b

Please sign in to comment.