mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Add RenderTargetHdrEnabler
This commit is contained in:
parent
349241d0ab
commit
f07780cf7b
8 changed files with 219 additions and 3 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 33de79bc62eb014298856ed5c6b6edbe819db26c
|
Subproject commit d5f929664c212804594fadb4e4cefe9e6a1f5d37
|
||||||
|
|
@ -110,6 +110,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService
|
||||||
public bool KeepDefaultMetaChanges { get; set; } = false;
|
public bool KeepDefaultMetaChanges { get; set; } = false;
|
||||||
public string DefaultModAuthor { get; set; } = DefaultTexToolsData.Author;
|
public string DefaultModAuthor { get; set; } = DefaultTexToolsData.Author;
|
||||||
public bool EditRawTileTransforms { get; set; } = false;
|
public bool EditRawTileTransforms { get; set; } = false;
|
||||||
|
public bool HdrRenderTargets { get; set; } = true;
|
||||||
|
|
||||||
public Dictionary<ColorId, uint> Colors { get; set; }
|
public Dictionary<ColorId, uint> Colors { get; set; }
|
||||||
= Enum.GetValues<ColorId>().ToDictionary(c => c, c => c.Data().DefaultColor);
|
= Enum.GetValues<ColorId>().ToDictionary(c => c, c => c.Data().DefaultColor);
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ public class HookOverrides
|
||||||
public bool ModelRendererOnRenderMaterial;
|
public bool ModelRendererOnRenderMaterial;
|
||||||
public bool ModelRendererUnkFunc;
|
public bool ModelRendererUnkFunc;
|
||||||
public bool PrepareColorTable;
|
public bool PrepareColorTable;
|
||||||
|
public bool RenderTargetManagerInitialize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ResourceLoadingHooks
|
public struct ResourceLoadingHooks
|
||||||
|
|
|
||||||
136
Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs
Normal file
136
Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs
Normal file
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <remarks> This array must be sorted by CreationOrder ascending. </remarks>
|
||||||
|
private static readonly ImmutableArray<ForcedTextureConfig> ForcedTextureConfigs =
|
||||||
|
[
|
||||||
|
new(9, TextureFormat.R16G16B16A16_FLOAT, "Main Diffuse GBuffer"),
|
||||||
|
new(10, TextureFormat.R16G16B16A16_FLOAT, "Hair Diffuse GBuffer"),
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly IComparer<ForcedTextureConfig> ForcedTextureConfigComparer
|
||||||
|
= Comparer<ForcedTextureConfig>.Create((lhs, rhs) => lhs.CreationOrder.CompareTo(rhs.CreationOrder));
|
||||||
|
|
||||||
|
private readonly Configuration _config;
|
||||||
|
|
||||||
|
private readonly ThreadLocal<TextureIndices> _textureIndices = new(() => new(-1, -1));
|
||||||
|
private readonly ThreadLocal<Dictionary<nint, (int TextureIndex, uint TextureFormat)>?> _textures = new(() => null);
|
||||||
|
|
||||||
|
public TextureReportRecord[]? TextureReport { get; private set; }
|
||||||
|
|
||||||
|
[Signature(Sigs.RenderTargetManagerInitialize, DetourName = nameof(RenderTargetManagerInitializeDetour))]
|
||||||
|
private Hook<RenderTargetManagerInitializeFunc> _renderTargetManagerInitialize = null!;
|
||||||
|
|
||||||
|
[Signature(Sigs.DeviceCreateTexture2D, DetourName = nameof(CreateTexture2DDetour))]
|
||||||
|
private Hook<CreateTexture2DFunc> _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<nint, (int TextureIndex, uint TextureFormat)> textures)
|
||||||
|
{
|
||||||
|
var rtmTextures = new Span<nint>(renderTargetManager, sizeof(RenderTargetManager) / sizeof(nint));
|
||||||
|
var report = new List<TextureReportRecord>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ using OtterGui.Classes;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Files.MaterialStructs;
|
||||||
using Penumbra.Interop.Hooks.Resources;
|
using Penumbra.Interop.Hooks.Resources;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
@ -462,8 +463,16 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic
|
||||||
return mtrlResource;
|
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)
|
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 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)
|
if (!Enabled || GetTotalMaterialCountForColorTable() == 0)
|
||||||
return _prepareColorTableHook.Original(thisPtr, stain0Id, stain1Id);
|
return _prepareColorTableHook.Original(thisPtr, stain0Id, stain1Id);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"Tags": [ "modding" ],
|
"Tags": [ "modding" ],
|
||||||
"DalamudApiLevel": 11,
|
"DalamudApiLevel": 11,
|
||||||
"LoadPriority": 69420,
|
"LoadPriority": 69420,
|
||||||
"LoadState": 2,
|
"LoadRequiredState": 2,
|
||||||
"LoadSync": true,
|
"LoadSync": true,
|
||||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.GameData.Files.StainMapStructs;
|
using Penumbra.GameData.Files.StainMapStructs;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.AdvancedWindow.Materials;
|
using Penumbra.UI.AdvancedWindow.Materials;
|
||||||
|
using CSGraphics = FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs.Debug;
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
private readonly RsfService _rsfService;
|
private readonly RsfService _rsfService;
|
||||||
private readonly SchedulerResourceManagementService _schedulerService;
|
private readonly SchedulerResourceManagementService _schedulerService;
|
||||||
private readonly ObjectIdentification _objectIdentification;
|
private readonly ObjectIdentification _objectIdentification;
|
||||||
|
private readonly RenderTargetHdrEnabler _renderTargetHdrEnabler;
|
||||||
|
|
||||||
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects,
|
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects,
|
||||||
IClientState clientState, IDataManager dataManager,
|
IClientState clientState, IDataManager dataManager,
|
||||||
|
|
@ -114,7 +116,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
||||||
Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer,
|
Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer,
|
||||||
HookOverrideDrawer hookOverrides, RsfService rsfService, GlobalVariablesDrawer globalVariablesDrawer,
|
HookOverrideDrawer hookOverrides, RsfService rsfService, GlobalVariablesDrawer globalVariablesDrawer,
|
||||||
SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification)
|
SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification, RenderTargetHdrEnabler renderTargetHdrEnabler)
|
||||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
||||||
{
|
{
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
|
@ -154,6 +156,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
_globalVariablesDrawer = globalVariablesDrawer;
|
_globalVariablesDrawer = globalVariablesDrawer;
|
||||||
_schedulerService = schedulerService;
|
_schedulerService = schedulerService;
|
||||||
_objectIdentification = objectIdentification;
|
_objectIdentification = objectIdentification;
|
||||||
|
_renderTargetHdrEnabler = renderTargetHdrEnabler;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
|
|
@ -189,6 +192,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
DrawData();
|
DrawData();
|
||||||
DrawCrcCache();
|
DrawCrcCache();
|
||||||
DrawResourceProblems();
|
DrawResourceProblems();
|
||||||
|
DrawRenderTargets();
|
||||||
_hookOverrides.Draw();
|
_hookOverrides.Draw();
|
||||||
DrawPlayerModelInfo();
|
DrawPlayerModelInfo();
|
||||||
_globalVariablesDrawer.Draw();
|
_globalVariablesDrawer.Draw();
|
||||||
|
|
@ -1135,6 +1139,54 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary> Draw information about render targets. </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary> Draw information about IPC options and availability. </summary>
|
/// <summary> Draw information about IPC options and availability. </summary>
|
||||||
private void DrawDebugTabIpc()
|
private void DrawDebugTabIpc()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -773,6 +773,7 @@ public class SettingsTab : ITab, IUiService
|
||||||
|
|
||||||
DrawCrashHandler();
|
DrawCrashHandler();
|
||||||
DrawMinimumDimensionConfig();
|
DrawMinimumDimensionConfig();
|
||||||
|
DrawHdrRenderTargets();
|
||||||
Checkbox("Auto Deduplicate on Import",
|
Checkbox("Auto Deduplicate on Import",
|
||||||
"Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.",
|
"Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.",
|
||||||
_config.AutoDeduplicateOnImport, v => _config.AutoDeduplicateOnImport = v);
|
_config.AutoDeduplicateOnImport, v => _config.AutoDeduplicateOnImport = v);
|
||||||
|
|
@ -902,6 +903,22 @@ public class SettingsTab : ITab, IUiService
|
||||||
_config.Save();
|
_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();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Draw a checkbox for the HTTP API that creates and destroys the web server when toggled. </summary>
|
/// <summary> Draw a checkbox for the HTTP API that creates and destroys the web server when toggled. </summary>
|
||||||
private void DrawEnableHttpApiBox()
|
private void DrawEnableHttpApiBox()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue