Ask the client for frames between its steps
CI / build (push) Successful in 1m31s

The frame pacer repaints the client's 3D windows at the user's frame rate
(HKCU\Software\T5DE\Graphics\FrameRate: 30, the default, adds nothing; 60, 120,
240, 360; 0 uncapped), so the client draws frames between its 30-per-second
steps. Only when affordable: a window is asked once its last frame is drawn,
and only if its frames take under three quarters of the interval; uncapped,
the client is left idle a third of a frame between frames. The engine's stats
now log frames per second.

The extra frames show the same scene for now.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-28 20:38:31 -07:00
1 parent 8055099920
commit 05a7be5d41
7 files changed
+233 -18

No files matched your search

+12 -7
View File
@@ -1,10 +1,11 @@
#pragma once
// The adapter's engine: what the client draws with. Everything is recorded for the renderer process, which draws the
// 3D view. The client's own Direct3D renderer gets everything too (state and resources: it still draws into render
// targets and reads textures back, which the renderer process doesn't yet), but while the renderer process covers the
// whole view, the client's renderer skips its frames' clears and draws. When the renderer process doesn't cover it
// (it failed, or shows only part of the view, for comparing) the client's renderer draws everything, from the next
// frame on.
// 3D view and the render targets it makes. The client's own Direct3D renderer gets state and resources too (as a
// fallback, and for reading textures back), but while the renderer process covers the whole view, the client's
// renderer skips its frames' clears and draws. When the renderer process doesn't cover it (it failed, or shows only
// part of the view, for comparing) the client's renderer draws everything, from the next frame on.
#include <windows.h>
#include <cstdint>
#include <memory>
@@ -16,8 +17,8 @@ namespace t5de {
class Engine final : public imvu::PiRenderEngine {
public:
// recorder may be inactive (no renderer process).
Engine(imvu::SharedPtr<imvu::PiRenderEngine> inner, std::shared_ptr<Recorder> recorder);
// recorder may be inactive (no renderer process); window is the 3D window drawn into (frame_pacer.hpp), or null.
Engine(imvu::SharedPtr<imvu::PiRenderEngine> inner, std::shared_ptr<Recorder> recorder, HWND window);
~Engine() override;
void resizeFrameBuffer(imvu::Vec2i size) override;
@@ -71,6 +72,7 @@ private:
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
std::shared_ptr<Recorder> recorder_;
HWND window_;
std::uint64_t frames_ = 0;
bool takenOver_ = false; // this frame is drawn by the renderer process only
imvu::PiRenderTarget* ourTarget_ = nullptr; // this frame's render target, if it's one of ours
@@ -87,6 +89,9 @@ private:
std::uint32_t flat = 0; // setProjection with an orthographic projection (2D)
};
Stats stats_;
LARGE_INTEGER statsStart_{}; // when the stats' 300 frames started
LARGE_INTEGER frameStart_{}; // when the current frame began
bool windowFrame_ = false; // the current frame is drawn into the window
int statsLogged_ = 0;
};