#nullable enable
using System;
using ImGuiNET;
using T3.Core.Animation;
using T3.Core.DataTypes.Vector;
using T3.Core.Video;
namespace T3.Editor.Gui.Windows.RenderExport;
///
/// Rough pre-export estimates of output file size and render time for the render-to-file UI. Both are
/// deliberately approximate ("guesstimates"): size depends on content; render time depends on graph complexity
/// (we proxy it with the editor's recent wall-clock frame time), motion-blur samples, codec speed, and the
/// machine. Good enough to flag "this will be huge / slow" before committing.
///
internal static class RenderExportEstimate
{
/// Estimated output size in bytes (0 when not estimable).
public static long EstimateBytes(VideoExportCodec codec, Int2 resolution, int frameCount, double durationSec, long bitRate)
{
if (frameCount <= 0)
return 0;
var (w, h) = codec.RoundToEncoderBlock(resolution.Width, resolution.Height);
var pixels = (double)w * h;
return codec switch
{
// HAP is fixed-ratio DXT (Snappy shaves a little).
VideoExportCodec.Hap => (long)(0.5 * pixels * frameCount),
VideoExportCodec.HapAlpha => (long)(1.0 * pixels * frameCount),
VideoExportCodec.HapQ => (long)(1.0 * pixels * frameCount),
// Rate-controlled: bitrate × duration.
VideoExportCodec.H264 or VideoExportCodec.Hevc or VideoExportCodec.VP9 or VideoExportCodec.AV1
=> (long)(bitRate / 8.0 * durationSec),
// ProRes 422 ≈ ~0.8 bits/pixel·frame; FFV1 lossless ≈ ~half of raw 8-bit RGB. Both rough.
VideoExportCodec.ProRes => (long)(0.10 * pixels * frameCount),
VideoExportCodec.FFV1 => (long)(0.5 * pixels * 3 * frameCount),
_ => 0,
};
}
/// Estimated total render time in seconds: per-frame graph render (recent editor frame time × motion
/// blur samples) plus a rough per-codec encode cost, across all frames.
public static double EstimateSeconds(VideoExportCodec codec, Int2 resolution, int frameCount, int motionBlurSamples)
{
if (frameCount <= 0)
return 0;
var samples = motionBlurSamples > 0 ? motionBlurSamples : 1;
var renderPerFrame = SmoothedRenderPerFrame();
var megapixels = (double)resolution.Width * resolution.Height / 1e6;
var encodePerFrame = megapixels / EncodeMegapixelsPerSec(codec);
return frameCount * (renderPerFrame * samples + encodePerFrame);
}
// The editor's recent wall-clock frame time captures the graph's render cost, but it jitters every frame —
// shown raw it makes the estimate flicker (34→35→33 s). Low-pass it, updated at most once per UI frame so the
// many per-item calls in one dropdown draw all see the same value. Guard against a stale/zero or absurd reading
// (e.g. right after a stall) with a 60 fps fallback.
private static double SmoothedRenderPerFrame()
{
var frame = ImGui.GetFrameCount();
if (frame != _smoothedFrame)
{
_smoothedFrame = frame;
var raw = Playback.LastFrameDuration is > 0 and < 10 ? Playback.LastFrameDuration : 1.0 / 60;
_smoothedRenderPerFrame = _smoothedRenderPerFrame > 0
? _smoothedRenderPerFrame * 0.9 + raw * 0.1
: raw;
}
return _smoothedRenderPerFrame;
}
private static int _smoothedFrame = -1;
private static double _smoothedRenderPerFrame;
// Rough encode throughput (megapixels/sec) on a typical desktop; H.264/HEVC assume the hardware path.
private static double EncodeMegapixelsPerSec(VideoExportCodec codec) => codec switch
{
VideoExportCodec.H264 => 300,
VideoExportCodec.Hevc => 200,
VideoExportCodec.Hap => 150,
VideoExportCodec.HapAlpha => 150,
VideoExportCodec.HapQ => 150,
VideoExportCodec.ProRes => 120,
VideoExportCodec.FFV1 => 80,
VideoExportCodec.AV1 => 40,
VideoExportCodec.VP9 => 25,
_ => 100,
};
public static string FormatBytes(long bytes)
{
if (bytes <= 0)
return "?";
var mb = bytes / 1024.0 / 1024;
return mb >= 1024 ? $"{mb / 1024:0.##} GB" : $"{mb:0.#} MB";
}
public static string FormatDuration(double seconds)
{
if (seconds < 1)
return "<1s";
if (seconds < 90)
return $"~{seconds:0}s";
if (seconds < 90 * 60)
return $"~{seconds / 60:0} min";
return $"~{seconds / 3600:0.#} h";
}
}