SHA256
Add Basic Reworked UI
This commit is contained in:
13 files changed
+218
-406
No files matched your search
Binary file not shown.
Binary file not shown.
+21
-64
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using ADOFAIRenderer.Data;
|
||||||
using ADOFAIRenderer.UI;
|
using ADOFAIRenderer.UI;
|
||||||
using ADOFAIRenderer.Util;
|
using ADOFAIRenderer.Util;
|
||||||
using ADOFAIRenderer.Util.FFmpeg;
|
using ADOFAIRenderer.Util.FFmpeg;
|
||||||
@@ -31,7 +32,7 @@ namespace ADOFAIRenderer.Core
|
|||||||
public bool skipPressToStart;
|
public bool skipPressToStart;
|
||||||
public string levelPath = "";
|
public string levelPath = "";
|
||||||
public bool isRenderCancelled;
|
public bool isRenderCancelled;
|
||||||
public RenderSettings Configuration;
|
public RenderConfig Configuration = null!;
|
||||||
|
|
||||||
// Render Sessions State (OOP Instance Fields)
|
// Render Sessions State (OOP Instance Fields)
|
||||||
public bool isRendering;
|
public bool isRendering;
|
||||||
@@ -52,47 +53,6 @@ namespace ADOFAIRenderer.Core
|
|||||||
public int throttleCount;
|
public int throttleCount;
|
||||||
public bool isThrottling;
|
public bool isThrottling;
|
||||||
|
|
||||||
public struct RenderSettings
|
|
||||||
{
|
|
||||||
public double TargetFPS;
|
|
||||||
public double VideoFPS;
|
|
||||||
|
|
||||||
public int RenderWidth;
|
|
||||||
public int RenderHeight;
|
|
||||||
|
|
||||||
public RateControlMode BitrateMode;
|
|
||||||
public int Bitrate;
|
|
||||||
public int MaxBitrate;
|
|
||||||
public int QP;
|
|
||||||
|
|
||||||
public string Codec;
|
|
||||||
public double EndWaitTime;
|
|
||||||
public bool ShowHitText;
|
|
||||||
public int? RandomSeed;
|
|
||||||
|
|
||||||
public RenderSettings(double targetFPS, double videoFPS, int renderWidth, int renderHeight,
|
|
||||||
RateControlMode bitrateMode, int bitrate, int maxBitrate, int qp,
|
|
||||||
string codec, double endWaitTime, bool showHitText, int? randomSeed)
|
|
||||||
{
|
|
||||||
TargetFPS = targetFPS;
|
|
||||||
VideoFPS = videoFPS;
|
|
||||||
|
|
||||||
RenderWidth = renderWidth;
|
|
||||||
RenderHeight = renderHeight;
|
|
||||||
|
|
||||||
BitrateMode = bitrateMode;
|
|
||||||
Bitrate = bitrate;
|
|
||||||
MaxBitrate = maxBitrate;
|
|
||||||
QP = qp;
|
|
||||||
|
|
||||||
Codec = codec;
|
|
||||||
EndWaitTime = endWaitTime;
|
|
||||||
ShowHitText = showHitText;
|
|
||||||
|
|
||||||
RandomSeed = randomSeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct CapturedFrame
|
internal struct CapturedFrame
|
||||||
{
|
{
|
||||||
public IntPtr NativePointer;
|
public IntPtr NativePointer;
|
||||||
@@ -209,7 +169,7 @@ namespace ADOFAIRenderer.Core
|
|||||||
DontDestroyOnLoad(_escapeHandlerObj);
|
DontDestroyOnLoad(_escapeHandlerObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
Time.captureDeltaTime = (float)(1 / Configuration.TargetFPS);
|
Time.captureDeltaTime = (float)(1 / Configuration.RenderFPS);
|
||||||
scrController.instance!.StartCoroutine(SetupPlay());
|
scrController.instance!.StartCoroutine(SetupPlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +187,7 @@ namespace ADOFAIRenderer.Core
|
|||||||
wonFrame = frameCount;
|
wonFrame = frameCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extraFrames = (long)Math.Ceiling(Configuration.EndWaitTime * Configuration.TargetFPS);
|
var extraFrames = (long)Math.Ceiling(Configuration.DelayAfterLevelEnd * Configuration.RenderFPS);
|
||||||
|
|
||||||
if (frameCount - wonFrame < extraFrames) return;
|
if (frameCount - wonFrame < extraFrames) return;
|
||||||
isRendering = isCapturing = false;
|
isRendering = isCapturing = false;
|
||||||
@@ -254,10 +214,10 @@ namespace ADOFAIRenderer.Core
|
|||||||
Melon<RenderMod>.Logger.Error($"[Render Skipped] Frame {frameCount} rendering failed after 60 retries. Supplying black frame.");
|
Melon<RenderMod>.Logger.Error($"[Render Skipped] Frame {frameCount} rendering failed after 60 retries. Supplying black frame.");
|
||||||
|
|
||||||
var currentFrame = frameCountForVideo++;
|
var currentFrame = frameCountForVideo++;
|
||||||
var targetFps = Configuration.TargetFPS;
|
var renderFPS = Configuration.RenderFPS;
|
||||||
var videoFps = Configuration.VideoFPS;
|
var videoFPS = Configuration.VideoFPS;
|
||||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
var effectiveVideoFps = Math.Min(videoFPS, renderFPS);
|
||||||
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / renderFPS);
|
||||||
|
|
||||||
if (videoFrameIndex > lastVideoFrameIndex)
|
if (videoFrameIndex > lastVideoFrameIndex)
|
||||||
{
|
{
|
||||||
@@ -317,11 +277,11 @@ namespace ADOFAIRenderer.Core
|
|||||||
|
|
||||||
var currentFrame = frameCountForVideo++;
|
var currentFrame = frameCountForVideo++;
|
||||||
|
|
||||||
var targetFps = Configuration.TargetFPS;
|
var renderFPS = Configuration.RenderFPS;
|
||||||
var videoFps = Configuration.VideoFPS;
|
var videoFps = Configuration.VideoFPS;
|
||||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
var effectiveVideoFps = Math.Min(videoFps, renderFPS);
|
||||||
|
|
||||||
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps);
|
var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / renderFPS);
|
||||||
|
|
||||||
if (videoFrameIndex > lastVideoFrameIndex)
|
if (videoFrameIndex > lastVideoFrameIndex)
|
||||||
{
|
{
|
||||||
@@ -396,6 +356,8 @@ namespace ADOFAIRenderer.Core
|
|||||||
scrController.instance.paused = scrController.instance.audioPaused = false;
|
scrController.instance.paused = scrController.instance.audioPaused = false;
|
||||||
scrController.instance.levelWasSkipped = true;
|
scrController.instance.levelWasSkipped = true;
|
||||||
|
|
||||||
|
if (!Configuration.ProcessAudio) yield break;
|
||||||
|
|
||||||
if (!scrConductor.instance || !scrConductor.instance.song) yield break;
|
if (!scrConductor.instance || !scrConductor.instance.song) yield break;
|
||||||
AudioSpectrumGenerator.Initialize(scrConductor.instance.song.clip);
|
AudioSpectrumGenerator.Initialize(scrConductor.instance.song.clip);
|
||||||
scrConductor.instance.song.clip = null;
|
scrConductor.instance.song.clip = null;
|
||||||
@@ -425,19 +387,14 @@ namespace ADOFAIRenderer.Core
|
|||||||
endRequested = false;
|
endRequested = false;
|
||||||
endSuccessful = false;
|
endSuccessful = false;
|
||||||
|
|
||||||
var targetFps = Configuration.TargetFPS;
|
var renderFPS = Configuration.RenderFPS;
|
||||||
var videoFps = Configuration.VideoFPS;
|
var videoFPS = Configuration.VideoFPS;
|
||||||
var bitrateMode = Configuration.BitrateMode;
|
var bitrateMode = Configuration.EncodingConfig!.RateControlMode;
|
||||||
var bitrate = Configuration.Bitrate;
|
var bitrate = Configuration.EncodingConfig!.Bitrate;
|
||||||
var maxBitrate = Configuration.MaxBitrate;
|
var maxBitrate = Configuration.EncodingConfig!.MaxBitrate;
|
||||||
var qp = Configuration.QP;
|
var qp = Configuration.EncodingConfig!.QP;
|
||||||
var codecStr = Configuration.Codec;
|
|
||||||
|
|
||||||
var selectedCodec = Codec.H264;
|
var selectedCodec = Configuration.Codec;
|
||||||
if (Enum.TryParse<Codec>(codecStr, true, out var parsedCodec))
|
|
||||||
{
|
|
||||||
selectedCodec = parsedCodec;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext = ".mp4";
|
var ext = ".mp4";
|
||||||
if (selectedCodec == Codec.AV1 || selectedCodec == Codec.VP9)
|
if (selectedCodec == Codec.AV1 || selectedCodec == Codec.VP9)
|
||||||
@@ -458,7 +415,7 @@ namespace ADOFAIRenderer.Core
|
|||||||
FFmpegNativeEncoder? videoFFmpeg = null;
|
FFmpegNativeEncoder? videoFFmpeg = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
var effectiveVideoFps = Math.Min(videoFPS, renderFPS);
|
||||||
var settings = new FFmpegNativeEncoder.EncoderSettings
|
var settings = new FFmpegNativeEncoder.EncoderSettings
|
||||||
{
|
{
|
||||||
Width = Configuration.RenderWidth,
|
Width = Configuration.RenderWidth,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ namespace ADOFAIRenderer.Data
|
|||||||
private struct FieldEntry
|
private struct FieldEntry
|
||||||
{
|
{
|
||||||
public List<DoubleLimit> Limits;
|
public List<DoubleLimit> Limits;
|
||||||
public bool Optional;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly VisualElement _rootVisualElement;
|
private readonly VisualElement _rootVisualElement;
|
||||||
@@ -25,34 +24,22 @@ namespace ADOFAIRenderer.Data
|
|||||||
/// Multiple limits can be added to the same field — they are all checked in order.
|
/// Multiple limits can be added to the same field — they are all checked in order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fieldName">The name of the DoubleField in the UXML.</param>
|
/// <param name="fieldName">The name of the DoubleField in the UXML.</param>
|
||||||
/// <param name="optional">
|
|
||||||
/// If true, the field is allowed to be empty. Limits are only enforced when a value is present.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="limits">The limits to apply.</param>
|
/// <param name="limits">The limits to apply.</param>
|
||||||
public DoubleInputValidator AddLimits(string fieldName, bool optional, params DoubleLimit[] limits)
|
public DoubleInputValidator AddLimits(string fieldName, params DoubleLimit[] limits)
|
||||||
{
|
{
|
||||||
if (!_fields.TryGetValue(fieldName, out var entry))
|
if (!_fields.TryGetValue(fieldName, out var entry))
|
||||||
{
|
{
|
||||||
entry = new FieldEntry
|
entry = new FieldEntry
|
||||||
{
|
{
|
||||||
Limits = new List<DoubleLimit>(),
|
Limits = new List<DoubleLimit>()
|
||||||
Optional = optional
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Optional = optional;
|
|
||||||
entry.Limits.AddRange(limits);
|
entry.Limits.AddRange(limits);
|
||||||
_fields[fieldName] = entry;
|
_fields[fieldName] = entry;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="AddLimits(string, bool, DoubleLimit[])"/>
|
|
||||||
/// <remarks>Shorthand for required (non-optional) fields.</remarks>
|
|
||||||
public DoubleInputValidator AddLimits(string fieldName, params DoubleLimit[] limits)
|
|
||||||
{
|
|
||||||
return AddLimits(fieldName, false, limits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries all registered fields from the root VisualElement and
|
/// Queries all registered fields from the root VisualElement and
|
||||||
/// registers value-changed callbacks that rollback invalid values.
|
/// registers value-changed callbacks that rollback invalid values.
|
||||||
@@ -73,12 +60,10 @@ namespace ADOFAIRenderer.Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
var limits = entry.Limits;
|
var limits = entry.Limits;
|
||||||
var optional = entry.Optional;
|
|
||||||
|
|
||||||
field.RegisterValueChangedCallback(evt =>
|
field.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
// Optional fields allow empty input — skip validation when cleared
|
if (string.IsNullOrEmpty(field.text))
|
||||||
if (optional && string.IsNullOrEmpty(field.text))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,16 @@ namespace ADOFAIRenderer.Data
|
|||||||
/// <typeparam name="T">The enum type to bind.</typeparam>
|
/// <typeparam name="T">The enum type to bind.</typeparam>
|
||||||
public class EnumDropdownBinder<T> where T : struct, Enum
|
public class EnumDropdownBinder<T> where T : struct, Enum
|
||||||
{
|
{
|
||||||
private readonly DropdownField _dropdown;
|
private readonly DropdownField? _dropdown;
|
||||||
private readonly List<T> _values;
|
private readonly List<T> _values;
|
||||||
|
private Action<T>? _onValueChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently selected enum value.
|
/// The currently selected enum value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T Value => _values[_dropdown.index >= 0 ? _dropdown.index : 0];
|
public T Value => _dropdown != null && _dropdown.index >= 0 && _dropdown.index < _values.Count
|
||||||
|
? _values[_dropdown.index]
|
||||||
|
: default;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries a <see cref="DropdownField"/> by name from the root VisualElement,
|
/// Queries a <see cref="DropdownField"/> by name from the root VisualElement,
|
||||||
@@ -30,18 +33,24 @@ namespace ADOFAIRenderer.Data
|
|||||||
/// <param name="defaultValue">The initial selected value. Defaults to the first enum member.</param>
|
/// <param name="defaultValue">The initial selected value. Defaults to the first enum member.</param>
|
||||||
public EnumDropdownBinder(VisualElement rootVisualElement, string fieldName, T? defaultValue = null)
|
public EnumDropdownBinder(VisualElement rootVisualElement, string fieldName, T? defaultValue = null)
|
||||||
{
|
{
|
||||||
|
_values = Enum.GetValues(typeof(T)).Cast<T>().ToList();
|
||||||
|
|
||||||
_dropdown = rootVisualElement.Q<DropdownField>(fieldName);
|
_dropdown = rootVisualElement.Q<DropdownField>(fieldName);
|
||||||
if (_dropdown == null)
|
if (_dropdown == null)
|
||||||
{
|
{
|
||||||
Melon<RenderMod>.Logger.Error(
|
Melon<RenderMod>.Logger.Error(
|
||||||
$"DropdownField '{fieldName}'을(를) 찾을 수 없습니다.");
|
$"DropdownField '{fieldName}'을(를) 찾을 수 없습니다.");
|
||||||
_values = new List<T>();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_values = Enum.GetValues(typeof(T)).Cast<T>().ToList();
|
|
||||||
_dropdown.choices = _values.Select(v => v.ToString()).ToList();
|
_dropdown.choices = _values.Select(v => v.ToString()).ToList();
|
||||||
|
|
||||||
|
// Register the callback BEFORE setting index so the initial selection fires it too
|
||||||
|
_dropdown.RegisterValueChangedCallback(_ =>
|
||||||
|
{
|
||||||
|
_onValueChanged?.Invoke(Value);
|
||||||
|
});
|
||||||
|
|
||||||
var initial = defaultValue ?? _values[0];
|
var initial = defaultValue ?? _values[0];
|
||||||
_dropdown.index = _values.IndexOf(initial);
|
_dropdown.index = _values.IndexOf(initial);
|
||||||
}
|
}
|
||||||
@@ -51,10 +60,13 @@ namespace ADOFAIRenderer.Data
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnValueChanged(Action<T> callback)
|
public void OnValueChanged(Action<T> callback)
|
||||||
{
|
{
|
||||||
_dropdown?.RegisterValueChangedCallback(evt =>
|
_onValueChanged = callback;
|
||||||
|
|
||||||
|
// Fire immediately with current value so the caller gets the initial state
|
||||||
|
if (_dropdown != null)
|
||||||
{
|
{
|
||||||
callback(Value);
|
callback(Value);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ namespace ADOFAIRenderer.Data
|
|||||||
private struct FieldEntry
|
private struct FieldEntry
|
||||||
{
|
{
|
||||||
public List<IntegerLimit> Limits;
|
public List<IntegerLimit> Limits;
|
||||||
public bool Optional;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly VisualElement _rootVisualElement;
|
private readonly VisualElement _rootVisualElement;
|
||||||
@@ -25,34 +24,22 @@ namespace ADOFAIRenderer.Data
|
|||||||
/// Multiple limits can be added to the same field — they are all checked in order.
|
/// Multiple limits can be added to the same field — they are all checked in order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fieldName">The name of the IntegerField in the UXML.</param>
|
/// <param name="fieldName">The name of the IntegerField in the UXML.</param>
|
||||||
/// <param name="optional">
|
|
||||||
/// If true, the field is allowed to be empty. Limits are only enforced when a value is present.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="limits">The limits to apply.</param>
|
/// <param name="limits">The limits to apply.</param>
|
||||||
public IntegerInputValidator AddLimits(string fieldName, bool optional, params IntegerLimit[] limits)
|
public IntegerInputValidator AddLimits(string fieldName, params IntegerLimit[] limits)
|
||||||
{
|
{
|
||||||
if (!_fields.TryGetValue(fieldName, out var entry))
|
if (!_fields.TryGetValue(fieldName, out var entry))
|
||||||
{
|
{
|
||||||
entry = new FieldEntry
|
entry = new FieldEntry
|
||||||
{
|
{
|
||||||
Limits = new List<IntegerLimit>(),
|
Limits = new List<IntegerLimit>()
|
||||||
Optional = optional
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Optional = optional;
|
|
||||||
entry.Limits.AddRange(limits);
|
entry.Limits.AddRange(limits);
|
||||||
_fields[fieldName] = entry;
|
_fields[fieldName] = entry;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="AddLimits(string, bool, IntegerLimit[])"/>
|
|
||||||
/// <remarks>Shorthand for required (non-optional) fields.</remarks>
|
|
||||||
public IntegerInputValidator AddLimits(string fieldName, params IntegerLimit[] limits)
|
|
||||||
{
|
|
||||||
return AddLimits(fieldName, false, limits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries all registered fields from the root VisualElement and
|
/// Queries all registered fields from the root VisualElement and
|
||||||
/// registers value-changed callbacks that rollback invalid values.
|
/// registers value-changed callbacks that rollback invalid values.
|
||||||
@@ -73,12 +60,10 @@ namespace ADOFAIRenderer.Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
var limits = entry.Limits;
|
var limits = entry.Limits;
|
||||||
var optional = entry.Optional;
|
|
||||||
|
|
||||||
field.RegisterValueChangedCallback(evt =>
|
field.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
// Optional fields allow empty input — skip validation when cleared
|
if (string.IsNullOrEmpty(field.text))
|
||||||
if (optional && string.IsNullOrEmpty(field.text))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-1
@@ -1,7 +1,31 @@
|
|||||||
namespace ADOFAIRenderer.Configuration
|
using ADOFAIRenderer.Util.FFmpeg;
|
||||||
|
|
||||||
|
namespace ADOFAIRenderer.Data
|
||||||
{
|
{
|
||||||
public class RenderConfig
|
public class RenderConfig
|
||||||
{
|
{
|
||||||
|
public double RenderFPS;
|
||||||
|
public double VideoFPS;
|
||||||
|
|
||||||
|
public int RenderWidth;
|
||||||
|
public int RenderHeight;
|
||||||
|
|
||||||
|
public Codec Codec;
|
||||||
|
public EncodingConfig? EncodingConfig = null;
|
||||||
|
|
||||||
|
public double DelayAfterLevelEnd;
|
||||||
|
public bool ShowJudgementText;
|
||||||
|
|
||||||
|
public int? RandomSeed = null;
|
||||||
|
public bool ProcessAudio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EncodingConfig
|
||||||
|
{
|
||||||
|
public RateControlMode RateControlMode;
|
||||||
|
|
||||||
|
public int Bitrate;
|
||||||
|
public int MaxBitrate;
|
||||||
|
public int QP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static class RenderEnvironmentPatches
|
public static class RenderEnvironmentPatches
|
||||||
{
|
{
|
||||||
private const bool RenderPatch = true;
|
private const bool RenderPatch = true;
|
||||||
|
private static double TimeSpent => RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.RenderFPS;
|
||||||
|
|
||||||
[HarmonyPatch(typeof(Time), "time", MethodType.Getter)]
|
[HarmonyPatch(typeof(Time), "time", MethodType.Getter)]
|
||||||
public static class TimePatch
|
public static class TimePatch
|
||||||
@@ -17,7 +18,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.timeBase);
|
__result = (float)(TimeSpent + RenderManager.Instance.timeBase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +30,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref double __result)
|
public static bool Prefix(ref double __result)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.timeBase;
|
__result = TimeSpent + RenderManager.Instance.timeBase;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.unscaledTimeBase);
|
__result = (float)(TimeSpent + RenderManager.Instance.unscaledTimeBase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +54,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref double __result)
|
public static bool Prefix(ref double __result)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.unscaledTimeBase;
|
__result = TimeSpent + RenderManager.Instance.unscaledTimeBase;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref double __result)
|
public static bool Prefix(ref double __result)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderManager.Instance.dspTimeBase;
|
__result = TimeSpent + RenderManager.Instance.dspTimeBase;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (RenderManager.Instance && (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance)) return true;
|
if (RenderManager.Instance && (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance)) return true;
|
||||||
__result = (float)(1 / RenderManager.Instance!.Configuration.TargetFPS);
|
__result = (float)(1 / RenderManager.Instance!.Configuration.RenderFPS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
public static bool Prefix(ref float __result)
|
public static bool Prefix(ref float __result)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance) return true;
|
||||||
__result = (float)(1 / RenderManager.Instance.Configuration.TargetFPS);
|
__result = (float)(1 / RenderManager.Instance.Configuration.RenderFPS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +147,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static bool Prefix(AudioSource __instance, float[] samples, int channel, FFTWindow window)
|
public static bool Prefix(AudioSource __instance, float[] samples, int channel, FFTWindow window)
|
||||||
{
|
{
|
||||||
if (!RenderManager.IsCapturingActive || !RenderPatch) return true;
|
if (!RenderManager.IsCapturingActive || !RenderPatch || !RenderManager.Instance.Configuration.ProcessAudio) return true;
|
||||||
AudioSpectrumGenerator.GetSpectrumData(samples, channel, window);
|
AudioSpectrumGenerator.GetSpectrumData(samples, channel, window);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,7 @@ namespace ADOFAIRenderer.Patch.Engine
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public static void Prefix(ref bool stream)
|
public static void Prefix(ref bool stream)
|
||||||
{
|
{
|
||||||
if (RenderManager.Instance != null)
|
if (RenderManager.Instance && RenderManager.Instance.Configuration.ProcessAudio)
|
||||||
{
|
{
|
||||||
stream = false;
|
stream = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace ADOFAIRenderer.Patch.Game
|
|||||||
{
|
{
|
||||||
public static bool Prefix()
|
public static bool Prefix()
|
||||||
{
|
{
|
||||||
return !RenderManager.IsActive || RenderManager.Instance is { Configuration: { ShowHitText: true } };
|
return !RenderManager.IsActive || RenderManager.Instance is { Configuration: { ShowJudgementText: true } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ namespace ADOFAIRenderer.Patch.Game
|
|||||||
if (RenderManager.Instance.wonFrame != -1)
|
if (RenderManager.Instance.wonFrame != -1)
|
||||||
{
|
{
|
||||||
var elapsedFrames = RenderManager.Instance.frameCount - RenderManager.Instance.wonFrame;
|
var elapsedFrames = RenderManager.Instance.frameCount - RenderManager.Instance.wonFrame;
|
||||||
var remainingTime = RenderManager.Instance.Configuration.EndWaitTime - elapsedFrames / RenderManager.Instance.Configuration.TargetFPS;
|
var remainingTime = RenderManager.Instance.Configuration.DelayAfterLevelEnd - elapsedFrames / RenderManager.Instance.Configuration.RenderFPS;
|
||||||
if (remainingTime < 0) remainingTime = 0;
|
if (remainingTime < 0) remainingTime = 0;
|
||||||
___txt.text += $"\nWaiting for {remainingTime:F1}s..";
|
___txt.text += $"\nWaiting for {remainingTime:F1}s..";
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ using MelonLoader.Utils;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.0.4-INDEV", "ADOFAI Editor Forum")]
|
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.0.5-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
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using ADOFAIRenderer.Core;
|
|
||||||
using ADOFAIRenderer.Util.FFmpeg;
|
|
||||||
using MelonLoader;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace ADOFAIRenderer.UI
|
|
||||||
{
|
|
||||||
public class RenderButton : MonoBehaviour
|
|
||||||
{
|
|
||||||
[Header("Input Fields")]
|
|
||||||
[SerializeField] private TMP_InputField targetFPSInput = null!;
|
|
||||||
[SerializeField] private TMP_InputField videoFPSInput = null!;
|
|
||||||
[SerializeField] private TMP_InputField renderWidthInput = null!;
|
|
||||||
[SerializeField] private TMP_InputField renderHeightInput = null!;
|
|
||||||
[SerializeField] private TMP_InputField waitAfterEndInput = null!;
|
|
||||||
[SerializeField] private TMP_InputField randomSeedInput = null!;
|
|
||||||
|
|
||||||
[Header("Dropdown")]
|
|
||||||
[SerializeField] private TMP_Dropdown codecDropdown = null!;
|
|
||||||
[SerializeField] private TMP_Dropdown bitrateModeDropdown = null!;
|
|
||||||
|
|
||||||
[Header("Toggle")]
|
|
||||||
[SerializeField] private Toggle showHitTextToggle = null!;
|
|
||||||
|
|
||||||
[Header("Button")]
|
|
||||||
[SerializeField] private Button advancedSettingsButton = null!;
|
|
||||||
|
|
||||||
[Header("Bitrate Dynamic UI Container")]
|
|
||||||
[SerializeField] private RectTransform bitrateContainer = null!;
|
|
||||||
|
|
||||||
// Dynamically instantiated inputs
|
|
||||||
private TMP_InputField _bitrateInput = null!;
|
|
||||||
private TMP_InputField _maxBitrateInput = null!;
|
|
||||||
private TMP_InputField _qpInput = null!;
|
|
||||||
|
|
||||||
private Button _buttonComponent = null!;
|
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
_buttonComponent = GetComponent<Button>();
|
|
||||||
_buttonComponent.onClick.AddListener(OnClick);
|
|
||||||
advancedSettingsButton.onClick.AddListener(AdvancedSettingsClick);
|
|
||||||
|
|
||||||
RenderMod.FixEventSystem();
|
|
||||||
|
|
||||||
if (codecDropdown != null)
|
|
||||||
{
|
|
||||||
codecDropdown.ClearOptions();
|
|
||||||
var options = System.Enum.GetNames(typeof(Codec)).ToList();
|
|
||||||
codecDropdown.AddOptions(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Initialize Bitrate Mode Dropdown
|
|
||||||
if (bitrateModeDropdown != null)
|
|
||||||
{
|
|
||||||
bitrateModeDropdown.ClearOptions();
|
|
||||||
var modeOptions = System.Enum.GetNames(typeof(RateControlMode)).ToList();
|
|
||||||
bitrateModeDropdown.AddOptions(modeOptions);
|
|
||||||
bitrateModeDropdown.onValueChanged.AddListener(delegate { UpdateUIByMode(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Dynamically instantiate input fields using targetFPSInput as a styling template
|
|
||||||
if (bitrateContainer != null && targetFPSInput != null)
|
|
||||||
{
|
|
||||||
// Instantiate bitrateInput
|
|
||||||
var bitrateInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
|
|
||||||
bitrateInputObj.name = "Dynamic_BitrateInput";
|
|
||||||
_bitrateInput = bitrateInputObj.GetComponent<TMP_InputField>();
|
|
||||||
_bitrateInput.text = "15000";
|
|
||||||
SetPlaceholderText(_bitrateInput, "비트레이트 (kbps)");
|
|
||||||
|
|
||||||
// Instantiate maxBitrateInput
|
|
||||||
var maxBitrateInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
|
|
||||||
maxBitrateInputObj.name = "Dynamic_MaxBitrateInput";
|
|
||||||
_maxBitrateInput = maxBitrateInputObj.GetComponent<TMP_InputField>();
|
|
||||||
_maxBitrateInput.text = "30000";
|
|
||||||
SetPlaceholderText(_maxBitrateInput, "최대 비트레이트 (kbps)");
|
|
||||||
|
|
||||||
// Instantiate qpInput
|
|
||||||
var qpInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
|
|
||||||
qpInputObj.name = "Dynamic_QPInput";
|
|
||||||
_qpInput = qpInputObj.GetComponent<TMP_InputField>();
|
|
||||||
_qpInput.text = "18";
|
|
||||||
SetPlaceholderText(_qpInput, "QP (0-51) / CRF");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Perform initial UI state updates based on default mode
|
|
||||||
UpdateUIByMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPlaceholderText(TMP_InputField inputField, string text)
|
|
||||||
{
|
|
||||||
if (inputField.placeholder == null) return;
|
|
||||||
var tmpText = inputField.placeholder.GetComponent<TMP_Text>();
|
|
||||||
|
|
||||||
if (tmpText)
|
|
||||||
{
|
|
||||||
tmpText.text = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateUIByMode()
|
|
||||||
{
|
|
||||||
if (bitrateModeDropdown == null) return;
|
|
||||||
if (_bitrateInput == null || _maxBitrateInput == null || _qpInput == null) return;
|
|
||||||
|
|
||||||
var selectedMode = (RateControlMode)bitrateModeDropdown.value;
|
|
||||||
|
|
||||||
switch (selectedMode)
|
|
||||||
{
|
|
||||||
case RateControlMode.CBR:
|
|
||||||
_bitrateInput.gameObject.SetActive(true);
|
|
||||||
_maxBitrateInput.gameObject.SetActive(false);
|
|
||||||
_qpInput.gameObject.SetActive(false);
|
|
||||||
break;
|
|
||||||
case RateControlMode.VBR:
|
|
||||||
_bitrateInput.gameObject.SetActive(true);
|
|
||||||
_maxBitrateInput.gameObject.SetActive(true);
|
|
||||||
_qpInput.gameObject.SetActive(false);
|
|
||||||
break;
|
|
||||||
case RateControlMode.CQP:
|
|
||||||
_bitrateInput.gameObject.SetActive(false);
|
|
||||||
_maxBitrateInput.gameObject.SetActive(false);
|
|
||||||
_qpInput.gameObject.SetActive(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitrateContainer != null)
|
|
||||||
{
|
|
||||||
Canvas.ForceUpdateCanvases();
|
|
||||||
LayoutRebuilder.ForceRebuildLayoutImmediate(bitrateContainer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
if (Input.GetKeyDown(KeyCode.Escape))
|
|
||||||
{
|
|
||||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnClick()
|
|
||||||
{
|
|
||||||
if (IsAnyFieldEmpty()) return;
|
|
||||||
if (RenderManager.IsActive) return;
|
|
||||||
|
|
||||||
if (!double.TryParse(targetFPSInput.text, out var targetFPS) || targetFPS <= 0)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Target FPS 값입니다. 0보다 큰 숫자여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!double.TryParse(videoFPSInput.text, out var videoFPS) || videoFPS <= 0)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Video FPS 값입니다. 0보다 큰 숫자여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!int.TryParse(renderWidthInput.text, out var renderWidth) || renderWidth <= 0 || renderWidth > SystemInfo.maxTextureSize)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error($"올바르지 않은 Render Width 값입니다. 1에서 {SystemInfo.maxTextureSize} 사이의 정수여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!int.TryParse(renderHeightInput.text, out var renderHeight) || renderHeight <= 0 || renderHeight > SystemInfo.maxTextureSize)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error($"올바르지 않은 Render Height 값입니다. 1에서 {SystemInfo.maxTextureSize} 사이의 정수여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bitrateMode = RateControlMode.CBR;
|
|
||||||
if (bitrateModeDropdown != null)
|
|
||||||
{
|
|
||||||
bitrateMode = (RateControlMode)bitrateModeDropdown.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bitrate = 0;
|
|
||||||
var maxBitrate = 0;
|
|
||||||
var qp = 18;
|
|
||||||
|
|
||||||
if (bitrateMode == RateControlMode.CBR || bitrateMode == RateControlMode.VBR)
|
|
||||||
{
|
|
||||||
if (!int.TryParse(_bitrateInput.text, out bitrate) || bitrate <= 0 || bitrate > 1_000_000)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Bitrate 값입니다. 1에서 1,000,000 (kbps) 사이의 정수여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitrateMode == RateControlMode.VBR)
|
|
||||||
{
|
|
||||||
if (!int.TryParse(_maxBitrateInput.text, out maxBitrate) || maxBitrate <= 0 || maxBitrate > 1_000_000)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Max Bitrate 값입니다. 1에서 1,000,000 (kbps) 사이의 정수여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (maxBitrate < bitrate)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("Max Bitrate는 Target Bitrate보다 크거나 같아야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitrateMode == RateControlMode.CQP)
|
|
||||||
{
|
|
||||||
if (!int.TryParse(_qpInput.text, out qp) || qp < 0 || qp > 51)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 QP 값입니다. 0에서 51 사이의 정수여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!double.TryParse(waitAfterEndInput.text, out var waitAfterEnd) || waitAfterEnd < 0)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Wait After End 값입니다. 0 이상의 숫자여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int? parsedRandomSeed = null;
|
|
||||||
var randomSeedText = randomSeedInput.text.Truncate(int.MaxValue);
|
|
||||||
if (!randomSeedText.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
if (!int.TryParse(randomSeedText, out var seedVal))
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Random Seed 값입니다. 비어 있거나 정수여야 합니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
parsedRandomSeed = seedVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RenderManager.Instance)
|
|
||||||
{
|
|
||||||
Melon<RenderMod>.Logger.Msg($"레벨 로드: {RenderManager.Instance.levelPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedCodecIndex = codecDropdown.value;
|
|
||||||
var selectedCodecName = codecDropdown.options[selectedCodecIndex].text;
|
|
||||||
|
|
||||||
var showHitText = showHitTextToggle != null && showHitTextToggle.isOn;
|
|
||||||
|
|
||||||
if (RenderManager.Instance)
|
|
||||||
{
|
|
||||||
RenderManager.Instance.Configuration = new RenderManager.RenderSettings(
|
|
||||||
targetFPS,
|
|
||||||
videoFPS,
|
|
||||||
renderWidth,
|
|
||||||
renderHeight,
|
|
||||||
bitrateMode,
|
|
||||||
bitrate,
|
|
||||||
maxBitrate,
|
|
||||||
qp,
|
|
||||||
selectedCodecName,
|
|
||||||
waitAfterEnd,
|
|
||||||
showHitText,
|
|
||||||
parsedRandomSeed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderManager.Instance!.SetupCaptureDirectory();
|
|
||||||
RenderManager.Instance.SetupGameScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AdvancedSettingsClick()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsAnyFieldEmpty()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(targetFPSInput.text)) return true;
|
|
||||||
if (string.IsNullOrWhiteSpace(videoFPSInput.text)) return true;
|
|
||||||
if (string.IsNullOrWhiteSpace(renderWidthInput.text)) return true;
|
|
||||||
if (string.IsNullOrWhiteSpace(renderHeightInput.text)) return true;
|
|
||||||
if (string.IsNullOrWhiteSpace(waitAfterEndInput.text)) return true;
|
|
||||||
|
|
||||||
if (_bitrateInput != null && _bitrateInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_bitrateInput.text)) return true;
|
|
||||||
if (_maxBitrateInput != null && _maxBitrateInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_maxBitrateInput.text)) return true;
|
|
||||||
if (_qpInput != null && _qpInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_qpInput.text)) return true;
|
|
||||||
|
|
||||||
return codecDropdown.value < 0 || (bitrateModeDropdown != null && bitrateModeDropdown.value < 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+135
-3
@@ -1,12 +1,144 @@
|
|||||||
|
using System.Text;
|
||||||
|
using ADOFAIRenderer.Core;
|
||||||
|
using ADOFAIRenderer.Data;
|
||||||
|
using ADOFAIRenderer.Util.FFmpeg;
|
||||||
|
using MelonLoader;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
namespace ADOFAIRenderer.Core
|
namespace ADOFAIRenderer.UI
|
||||||
{
|
{
|
||||||
public class RenderConfigUIManager : MonoBehaviour
|
public class RenderConfigUIManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
void Awake()
|
[SerializeField]
|
||||||
{
|
private UIDocument uiDocument = null!;
|
||||||
|
|
||||||
|
private VisualElement _root = null!;
|
||||||
|
private IntegerInputValidator _intValidator = null!;
|
||||||
|
private DoubleInputValidator _doubleValidator = null!;
|
||||||
|
private EnumDropdownBinder<Codec> _codecBinder = null!;
|
||||||
|
|
||||||
|
private Button _goRenderButton = null!;
|
||||||
|
private Button _encodingConfigButton = null!;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_root = uiDocument.rootVisualElement;
|
||||||
|
RenderManager.Instance.Configuration = new RenderConfig();
|
||||||
|
|
||||||
|
_intValidator = new IntegerInputValidator(_root);
|
||||||
|
_doubleValidator = new DoubleInputValidator(_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Msg("[UI] Start 진입");
|
||||||
|
|
||||||
|
// Init Validators
|
||||||
|
_intValidator
|
||||||
|
.AddLimits("RenderWidth", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(1, 7680))
|
||||||
|
.AddLimits("RenderHeight", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(1, 4320));
|
||||||
|
|
||||||
|
_doubleValidator
|
||||||
|
.AddLimits("RenderFPS", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 9600))
|
||||||
|
.AddLimits("VideoFPS", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 4800))
|
||||||
|
.AddLimits("DelayAfterLevelEnd", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 86400));
|
||||||
|
|
||||||
|
_intValidator.SetupValidation();
|
||||||
|
_doubleValidator.SetupValidation();
|
||||||
|
Melon<RenderMod>.Logger.Msg("[UI] Validator 설정 완료");
|
||||||
|
|
||||||
|
// Init Enum Binders & Event Listeners
|
||||||
|
_codecBinder = new EnumDropdownBinder<Codec>(_root, "CodecDropdown", Codec.H264);
|
||||||
|
Melon<RenderMod>.Logger.Msg("[UI] CodecBinder 생성 완료");
|
||||||
|
_codecBinder.OnValueChanged(value => RenderManager.Instance.Configuration.Codec = value);
|
||||||
|
Melon<RenderMod>.Logger.Msg("[UI] CodecBinder 콜백 등록 완료");
|
||||||
|
|
||||||
|
// Bind Data
|
||||||
|
_root.dataSource = RenderManager.Instance.Configuration;
|
||||||
|
|
||||||
|
// Attach Button Event Listener
|
||||||
|
_goRenderButton = _root.Q<Button>("GoRenderButton");
|
||||||
|
if (_goRenderButton != null)
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Msg($"[UI] GoRenderButton 발견: pickingMode={_goRenderButton.pickingMode}, enabledSelf={_goRenderButton.enabledSelf}");
|
||||||
|
_goRenderButton.RegisterCallback<ClickEvent>(_ => TryGoRender());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Error("Button 'GoRenderButton'을(를) 찾을 수 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_encodingConfigButton = _root.Q<Button>("EncodingSettingsButton");
|
||||||
|
if (_encodingConfigButton != null)
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Msg($"[UI] EncodingSettingsButton 발견: pickingMode={_encodingConfigButton.pickingMode}, enabledSelf={_encodingConfigButton.enabledSelf}");
|
||||||
|
_encodingConfigButton.RegisterCallback<ClickEvent>(_ => OpenEncodingConfig());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Error("Button 'EncodingSettingsButton'을(를) 찾을 수 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenEncodingConfig()
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Msg("[UI] OpenEncodingConfig 호출됨");
|
||||||
|
RenderManager.Instance.Configuration.EncodingConfig ??= GenerateEncodingConfig(_codecBinder.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryGoRender()
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Msg("[UI] TryGoRender 호출됨");
|
||||||
|
|
||||||
|
var widthField = _root.Q<IntegerField>("RenderWidth");
|
||||||
|
var heightField = _root.Q<IntegerField>("RenderHeight");
|
||||||
|
var randomSeedField = _root.Q<IntegerField>("RandomSeed");
|
||||||
|
var renderFpsField = _root.Q<DoubleField>("RenderFPS");
|
||||||
|
var videoFpsField = _root.Q<DoubleField>("VideoFPS");
|
||||||
|
var delayField = _root.Q<DoubleField>("DelayAfterLevelEnd");
|
||||||
|
|
||||||
|
var showJudgementText = _root.Q<Toggle>("ShowJudgementText");
|
||||||
|
var processAudio = _root.Q<Toggle>("ProcessAudioToggle");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(widthField.text) ||
|
||||||
|
string.IsNullOrEmpty(heightField.text) ||
|
||||||
|
string.IsNullOrEmpty(renderFpsField.text) ||
|
||||||
|
string.IsNullOrEmpty(videoFpsField.text) ||
|
||||||
|
string.IsNullOrEmpty(delayField.text))
|
||||||
|
{
|
||||||
|
Melon<RenderMod>.Logger.Error("필수 입력 값이 비어 있습니다. 모든 설정을 입력해 주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy validated values to Configuration manually
|
||||||
|
var config = RenderManager.Instance.Configuration;
|
||||||
|
config.RenderWidth = widthField.value;
|
||||||
|
config.RenderHeight = heightField.value;
|
||||||
|
config.RenderFPS = renderFpsField.value;
|
||||||
|
config.VideoFPS = videoFpsField.value;
|
||||||
|
config.DelayAfterLevelEnd = delayField.value;
|
||||||
|
|
||||||
|
config.ShowJudgementText = showJudgementText.value;
|
||||||
|
config.ProcessAudio = processAudio.value;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(randomSeedField.text))
|
||||||
|
config.RandomSeed = randomSeedField.value;
|
||||||
|
|
||||||
|
RenderManager.Instance.Configuration.EncodingConfig ??= GenerateEncodingConfig(_codecBinder.Value);
|
||||||
|
|
||||||
|
RenderManager.Instance.SetupCaptureDirectory();
|
||||||
|
RenderManager.Instance.SetupGameScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EncodingConfig GenerateEncodingConfig(Codec codec)
|
||||||
|
{
|
||||||
|
return new EncodingConfig
|
||||||
|
{
|
||||||
|
RateControlMode = RateControlMode.CBR,
|
||||||
|
Bitrate = 30000, MaxBitrate = 50000,
|
||||||
|
QP = 18
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user