Document Render Core And API

This commit is contained in:
copecone committed 2026-10-15 16:17:13 +09:00
1 parent 354c42fdd9
commit 8deb64fd4d
22 files changed
+1241 -27

No files matched your search

+12
View File
@@ -4,14 +4,22 @@ using FFmpeg.AutoGen;
namespace ADOFAIRenderer.Data;
/// <summary>
/// Video codec of the output. The encoder implementation (hardware or software) is chosen at run time from the
/// encoders available for the codec (VideoEncoder, GPUUtil.GetPrioritizedEncoders).
/// Values: <c>H264</c> (H.264 / AVC), <c>H265</c> (H.265 / HEVC), <c>VP9</c>, <c>AV1</c>, <c>VVC</c> (H.266).
/// The audio codec paired with each value is given by CodecUtil.AudioCodecMatch.
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum Codec
{
H264, H265, VP9, AV1, VVC
}
/// <summary>Audio codec choice and supported sample rates per video codec.</summary>
internal static class CodecUtil
{
/// <summary>Audio codec encoded together with each video codec (AAC for H.264/H.265, Opus for the others).</summary>
public static readonly Dictionary<Codec, AVCodecID> AudioCodecMatch = new()
{
[Codec.H264] = AVCodecID.AV_CODEC_ID_AAC,
@@ -21,6 +29,10 @@ internal static class CodecUtil
[Codec.VVC] = AVCodecID.AV_CODEC_ID_OPUS
};
/// <summary>
/// Output sample rates (Hz) the mod allows per audio codec. RenderManager.SetupPlay replaces a configured rate that
/// is not in the list with 48000 Hz.
/// </summary>
public static readonly Dictionary<AVCodecID, List<int>> AvailableSampleRate = new()
{
[AVCodecID.AV_CODEC_ID_AAC] = new List<int> { 44100, 48000, 96000 },
+18
View File
@@ -10,13 +10,20 @@ namespace ADOFAIRenderer.Data;
/// Converts Unity <see cref="Color"/> and nullable <see cref="Color"/> to and from hex string (#RRGGBBAA).
/// Pure C# implementation to completely avoid Unity internal native ECalls (such as Color.linear or ColorUtility.TryParseHtmlString).
/// </summary>
/// <remarks>
/// Reading accepts "#RRGGBB" (alpha 1), "#RRGGBBAA" (the '#' is optional), an object {"r","g","b","a"} with 0–1 floats
/// (missing r/g/b = 0, missing a = 1), or null. Invalid or unsupported input reads as null for Color? and as
/// default(Color) (all components 0) for Color; no exception is thrown for an unparsable string.
/// </remarks>
public class ColorJsonConverter : JsonConverter
{
/// <summary>True for <see cref="Color"/> and <see cref="Nullable{Color}"/>.</summary>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Color) || objectType == typeof(Color?);
}
/// <summary>Writes the color as "#RRGGBBAA" (see <see cref="ToHex"/>), or a JSON null for a null value.</summary>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
@@ -29,6 +36,7 @@ public class ColorJsonConverter : JsonConverter
writer.WriteValue(ToHex(color));
}
/// <summary>Reads a color in one of the accepted forms (see the class remarks).</summary>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
bool isNullable = Nullable.GetUnderlyingType(objectType) != null || !objectType.IsValueType;
@@ -62,6 +70,10 @@ public class ColorJsonConverter : JsonConverter
return isNullable ? null : (object)default(Color);
}
/// <summary>
/// Formats the color as "#RRGGBBAA" with uppercase hex digits. Each component is scaled to 0–255, rounded and clamped.
/// The components are written as stored (no gamma conversion).
/// </summary>
public static string ToHex(Color color)
{
byte r = (byte)Math.Clamp((int)Math.Round(color.r * 255f), 0, 255);
@@ -71,6 +83,12 @@ public class ColorJsonConverter : JsonConverter
return $"#{r:X2}{g:X2}{b:X2}{a:X2}";
}
/// <summary>
/// Parses "RRGGBB" or "RRGGBBAA" (optional leading '#', surrounding white space ignored, case-insensitive).
/// </summary>
/// <param name="hex">Text to parse.</param>
/// <param name="color">Parsed color (components / 255; alpha 1 for the 6-digit form), or default when parsing fails.</param>
/// <returns>True when the text was parsed.</returns>
public static bool TryParseHex(string? hex, out Color color)
{
color = default;
+9
View File
@@ -2,10 +2,19 @@
namespace ADOFAIRenderer.Data;
/// <summary>Video bitrate control mode (see VideoEncodingConfig and FFmpegNativeEncoder.ApplyQualitySettings).</summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum RateControlMode
{
/// <summary>Constant bitrate at VideoEncodingConfig.Bitrate (kbps).</summary>
CBR,
/// <summary>
/// Variable bitrate averaging VideoEncodingConfig.Bitrate (kbps), peaking at MaxBitrate (kbps) when it is larger,
/// otherwise at 1.5 × Bitrate.
/// </summary>
VBR,
/// <summary>Constant quality with VideoEncodingConfig.QP (lower is better; range depends on the encoder).</summary>
CQP
}
+86
View File
@@ -4,22 +4,50 @@ using UnityEngine;
namespace ADOFAIRenderer.Data;
/// <summary>
/// Settings of one render: resolution, frame rates, encoding and advanced options. Serialized to JSON in the preset
/// files (PresetManager). <see cref="ADOFAIRenderer.API.RenderSession.Start"/> renders with a copy (<see cref="Clone"/>).
/// </summary>
[SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
public class RenderConfig
{
/// <summary>
/// Game frames simulated per second of render time (the game clock advances 1 / RenderFPS seconds per frame).
/// Values above <see cref="VideoFPS"/> render extra frames between video frames, which are not captured
/// (the game state then advances in smaller time steps).
/// </summary>
public double RenderFPS { get; set; }
/// <summary>
/// Frame rate of the output video. The effective rate is min(VideoFPS, RenderFPS).
/// </summary>
public double VideoFPS { get; set; }
/// <summary>Width of the rendered frames and the output video in pixels (also reported by the Screen.width patch).</summary>
public int RenderWidth { get; set; }
/// <summary>Height of the rendered frames and the output video in pixels (also reported by the Screen.height patch).</summary>
public int RenderHeight { get; set; }
/// <summary>Video encoder settings.</summary>
public VideoEncodingConfig VideoConfig { get; set; } = new();
/// <summary>Audio rendering and encoder settings.</summary>
public AudioEncodingConfig AudioConfig { get; set; } = new();
/// <summary>Advanced options (random seed, spectrum processing, tile hiding, container, planet colors).</summary>
public AdvancedConfig AdvancedConfig { get; set; } = new();
/// <summary>Seconds of render time recorded after the end condition first holds (default 15).</summary>
public double DelayAfterEnd { get; set; } = 15;
/// <summary>Whether the judgement texts (hit accuracy texts) are shown during the render (scrHitTextManagerPatch). Default false.</summary>
public bool ShowJudgementText { get; set; } = false;
/// <summary>
/// Copies every value of <paramref name="source"/> into this object; the nested configs are copied value by value
/// into the existing nested objects. Does nothing when <paramref name="source"/> is null.
/// </summary>
public void CopyFrom(RenderConfig? source)
{
if (source == null) return;
@@ -35,6 +63,7 @@ public class RenderConfig
AdvancedConfig.CopyFrom(source.AdvancedConfig);
}
/// <summary>Returns a deep copy (nested configs are new objects).</summary>
public RenderConfig Clone()
{
var clone = new RenderConfig();
@@ -43,23 +72,48 @@ public class RenderConfig
}
}
/// <summary>Advanced render options.</summary>
[SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
public class AdvancedConfig
{
/// <summary>
/// Seed for UnityEngine.Random set before the game scene loads, so random visual effects repeat between renders;
/// null leaves the random state unchanged.
/// </summary>
public int? RandomSeed { get; set; } = null;
/// <summary>
/// When true, AudioSource.GetSpectrumData is answered from the offline song audio at the render time
/// (AudioSpectrumGenerator), so audio-reactive visuals follow the rendered audio. Default true.
/// </summary>
public bool ProcessAudio { get; set; } = true;
/// <summary>
/// When true, the level's floor tiles (including hold meshes and the portal particles of the level's floors) and the
/// planets are hidden during the render, so only the background and decorations remain (scnGamePatch, scrFloorPatch,
/// RenderManager.OnLastUpdate). Default false.
/// </summary>
public bool NonTile { 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";
/// <summary>Color of the fire (red) planet during the render; null keeps the user's setting. Serialized as "#RRGGBBAA".</summary>
[JsonConverter(typeof(ColorJsonConverter))]
public Color? firePlanetColor = null;
/// <summary>Color of the ice (blue) planet during the render; null keeps the user's setting. Serialized as "#RRGGBBAA".</summary>
[JsonConverter(typeof(ColorJsonConverter))]
public Color? icePlanetColor = null;
/// <summary>
/// Color of the third (wind, planetGreen) planet during the render (PlanetarySystemPatch); null keeps the game's color.
/// Serialized as "#RRGGBBAA".
/// </summary>
[JsonConverter(typeof(ColorJsonConverter))]
public Color? windPlanetColor = null;
/// <summary>Copies every value of <paramref name="source"/>; does nothing when it is null.</summary>
public void CopyFrom(AdvancedConfig? source)
{
if (source == null) return;
@@ -72,6 +126,7 @@ public class AdvancedConfig
windPlanetColor = source.windPlanetColor;
}
/// <summary>Returns a copy of this object.</summary>
public AdvancedConfig Clone()
{
var clone = new AdvancedConfig();
@@ -80,18 +135,35 @@ public class AdvancedConfig
}
}
/// <summary>Video encoder settings (passed to FFmpegNativeEncoder.EncoderSettings).</summary>
public class VideoEncodingConfig
{
/// <summary>Video codec (default H.264). Also selects the audio codec (CodecUtil.AudioCodecMatch).</summary>
public Codec Codec { get; set; } = Codec.H264;
/// <summary>Bitrate control mode (default CBR).</summary>
public RateControlMode RateControlMode { get; set; } = RateControlMode.CBR;
/// <summary>Target video bitrate in kbps for CBR and VBR (default 30000 = 30 Mbps).</summary>
public int Bitrate { get; set; } = 30000;
/// <summary>Peak video bitrate in kbps for VBR, used when larger than Bitrate (otherwise 1.5 × Bitrate). Default 50000.</summary>
public int MaxBitrate { get; set; } = 50000;
/// <summary>
/// Quality value for CQP: lower is higher quality. Clamped to the range of the selected encoder; a negative value
/// selects 18. Default 18.
/// </summary>
// ReSharper disable once InconsistentNaming
public int QP { get; set; } = 18;
/// <summary>Keyframe interval in seconds (GOP length = round(effective video FPS × interval) frames, at least 1). Default 2.</summary>
public double KeyframeInterval { get; set; } = 2.0;
/// <summary>When true, only software (CPU) encoders are tried; hardware encoders are skipped.</summary>
public bool ForceSoftwareEncoder { get; set; }
/// <summary>Copies every value of <paramref name="source"/>; does nothing when it is null.</summary>
public void CopyFrom(VideoEncodingConfig? source)
{
if (source == null) return;
@@ -104,6 +176,7 @@ public class VideoEncodingConfig
ForceSoftwareEncoder = source.ForceSoftwareEncoder;
}
/// <summary>Returns a copy of this object.</summary>
public VideoEncodingConfig Clone()
{
var clone = new VideoEncodingConfig();
@@ -112,32 +185,44 @@ public class VideoEncodingConfig
}
}
/// <summary>
/// Audio rendering and encoder settings. Non-positive values of the numeric settings read back as their defaults
/// (48000 Hz, 2 channels, 256000 bps).
/// </summary>
public class AudioEncodingConfig
{
private int _audioSampleRate = 48000;
private int _audioChannels = 2;
private int _audioBitrate = 256000;
/// <summary>When true, the song and sound effects are mixed offline and encoded as an audio stream. Default true.</summary>
public bool RenderAudio { get; set; } = true;
/// <summary>
/// Output sample rate in Hz (default 48000). A rate the audio codec does not support in the mod
/// (CodecUtil.AvailableSampleRate) is replaced with 48000 when the render starts.
/// </summary>
public int AudioSampleRate
{
get => _audioSampleRate <= 0 ? 48000 : _audioSampleRate;
set => _audioSampleRate = value <= 0 ? 48000 : value;
}
/// <summary>Number of output audio channels (default 2).</summary>
public int AudioChannels
{
get => _audioChannels <= 0 ? 2 : _audioChannels;
set => _audioChannels = value <= 0 ? 2 : value;
}
/// <summary>Audio bitrate in bits per second (default 256000 = 256 kbps). Unit differs from the video bitrate (kbps).</summary>
public int AudioBitrate
{
get => _audioBitrate <= 0 ? 256000 : _audioBitrate;
set => _audioBitrate = value <= 0 ? 256000 : value;
}
/// <summary>Copies every value of <paramref name="source"/>; does nothing when it is null.</summary>
public void CopyFrom(AudioEncodingConfig? source)
{
if (source == null) return;
@@ -147,6 +232,7 @@ public class AudioEncodingConfig
AudioBitrate = source.AudioBitrate;
}
/// <summary>Returns a copy of this object.</summary>
public AudioEncodingConfig Clone()
{
var clone = new AudioEncodingConfig();