mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +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 string DefaultModAuthor { get; set; } = DefaultTexToolsData.Author;
|
||||
public bool EditRawTileTransforms { get; set; } = false;
|
||||
public bool HdrRenderTargets { get; set; } = true;
|
||||
|
||||
public Dictionary<ColorId, uint> Colors { get; set; }
|
||||
= Enum.GetValues<ColorId>().ToDictionary(c => c, c => c.Data().DefaultColor);
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ public class HookOverrides
|
|||
public bool ModelRendererOnRenderMaterial;
|
||||
public bool ModelRendererUnkFunc;
|
||||
public bool PrepareColorTable;
|
||||
public bool RenderTargetManagerInitialize;
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
private void DrawDebugTabIpc()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary> Draw a checkbox for the HTTP API that creates and destroys the web server when toggled. </summary>
|
||||
private void DrawEnableHttpApiBox()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue