mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Merge branch 'rt-hdr'
This commit is contained in:
commit
aebd22ed64
9 changed files with 264 additions and 25 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
|
||||
|
|
|
|||
137
Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs
Normal file
137
Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
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 ForcedTextureConfig(9, TextureFormat.R16G16B16A16_FLOAT, "Opaque Diffuse GBuffer"),
|
||||
new ForcedTextureConfig(10, TextureFormat.R16G16B16A16_FLOAT, "Semitransparent 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 TextureIndices(-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 readonly Hook<RenderTargetManagerInitializeFunc> _renderTargetManagerInitialize = null!;
|
||||
|
||||
[Signature(Sigs.DeviceCreateTexture2D, DetourName = nameof(CreateTexture2DDetour))]
|
||||
private readonly 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 ForcedTextureConfig(creationOrder, 0, string.Empty), ForcedTextureConfigComparer);
|
||||
return i >= 0 ? ForcedTextureConfigs[i] : null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool _)
|
||||
{
|
||||
_createTexture2D.Dispose();
|
||||
_renderTargetManagerInitialize.Dispose();
|
||||
}
|
||||
|
||||
private nint RenderTargetManagerInitializeDetour(RenderTargetManager* @this)
|
||||
{
|
||||
_createTexture2D.Enable();
|
||||
_textureIndices.Value = new TextureIndices(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 TextureIndices(-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 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;
|
||||
}
|
||||
|
||||
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 TextureReportRecord(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;
|
||||
|
|
@ -63,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;
|
||||
|
|
@ -110,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<CharacterBaseOnRenderMaterialDelegate>("Human.OnRenderMaterial", vTables.HumanVTable[64],
|
||||
|
|
@ -462,8 +461,18 @@ 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)
|
||||
: 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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
private readonly RsfService _rsfService;
|
||||
private readonly SchedulerResourceManagementService _schedulerService;
|
||||
private readonly ObjectIdentification _objectIdentification;
|
||||
private readonly RenderTargetDrawer _renderTargetDrawer;
|
||||
|
||||
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects,
|
||||
IClientState clientState, IDataManager dataManager,
|
||||
|
|
@ -114,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)
|
||||
SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification, RenderTargetDrawer renderTargetDrawer)
|
||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
||||
{
|
||||
IsOpen = true;
|
||||
|
|
@ -154,6 +155,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
_globalVariablesDrawer = globalVariablesDrawer;
|
||||
_schedulerService = schedulerService;
|
||||
_objectIdentification = objectIdentification;
|
||||
_renderTargetDrawer = renderTargetDrawer;
|
||||
_objects = objects;
|
||||
_clientState = clientState;
|
||||
_dataManager = dataManager;
|
||||
|
|
@ -189,6 +191,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
DrawData();
|
||||
DrawCrcCache();
|
||||
DrawResourceProblems();
|
||||
_renderTargetDrawer.Draw();
|
||||
_hookOverrides.Draw();
|
||||
DrawPlayerModelInfo();
|
||||
_globalVariablesDrawer.Draw();
|
||||
|
|
|
|||
59
Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs
Normal file
59
Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs
Normal file
|
|
@ -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
|
||||
{
|
||||
/// <summary> Draw information about render targets. </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -773,6 +774,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 +904,33 @@ public class SettingsTab : ITab, IUiService
|
|||
_config.Save();
|
||||
}
|
||||
|
||||
private void DrawHdrRenderTargets()
|
||||
{
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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