SHA256
Document Render Core And API
This commit is contained in:
1 parent
354c42fdd9
commit
8deb64fd4d
22 files changed
+1241
-27
No files matched your search
+69
-9
@@ -18,6 +18,21 @@ namespace ADOFAIRenderer.API;
|
||||
/// When <c>RenderFPS</c> exceeds <c>VideoFPS</c>, intermediate game rendering frames are skipped and the hook
|
||||
/// will <b>not</b> be invoked on every rendered game frame.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Ping-pong model:</b> the renderer owns two RenderTextures with the size and format of the game frame.
|
||||
/// <see cref="Source"/> is the current frame, <see cref="Destination"/> is the buffer the next pass writes.
|
||||
/// A pass draws from <see cref="Source"/> into <see cref="Destination"/> and then calls <see cref="Swap"/>, which makes
|
||||
/// the written buffer the new <see cref="Source"/>. <see cref="Blit(Material, int)"/>, and by default
|
||||
/// <see cref="DrawToDestination"/> and <see cref="ExecuteCommandBuffer"/>, swap automatically. The texture in
|
||||
/// <see cref="Current"/> after the last hook is encoded. When no hook swaps or replaces the frame, the game frame is
|
||||
/// encoded unchanged. The first <see cref="Source"/> of every frame is the game's own frame texture
|
||||
/// (<see cref="OriginalSource"/>); do not write into it.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Threading and lifetime:</b> hooks run on the main Unity thread at the end of the frame, after the game has
|
||||
/// rendered. The textures are valid only during the hook call: do not keep references to <see cref="Source"/>,
|
||||
/// <see cref="Destination"/> or the context. <c>RenderTexture.active</c> is restored after all hooks have run.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
@@ -25,12 +40,22 @@ namespace ADOFAIRenderer.API;
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
|
||||
public class FrameHookContext
|
||||
{
|
||||
/// <summary>Game frame of this capture (camera quad texture); never written by the context.</summary>
|
||||
private RenderTexture _originalSource = null!;
|
||||
|
||||
/// <summary>First ping-pong buffer, owned by FrameHookPipeline.</summary>
|
||||
private RenderTexture _pingRT = null!;
|
||||
|
||||
/// <summary>Second ping-pong buffer, owned by FrameHookPipeline.</summary>
|
||||
private RenderTexture _pongRT = null!;
|
||||
|
||||
/// <summary>Current frame: the original source, a ping-pong buffer, or a texture set by ReplaceCurrent.</summary>
|
||||
private RenderTexture _currentSource = null!;
|
||||
|
||||
/// <summary>Buffer the next pass writes: always the ping or pong buffer.</summary>
|
||||
private RenderTexture _currentDestination = null!;
|
||||
|
||||
/// <summary>True after the first Swap or ReplaceCurrent of this frame.</summary>
|
||||
private bool _hasModified;
|
||||
|
||||
/// <summary>
|
||||
@@ -45,6 +70,7 @@ public class FrameHookContext
|
||||
|
||||
/// <summary>
|
||||
/// The active <see cref="RenderTexture"/> representing the current state of the modified frame.
|
||||
/// Same texture as <see cref="Source"/>; after the last hook it is the frame that gets encoded.
|
||||
/// </summary>
|
||||
public RenderTexture Current => _currentSource;
|
||||
|
||||
@@ -59,7 +85,7 @@ public class FrameHookContext
|
||||
public bool HasModified => _hasModified;
|
||||
|
||||
/// <summary>
|
||||
/// The sequential 0-based index of the video frame currently being encoded.
|
||||
/// The sequential 0-based index of the video frame currently being captured (its position in the output video).
|
||||
/// </summary>
|
||||
public long FrameIndex { get; private set; }
|
||||
|
||||
@@ -69,12 +95,13 @@ public class FrameHookContext
|
||||
public long VideoFrameIndex => FrameIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The sequential 0-based index of the game render frame.
|
||||
/// The sequential 0-based index of the game render frame, counted from the start of capture.
|
||||
/// With <c>RenderFPS</c> above the video frame rate it advances by more than one between hook calls.
|
||||
/// </summary>
|
||||
public long RenderFrameIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timestamp of the current frame in seconds relative to video start.
|
||||
/// The timestamp of the current frame in seconds relative to video start (<see cref="FrameIndex"/> / <see cref="VideoFPS"/>).
|
||||
/// </summary>
|
||||
public double VideoTime { get; private set; }
|
||||
|
||||
@@ -84,30 +111,32 @@ public class FrameHookContext
|
||||
public double DeltaTime => VideoFPS > 0 ? 1.0 / VideoFPS : 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the frame in pixels.
|
||||
/// The width of the frame in pixels (width of the current <see cref="Source"/>).
|
||||
/// </summary>
|
||||
public int Width => _currentSource.width;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the frame in pixels.
|
||||
/// The height of the frame in pixels (height of the current <see cref="Source"/>).
|
||||
/// </summary>
|
||||
public int Height => _currentSource.height;
|
||||
|
||||
/// <summary>
|
||||
/// The configured render frames per second.
|
||||
/// The configured render frames per second (game frames simulated per second of render time).
|
||||
/// </summary>
|
||||
public double RenderFPS { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The output video frames per second.
|
||||
/// The output video frames per second: the effective rate, min(configured <c>VideoFPS</c>, <see cref="RenderFPS"/>).
|
||||
/// </summary>
|
||||
public double VideoFPS { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of successful modification passes completed for the current frame.
|
||||
/// The number of modification passes completed for the current frame (calls of <see cref="Swap"/> and
|
||||
/// <see cref="ReplaceCurrent"/>, including the automatic swaps of the helper methods). 0 at the start of every frame.
|
||||
/// </summary>
|
||||
public int PassCount { get; private set; }
|
||||
|
||||
/// <summary>Creates a context for the first frame; see <see cref="Reset"/> for the parameters.</summary>
|
||||
internal FrameHookContext(
|
||||
RenderTexture originalSource,
|
||||
RenderTexture pingRT,
|
||||
@@ -122,6 +151,10 @@ public class FrameHookContext
|
||||
Reset(originalSource, pingRT, pongRT, firstDestination, frameIndex, renderFrameIndex, videoTime, renderFps, videoFps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the context for a new frame: Source = <paramref name="originalSource"/>, Destination =
|
||||
/// <paramref name="firstDestination"/> (ping or pong), not modified, PassCount 0, and the frame metadata.
|
||||
/// </summary>
|
||||
internal void Reset(
|
||||
RenderTexture originalSource,
|
||||
RenderTexture pingRT,
|
||||
@@ -152,6 +185,11 @@ public class FrameHookContext
|
||||
/// <summary>
|
||||
/// Blits from <see cref="Source"/> to <see cref="Destination"/> using the specified <see cref="Material"/> and automatically swaps ping-pong buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses <c>Graphics.Blit</c>, which binds <see cref="Source"/> as the material's <c>_MainTex</c> and changes
|
||||
/// <c>RenderTexture.active</c>.
|
||||
/// Reference: https://docs.unity3d.com/ScriptReference/Graphics.Blit.html
|
||||
/// </remarks>
|
||||
/// <param name="material">The material containing the shader to apply, or <c>null</c> for a standard copy.</param>
|
||||
/// <param name="pass">The shader pass index to use, or -1 to execute all passes.</param>
|
||||
public void Blit(Material? material = null, int pass = -1)
|
||||
@@ -174,6 +212,10 @@ public class FrameHookContext
|
||||
/// <summary>
|
||||
/// Blits from a custom source <see cref="Texture"/> to <see cref="Destination"/> using the specified <see cref="Material"/> and automatically swaps ping-pong buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The current <see cref="Source"/> is not read; the result replaces the frame. <paramref name="customSource"/> is
|
||||
/// stretched to the size of <see cref="Destination"/>.
|
||||
/// </remarks>
|
||||
/// <param name="customSource">The custom source texture to blit from.</param>
|
||||
/// <param name="material">The material containing the shader to apply, or <c>null</c> for a standard copy.</param>
|
||||
/// <param name="pass">The shader pass index to use, or -1 to execute all passes.</param>
|
||||
@@ -200,6 +242,7 @@ public class FrameHookContext
|
||||
/// <summary>
|
||||
/// Copies the contents of <see cref="Source"/> into <see cref="Destination"/> without swapping buffers.
|
||||
/// Call this before drawing custom overlays into <see cref="Destination"/> if you wish to preserve the background.
|
||||
/// Does not count as a modification; call <see cref="Swap"/> after drawing to make the result the current frame.
|
||||
/// </summary>
|
||||
public void Copy()
|
||||
{
|
||||
@@ -207,7 +250,8 @@ public class FrameHookContext
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears <see cref="Destination"/> with the specified color.
|
||||
/// Clears <see cref="Destination"/> with the specified color (color only; no depth buffer is cleared).
|
||||
/// Does not swap; the previous <c>RenderTexture.active</c> is restored.
|
||||
/// </summary>
|
||||
/// <param name="clearColor">The color to clear with, or <c>null</c> for <see cref="Color.clear"/>.</param>
|
||||
public void ClearDestination(Color? clearColor = null)
|
||||
@@ -261,6 +305,11 @@ public class FrameHookContext
|
||||
/// <summary>
|
||||
/// Executes a <see cref="CommandBuffer"/> against the GPU and optionally swaps buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The context does not set a render target for the command buffer: the commands must render into
|
||||
/// <see cref="Destination"/> themselves (for example with <c>SetRenderTarget</c> or <c>Blit(Source, Destination)</c>),
|
||||
/// so build or update the buffer inside the hook with the current <see cref="Source"/> and <see cref="Destination"/>.
|
||||
/// </remarks>
|
||||
/// <param name="commandBuffer">The command buffer to execute.</param>
|
||||
/// <param name="autoSwap">If <c>true</c> (default), automatically calls <see cref="Swap"/> after execution.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="commandBuffer"/> is null.</exception>
|
||||
@@ -278,6 +327,11 @@ public class FrameHookContext
|
||||
/// Swaps the active <see cref="Source"/> and <see cref="Destination"/> ping-pong buffers.
|
||||
/// Call this after manually writing or rendering into <see cref="Destination"/> so that subsequent hooks or the video encoder read the updated frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// After the call, <see cref="Source"/> is the buffer that was <see cref="Destination"/>, and <see cref="Destination"/>
|
||||
/// is the other ping-pong buffer (its content is left over from an earlier pass or frame; the next pass overwrites it).
|
||||
/// A texture set by <see cref="ReplaceCurrent"/> is dropped by a swap.
|
||||
/// </remarks>
|
||||
public void Swap()
|
||||
{
|
||||
_hasModified = true;
|
||||
@@ -291,6 +345,12 @@ public class FrameHookContext
|
||||
/// <summary>
|
||||
/// Replaces the current modified frame texture with a custom <see cref="RenderTexture"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The texture becomes <see cref="Source"/> / <see cref="Current"/>; <see cref="Destination"/> is unchanged.
|
||||
/// The texture is read back for encoding when it is still current after the last hook, so it must stay alive and
|
||||
/// unchanged until the end of the hook chain, and it should have the frame's width, height and a format the capture
|
||||
/// can read: the capture sizes its buffers from the texture, and the encoder expects the render resolution.
|
||||
/// </remarks>
|
||||
/// <param name="customTexture">The custom texture to set as the active frame.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="customTexture"/> is null.</exception>
|
||||
public void ReplaceCurrent(RenderTexture customTexture)
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace ADOFAIRenderer.API;
|
||||
/// <b>Execution Frequency Note:</b> Frame hooks are only invoked for frames that are actually encoded into the final video (<c>VideoFPS</c>).
|
||||
/// When <c>RenderFPS</c> is higher than <c>VideoFPS</c>, intermediate game render frames are skipped and this hook
|
||||
/// will <b>not</b> be called on every game frame.
|
||||
/// Register an implementation per render with <see cref="RenderSession.AddFrameHook(IFrameHook)"/> or for every render
|
||||
/// with <see cref="RenderSession.AddGlobalFrameHook(IFrameHook)"/>.
|
||||
/// </remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
public interface IFrameHook
|
||||
@@ -18,6 +20,10 @@ public interface IFrameHook
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is not called on every rendered game frame if <c>RenderFPS</c> exceeds <c>VideoFPS</c>.
|
||||
/// It is called on the main Unity thread at the end of the frame. Draw from <see cref="FrameHookContext.Source"/>
|
||||
/// into <see cref="FrameHookContext.Destination"/> and swap (see <see cref="FrameHookContext"/>); returning without
|
||||
/// a swap leaves the frame unchanged. Do not keep the context or its textures after returning.
|
||||
/// An exception thrown here is logged and the next hook still runs.
|
||||
/// </remarks>
|
||||
/// <param name="context">The frame hook context providing access to ping-pong textures and frame metadata.</param>
|
||||
void OnRenderFrame(FrameHookContext context);
|
||||
|
||||
+14
-3
@@ -7,6 +7,9 @@ namespace ADOFAIRenderer.API;
|
||||
/// <summary>
|
||||
/// Represents the result and metadata of a completed or canceled render session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Passed to the completion callback registered with <see cref="RenderSession.OnComplete"/>. Immutable.
|
||||
/// </remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
public class RenderResult
|
||||
@@ -17,7 +20,10 @@ public class RenderResult
|
||||
public bool Success { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute output path of the rendered video file. Empty string if rendering failed or output was not created.
|
||||
/// The output path of the rendered video file: the path given to <see cref="RenderSession.WithOutputPath"/> as passed
|
||||
/// (relative if it was relative), or the path of the time-stamped file in the output directory chosen in the mod
|
||||
/// settings (by default <game root>/Render).
|
||||
/// Empty string if rendering failed or was cancelled (the unfinished file is deleted).
|
||||
/// </summary>
|
||||
public string OutputPath { get; }
|
||||
|
||||
@@ -27,12 +33,13 @@ public class RenderResult
|
||||
public long TotalFrames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The calculated video duration in seconds (based on TotalFrames and FPS).
|
||||
/// The calculated video duration in seconds: <see cref="TotalFrames"/> divided by the effective video frame rate
|
||||
/// (min of the configured VideoFPS and RenderFPS); 0 when the frame rate is not positive.
|
||||
/// </summary>
|
||||
public double VideoDuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total wall-clock time spent rendering.
|
||||
/// Total wall-clock time spent rendering, from the end of the countdown (start of capture) to the end of the render.
|
||||
/// </summary>
|
||||
public TimeSpan RenderDuration { get; }
|
||||
|
||||
@@ -43,6 +50,8 @@ public class RenderResult
|
||||
|
||||
/// <summary>
|
||||
/// Error message or reason if rendering failed or was canceled; <c>null</c> on success.
|
||||
/// A cancelled render has "Rendering was cancelled by user."; a failed render has "Rendering failed: " followed by
|
||||
/// the reason. Use <see cref="Success"/> to test the outcome; the texts are meant for display.
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; }
|
||||
|
||||
@@ -82,6 +91,7 @@ public class RenderResult
|
||||
/// <param name="fps">The effective frames per second used to compute video duration.</param>
|
||||
/// <param name="renderDuration">Total wall-clock time spent rendering.</param>
|
||||
/// <returns>A new <see cref="RenderResult"/> representing a successful render session.</returns>
|
||||
/// <remarks>The file size is read from disk; it is 0 when the file does not exist or cannot be read.</remarks>
|
||||
internal static RenderResult CreateSuccess(string outputPath, long totalFrames, double fps, TimeSpan renderDuration)
|
||||
{
|
||||
var fileSizeBytes = 0L;
|
||||
@@ -103,6 +113,7 @@ public class RenderResult
|
||||
/// <param name="fps">The effective frames per second used to compute video duration.</param>
|
||||
/// <param name="renderDuration">Total wall-clock time spent rendering prior to termination.</param>
|
||||
/// <returns>A new <see cref="RenderResult"/> representing a failed or canceled render session.</returns>
|
||||
/// <remarks>OutputPath is empty and FileSizeBytes is 0. A null message is replaced by a generic one.</remarks>
|
||||
internal static RenderResult CreateFailure(string? errorMessage, long totalFrames, double fps, TimeSpan renderDuration)
|
||||
{
|
||||
var videoDuration = fps > 0 ? totalFrames / fps : 0.0;
|
||||
|
||||
+180
-5
@@ -10,13 +10,41 @@ using UnityEngine;
|
||||
namespace ADOFAIRenderer.API;
|
||||
|
||||
/// <summary>
|
||||
/// Fluent builder for configuring and starting a render session.
|
||||
/// Fluent builder for configuring and starting a render session, and the registry of global frame hooks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Usage:</b> create an instance, configure it with the <c>With…</c>, <c>On…</c> and <c>AddFrameHook</c> methods,
|
||||
/// then call <see cref="Start"/>. The builder's values are copied into the render when <see cref="Start"/> is called;
|
||||
/// changing the builder afterwards does not affect the running render. A builder can be reused for later renders.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Lifecycle of a render:</b> <see cref="Start"/> loads the game scene of the level. When the scene has loaded,
|
||||
/// a 3-second countdown runs, then capture starts and the level plays with the game clock driven by the render
|
||||
/// frame rate (offline, not real time). The render ends successfully <see cref="RenderConfig.DelayAfterEnd"/> seconds
|
||||
/// (render time) after the end condition first holds, or is cancelled by the user (ESC), or fails. In every case
|
||||
/// the completion callback receives a <see cref="RenderResult"/> and the game returns to the level editor.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Threading:</b> every member must be called on the main Unity thread, except the global hook members, which are
|
||||
/// thread-safe (they lock an internal object). All callbacks (end condition, completion, last update, frame hooks)
|
||||
/// are invoked on the main Unity thread.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Frame hooks</b> run on each captured video frame before it is encoded, in this order: session hooks in the order
|
||||
/// they were added, then global hooks in registration order.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
public class RenderSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle returned by AddGlobalFrameHook. Key is the object the caller registered (delegate, material or IFrameHook)
|
||||
/// and is used by RemoveGlobalFrameHook; Action is the delegate the pipeline invokes.
|
||||
/// Dispose removes this exact registration once; later calls do nothing.
|
||||
/// </summary>
|
||||
private sealed class GlobalHookRegistration : IDisposable
|
||||
{
|
||||
public object Key { get; }
|
||||
@@ -37,6 +65,10 @@ public class RenderSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Session hook added to this builder. Key is the object the caller passed (delegate, material or IFrameHook) and
|
||||
/// is used by RemoveFrameHook; Action is the delegate copied to the render by Start.
|
||||
/// </summary>
|
||||
private sealed class SessionHookEntry
|
||||
{
|
||||
public object Key { get; }
|
||||
@@ -49,11 +81,22 @@ public class RenderSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Global hooks in registration order. Guarded by GlobalHooksLock.</summary>
|
||||
private static readonly List<GlobalHookRegistration> GlobalFrameHooks = new();
|
||||
|
||||
/// <summary>Lock for GlobalFrameHooks and _globalHooksVersion (global hooks may be changed from any thread).</summary>
|
||||
private static readonly object GlobalHooksLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Incremented on every change of GlobalFrameHooks. RenderManager compares it each captured frame to rebuild its
|
||||
/// cached hook list only after a change. Guarded by GlobalHooksLock.
|
||||
/// </summary>
|
||||
private static int _globalHooksVersion;
|
||||
|
||||
/// <summary>Session hooks of this builder in the order they were added.</summary>
|
||||
private readonly List<SessionHookEntry> _frameHooks = new();
|
||||
|
||||
/// <summary>Autoplay setting copied to the render by Start (default true).</summary>
|
||||
private bool _auto = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -63,27 +106,48 @@ public class RenderSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Time-related patch controlling frame timing and clock synchronization.
|
||||
/// While capturing, <c>Time.time</c>, <c>Time.timeAsDouble</c>, <c>Time.unscaledTime</c>,
|
||||
/// <c>Time.unscaledTimeAsDouble</c>, <c>Time.deltaTime</c>, <c>Time.unscaledDeltaTime</c>, <c>Time.frameCount</c>
|
||||
/// and <c>AudioSettings.dspTime</c> return values of the render clock (one frame = 1 / RenderFPS seconds),
|
||||
/// so the game advances exactly one render frame per Unity frame regardless of how long a frame takes.
|
||||
/// Disabling it leaves these values to Unity.
|
||||
/// </summary>
|
||||
Time,
|
||||
|
||||
/// <summary>
|
||||
/// Screen-related patch controlling screen resolution.
|
||||
/// While the render is active (from the countdown on), <c>Screen.width</c> and <c>Screen.height</c> return
|
||||
/// <see cref="RenderConfig.RenderWidth"/> and <see cref="RenderConfig.RenderHeight"/>, so the game creates its
|
||||
/// camera RenderTexture at the render resolution.
|
||||
/// </summary>
|
||||
Screen
|
||||
}
|
||||
|
||||
/// <summary>Time patch setting copied to the render by Start (default true).</summary>
|
||||
private bool _timePatch = true;
|
||||
|
||||
/// <summary>Screen patch setting copied to the render by Start (default true).</summary>
|
||||
private bool _screenPatch = true;
|
||||
|
||||
/// <summary>Custom output file path, or null for the default output directory.</summary>
|
||||
private string? _outputPath;
|
||||
|
||||
/// <summary>Notification setting copied to the render by Start (default true).</summary>
|
||||
private bool _notification = true;
|
||||
|
||||
/// <summary>Custom end condition, or null to keep the default (level cleared).</summary>
|
||||
private Func<bool>? _endCondition;
|
||||
|
||||
/// <summary>Combined completion callbacks, or null to keep the default (notification).</summary>
|
||||
private Action<RenderResult>? _onComplete;
|
||||
|
||||
/// <summary>Combined last-update callbacks, or null for none.</summary>
|
||||
private Action? _onLastUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether auto mode is enabled.
|
||||
/// Sets whether auto mode (the game's autoplay, <c>RDC.auto</c>) is enabled during the render.
|
||||
/// With auto mode off, the level is not played automatically; the render still enables the game's no-fail
|
||||
/// settings (<c>scrController.noFail</c>, <c>scrController.noFailInfiniteMargin</c>).
|
||||
/// </summary>
|
||||
/// <param name="enabled"><c>true</c> to enable auto mode; otherwise, <c>false</c>. Default is <c>true</c>.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
@@ -115,6 +179,13 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Sets the custom condition function that determines when the render session should end.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The condition is polled on the main thread once per frame from the start of the countdown until the render ends.
|
||||
/// When it first returns <c>true</c>, the render continues for <see cref="RenderConfig.DelayAfterEnd"/> seconds of
|
||||
/// render time and then finishes successfully; it is not required to stay <c>true</c>. The default condition is
|
||||
/// that the level is cleared (<c>scrController.instance.state == States.Won</c>). Calling this method again
|
||||
/// replaces the previous condition. Exceptions thrown by the condition are not caught.
|
||||
/// </remarks>
|
||||
/// <param name="endCondition">A delegate returning <c>true</c> when the session end condition is met.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession WithEndCondition(Func<bool> endCondition)
|
||||
@@ -126,6 +197,13 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Sets the custom output path for the rendered file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The directory of the path is created by <see cref="Start"/> when it does not exist. An existing file at the path
|
||||
/// is overwritten. The container format follows the file extension. With <c>null</c>, the file is named
|
||||
/// <c>yyyy-MM-dd HH-mm-ss.<ext></c> (local start time, extension from <see cref="AdvancedConfig.FileExtension"/>)
|
||||
/// in the output directory chosen in the mod settings. When the render is cancelled or fails, the unfinished file
|
||||
/// is deleted.
|
||||
/// </remarks>
|
||||
/// <param name="outputPath">The file path where the output will be saved, or <c>null</c> to use the default capture directory.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession WithOutputPath(string? outputPath)
|
||||
@@ -137,6 +215,10 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Sets whether desktop notifications should be displayed when rendering completes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The notification is shown by the default completion callback only. When a callback is added with
|
||||
/// <see cref="OnComplete"/>, it replaces the default callback and no notification is shown. Default is <c>true</c>.
|
||||
/// </remarks>
|
||||
/// <param name="enabled"><c>true</c> to enable desktop notifications; otherwise, <c>false</c>.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession WithNotification(bool enabled)
|
||||
@@ -148,6 +230,13 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Adds a callback action to be invoked when the render session completes or fails.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called once per render on the main thread: on success after the output file has been finished, on cancel or
|
||||
/// failure after the unfinished file has been deleted, and before the game returns to the level editor.
|
||||
/// It is not called when the render stops because the game quits. Multiple calls combine the callbacks (invoked in
|
||||
/// the order added). The combined callback replaces the default one, which shows the desktop notification.
|
||||
/// An exception thrown by a callback is logged and stops the callbacks added after it.
|
||||
/// </remarks>
|
||||
/// <param name="callback">The callback action that receives the resulting <see cref="RenderResult"/>.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession OnComplete(Action<RenderResult> callback)
|
||||
@@ -159,6 +248,11 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Adds a callback action to be invoked on the last update of each frame in the render session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called on the main thread every Unity frame after all other <c>LateUpdate</c> calls, from the moment the game scene
|
||||
/// of the render has loaded until the render ends (including the countdown, before capture starts).
|
||||
/// Multiple calls combine the callbacks. An exception thrown by a callback is logged.
|
||||
/// </remarks>
|
||||
/// <param name="callback">The callback action to execute.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession OnLastUpdate(Action callback)
|
||||
@@ -172,9 +266,19 @@ public class RenderSession
|
||||
/// The hook can modify the rendered frame using GPU ping-pong double buffering.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Execution Frequency Note:</b> This hook is invoked only for frames that are captured for the output video.
|
||||
/// When <c>RenderFPS</c> is higher than <c>VideoFPS</c>, intermediate game render frames are skipped,
|
||||
/// so this hook will <b>not</b> be called on every game frame.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The hook is called on the main Unity thread at the end of the frame, after the game has rendered it and before
|
||||
/// the GPU readback for encoding is requested. Write the result into <see cref="FrameHookContext.Destination"/> and
|
||||
/// swap (or use <see cref="FrameHookContext.Blit(Material, int)"/>, which swaps); a hook that does not swap leaves
|
||||
/// the frame unchanged. An exception thrown by the hook is logged and the next hook still runs.
|
||||
/// Session hooks apply only to the render started by <see cref="Start"/> and are discarded when it ends.
|
||||
/// The same delegate can be added more than once; it then runs once per addition.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="hook">The callback action that receives a <see cref="FrameHookContext"/>.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
@@ -190,6 +294,13 @@ public class RenderSession
|
||||
/// Adds a material-based post-processing pass to be executed for each encoded video frame.
|
||||
/// Automatically performs a GPU blit from source to destination and swaps buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Equivalent to a hook calling <see cref="FrameHookContext.Blit(Material, int)"/>: the current frame is bound as the
|
||||
/// material's <c>_MainTex</c> by <c>Graphics.Blit</c>. The material is referenced, not copied, so property changes
|
||||
/// made to it later apply to the following frames. Execution rules are the same as
|
||||
/// <see cref="AddFrameHook(Action{FrameHookContext})"/>.
|
||||
/// Reference: https://docs.unity3d.com/ScriptReference/Graphics.Blit.html
|
||||
/// </remarks>
|
||||
/// <param name="material">The material containing the shader to apply.</param>
|
||||
/// <param name="pass">The shader pass index to use, or -1 to execute all passes.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
@@ -204,6 +315,10 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Adds a custom frame modification hook implementing <see cref="IFrameHook"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="IFrameHook.OnRenderFrame"/> is invoked with the same rules as
|
||||
/// <see cref="AddFrameHook(Action{FrameHookContext})"/>. Remove it with <see cref="RemoveFrameHook(IFrameHook)"/>.
|
||||
/// </remarks>
|
||||
/// <param name="hook">The hook instance to execute.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="hook"/> is null.</exception>
|
||||
@@ -239,6 +354,11 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Removes a previously added frame modification hook callback from this session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Removes the first entry (in the order added) whose registered object is <paramref name="hook"/> or whose delegate
|
||||
/// equals it. When the same delegate was added several times, one call removes one addition. Does nothing when no
|
||||
/// entry matches. Affects only renders started after this call.
|
||||
/// </remarks>
|
||||
/// <param name="hook">The hook action to remove.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession RemoveFrameHook(Action<FrameHookContext> hook)
|
||||
@@ -251,6 +371,10 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Removes a previously added material-based post-processing pass from this session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Removes every pass added with <paramref name="material"/> (any pass index). Does nothing when the material is
|
||||
/// null or destroyed. Affects only renders started after this call.
|
||||
/// </remarks>
|
||||
/// <param name="material">The material to remove.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession RemoveFrameHook(Material material)
|
||||
@@ -271,6 +395,9 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Removes a previously added <see cref="IFrameHook"/> from this session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Removes the first entry added with this instance (reference equality). Affects only renders started after this call.
|
||||
/// </remarks>
|
||||
/// <param name="hook">The hook instance to remove.</param>
|
||||
/// <returns>The current <see cref="RenderSession"/> instance for method chaining.</returns>
|
||||
public RenderSession RemoveFrameHook(IFrameHook hook)
|
||||
@@ -294,9 +421,19 @@ public class RenderSession
|
||||
/// Registers a global frame modification hook that will execute on every render session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Execution Frequency Note:</b> Global frame hooks are executed only for frames that are captured for the output video.
|
||||
/// When <c>RenderFPS</c> is higher than <c>VideoFPS</c>, intermediate game render frames are skipped,
|
||||
/// so this hook will <b>not</b> be called on every game frame.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The registration stays active for every following render (and a render already running, from its next
|
||||
/// captured frame) until the returned handle is disposed or the hook is removed with
|
||||
/// <see cref="RemoveGlobalFrameHook(Action{FrameHookContext})"/> or <see cref="ClearGlobalFrameHooks"/>.
|
||||
/// Global hooks run after the session hooks, in registration order, on the main Unity thread; an exception thrown
|
||||
/// by the hook is logged and the next hook still runs. This method is thread-safe. Registering the same delegate
|
||||
/// twice creates two registrations.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="hook">The callback action that receives a <see cref="FrameHookContext"/>.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> registration handle that unregisters the hook when disposed.</returns>
|
||||
@@ -316,6 +453,10 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Registers a global material-based post-processing pass that will execute on every render session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Same lifetime and ordering as <see cref="AddGlobalFrameHook(Action{FrameHookContext})"/>; the pass is applied as
|
||||
/// <see cref="FrameHookContext.Blit(Material, int)"/>. Thread-safe.
|
||||
/// </remarks>
|
||||
/// <param name="material">The material containing the shader to apply.</param>
|
||||
/// <param name="pass">The shader pass index to use, or -1 to execute all passes.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> registration handle that unregisters the hook when disposed.</returns>
|
||||
@@ -335,6 +476,9 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Registers a global frame modification hook implementing <see cref="IFrameHook"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Same lifetime and ordering as <see cref="AddGlobalFrameHook(Action{FrameHookContext})"/>. Thread-safe.
|
||||
/// </remarks>
|
||||
/// <param name="hook">The hook instance to execute.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> registration handle that unregisters the hook when disposed.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="hook"/> is null.</exception>
|
||||
@@ -353,6 +497,10 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Unregisters a previously registered global frame modification hook.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Removes the first registration (in registration order) whose registered object is <paramref name="hook"/> or whose
|
||||
/// delegate equals it. Thread-safe; a running render stops calling the hook from its next captured frame.
|
||||
/// </remarks>
|
||||
/// <param name="hook">The hook action to remove.</param>
|
||||
/// <returns><c>true</c> if the hook was successfully removed; otherwise, <c>false</c>.</returns>
|
||||
public static bool RemoveGlobalFrameHook(Action<FrameHookContext> hook)
|
||||
@@ -373,6 +521,7 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Unregisters a previously registered global material-based post-processing pass.
|
||||
/// </summary>
|
||||
/// <remarks>Removes every registration made with <paramref name="material"/> (any pass index). Thread-safe.</remarks>
|
||||
/// <param name="material">The material to remove.</param>
|
||||
/// <returns><c>true</c> if any hooks matching <paramref name="material"/> were removed; otherwise, <c>false</c>.</returns>
|
||||
public static bool RemoveGlobalFrameHook(Material material)
|
||||
@@ -399,6 +548,7 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Unregisters a previously registered global frame modification hook implementing <see cref="IFrameHook"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Removes the first registration made with this instance (reference equality). Thread-safe.</remarks>
|
||||
/// <param name="hook">The hook instance to remove.</param>
|
||||
/// <returns><c>true</c> if the hook was successfully removed; otherwise, <c>false</c>.</returns>
|
||||
public static bool RemoveGlobalFrameHook(IFrameHook hook)
|
||||
@@ -416,6 +566,7 @@ public class RenderSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes one registration (called by its Dispose). Does nothing when it was already removed.</summary>
|
||||
private static void RemoveGlobalRegistration(GlobalHookRegistration registration)
|
||||
{
|
||||
lock (GlobalHooksLock)
|
||||
@@ -430,6 +581,10 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Clears all registered global frame modification hooks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Removes the registrations of every mod, not only the caller's. Handles returned earlier stay valid; disposing them
|
||||
/// does nothing. Thread-safe.
|
||||
/// </remarks>
|
||||
public static void ClearGlobalFrameHooks()
|
||||
{
|
||||
lock (GlobalHooksLock)
|
||||
@@ -443,7 +598,7 @@ public class RenderSession
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether any global frame modification hooks are currently registered.
|
||||
/// Indicates whether any global frame modification hooks are currently registered (by any mod). Thread-safe.
|
||||
/// </summary>
|
||||
public static bool HasGlobalFrameHooks
|
||||
{
|
||||
@@ -457,7 +612,7 @@ public class RenderSession
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of currently registered global frame modification hooks.
|
||||
/// The number of currently registered global frame modification hooks (by any mod). Thread-safe.
|
||||
/// </summary>
|
||||
public static int GlobalFrameHookCount
|
||||
{
|
||||
@@ -470,6 +625,7 @@ public class RenderSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Change counter of the global hook list (see _globalHooksVersion). Thread-safe.</summary>
|
||||
internal static int GlobalHooksVersion
|
||||
{
|
||||
get
|
||||
@@ -481,6 +637,7 @@ public class RenderSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Appends the delegates of every global hook, in registration order, to <paramref name="destination"/>. Thread-safe.</summary>
|
||||
internal static void CopyGlobalFrameHooksTo(List<Action<FrameHookContext>> destination)
|
||||
{
|
||||
lock (GlobalHooksLock)
|
||||
@@ -495,11 +652,29 @@ public class RenderSession
|
||||
/// <summary>
|
||||
/// Applies the configured flags and initiates the render session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Must be called on the main Unity thread while no render is running; this method does not check for a running
|
||||
/// render. It copies the builder's options and hooks into the render, copies <paramref name="renderConfig"/>
|
||||
/// (later changes to the caller's object do not affect the render), creates the output directory, and starts
|
||||
/// loading the game scene (<c>scnGame</c>) with the level. It returns immediately; the render runs over the following
|
||||
/// frames and reports its end through the <see cref="OnComplete"/> callback.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Options that were not set keep their defaults: auto mode on, both patches on, default output path, notification
|
||||
/// on, end condition "level cleared", default completion callback (notification), no last-update callback.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="renderConfig">The configuration settings for the render session.</param>
|
||||
/// <param name="levelPath">The file path of the level to render.</param>
|
||||
/// <param name="levelPath">The file path of the level to render (the .adofai file).</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the output directory cannot be determined from the output path.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown by the directory creation when the output path has no directory part (for example a bare file name).
|
||||
/// </exception>
|
||||
public void Start(RenderConfig renderConfig, string levelPath)
|
||||
{
|
||||
// Callbacks not set on this builder keep the values RenderContext holds; RenderContext.Reset at the end of every
|
||||
// render restores the defaults.
|
||||
RenderContext.Auto = _auto;
|
||||
RenderContext.TimePatch = _timePatch;
|
||||
RenderContext.ScreenPatch = _screenPatch;
|
||||
|
||||
Reference in new issue
Block a user