using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using Penumbra.GameData.Files; using Penumbra.String.Functions; using SharpGen.Runtime; using Vortice.Direct3D11; using Vortice.DXGI; using MapFlags = Vortice.Direct3D11.MapFlags; namespace Glamourer.Interop.Material; public static unsafe class DirectXTextureHelper { /// Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. /// A pointer to the internal texture struct containing the GPU handle. /// The returned color table. /// Whether the table could be fetched. public static bool TryGetColorTable(Texture* texture, out MtrlFile.ColorTable table) { if (texture == null) { table = default; return false; } try { // Create direct x resource and ensure that it is kept alive. using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D); tex.AddRef(); table = GetResourceData(tex, CreateStagedClone, GetTextureData); return true; } catch { return false; } } /// Create a staging clone of the existing texture handle for stability reasons. private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource) { var desc = resource.Description1 with { Usage = ResourceUsage.Staging, BindFlags = 0, CPUAccessFlags = CpuAccessFlags.Read, MiscFlags = 0, }; return resource.Device.As().CreateTexture2D1(desc); } /// Turn a mapped texture into a color table. private static MtrlFile.ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) { var desc = resource.Description1; if (desc.Format is not Format.R16G16B16A16_Float || desc.Width != MaterialService.TextureWidth || desc.Height != MaterialService.TextureHeight || map.DepthPitch != map.RowPitch * desc.Height) throw new InvalidDataException("The texture was not a valid color table texture."); return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch); } /// Transform the GPU data into the color table. /// The pointer to the raw texture data. /// The size of the raw texture data. /// The height of the texture. (Needs to be 16). /// The stride in the texture data. /// private static MtrlFile.ColorTable ReadTexture(nint data, int length, int height, int pitch) { // Check that the data has sufficient dimension and size. var expectedSize = sizeof(Half) * MaterialService.TextureWidth * height * 4; if (length < expectedSize || sizeof(MtrlFile.ColorTable) != expectedSize || height != MaterialService.TextureHeight) return default; var ret = new MtrlFile.ColorTable(); var target = (byte*)&ret; // If the stride is the same as in the table, just copy. if (pitch == MaterialService.TextureWidth) MemoryUtility.MemCpyUnchecked(target, (void*)data, length); // Otherwise, adapt the stride. else for (var y = 0; y < height; ++y) { MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch, MaterialService.TextureWidth * sizeof(Half) * 4); } return ret; } /// Get resources of a texture. private static TRet GetResourceData(T res, Func cloneResource, Func getData) where T : ID3D11Resource { using var stagingRes = cloneResource(res); res.Device.ImmediateContext.CopyResource(stagingRes, res); stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError(); try { return getData(stagingRes, mapInfo); } finally { stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0); } } }