SHA256
Add Fix Custom FPS Render Timing Option
This commit is contained in:
6 files changed
+157
-1
No files matched your search
@@ -102,6 +102,15 @@ public class AdvancedConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HighResBloomCorrection { get; set; } = false;
|
public bool HighResBloomCorrection { get; set; } = false;
|
||||||
|
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
/// <summary>
|
||||||
|
/// When true, fixes in-game and render bugs during SetFramerate events (where objects disappear on the first frame
|
||||||
|
/// or parallax/tiles lag behind) by deferring manual camera rendering to the end of the frame after all LateUpdates.
|
||||||
|
/// Default true.
|
||||||
|
/// </summary>
|
||||||
|
public bool FixCustomFrameRate { get; set; } = true;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
|
|
||||||
/// <summary>File extension of the default output file name, without the dot (e.g. "mkv", "mp4"); selects the container.</summary>
|
/// <summary>File extension of the default output file name, without the dot (e.g. "mkv", "mp4"); selects the container.</summary>
|
||||||
public string FileExtension { get; set; } = "mkv";
|
public string FileExtension { get; set; } = "mkv";
|
||||||
|
|
||||||
@@ -128,6 +137,9 @@ public class AdvancedConfig
|
|||||||
ProcessAudio = source.ProcessAudio;
|
ProcessAudio = source.ProcessAudio;
|
||||||
NonTile = source.NonTile;
|
NonTile = source.NonTile;
|
||||||
HighResBloomCorrection = source.HighResBloomCorrection;
|
HighResBloomCorrection = source.HighResBloomCorrection;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
FixCustomFrameRate = source.FixCustomFrameRate;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
FileExtension = source.FileExtension;
|
FileExtension = source.FileExtension;
|
||||||
firePlanetColor = source.firePlanetColor;
|
firePlanetColor = source.firePlanetColor;
|
||||||
icePlanetColor = source.icePlanetColor;
|
icePlanetColor = source.icePlanetColor;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"ui.render_config.process_audio": "Process Audio",
|
"ui.render_config.process_audio": "Process Audio",
|
||||||
"ui.render_config.non_tile": "Non-Tile Mode",
|
"ui.render_config.non_tile": "Non-Tile Mode",
|
||||||
"ui.render_config.high_res_bloom_correction": "High-Resolution Bloom Correction",
|
"ui.render_config.high_res_bloom_correction": "High-Resolution Bloom Correction",
|
||||||
|
"ui.render_config.fix_custom_frame_rate": "Fix Custom FPS Render Timing",
|
||||||
"ui.render_config.random_seed": "Random Seed (Optional)",
|
"ui.render_config.random_seed": "Random Seed (Optional)",
|
||||||
"ui.render_config.validation_title": "Invalid Settings",
|
"ui.render_config.validation_title": "Invalid Settings",
|
||||||
"ui.render_config.validation_error": "Please check your input values:\n- Width and Height must be integers > 0\n- Render FPS and Video FPS must be > 0\n- Delay After End must be >= 0",
|
"ui.render_config.validation_error": "Please check your input values:\n- Width and Height must be integers > 0\n- Render FPS and Video FPS must be > 0\n- Delay After End must be >= 0",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"ui.render_config.process_audio": "오디오 처리",
|
"ui.render_config.process_audio": "오디오 처리",
|
||||||
"ui.render_config.non_tile": "논타일 모드",
|
"ui.render_config.non_tile": "논타일 모드",
|
||||||
"ui.render_config.high_res_bloom_correction": "고해상도 블룸 보정",
|
"ui.render_config.high_res_bloom_correction": "고해상도 블룸 보정",
|
||||||
|
"ui.render_config.fix_custom_frame_rate": "커스텀 FPS 렌더 타이밍 보정",
|
||||||
"ui.render_config.random_seed": "랜덤 시드 (선택 사항)",
|
"ui.render_config.random_seed": "랜덤 시드 (선택 사항)",
|
||||||
"ui.render_config.validation_title": "설정 오류",
|
"ui.render_config.validation_title": "설정 오류",
|
||||||
"ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.",
|
"ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.",
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using ADOFAIRenderer.Core;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ADOFAIRenderer.Patch.Game;
|
||||||
|
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
/// <summary>
|
||||||
|
/// Fixes the in-game and render bug where objects (floors, decorations, planets) disappear on the first frame of
|
||||||
|
/// a SetFramerate event (due to camera Z=0 near clip culling) and where parallax/tiles lag behind on subsequent frames.
|
||||||
|
/// <para>
|
||||||
|
/// Defers the manual camera rendering (<c>scrCamera.UpdateCustomFrameRateScreen</c>) to the very end of the frame
|
||||||
|
/// (execution order <c>int.MaxValue - 1</c>), after all components (<c>scrCamera</c>, <c>scrFloor</c>,
|
||||||
|
/// <c>scrDecorationManager</c>, <c>scrPlanet</c>, <c>scrParallax</c>, and <c>scrController</c>) have finished their
|
||||||
|
/// <c>LateUpdate</c>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPatch]
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
public static class CustomFrameRateDeferPatch
|
||||||
|
{
|
||||||
|
private static bool _isExecutingDeferred;
|
||||||
|
private static scrCamera? _pendingCamera;
|
||||||
|
|
||||||
|
private static readonly Action<scrCamera>? UpdateCustomFrameRateScreenInvoker =
|
||||||
|
AccessTools.MethodDelegate<Action<scrCamera>>(AccessTools.Method(typeof(scrCamera), "UpdateCustomFrameRateScreen"));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the SetFramerate render timing fix is enabled.
|
||||||
|
/// Active if rendering session has the option enabled, or enabled by default during normal in-game play.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (RenderManager.IsActive && RenderManager.Instance?.Configuration?.AdvancedConfig != null)
|
||||||
|
{
|
||||||
|
return RenderManager.Instance.Configuration.AdvancedConfig.FixCustomFrameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intercepts manual camera render calls from SetCustomFrameRate (Update phase) or CheckUpdateFrameRateScreen
|
||||||
|
/// (LateUpdate phase). If not currently executing the deferred render pass, records the camera instance and
|
||||||
|
/// suppresses the premature render.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(scrCamera), "UpdateCustomFrameRateScreen")]
|
||||||
|
private static bool DeferManualRender(scrCamera __instance)
|
||||||
|
{
|
||||||
|
if (!IsEnabled) return true;
|
||||||
|
if (_isExecutingDeferred) return true;
|
||||||
|
|
||||||
|
_pendingCamera = __instance;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the runner component is attached to the camera when it starts.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(scrCamera), "Start")]
|
||||||
|
private static void AttachRunner(scrCamera __instance)
|
||||||
|
{
|
||||||
|
if (!__instance) return;
|
||||||
|
if (!__instance.gameObject.GetComponent<CustomFrameRateDeferredRunner>())
|
||||||
|
{
|
||||||
|
__instance.gameObject.AddComponent<CustomFrameRateDeferredRunner>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked at the very end of LateUpdate (Order int.MaxValue - 1). If a manual render was requested during the frame,
|
||||||
|
/// executes the camera's original UpdateCustomFrameRateScreen once with all objects fully prepared.
|
||||||
|
/// </summary>
|
||||||
|
internal static void ExecuteDeferredRender()
|
||||||
|
{
|
||||||
|
if (_pendingCamera == null) return;
|
||||||
|
|
||||||
|
var cam = _pendingCamera;
|
||||||
|
_pendingCamera = null;
|
||||||
|
|
||||||
|
if (!cam) return;
|
||||||
|
|
||||||
|
_isExecutingDeferred = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UpdateCustomFrameRateScreenInvoker?.Invoke(cam);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isExecutingDeferred = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runner component attached to scrCamera that executes deferred custom frame rate renders at the end of LateUpdate.
|
||||||
|
/// Execution order <c>int.MaxValue - 1</c> guarantees it runs after all normal game components (including
|
||||||
|
/// scrController at 400), but right before PostRenderHook at <c>int.MaxValue</c>.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultExecutionOrder(int.MaxValue - 1)]
|
||||||
|
internal class CustomFrameRateDeferredRunner : MonoBehaviour
|
||||||
|
{
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
CustomFrameRateDeferPatch.ExecuteDeferredRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
+1
-1
@@ -9,7 +9,7 @@ using MelonLoader.Utils;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.3.1-INDEV", "ADOFAI Editor Forum")]
|
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.3.2-INDEV", "ADOFAI Editor Forum")]
|
||||||
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
||||||
|
|
||||||
namespace ADOFAIRenderer;
|
namespace ADOFAIRenderer;
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ internal static class RenderConfigWindow
|
|||||||
private static O5Toggle? _processAudioToggle;
|
private static O5Toggle? _processAudioToggle;
|
||||||
private static O5Toggle? _nonTileToggle;
|
private static O5Toggle? _nonTileToggle;
|
||||||
private static O5Toggle? _highResBloomCorrectionToggle;
|
private static O5Toggle? _highResBloomCorrectionToggle;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
private static O5Toggle? _fixCustomFrameRateToggle;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
|
|
||||||
// Planet color overrides: the toggle enables the override, the picker holds the color. A disabled toggle stores
|
// Planet color overrides: the toggle enables the override, the picker holds the color. A disabled toggle stores
|
||||||
// null in AdvancedConfig (no override is applied by PersistencePatch) and blocks the picker.
|
// null in AdvancedConfig (no override is applied by PersistencePatch) and blocks the picker.
|
||||||
@@ -791,6 +794,24 @@ internal static class RenderConfigWindow
|
|||||||
generateLabel: false
|
generateLabel: false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
// Fix SetFramerate Render Timing (AdvancedConfig.FixCustomFrameRate): defers manual camera rendering to frame end
|
||||||
|
_fixCustomFrameRateToggle = CreateToggleField(
|
||||||
|
ctx, CreateFieldSlot(advRight),
|
||||||
|
labelKey: "",
|
||||||
|
defaultValue: true,
|
||||||
|
value: config.AdvancedConfig.FixCustomFrameRate,
|
||||||
|
toggleTextKey: "ui.render_config.fix_custom_frame_rate",
|
||||||
|
id: "toggle_fix_custom_frame_rate",
|
||||||
|
onChanged: val =>
|
||||||
|
{
|
||||||
|
config.AdvancedConfig.FixCustomFrameRate = val;
|
||||||
|
MarkConfigModified();
|
||||||
|
},
|
||||||
|
generateLabel: false
|
||||||
|
);
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
|
|
||||||
// 7-1. Planet Color Settings (Optional).
|
// 7-1. Planet Color Settings (Optional).
|
||||||
// For each planet: the toggle turns the override on/off (off stores null and blocks/collapses the picker);
|
// For each planet: the toggle turns the override on/off (off stores null and blocks/collapses the picker);
|
||||||
// the picker writes its color into the config only while the toggle is on.
|
// the picker writes its color into the config only while the toggle is on.
|
||||||
@@ -1000,6 +1021,9 @@ internal static class RenderConfigWindow
|
|||||||
_processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false);
|
_processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false);
|
||||||
_nonTileToggle?.Set(config.AdvancedConfig.NonTile, false);
|
_nonTileToggle?.Set(config.AdvancedConfig.NonTile, false);
|
||||||
_highResBloomCorrectionToggle?.Set(config.AdvancedConfig.HighResBloomCorrection, false);
|
_highResBloomCorrectionToggle?.Set(config.AdvancedConfig.HighResBloomCorrection, false);
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
_fixCustomFrameRateToggle?.Set(config.AdvancedConfig.FixCustomFrameRate, false);
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
|
|
||||||
// Planet colors: toggle on when an override is stored; the picker shows the stored color or the default and is
|
// Planet colors: toggle on when an override is stored; the picker shows the stored color or the default and is
|
||||||
// blocked without an override
|
// blocked without an override
|
||||||
@@ -1182,6 +1206,9 @@ internal static class RenderConfigWindow
|
|||||||
if (_processAudioToggle != null) target.AdvancedConfig.ProcessAudio = _processAudioToggle.Value;
|
if (_processAudioToggle != null) target.AdvancedConfig.ProcessAudio = _processAudioToggle.Value;
|
||||||
if (_nonTileToggle != null) target.AdvancedConfig.NonTile = _nonTileToggle.Value;
|
if (_nonTileToggle != null) target.AdvancedConfig.NonTile = _nonTileToggle.Value;
|
||||||
if (_highResBloomCorrectionToggle != null) target.AdvancedConfig.HighResBloomCorrection = _highResBloomCorrectionToggle.Value;
|
if (_highResBloomCorrectionToggle != null) target.AdvancedConfig.HighResBloomCorrection = _highResBloomCorrectionToggle.Value;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG START] ===
|
||||||
|
if (_fixCustomFrameRateToggle != null) target.AdvancedConfig.FixCustomFrameRate = _fixCustomFrameRateToggle.Value;
|
||||||
|
// === [MARKER: FIX_SET_FRAMERATE_BUG END] ===
|
||||||
|
|
||||||
// Planet Colors
|
// Planet Colors
|
||||||
target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)
|
target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)
|
||||||
|
|||||||
Reference in new issue
Block a user