From f07780cf7babb92e7421bea0df59ad8d6dc00592 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Wed, 8 Jan 2025 20:02:14 +0100 Subject: [PATCH 1/4] Add RenderTargetHdrEnabler --- Penumbra.GameData | 2 +- Penumbra/Configuration.cs | 1 + Penumbra/Interop/Hooks/HookSettings.cs | 1 + .../PostProcessing/RenderTargetHdrEnabler.cs | 136 ++++++++++++++++++ .../PostProcessing/ShaderReplacementFixer.cs | 9 ++ Penumbra/Penumbra.json | 2 +- Penumbra/UI/Tabs/Debug/DebugTab.cs | 54 ++++++- Penumbra/UI/Tabs/SettingsTab.cs | 17 +++ 8 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs diff --git a/Penumbra.GameData b/Penumbra.GameData index 33de79bc..d5f92966 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 33de79bc62eb014298856ed5c6b6edbe819db26c +Subproject commit d5f929664c212804594fadb4e4cefe9e6a1f5d37 diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index ec5784f8..df44a51a 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -110,6 +110,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool KeepDefaultMetaChanges { get; set; } = false; public string DefaultModAuthor { get; set; } = DefaultTexToolsData.Author; public bool EditRawTileTransforms { get; set; } = false; + public bool HdrRenderTargets { get; set; } = true; public Dictionary Colors { get; set; } = Enum.GetValues().ToDictionary(c => c, c => c.Data().DefaultColor); diff --git a/Penumbra/Interop/Hooks/HookSettings.cs b/Penumbra/Interop/Hooks/HookSettings.cs index 2aeeb14b..b95e5789 100644 --- a/Penumbra/Interop/Hooks/HookSettings.cs +++ b/Penumbra/Interop/Hooks/HookSettings.cs @@ -86,6 +86,7 @@ public class HookOverrides public bool ModelRendererOnRenderMaterial; public bool ModelRendererUnkFunc; public bool PrepareColorTable; + public bool RenderTargetManagerInitialize; } public struct ResourceLoadingHooks diff --git a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs new file mode 100644 index 00000000..d620935e --- /dev/null +++ b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs @@ -0,0 +1,136 @@ +using System.Collections.Immutable; +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using OtterGui.Services; +using Penumbra.GameData; + +namespace Penumbra.Interop.Hooks.PostProcessing; + +public unsafe class RenderTargetHdrEnabler : IService, IDisposable +{ + /// This array must be sorted by CreationOrder ascending. + private static readonly ImmutableArray ForcedTextureConfigs = + [ + new(9, TextureFormat.R16G16B16A16_FLOAT, "Main Diffuse GBuffer"), + new(10, TextureFormat.R16G16B16A16_FLOAT, "Hair Diffuse GBuffer"), + ]; + + private static readonly IComparer ForcedTextureConfigComparer + = Comparer.Create((lhs, rhs) => lhs.CreationOrder.CompareTo(rhs.CreationOrder)); + + private readonly Configuration _config; + + private readonly ThreadLocal _textureIndices = new(() => new(-1, -1)); + private readonly ThreadLocal?> _textures = new(() => null); + + public TextureReportRecord[]? TextureReport { get; private set; } + + [Signature(Sigs.RenderTargetManagerInitialize, DetourName = nameof(RenderTargetManagerInitializeDetour))] + private Hook _renderTargetManagerInitialize = null!; + + [Signature(Sigs.DeviceCreateTexture2D, DetourName = nameof(CreateTexture2DDetour))] + private Hook _createTexture2D = null!; + + public RenderTargetHdrEnabler(IGameInteropProvider interop, Configuration config) + { + _config = config; + interop.InitializeFromAttributes(this); + if (config.HdrRenderTargets && !HookOverrides.Instance.PostProcessing.RenderTargetManagerInitialize) + _renderTargetManagerInitialize.Enable(); + } + + ~RenderTargetHdrEnabler() + => Dispose(false); + + public static ForcedTextureConfig? GetForcedTextureConfig(int creationOrder) + { + var i = ForcedTextureConfigs.BinarySearch(new(creationOrder, 0, string.Empty), ForcedTextureConfigComparer); + return i >= 0 ? ForcedTextureConfigs[i] : null; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool _) + { + _renderTargetManagerInitialize.Disable(); + if (_createTexture2D.IsEnabled) + _createTexture2D.Disable(); + + _createTexture2D.Dispose(); + _renderTargetManagerInitialize.Dispose(); + } + + private nint RenderTargetManagerInitializeDetour(RenderTargetManager* @this) + { + _createTexture2D.Enable(); + _textureIndices.Value = new(0, 0); + _textures.Value = _config.DebugMode ? [] : null; + try + { + return _renderTargetManagerInitialize.Original(@this); + } + finally + { + if (_textures.Value != null) + { + TextureReport = CreateTextureReport(@this, _textures.Value); + _textures.Value = null; + } + _textureIndices.Value = new(-1, -1); + _createTexture2D.Disable(); + } + } + + private Texture* CreateTexture2DDetour( + Device* @this, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk) + { + var originalTextureFormat = textureFormat; + var indices = _textureIndices.IsValueCreated ? _textureIndices.Value : new(-1, -1); + if (indices.ConfigIndex >= 0 && indices.ConfigIndex < ForcedTextureConfigs.Length && + ForcedTextureConfigs[indices.ConfigIndex].CreationOrder == indices.CreationOrder) + { + var config = ForcedTextureConfigs[indices.ConfigIndex++]; + textureFormat = (uint)config.ForcedTextureFormat; + } + + if (indices.CreationOrder >= 0) + { + ++indices.CreationOrder; + _textureIndices.Value = indices; + } + + var texture = _createTexture2D.Original(@this, size, mipLevel, textureFormat, flags, unk); + if (_textures.IsValueCreated) + _textures.Value?.Add((nint)texture, (indices.CreationOrder - 1, originalTextureFormat)); + return texture; + } + + private static TextureReportRecord[] CreateTextureReport(RenderTargetManager* renderTargetManager, Dictionary textures) + { + var rtmTextures = new Span(renderTargetManager, sizeof(RenderTargetManager) / sizeof(nint)); + var report = new List(); + for (var i = 0; i < rtmTextures.Length; ++i) + { + if (textures.TryGetValue(rtmTextures[i], out var texture)) + report.Add(new(i * sizeof(nint), texture.TextureIndex, (TextureFormat)texture.TextureFormat)); + } + return report.ToArray(); + } + + private delegate nint RenderTargetManagerInitializeFunc(RenderTargetManager* @this); + + private delegate Texture* CreateTexture2DFunc(Device* @this, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk); + + private record struct TextureIndices(int CreationOrder, int ConfigIndex); + + public readonly record struct ForcedTextureConfig(int CreationOrder, TextureFormat ForcedTextureFormat, string Comment); + + public readonly record struct TextureReportRecord(nint Offset, int CreationOrder, TextureFormat OriginalTextureFormat); +} diff --git a/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs b/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs index 40958eb4..3b41e752 100644 --- a/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs +++ b/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs @@ -7,6 +7,7 @@ using OtterGui.Classes; using OtterGui.Services; using Penumbra.Communication; using Penumbra.GameData; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.Interop.Hooks.Resources; using Penumbra.Interop.Structs; using Penumbra.Services; @@ -462,8 +463,16 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic return mtrlResource; } + private static int GetDataSetExpectedSize(uint dataFlags) + => (dataFlags & 4) != 0 + ? ColorTable.Size + ((dataFlags & 8) != 0 ? ColorDyeTable.Size : 0) + : 0; + private Texture* PrepareColorTableDetour(MaterialResourceHandle* thisPtr, byte stain0Id, byte stain1Id) { + if (thisPtr->DataSetSize < GetDataSetExpectedSize(thisPtr->DataFlags)) + Penumbra.Log.Warning($"Material at {thisPtr->FileName} has data set of size {thisPtr->DataSetSize} bytes, but should have at least {GetDataSetExpectedSize(thisPtr->DataFlags)} bytes. This may cause crashes due to access violations."); + // If we don't have any on-screen instances of modded characterlegacy.shpk, we don't need the slow path at all. if (!Enabled || GetTotalMaterialCountForColorTable() == 0) return _prepareColorTableHook.Original(thisPtr, stain0Id, stain1Id); diff --git a/Penumbra/Penumbra.json b/Penumbra/Penumbra.json index 4790da18..968bb750 100644 --- a/Penumbra/Penumbra.json +++ b/Penumbra/Penumbra.json @@ -10,7 +10,7 @@ "Tags": [ "modding" ], "DalamudApiLevel": 11, "LoadPriority": 69420, - "LoadState": 2, + "LoadRequiredState": 2, "LoadSync": true, "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 8b2bcd77..a759e11a 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -42,6 +42,7 @@ using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.GameData.Files.StainMapStructs; using Penumbra.String.Classes; using Penumbra.UI.AdvancedWindow.Materials; +using CSGraphics = FFXIVClientStructs.FFXIV.Client.Graphics; namespace Penumbra.UI.Tabs.Debug; @@ -104,6 +105,7 @@ public class DebugTab : Window, ITab, IUiService private readonly RsfService _rsfService; private readonly SchedulerResourceManagementService _schedulerService; private readonly ObjectIdentification _objectIdentification; + private readonly RenderTargetHdrEnabler _renderTargetHdrEnabler; public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects, IClientState clientState, IDataManager dataManager, @@ -114,7 +116,7 @@ public class DebugTab : Window, ITab, IUiService TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes, Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer, HookOverrideDrawer hookOverrides, RsfService rsfService, GlobalVariablesDrawer globalVariablesDrawer, - SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification) + SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification, RenderTargetHdrEnabler renderTargetHdrEnabler) : base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse) { IsOpen = true; @@ -154,6 +156,7 @@ public class DebugTab : Window, ITab, IUiService _globalVariablesDrawer = globalVariablesDrawer; _schedulerService = schedulerService; _objectIdentification = objectIdentification; + _renderTargetHdrEnabler = renderTargetHdrEnabler; _objects = objects; _clientState = clientState; _dataManager = dataManager; @@ -189,6 +192,7 @@ public class DebugTab : Window, ITab, IUiService DrawData(); DrawCrcCache(); DrawResourceProblems(); + DrawRenderTargets(); _hookOverrides.Draw(); DrawPlayerModelInfo(); _globalVariablesDrawer.Draw(); @@ -1135,6 +1139,54 @@ public class DebugTab : Window, ITab, IUiService } + /// Draw information about render targets. + private unsafe void DrawRenderTargets() + { + if (!ImGui.CollapsingHeader("Render Targets")) + return; + + var report = _renderTargetHdrEnabler.TextureReport; + if (report == null) + { + ImGui.TextUnformatted("The RenderTargetManager report has not been gathered."); + ImGui.TextUnformatted("Please restart the game with Debug Mode and Wait for Plugins on Startup enabled to fill this section."); + return; + } + + using var table = Table("##RenderTargetTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + if (!table) + return; + + ImUtf8.TableSetupColumn("Offset"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); + ImUtf8.TableSetupColumn("Creation Order"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); + ImUtf8.TableSetupColumn("Original Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); + ImUtf8.TableSetupColumn("Current Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); + ImUtf8.TableSetupColumn("Comment"u8, ImGuiTableColumnFlags.WidthStretch, 0.3f); + ImGui.TableHeadersRow(); + + foreach (var record in report) + { + ImGui.TableNextColumn(); + ImUtf8.Text($"0x{record.Offset:X}"); + ImGui.TableNextColumn(); + ImUtf8.Text($"{record.CreationOrder}"); + ImGui.TableNextColumn(); + ImUtf8.Text($"{record.OriginalTextureFormat}"); + ImGui.TableNextColumn(); + var texture = *(CSGraphics.Kernel.Texture**)((nint)CSGraphics.Render.RenderTargetManager.Instance() + record.Offset); + if (texture != null) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiUtil.HalfBlendText(0xFF), texture->TextureFormat != record.OriginalTextureFormat); + ImUtf8.Text($"{texture->TextureFormat}"); + } + ImGui.TableNextColumn(); + var forcedConfig = RenderTargetHdrEnabler.GetForcedTextureConfig(record.CreationOrder); + if (forcedConfig.HasValue) + ImGui.TextUnformatted(forcedConfig.Value.Comment); + } + } + + /// Draw information about IPC options and availability. private void DrawDebugTabIpc() { diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 46e214cf..64fa57a5 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -773,6 +773,7 @@ public class SettingsTab : ITab, IUiService DrawCrashHandler(); DrawMinimumDimensionConfig(); + DrawHdrRenderTargets(); Checkbox("Auto Deduplicate on Import", "Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.", _config.AutoDeduplicateOnImport, v => _config.AutoDeduplicateOnImport = v); @@ -902,6 +903,22 @@ public class SettingsTab : ITab, IUiService _config.Save(); } + private void DrawHdrRenderTargets() + { + var item = _config.HdrRenderTargets ? 1 : 0; + ImGui.SetNextItemWidth(ImGui.CalcTextSize("M").X * 5.0f + ImGui.GetFrameHeight()); + var edited = ImGui.Combo("##hdrRenderTarget", ref item, "SDR\0HDR\0"); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker("Diffuse Dynamic Range", + "Set the dynamic range that can be used for diffuse colors in materials without causing visual artifacts.\nChanging this setting requires a game restart. It also only works if Wait for Plugins on Startup is enabled."); + + if (!edited) + return; + + _config.HdrRenderTargets = item != 0; + _config.Save(); + } + /// Draw a checkbox for the HTTP API that creates and destroys the web server when toggled. private void DrawEnableHttpApiBox() { From e8300fc5c83acc6f86dbaa5086869f721478317b Mon Sep 17 00:00:00 2001 From: Exter-N Date: Thu, 9 Jan 2025 20:42:48 +0100 Subject: [PATCH 2/4] Improve RT-HDR texture comments --- .../Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs index d620935e..80106fc9 100644 --- a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs +++ b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs @@ -14,8 +14,8 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable /// This array must be sorted by CreationOrder ascending. private static readonly ImmutableArray ForcedTextureConfigs = [ - new(9, TextureFormat.R16G16B16A16_FLOAT, "Main Diffuse GBuffer"), - new(10, TextureFormat.R16G16B16A16_FLOAT, "Hair Diffuse GBuffer"), + new(9, TextureFormat.R16G16B16A16_FLOAT, "Opaque Diffuse GBuffer"), + new(10, TextureFormat.R16G16B16A16_FLOAT, "Semitransparent Diffuse GBuffer"), ]; private static readonly IComparer ForcedTextureConfigComparer From 0758739666917a98304d8cdb8288924a67152084 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Jan 2025 13:46:08 +0100 Subject: [PATCH 3/4] Cleanup UI code. --- Penumbra/UI/Tabs/Debug/DebugTab.cs | 57 ++----------------- Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs | 59 ++++++++++++++++++++ Penumbra/UI/Tabs/SettingsTab.cs | 34 +++++++---- 3 files changed, 86 insertions(+), 64 deletions(-) create mode 100644 Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index a759e11a..77eeb3d7 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -42,7 +42,6 @@ using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.GameData.Files.StainMapStructs; using Penumbra.String.Classes; using Penumbra.UI.AdvancedWindow.Materials; -using CSGraphics = FFXIVClientStructs.FFXIV.Client.Graphics; namespace Penumbra.UI.Tabs.Debug; @@ -105,7 +104,7 @@ public class DebugTab : Window, ITab, IUiService private readonly RsfService _rsfService; private readonly SchedulerResourceManagementService _schedulerService; private readonly ObjectIdentification _objectIdentification; - private readonly RenderTargetHdrEnabler _renderTargetHdrEnabler; + private readonly RenderTargetDrawer _renderTargetDrawer; public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects, IClientState clientState, IDataManager dataManager, @@ -116,7 +115,7 @@ public class DebugTab : Window, ITab, IUiService TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes, Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer, HookOverrideDrawer hookOverrides, RsfService rsfService, GlobalVariablesDrawer globalVariablesDrawer, - SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification, RenderTargetHdrEnabler renderTargetHdrEnabler) + SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification, RenderTargetDrawer renderTargetDrawer) : base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse) { IsOpen = true; @@ -156,7 +155,7 @@ public class DebugTab : Window, ITab, IUiService _globalVariablesDrawer = globalVariablesDrawer; _schedulerService = schedulerService; _objectIdentification = objectIdentification; - _renderTargetHdrEnabler = renderTargetHdrEnabler; + _renderTargetDrawer = renderTargetDrawer; _objects = objects; _clientState = clientState; _dataManager = dataManager; @@ -192,7 +191,7 @@ public class DebugTab : Window, ITab, IUiService DrawData(); DrawCrcCache(); DrawResourceProblems(); - DrawRenderTargets(); + _renderTargetDrawer.Draw(); _hookOverrides.Draw(); DrawPlayerModelInfo(); _globalVariablesDrawer.Draw(); @@ -1139,54 +1138,6 @@ public class DebugTab : Window, ITab, IUiService } - /// Draw information about render targets. - private unsafe void DrawRenderTargets() - { - if (!ImGui.CollapsingHeader("Render Targets")) - return; - - var report = _renderTargetHdrEnabler.TextureReport; - if (report == null) - { - ImGui.TextUnformatted("The RenderTargetManager report has not been gathered."); - ImGui.TextUnformatted("Please restart the game with Debug Mode and Wait for Plugins on Startup enabled to fill this section."); - return; - } - - using var table = Table("##RenderTargetTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); - if (!table) - return; - - ImUtf8.TableSetupColumn("Offset"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); - ImUtf8.TableSetupColumn("Creation Order"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); - ImUtf8.TableSetupColumn("Original Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); - ImUtf8.TableSetupColumn("Current Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); - ImUtf8.TableSetupColumn("Comment"u8, ImGuiTableColumnFlags.WidthStretch, 0.3f); - ImGui.TableHeadersRow(); - - foreach (var record in report) - { - ImGui.TableNextColumn(); - ImUtf8.Text($"0x{record.Offset:X}"); - ImGui.TableNextColumn(); - ImUtf8.Text($"{record.CreationOrder}"); - ImGui.TableNextColumn(); - ImUtf8.Text($"{record.OriginalTextureFormat}"); - ImGui.TableNextColumn(); - var texture = *(CSGraphics.Kernel.Texture**)((nint)CSGraphics.Render.RenderTargetManager.Instance() + record.Offset); - if (texture != null) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiUtil.HalfBlendText(0xFF), texture->TextureFormat != record.OriginalTextureFormat); - ImUtf8.Text($"{texture->TextureFormat}"); - } - ImGui.TableNextColumn(); - var forcedConfig = RenderTargetHdrEnabler.GetForcedTextureConfig(record.CreationOrder); - if (forcedConfig.HasValue) - ImGui.TextUnformatted(forcedConfig.Value.Comment); - } - } - - /// Draw information about IPC options and availability. private void DrawDebugTabIpc() { diff --git a/Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs b/Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs new file mode 100644 index 00000000..09c8b06c --- /dev/null +++ b/Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs @@ -0,0 +1,59 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using ImGuiNET; +using OtterGui; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.Interop.Hooks.PostProcessing; + +namespace Penumbra.UI.Tabs.Debug; + +public class RenderTargetDrawer(RenderTargetHdrEnabler renderTargetHdrEnabler) : IUiService +{ + /// Draw information about render targets. + public unsafe void Draw() + { + if (!ImUtf8.CollapsingHeader("Render Targets"u8)) + return; + + var report = renderTargetHdrEnabler.TextureReport; + if (report == null) + { + ImUtf8.Text("The RenderTargetManager report has not been gathered."u8); + ImUtf8.Text("Please restart the game with Debug Mode and Wait for Plugins on Startup enabled to fill this section."u8); + return; + } + + using var table = ImUtf8.Table("##RenderTargetTable"u8, 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + if (!table) + return; + + ImUtf8.TableSetupColumn("Offset"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); + ImUtf8.TableSetupColumn("Creation Order"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); + ImUtf8.TableSetupColumn("Original Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); + ImUtf8.TableSetupColumn("Current Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); + ImUtf8.TableSetupColumn("Comment"u8, ImGuiTableColumnFlags.WidthStretch, 0.3f); + ImGui.TableHeadersRow(); + + foreach (var record in report) + { + ImUtf8.DrawTableColumn($"0x{record.Offset:X}"); + ImUtf8.DrawTableColumn($"{record.CreationOrder}"); + ImUtf8.DrawTableColumn($"{record.OriginalTextureFormat}"); + ImGui.TableNextColumn(); + var texture = *(Texture**)((nint)RenderTargetManager.Instance() + + record.Offset); + if (texture != null) + { + using var color = Dalamud.Interface.Utility.Raii.ImRaii.PushColor(ImGuiCol.Text, ImGuiUtil.HalfBlendText(0xFF), + texture->TextureFormat != record.OriginalTextureFormat); + ImUtf8.Text($"{texture->TextureFormat}"); + } + + ImGui.TableNextColumn(); + var forcedConfig = RenderTargetHdrEnabler.GetForcedTextureConfig(record.CreationOrder); + if (forcedConfig.HasValue) + ImUtf8.Text(forcedConfig.Value.Comment); + } + } +} diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 64fa57a5..c7f66859 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -10,6 +10,7 @@ using OtterGui.Compression; using OtterGui.Custom; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Api; using Penumbra.Collections; @@ -905,18 +906,29 @@ public class SettingsTab : ITab, IUiService private void DrawHdrRenderTargets() { - var item = _config.HdrRenderTargets ? 1 : 0; - ImGui.SetNextItemWidth(ImGui.CalcTextSize("M").X * 5.0f + ImGui.GetFrameHeight()); - var edited = ImGui.Combo("##hdrRenderTarget", ref item, "SDR\0HDR\0"); + ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("M"u8).X * 5.0f + ImGui.GetFrameHeight()); + using (var combo = ImUtf8.Combo("##hdrRenderTarget"u8, _config.HdrRenderTargets ? "HDR"u8 : "SDR"u8)) + { + if (combo) + { + if (ImUtf8.Selectable("HDR"u8, _config.HdrRenderTargets) && !_config.HdrRenderTargets) + { + _config.HdrRenderTargets = true; + _config.Save(); + } + + if (ImUtf8.Selectable("SDR"u8, !_config.HdrRenderTargets) && _config.HdrRenderTargets) + { + _config.HdrRenderTargets = false; + _config.Save(); + } + } + } + ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker("Diffuse Dynamic Range", - "Set the dynamic range that can be used for diffuse colors in materials without causing visual artifacts.\nChanging this setting requires a game restart. It also only works if Wait for Plugins on Startup is enabled."); - - if (!edited) - return; - - _config.HdrRenderTargets = item != 0; - _config.Save(); + ImUtf8.LabeledHelpMarker("Diffuse Dynamic Range"u8, + "Set the dynamic range that can be used for diffuse colors in materials without causing visual artifacts.\n"u8 + + "Changing this setting requires a game restart. It also only works if Wait for Plugins on Startup is enabled."u8); } /// Draw a checkbox for the HTTP API that creates and destroys the web server when toggled. From e73b3e85bdd9e136761108299040433a3314e937 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Jan 2025 13:46:28 +0100 Subject: [PATCH 4/4] Autoformat and remove nagging. --- .../PostProcessing/RenderTargetHdrEnabler.cs | 41 +++++++++-------- .../PostProcessing/ShaderReplacementFixer.cs | 46 +++++++++---------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs index 80106fc9..b7ae771b 100644 --- a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs +++ b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs @@ -14,8 +14,8 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable /// This array must be sorted by CreationOrder ascending. private static readonly ImmutableArray ForcedTextureConfigs = [ - new(9, TextureFormat.R16G16B16A16_FLOAT, "Opaque Diffuse GBuffer"), - new(10, TextureFormat.R16G16B16A16_FLOAT, "Semitransparent Diffuse GBuffer"), + new ForcedTextureConfig(9, TextureFormat.R16G16B16A16_FLOAT, "Opaque Diffuse GBuffer"), + new ForcedTextureConfig(10, TextureFormat.R16G16B16A16_FLOAT, "Semitransparent Diffuse GBuffer"), ]; private static readonly IComparer ForcedTextureConfigComparer @@ -23,16 +23,17 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable private readonly Configuration _config; - private readonly ThreadLocal _textureIndices = new(() => new(-1, -1)); + private readonly ThreadLocal _textureIndices = new(() => new TextureIndices(-1, -1)); + private readonly ThreadLocal?> _textures = new(() => null); public TextureReportRecord[]? TextureReport { get; private set; } [Signature(Sigs.RenderTargetManagerInitialize, DetourName = nameof(RenderTargetManagerInitializeDetour))] - private Hook _renderTargetManagerInitialize = null!; + private readonly Hook _renderTargetManagerInitialize = null!; [Signature(Sigs.DeviceCreateTexture2D, DetourName = nameof(CreateTexture2DDetour))] - private Hook _createTexture2D = null!; + private readonly Hook _createTexture2D = null!; public RenderTargetHdrEnabler(IGameInteropProvider interop, Configuration config) { @@ -47,7 +48,7 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable public static ForcedTextureConfig? GetForcedTextureConfig(int creationOrder) { - var i = ForcedTextureConfigs.BinarySearch(new(creationOrder, 0, string.Empty), ForcedTextureConfigComparer); + var i = ForcedTextureConfigs.BinarySearch(new ForcedTextureConfig(creationOrder, 0, string.Empty), ForcedTextureConfigComparer); return i >= 0 ? ForcedTextureConfigs[i] : null; } @@ -59,10 +60,6 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable private void Dispose(bool _) { - _renderTargetManagerInitialize.Disable(); - if (_createTexture2D.IsEnabled) - _createTexture2D.Disable(); - _createTexture2D.Dispose(); _renderTargetManagerInitialize.Dispose(); } @@ -70,8 +67,8 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable private nint RenderTargetManagerInitializeDetour(RenderTargetManager* @this) { _createTexture2D.Enable(); - _textureIndices.Value = new(0, 0); - _textures.Value = _config.DebugMode ? [] : null; + _textureIndices.Value = new TextureIndices(0, 0); + _textures.Value = _config.DebugMode ? [] : null; try { return _renderTargetManagerInitialize.Original(@this); @@ -80,10 +77,11 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable { if (_textures.Value != null) { - TextureReport = CreateTextureReport(@this, _textures.Value); + TextureReport = CreateTextureReport(@this, _textures.Value); _textures.Value = null; } - _textureIndices.Value = new(-1, -1); + + _textureIndices.Value = new TextureIndices(-1, -1); _createTexture2D.Disable(); } } @@ -92,9 +90,10 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable Device* @this, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk) { var originalTextureFormat = textureFormat; - var indices = _textureIndices.IsValueCreated ? _textureIndices.Value : new(-1, -1); - if (indices.ConfigIndex >= 0 && indices.ConfigIndex < ForcedTextureConfigs.Length && - ForcedTextureConfigs[indices.ConfigIndex].CreationOrder == indices.CreationOrder) + var indices = _textureIndices.IsValueCreated ? _textureIndices.Value : new TextureIndices(-1, -1); + if (indices.ConfigIndex >= 0 + && indices.ConfigIndex < ForcedTextureConfigs.Length + && ForcedTextureConfigs[indices.ConfigIndex].CreationOrder == indices.CreationOrder) { var config = ForcedTextureConfigs[indices.ConfigIndex++]; textureFormat = (uint)config.ForcedTextureFormat; @@ -112,15 +111,17 @@ public unsafe class RenderTargetHdrEnabler : IService, IDisposable return texture; } - private static TextureReportRecord[] CreateTextureReport(RenderTargetManager* renderTargetManager, Dictionary textures) + private static TextureReportRecord[] CreateTextureReport(RenderTargetManager* renderTargetManager, + Dictionary textures) { var rtmTextures = new Span(renderTargetManager, sizeof(RenderTargetManager) / sizeof(nint)); - var report = new List(); + var report = new List(); for (var i = 0; i < rtmTextures.Length; ++i) { if (textures.TryGetValue(rtmTextures[i], out var texture)) - report.Add(new(i * sizeof(nint), texture.TextureIndex, (TextureFormat)texture.TextureFormat)); + report.Add(new TextureReportRecord(i * sizeof(nint), texture.TextureIndex, (TextureFormat)texture.TextureFormat)); } + return report.ToArray(); } diff --git a/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs b/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs index 3b41e752..f70ea06e 100644 --- a/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs +++ b/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs @@ -64,8 +64,6 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic private readonly ResourceHandleDestructor _resourceHandleDestructor; private readonly CommunicatorService _communicator; - private readonly CharacterUtility _utility; - private readonly ModelRenderer _modelRenderer; private readonly HumanSetupScalingHook _humanSetupScalingHook; private readonly ModdedShaderPackageState _skinState; @@ -111,31 +109,31 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic CommunicatorService communicator, HookManager hooks, CharacterBaseVTables vTables, HumanSetupScalingHook humanSetupScalingHook) { _resourceHandleDestructor = resourceHandleDestructor; - _utility = utility; - _modelRenderer = modelRenderer; - _communicator = communicator; - _humanSetupScalingHook = humanSetupScalingHook; + var utility1 = utility; + var modelRenderer1 = modelRenderer; + _communicator = communicator; + _humanSetupScalingHook = humanSetupScalingHook; _skinState = new ModdedShaderPackageState( - () => (ShaderPackageResourceHandle**)&_utility.Address->SkinShpkResource, - () => (ShaderPackageResourceHandle*)_utility.DefaultSkinShpkResource); + () => (ShaderPackageResourceHandle**)&utility1.Address->SkinShpkResource, + () => (ShaderPackageResourceHandle*)utility1.DefaultSkinShpkResource); _characterStockingsState = new ModdedShaderPackageState( - () => (ShaderPackageResourceHandle**)&_utility.Address->CharacterStockingsShpkResource, - () => (ShaderPackageResourceHandle*)_utility.DefaultCharacterStockingsShpkResource); + () => (ShaderPackageResourceHandle**)&utility1.Address->CharacterStockingsShpkResource, + () => (ShaderPackageResourceHandle*)utility1.DefaultCharacterStockingsShpkResource); _characterLegacyState = new ModdedShaderPackageState( - () => (ShaderPackageResourceHandle**)&_utility.Address->CharacterLegacyShpkResource, - () => (ShaderPackageResourceHandle*)_utility.DefaultCharacterLegacyShpkResource); - _irisState = new ModdedShaderPackageState(() => _modelRenderer.IrisShaderPackage, () => _modelRenderer.DefaultIrisShaderPackage); - _characterGlassState = new ModdedShaderPackageState(() => _modelRenderer.CharacterGlassShaderPackage, - () => _modelRenderer.DefaultCharacterGlassShaderPackage); - _characterTransparencyState = new ModdedShaderPackageState(() => _modelRenderer.CharacterTransparencyShaderPackage, - () => _modelRenderer.DefaultCharacterTransparencyShaderPackage); - _characterTattooState = new ModdedShaderPackageState(() => _modelRenderer.CharacterTattooShaderPackage, - () => _modelRenderer.DefaultCharacterTattooShaderPackage); - _characterOcclusionState = new ModdedShaderPackageState(() => _modelRenderer.CharacterOcclusionShaderPackage, - () => _modelRenderer.DefaultCharacterOcclusionShaderPackage); + () => (ShaderPackageResourceHandle**)&utility1.Address->CharacterLegacyShpkResource, + () => (ShaderPackageResourceHandle*)utility1.DefaultCharacterLegacyShpkResource); + _irisState = new ModdedShaderPackageState(() => modelRenderer1.IrisShaderPackage, () => modelRenderer1.DefaultIrisShaderPackage); + _characterGlassState = new ModdedShaderPackageState(() => modelRenderer1.CharacterGlassShaderPackage, + () => modelRenderer1.DefaultCharacterGlassShaderPackage); + _characterTransparencyState = new ModdedShaderPackageState(() => modelRenderer1.CharacterTransparencyShaderPackage, + () => modelRenderer1.DefaultCharacterTransparencyShaderPackage); + _characterTattooState = new ModdedShaderPackageState(() => modelRenderer1.CharacterTattooShaderPackage, + () => modelRenderer1.DefaultCharacterTattooShaderPackage); + _characterOcclusionState = new ModdedShaderPackageState(() => modelRenderer1.CharacterOcclusionShaderPackage, + () => modelRenderer1.DefaultCharacterOcclusionShaderPackage); _hairMaskState = - new ModdedShaderPackageState(() => _modelRenderer.HairMaskShaderPackage, () => _modelRenderer.DefaultHairMaskShaderPackage); + new ModdedShaderPackageState(() => modelRenderer1.HairMaskShaderPackage, () => modelRenderer1.DefaultHairMaskShaderPackage); _humanSetupScalingHook.SetupReplacements += SetupHssReplacements; _humanOnRenderMaterialHook = hooks.CreateHook("Human.OnRenderMaterial", vTables.HumanVTable[64], @@ -463,6 +461,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic return mtrlResource; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static int GetDataSetExpectedSize(uint dataFlags) => (dataFlags & 4) != 0 ? ColorTable.Size + ((dataFlags & 8) != 0 ? ColorDyeTable.Size : 0) @@ -471,7 +470,8 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic private Texture* PrepareColorTableDetour(MaterialResourceHandle* thisPtr, byte stain0Id, byte stain1Id) { if (thisPtr->DataSetSize < GetDataSetExpectedSize(thisPtr->DataFlags)) - Penumbra.Log.Warning($"Material at {thisPtr->FileName} has data set of size {thisPtr->DataSetSize} bytes, but should have at least {GetDataSetExpectedSize(thisPtr->DataFlags)} bytes. This may cause crashes due to access violations."); + Penumbra.Log.Warning( + $"Material at {thisPtr->FileName} has data set of size {thisPtr->DataSetSize} bytes, but should have at least {GetDataSetExpectedSize(thisPtr->DataFlags)} bytes. This may cause crashes due to access violations."); // If we don't have any on-screen instances of modded characterlegacy.shpk, we don't need the slow path at all. if (!Enabled || GetTotalMaterialCountForColorTable() == 0)