diff --git a/adapter/engine.cpp b/adapter/engine.cpp index 99a954a..5dd9236 100644 --- a/adapter/engine.cpp +++ b/adapter/engine.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -263,6 +264,15 @@ void Engine::beginRendering(imvu::PiRenderTarget* target) { QueryPerformanceCounter(&frameStart_); windowFrame_ = target == nullptr; + if (windowFrame_) { + if (lastWindowFrame_ != 0) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + longestGap_ = std::max(longestGap_, static_cast(frameStart_.QuadPart - lastWindowFrame_) / + static_cast(frequency.QuadPart)); + } + lastWindowFrame_ = frameStart_.QuadPart; + } // Frames into our render targets are drawn by the renderer process only; the client's renderer doesn't know them. ourTarget_ = ours(target) ? target : nullptr; // Decided per frame: frames into the client renderer's render targets, and frames while the renderer process @@ -324,11 +334,11 @@ void Engine::endRendering() const Recorder::Counters sent = recorder_->takeCounters(); if (statsLogged_ < 6 || frames_ % 3000 == 0) { ++statsLogged_; - log::info("engine: last 300 frames: {:.0f} per second; each {:.2f} ms beginRendering to endRendering, {:.2f} ms " - "in the client renderer's methods, {:.2f} ms 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, + log::info("engine: last 300 frames: {:.0f} per second, at most {:.1f} ms apart; each {:.2f} ms beginRendering to " + "endRendering, {:.2f} ms in the client renderer's methods, {:.2f} ms 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, longestGap_ * 1000, frameSeconds_ * 1000 / 300, static_cast(innerTicks - innerTicksAtStats_) * 1000 / static_cast(frequency.QuadPart) / 300, sent.sendSeconds * 1000 / 300, static_cast(sent.bytes) / 1024 / 300, sent.bufferUpdates / 300.0, @@ -337,6 +347,7 @@ void Engine::endRendering() } stats_ = {}; frameSeconds_ = 0; + longestGap_ = 0; innerTicksAtStats_ = innerTicks; } } diff --git a/adapter/engine.hpp b/adapter/engine.hpp index 8ed01d5..05e576d 100644 --- a/adapter/engine.hpp +++ b/adapter/engine.hpp @@ -91,6 +91,8 @@ private: Stats stats_; LARGE_INTEGER statsStart_{}; // when the stats' 300 frames started LARGE_INTEGER frameStart_{}; // when the current frame began + std::int64_t lastWindowFrame_ = 0; // when the last window frame began + double longestGap_ = 0; // the stats' longest time between window frames' beginnings, in seconds double frameSeconds_ = 0; // the stats' window frames' time, beginRendering to endRendering bool windowFrame_ = false; // the current frame is drawn into the window bool innerFrame_ = false; // the current frame is begun in the client's renderer diff --git a/adapter/scene_hooks.cpp b/adapter/scene_hooks.cpp index ba9dca6..4655098 100644 --- a/adapter/scene_hooks.cpp +++ b/adapter/scene_hooks.cpp @@ -26,6 +26,8 @@ GetTransform originalGetCameraTransform = nullptr; float cameraEasing = 0; // the share of the way to its target the camera moves at each tick std::atomic stepTime = 0; std::atomic steps = 0; +// How many ticks the last step covered: the client steps a scene several ticks at once when it falls behind. +std::atomic stepTicks = 1; std::atomic stepHooked = false; std::atomic poseHooked = false; std::atomic cameraHooked = false; @@ -50,10 +52,16 @@ void __fastcall step(void* self, void* /*edx*/) { LARGE_INTEGER now; QueryPerformanceCounter(&now); - if (now.QuadPart - stepTime.load() > frequency().QuadPart / 200) { + const std::int64_t last = stepTime.load(); + if (now.QuadPart - last > frequency().QuadPart / 200) { ++steps; + if (last != 0 && millisecondsPerFrame != nullptr && *millisecondsPerFrame != 0) { + const double ticks = static_cast(now.QuadPart - last) * 1000.0 / + static_cast(frequency().QuadPart) / *millisecondsPerFrame; + stepTicks = std::clamp(static_cast(ticks + 0.5), 1, 5); + } + stepTime = now.QuadPart; } - stepTime = now.QuadPart; originalStep(self); } @@ -76,8 +84,8 @@ T& field(void* object, std::size_t offset) return *reinterpret_cast(static_cast(object) + offset); } -// Each effect's time at the last two ticks it was posed at, to tell an animation playing on (one frame a tick) from -// one held, looping back or just started (posed as it is). +// Each effect's time at the last two ticks it was posed at, to tell an animation playing on (at any rate: the time it +// moved per tick at the last step) from one held, looping back or just started (posed as it is). struct EffectHistory { std::uint64_t step = 0; float time = 0; @@ -99,9 +107,9 @@ float tick_fraction() } // Our ModelInstance::updateCalSkeleton, called where ModelInstance::render poses the model before drawing it: between -// ticks, poses it at the time between them. Each playing animation that moved on a frame at the last tick is set -// ahead by the fraction of a frame since, the skeleton posed (by IMVU's), and the times put back, with the skeleton -// marked to be posed again, so everything else sees the tick's pose. +// ticks, poses it at the time between them. Each playing animation that moved on at the last step is set ahead by the +// fraction of a tick since, at the rate it moved (up to 4 frames a tick), the skeleton posed (by IMVU's), and the +// times put back, with the skeleton marked to be posed again, so everything else sees the tick's pose. void __fastcall updateCalSkeleton(void* model, void* /*edx*/) { const float fraction = tick_fraction(); @@ -136,13 +144,13 @@ void __fastcall updateCalSkeleton(void* model, void* /*edx*/) history.time = time; history.step = tick; } - const float moved = history.time - history.previous; - if (history.previous < 0 || moved < frame * 0.5f || moved > frame * 1.5f) { + const float perTick = (history.time - history.previous) / static_cast(stepTicks.load()); + if (history.previous < 0 || perTick <= frame * 0.01f || perTick > frame * 4) { continue; // held, looped back, or just started: as it is } float& posedTime = field(animation, animationTime); saved[count++] = {&posedTime, posedTime}; - posedTime = time + fraction * frame; + posedTime = time + fraction * perTick; } if (count == 0) { originalUpdateCalSkeleton(model); @@ -162,13 +170,16 @@ void __fastcall updateCalSkeleton(void* model, void* /*edx*/) } // The camera (SceneWindow.dll's CameraController::step, at each tick): each of these values moves a share k of the way -// to its target (k is 0.4): the camera's position (+0x118, 3 floats, to +0xf0), and its orbit angles and zoom, in -// degrees (+0x140, 3 floats, to +0x134; the difference taken between -180 and 180). -// A fraction f of a tick on, the same easing has moved it 1 - (1 - k)^f of the way: exactly, nothing guessed. +// to its target (k is 0.4): the camera's position (+0x118, 3 floats, to +0xf0; panning moves the target), and its +// orbit angles and zoom, in degrees (+0x140, 3 floats, to +0x134; the difference taken between -180 and 180). +// A fraction f of a tick on, the same easing has moved it 1 - (1 - k)^f of the way: exactly, nothing guessed. The point +// the camera looks at (+0x160, 3 floats) is the position plus an offset (setCameraFocus, at each tick), so it moves with +// the position. constexpr std::size_t cameraPosition = 0x118; constexpr std::size_t cameraPositionTarget = 0xf0; constexpr std::size_t cameraAngles = 0x140; constexpr std::size_t cameraAnglesTarget = 0x134; +constexpr std::size_t cameraFocus = 0x160; // Nonzero: the camera shows a fixed view ("take"), not eased. constexpr std::size_t cameraTake = 0xec; @@ -191,11 +202,15 @@ void* __fastcall getCameraTransform(void* camera, void* /*edx*/, void* result) return originalGetCameraTransform(camera, result); } const float moved = 1.0f - std::pow(1.0f - cameraEasing, fraction); - float saved[6]; + float saved[9]; for (int i = 0; i < 3; ++i) { float& position = field(camera, cameraPosition + i * 4); + float& focus = field(camera, cameraFocus + i * 4); saved[i] = position; - position += (field(camera, cameraPositionTarget + i * 4) - position) * moved; + saved[6 + i] = focus; + const float step = (field(camera, cameraPositionTarget + i * 4) - position) * moved; + position += step; + focus += step; float& angle = field(camera, cameraAngles + i * 4); saved[3 + i] = angle; angle = degrees180(angle + degrees180(field(camera, cameraAnglesTarget + i * 4) - angle) * moved); @@ -204,6 +219,7 @@ void* __fastcall getCameraTransform(void* camera, void* /*edx*/, void* result) for (int i = 0; i < 3; ++i) { field(camera, cameraPosition + i * 4) = saved[i]; field(camera, cameraAngles + i * 4) = saved[3 + i]; + field(camera, cameraFocus + i * 4) = saved[6 + i]; } return transform; }