fix: fixed 60 FPS render rate, uncapped physics/input
- Physics + input + light animation: run at full speed every iteration - Render: accumulator-based 60 FPS limiter — only renders when enough time has accumulated (16.67ms). If not, Thread.Sleep(1) and continue. - Video: captured at exactly 60 FPS, FFmpeg downsamples to 30 - Physics stays smooth even if render can't keep up
This commit is contained in:
@@ -79,24 +79,16 @@ class Program
|
|||||||
string? recordingPath = null;
|
string? recordingPath = null;
|
||||||
int recordedFrames = 0;
|
int recordedFrames = 0;
|
||||||
|
|
||||||
const double targetFps = 60.0;
|
const double renderFps = 60.0;
|
||||||
var targetFrameTime = 1.0 / targetFps;
|
var renderFrameTime = 1.0 / renderFps;
|
||||||
var lastFrameTime = 0.0;
|
var renderAccumulator = 0.0;
|
||||||
|
|
||||||
while (!window.ShouldClose)
|
while (!window.ShouldClose)
|
||||||
{
|
{
|
||||||
timing.Tick();
|
timing.Tick();
|
||||||
var now = timing.TotalTime;
|
renderAccumulator += timing.DeltaTime;
|
||||||
var elapsed = now - lastFrameTime;
|
|
||||||
if (elapsed < targetFrameTime)
|
|
||||||
{
|
|
||||||
var sleepMs = (int)((targetFrameTime - elapsed) * 1000);
|
|
||||||
if (sleepMs > 0) Thread.Sleep(sleepMs);
|
|
||||||
timing.Tick();
|
|
||||||
now = timing.TotalTime;
|
|
||||||
}
|
|
||||||
lastFrameTime = now;
|
|
||||||
|
|
||||||
|
// Physics + input run at full speed (uncapped)
|
||||||
window.PumpEvents();
|
window.PumpEvents();
|
||||||
input.BeginFrame();
|
input.BeginFrame();
|
||||||
|
|
||||||
@@ -396,32 +388,44 @@ class Program
|
|||||||
renderer.EndImGuiFrame();
|
renderer.EndImGuiFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set recording flag before render so renderer can capture
|
// Fixed render rate: 60 FPS
|
||||||
vkRenderer!.IsRecording = isRecording;
|
renderAccumulator -= renderFrameTime;
|
||||||
|
if (renderAccumulator >= 0)
|
||||||
renderer.RenderWorld(world);
|
|
||||||
queue.CompletePendingScreenshots();
|
|
||||||
// If recording, get captured frame and write to FFmpeg
|
|
||||||
if (isRecording && ffmpegProcess != null && !ffmpegProcess.HasExited)
|
|
||||||
{
|
{
|
||||||
var frameData = vkRenderer.CapturedFrame;
|
// Set recording flag before render so renderer can capture
|
||||||
if (frameData != null && frameData.Length > 0)
|
vkRenderer!.IsRecording = isRecording;
|
||||||
|
|
||||||
|
renderer.RenderWorld(world);
|
||||||
|
queue.CompletePendingScreenshots();
|
||||||
|
|
||||||
|
// If recording, get captured frame and write to FFmpeg
|
||||||
|
if (isRecording && ffmpegProcess != null && !ffmpegProcess.HasExited)
|
||||||
{
|
{
|
||||||
try
|
var frameData = vkRenderer.CapturedFrame;
|
||||||
|
if (frameData != null && frameData.Length > 0)
|
||||||
{
|
{
|
||||||
ffmpegProcess.StandardInput.BaseStream.Write(frameData, 0, frameData.Length);
|
try
|
||||||
ffmpegProcess.StandardInput.BaseStream.Flush();
|
{
|
||||||
recordedFrames++;
|
ffmpegProcess.StandardInput.BaseStream.Write(frameData, 0, frameData.Length);
|
||||||
}
|
ffmpegProcess.StandardInput.BaseStream.Flush();
|
||||||
catch (Exception ex)
|
recordedFrames++;
|
||||||
{
|
}
|
||||||
Console.WriteLine($"[App] FFmpeg write error: {ex.Message}");
|
catch (Exception ex)
|
||||||
isRecording = false;
|
{
|
||||||
|
Console.WriteLine($"[App] FFmpeg write error: {ex.Message}");
|
||||||
|
isRecording = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
frames++;
|
frames++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not enough time for a render frame — sleep briefly
|
||||||
|
Thread.Sleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (timing.TotalTime - lastFpsTime >= 1.0)
|
if (timing.TotalTime - lastFpsTime >= 1.0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"FPS: {frames}, Delta: {timing.DeltaTime * 1000.0:F2} ms");
|
Console.WriteLine($"FPS: {frames}, Delta: {timing.DeltaTime * 1000.0:F2} ms");
|
||||||
|
|||||||
Reference in New Issue
Block a user