diff --git a/adapter/engine.cpp b/adapter/engine.cpp index 6220243..41e8669 100644 --- a/adapter/engine.cpp +++ b/adapter/engine.cpp @@ -303,8 +303,10 @@ void Engine::endRendering() LARGE_INTEGER end, frequency; QueryPerformanceCounter(&end); QueryPerformanceFrequency(&frequency); - frame_pacer::frameDrawn(window_, static_cast(end.QuadPart - frameStart_.QuadPart) / - static_cast(frequency.QuadPart)); + const double seconds = + static_cast(end.QuadPart - frameStart_.QuadPart) / static_cast(frequency.QuadPart); + frameSeconds_ += seconds; + frame_pacer::frameDrawn(window_, seconds); } ourTarget_ = nullptr; ++frames_; @@ -315,14 +317,20 @@ void Engine::endRendering() const double seconds = static_cast(ended.QuadPart - statsStart_.QuadPart) / static_cast(frequency.QuadPart); statsStart_ = ended; + const Recorder::Counters sent = recorder_->takeCounters(); if (statsLogged_ < 6 || frames_ % 3000 == 0) { ++statsLogged_; - log::info("engine: last 300 frames: {:.0f} per second; drawTriangles {} (and {} with buffers not ours), " - "drawTrianglesFromMemory {} (and {} without indices), point sprites {}, lines {}, 2D projections {}", - 300 / seconds, stats_.triangles, stats_.foreignTriangles, stats_.indexed, stats_.unindexed, - stats_.sprites, stats_.lines, stats_.flat); + log::info("engine: last 300 frames: {:.0f} per second; each {:.2f} ms beginRendering to endRendering, {:.2f} ms " + "of it sending, {:.0f} KB sent, {:.1f} buffer updates ({:.0f} KB); drawTriangles {} (and {} with " + "buffers not ours), drawTrianglesFromMemory {} (and {} without indices), point sprites {}, lines {}, " + "2D projections {}", + 300 / seconds, frameSeconds_ * 1000 / 300, sent.sendSeconds * 1000 / 300, + static_cast(sent.bytes) / 1024 / 300, sent.bufferUpdates / 300.0, + static_cast(sent.bufferBytes) / 1024 / 300, stats_.triangles, stats_.foreignTriangles, + stats_.indexed, stats_.unindexed, stats_.sprites, stats_.lines, stats_.flat); } stats_ = {}; + frameSeconds_ = 0; } } diff --git a/adapter/engine.hpp b/adapter/engine.hpp index 245a461..e6df2a3 100644 --- a/adapter/engine.hpp +++ b/adapter/engine.hpp @@ -91,6 +91,7 @@ private: Stats stats_; LARGE_INTEGER statsStart_{}; // when the stats' 300 frames started LARGE_INTEGER frameStart_{}; // when the current frame began + double frameSeconds_ = 0; // the stats' window frames' time, beginRendering to endRendering bool windowFrame_ = false; // the current frame is drawn into the window int statsLogged_ = 0; diff --git a/adapter/frame_pacer.cpp b/adapter/frame_pacer.cpp index 82c1697..95572b5 100644 --- a/adapter/frame_pacer.cpp +++ b/adapter/frame_pacer.cpp @@ -14,10 +14,8 @@ namespace { // The client's own rate: its ticks already draw these frames. constexpr DWORD clientRate = 30; -// Extra frames only if a frame takes less than this share of the interval. -constexpr double affordable = 0.75; -// Uncapped, the client is left idle this share of a frame's time between frames. -constexpr double uncappedIdle = 1.0 / 3.0; +// The client is left idle at least this share of a frame's time between frames. +constexpr double idleShare = 1.0 / 3.0; // The user's frame rate, from the registry (unset or unreadable: the client's own). DWORD read_rate() @@ -88,8 +86,8 @@ private: double askedAt = 0; // when a repaint was last asked for }; - // Re-reads the setting now and then; above the client's rate, asks each window for a repaint at the rate, when - // affordable. + // Re-reads the setting now and then; above the client's rate, asks each window for repaints at the rate, or as often + // as leaves the client its idle time. void run() { HANDLE timer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); @@ -116,9 +114,8 @@ private: if (w.pending && now - w.askedAt < 0.25) { continue; // not drawn yet (asked again after a while: the client may have skipped it) } - const bool due = rate == 0 ? now - w.drawnAt >= w.frameTime * uncappedIdle - : now - w.askedAt >= interval && w.frameTime < interval * affordable; - if (due) { + // Since the last frame ended: the rest of the interval since it began, and the idle time. + if (now - w.drawnAt >= std::max(interval - w.frameTime, w.frameTime * idleShare)) { w.pending = true; w.askedAt = now; InvalidateRect(w.window, nullptr, FALSE); diff --git a/adapter/frame_pacer.hpp b/adapter/frame_pacer.hpp index 7b5a22c..e77fa2d 100644 --- a/adapter/frame_pacer.hpp +++ b/adapter/frame_pacer.hpp @@ -6,9 +6,10 @@ // Graphics, FrameRate): 30, the client's own and the default, adds nothing; 60, 120, 240 or 360 add frames between // ticks; 0 is uncapped. // -// Drawing runs on the client's main thread, with its input and interface: extra frames are only asked for when they're -// affordable. A window gets one only once its last frame is drawn, and only if its frames take less than three -// quarters of the interval; uncapped, the client is left idle a third of a frame's time between frames. +// The rate counts all of a window's frames, the client's own included: a window is asked for one once its last frame +// is drawn and the interval since it began has passed. Drawing runs on the client's main thread, with its input and +// interface, so the client is also always left idle a third of a frame's time between frames: when the rate can't be +// reached, the client draws as many frames as that allows. #include diff --git a/adapter/recorder.cpp b/adapter/recorder.cpp index 723f50e..2fa39f4 100644 --- a/adapter/recorder.cpp +++ b/adapter/recorder.cpp @@ -35,7 +35,13 @@ void Recorder::flush(bool endsFrame) writer_.clear(); return; } + LARGE_INTEGER start, end, frequency; + QueryPerformanceCounter(&start); process_->send(writer_.bytes(), endsFrame); + QueryPerformanceCounter(&end); + QueryPerformanceFrequency(&frequency); + counters_.bytes += writer_.size(); + counters_.sendSeconds += static_cast(end.QuadPart - start.QuadPart) / static_cast(frequency.QuadPart); writer_.clear(); } @@ -112,6 +118,8 @@ void Recorder::updateBuffer(std::uint32_t id, const void* data, std::uint32_t st std::uint32_t count) { const std::size_t size = std::size_t{elementSize} * count; + ++counters_.bufferUpdates; + counters_.bufferBytes += size; if (std::byte* payload = add(protocol::Op::UpdateBuffer, protocol::UpdateBuffer{id, elementSize, count}, size); payload != nullptr && data != nullptr) { pack(payload, data, stride, elementSize, count); diff --git a/adapter/recorder.hpp b/adapter/recorder.hpp index d9f80cd..0a49510 100644 --- a/adapter/recorder.hpp +++ b/adapter/recorder.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "renderer_process.hpp" #include "t5de/imvu/types.hpp" @@ -52,12 +53,23 @@ public: std::uint32_t count); void destroyBuffer(std::uint32_t id); + // Diagnostics: what's been sent, and the time spent sending (waiting for the renderer process to take it). + struct Counters { + std::uint64_t bytes = 0; + double sendSeconds = 0; + std::uint32_t bufferUpdates = 0; + std::uint64_t bufferBytes = 0; + }; + // Returns the counters since the last call. + Counters takeCounters() { return std::exchange(counters_, {}); } + private: void addPixels(protocol::Op op, std::uint32_t id, const imvu::Pixmap& pixmap, std::uint32_t flags); std::unique_ptr process_; protocol::Writer writer_; std::uint32_t nextId_ = 1; + Counters counters_; }; // Copies count elements of elementSize bytes, stride bytes apart (0: packed), into destination, packed.