Compare commits

4 Commits
7 changed files with 112 additions and 10 deletions

No files matched your search

+8
View File
@@ -95,6 +95,13 @@ public class AdvancedConfig
/// </summary>
public bool NonTile { get; set; } = false;
/// <summary>
/// When true, the game's bloom (<c>VideoBloom</c>) computes its blur size as if the frame were 720 pixels high, so the
/// blur passes on the 198-pixel-high bloom buffer are the same as in a 720p frame at any render height
/// (VideoBloomPatch). Default false.
/// </summary>
public bool HighResBloomCorrection { get; set; } = false;
/// <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";
@@ -120,6 +127,7 @@ public class AdvancedConfig
RandomSeed = source.RandomSeed;
ProcessAudio = source.ProcessAudio;
NonTile = source.NonTile;
HighResBloomCorrection = source.HighResBloomCorrection;
FileExtension = source.FileExtension;
firePlanetColor = source.firePlanetColor;
icePlanetColor = source.icePlanetColor;
+1
View File
@@ -42,6 +42,7 @@
"ui.render_config.file_extension": "File Extension",
"ui.render_config.process_audio": "Process Audio",
"ui.render_config.non_tile": "Non-Tile Mode",
"ui.render_config.high_res_bloom_correction": "High-Resolution Bloom Correction",
"ui.render_config.random_seed": "Random Seed (Optional)",
"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",
+1
View File
@@ -42,6 +42,7 @@
"ui.render_config.file_extension": "파일 확장자",
"ui.render_config.process_audio": "오디오 처리",
"ui.render_config.non_tile": "논타일 모드",
"ui.render_config.high_res_bloom_correction": "고해상도 블룸 보정",
"ui.render_config.random_seed": "랜덤 시드 (선택 사항)",
"ui.render_config.validation_title": "설정 오류",
"ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.",
+59
View File
@@ -0,0 +1,59 @@
using ADOFAIRenderer.Core;
using HarmonyLib;
using UnityEngine;
namespace ADOFAIRenderer.Patch.Game;
/// <summary>
/// Makes the game's bloom compute its blur size as if the frame were 720 pixels high
/// ("High-Resolution Bloom Correction" option).
/// <para>
/// Patched member: <c>VideoBloom.OnRenderImage(RenderTexture source, RenderTexture destination)</c>. The game scales the
/// frame down to a bloom buffer 198 pixels high (<c>scale = 198 / source.height</c>) and sets the blur radius in bloom
/// buffer pixels to <c>KernelSize * MediumKernelScale * scale</c> (and <c>KernelSize * LargeKernelScale * scale</c>).
/// <c>BloomBlit</c> turns the radius into the number of 5-tap blur passes and the blend amount of the last pass.
/// </para>
/// <para>
/// While the render is active (<see cref="RenderManager.IsActive"/>) and <c>AdvancedConfig.HighResBloomCorrection</c> is
/// enabled, the prefix multiplies <c>KernelSize</c> by <c>source.height / 720</c> for the duration of the call, so the
/// radius equals <c>KernelSize * MediumKernelScale * 198 / 720</c> and the blur passes on the bloom buffer are the same
/// as in a 720p frame. The finalizer restores the original <c>KernelSize</c>. The bloom buffer size and the downscale
/// from the source frame keep the game's behavior.
/// </para>
/// </summary>
[HarmonyPatch(typeof(VideoBloom))]
public class VideoBloomPatch
{
/// <summary>Frame height in pixels the blur size is computed for.</summary>
private const float ReferenceHeight = 720f;
/// <summary>Prefix of <c>VideoBloom.OnRenderImage</c>.</summary>
/// <param name="__instance">The bloom component on the game camera.</param>
/// <param name="source">The camera frame; its height selects the multiplier.</param>
/// <param name="__state">Original <c>KernelSize</c>, or null when it was not changed.</param>
[HarmonyPrefix]
[HarmonyPatch("OnRenderImage")]
// ReSharper disable InconsistentNaming
public static void ScaleKernelSize(VideoBloom __instance, RenderTexture source, out float? __state)
// ReSharper restore InconsistentNaming
{
__state = null;
if (!source || !RenderManager.IsActive || !RenderManager.Instance.Configuration.AdvancedConfig.HighResBloomCorrection)
return;
__state = __instance.KernelSize;
__instance.KernelSize *= source.height / ReferenceHeight;
}
/// <summary>Finalizer of <c>VideoBloom.OnRenderImage</c>: restores <c>KernelSize</c> when the prefix changed it.</summary>
/// <param name="__instance">The bloom component on the game camera.</param>
/// <param name="__state">Original <c>KernelSize</c> stored by the prefix, or null.</param>
[HarmonyFinalizer]
[HarmonyPatch("OnRenderImage")]
// ReSharper disable InconsistentNaming
public static void RestoreKernelSize(VideoBloom __instance, float? __state)
// ReSharper restore InconsistentNaming
{
if (__state.HasValue) __instance.KernelSize = __state.Value;
}
}
+23 -2
View File
@@ -139,6 +139,7 @@ internal static class RenderConfigWindow
private static O5InputField? _randomSeedInput;
private static O5Toggle? _processAudioToggle;
private static O5Toggle? _nonTileToggle;
private static O5Toggle? _highResBloomCorrectionToggle;
// 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.
@@ -644,7 +645,7 @@ internal static class RenderConfigWindow
{
var trimmed = val?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(trimmed) ||
string.Equals(trimmed, GlobalSettings.DefaultOutputDirectory, StringComparison.OrdinalIgnoreCase))
string.Equals(trimmed, GlobalSettings.DefaultOutputDirectory, StringComparison.Ordinal))
{
GlobalSettings.Current.OutputDirectory = null;
}
@@ -698,6 +699,7 @@ internal static class RenderConfigWindow
// Section 6: Advanced Settings card (RenderConfig.AdvancedConfig)
// File Extension | Random Seed
// Process Audio | Non-Tile Mode
// High-Resolution Bloom Correction |
// "Planet Colors" label, then three columns: Fire | Ice | Wind (toggle + color picker each)
// ---------------------------------------------------------------------------------------------
// 7. Advanced Settings (Collapsible Card)
@@ -739,6 +741,23 @@ internal static class RenderConfigWindow
generateLabel: false
);
// High-Resolution Bloom Correction (AdvancedConfig.HighResBloomCorrection): the game's bloom computes its blur
// size as if the frame were 720 pixels high (VideoBloomPatch)
_highResBloomCorrectionToggle = CreateToggleField(
ctx, CreateFieldSlot(advLeft),
labelKey: "",
defaultValue: false,
value: config.AdvancedConfig.HighResBloomCorrection,
toggleTextKey: "ui.render_config.high_res_bloom_correction",
id: "toggle_high_res_bloom_correction",
onChanged: val =>
{
config.AdvancedConfig.HighResBloomCorrection = val;
MarkConfigModified();
},
generateLabel: false
);
// Random Seed (AdvancedConfig.RandomSeed): integer passed to UnityEngine.Random.InitState before the game scene
// loads; empty text stores null (no seeding). Text that is neither empty nor an integer keeps the previous value.
_randomSeedInput = CreateInputField(
@@ -980,6 +999,7 @@ internal static class RenderConfigWindow
_randomSeedInput?.Set(config.AdvancedConfig.RandomSeed?.ToString() ?? "", false);
_processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false);
_nonTileToggle?.Set(config.AdvancedConfig.NonTile, false);
_highResBloomCorrectionToggle?.Set(config.AdvancedConfig.HighResBloomCorrection, false);
// Planet colors: toggle on when an override is stored; the picker shows the stored color or the default and is
// blocked without an override
@@ -1074,7 +1094,7 @@ internal static class RenderConfigWindow
{
var path = _outputDirInput.Value.Trim();
if (string.IsNullOrWhiteSpace(path) ||
string.Equals(path, GlobalSettings.DefaultOutputDirectory, StringComparison.OrdinalIgnoreCase))
string.Equals(path, GlobalSettings.DefaultOutputDirectory, StringComparison.Ordinal))
{
GlobalSettings.Current.OutputDirectory = null;
}
@@ -1161,6 +1181,7 @@ internal static class RenderConfigWindow
}
if (_processAudioToggle != null) target.AdvancedConfig.ProcessAudio = _processAudioToggle.Value;
if (_nonTileToggle != null) target.AdvancedConfig.NonTile = _nonTileToggle.Value;
if (_highResBloomCorrectionToggle != null) target.AdvancedConfig.HighResBloomCorrection = _highResBloomCorrectionToggle.Value;
// Planet Colors
target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)
+8
View File
@@ -1080,6 +1080,14 @@ namespace ADOFAIRenderer.Util.FFmpeg
{
TrySetOption("x265-params", "hrd=1:strict-cbr=1");
}
// NVENC: without "rc", nvenc_setup_rate_control selects CONSTQP when "qp" is set and VBR otherwise;
// bit_rate and rc_max_rate being equal does not select CBR. "rc=cbr" selects NV_ENC_PARAMS_RC_CBR.
// Reference: https://github.com/FFmpeg/FFmpeg/blob/release/8.0/libavcodec/nvenc.c (nvenc_setup_rate_control),
// https://github.com/FFmpeg/FFmpeg/blob/release/8.0/libavcodec/nvenc_h264.c ("rc" option, "cbr" constant)
else if (encoder.Kind == EncoderKind.NVENC)
{
TrySetOption("rc", "cbr");
}
}
break;
+12 -8
View File
@@ -18,8 +18,8 @@ namespace ADOFAIRenderer.Util.OS;
/// </para>
/// <para>- <b>macOS</b>: <c>osascript</c> running AppleScript <c>choose folder</c>.</para>
/// <para>
/// - <b>Linux</b>: <c>zenity --file-selection --directory</c>; when zenity does not return an existing directory (not installed,
/// cancelled or failed), <c>kdialog --getexistingdirectory</c>.
/// - <b>Linux</b>: <c>zenity --file-selection --directory</c>; when zenity is not installed or fails (exit code other than 0
/// or 1), <c>kdialog --getexistingdirectory</c>. A cancelled zenity dialog (exit code 1) ends the selection.
/// </para>
/// <para>
/// The dialog runs off the main Unity thread so the game keeps rendering frames while it is open. Only one dialog can be open
@@ -688,13 +688,13 @@ internal static class FolderDialogUtil
/// <summary>
/// Runs <c>zenity --file-selection --directory --title=... [--filename=initialDirectory]</c> and blocks until it exits.
/// When zenity cannot be started, exits with a non-zero code (cancel or error) or prints a path that is not an existing
/// directory, runs <c>kdialog --getexistingdirectory [initialDirectory] --title ...</c> instead. Both commands print the
/// Exit code 0 returns the printed path when it is an existing directory, and exit code 1 (cancelled or closed) returns null
/// without opening a second dialog. When zenity cannot be started or exits with any other code, runs <c>kdialog --getexistingdirectory [initialDirectory] --title ...</c> instead. Both commands print the
/// selected path to standard output.
/// </summary>
/// <param name="initialDirectory">Folder shown first; used only when it is an existing directory.</param>
/// <returns>
/// The selected existing directory; null when both dialogs return nothing, when kdialog cannot be started, or on an
/// The selected existing directory; null when the dialog is cancelled, when kdialog cannot be started, or on an
/// exception (logged as a warning).
/// </returns>
private static string? OpenLinuxFolderBrowser(string initialDirectory)
@@ -725,15 +725,19 @@ internal static class FolderDialogUtil
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit();
if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && Directory.Exists(output))
if (process.ExitCode == 0)
{
return output;
return !string.IsNullOrEmpty(output) && Directory.Exists(output) ? output : null;
}
// zenity exit code 1: the dialog was cancelled or closed.
// Reference: https://gitlab.gnome.org/GNOME/zenity/-/raw/master/src/util.c (ZENITY_OK_DEFAULT 0, ZENITY_CANCEL_DEFAULT 1, ZENITY_ESC_DEFAULT 1, ZENITY_ERROR_DEFAULT -1).
if (process.ExitCode == 1) return null;
}
}
catch
{
// Fallback to kdialog if zenity fails to start
// Falls back to kdialog when zenity is not installed or cannot be started.
}
var kdialogInfo = new ProcessStartInfo