Files
dhkatzandClaude Opus 5.5 399d934a08
CI / build (push) Successful in 32s
Pace all frames to the frame rate, at best effort
The pacer counted only its own frames, so the client's 30 came on top (60 gave
about 80), and it added no frames at all when the rate was out of reach. It now
asks for a window's next frame once the interval since its last frame began has
passed, and the client has had a third of a frame's time idle: all frames
within the rate, and as many as the client can draw when it can't reach it.

The engine's stats also break a frame's time down: sending to the renderer
process, bytes sent, buffer updates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 21:16:22 -07:00

132 lines
4.4 KiB
C++

#include "recorder.hpp"
#include <cstring>
#include "log.hpp"
#include "pixmap.hpp"
namespace t5de {
namespace {
// Between frames, recorded commands (mostly textures and buffers being loaded) are sent once they reach this size.
constexpr std::size_t flushSize = 4 * 1024 * 1024;
} // namespace
void pack(std::byte* destination, const void* source, std::uint32_t stride, std::uint32_t elementSize,
std::uint32_t count)
{
const auto* from = static_cast<const std::byte*>(source);
if (stride == 0 || stride == elementSize) {
std::memcpy(destination, from, std::size_t{elementSize} * count);
return;
}
for (std::uint32_t i = 0; i < count; ++i) {
std::memcpy(destination + std::size_t{i} * elementSize, from + std::size_t{i} * stride, elementSize);
}
}
Recorder::Recorder(std::unique_ptr<RendererProcess> process) : process_(std::move(process)) {}
void Recorder::flush(bool endsFrame)
{
if (!active()) {
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<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
writer_.clear();
}
void Recorder::flushIfLarge()
{
if (writer_.size() >= flushSize) {
flush(false);
}
}
void Recorder::addPixels(protocol::Op op, std::uint32_t id, const imvu::Pixmap& pixmap, std::uint32_t flags)
{
const auto pixels = pixmap_pixels(pixmap);
if (!pixels || pixels->pixels == nullptr) {
return;
}
const std::size_t rowSize = std::size_t{pixels->width} * 4;
std::byte* payload = nullptr;
if (op == protocol::Op::CreateTexture) {
payload = add(op, protocol::CreateTexture{id, pixels->width, pixels->height, flags}, rowSize * pixels->height);
} else {
payload = add(op, protocol::UpdateTexture{id, pixels->width, pixels->height}, rowSize * pixels->height);
}
if (payload == nullptr) {
return;
}
const auto* rows = reinterpret_cast<const std::byte*>(pixels->pixels);
for (std::uint32_t y = 0; y < pixels->height; ++y) {
std::memcpy(payload + y * rowSize, rows + std::size_t{y} * pixels->rowBytes, rowSize);
}
flushIfLarge();
}
std::uint32_t Recorder::createRenderTarget(std::uint32_t width, std::uint32_t height)
{
const std::uint32_t id = nextId_++;
add(protocol::Op::CreateRenderTarget, protocol::CreateRenderTarget{id, width, height});
return id;
}
std::span<const std::byte> Recorder::readBack()
{
return active() ? process_->readBack() : std::span<const std::byte>{};
}
std::uint32_t Recorder::createTexture(const imvu::Pixmap& pixmap, std::uint32_t flags)
{
const std::uint32_t id = nextId_++;
addPixels(protocol::Op::CreateTexture, id, pixmap, flags);
return id;
}
void Recorder::updateTexture(std::uint32_t id, const imvu::Pixmap& pixmap)
{
addPixels(protocol::Op::UpdateTexture, id, pixmap, 0);
}
void Recorder::destroyTexture(std::uint32_t id) { add(protocol::Op::DestroyTexture, protocol::DestroyTexture{id}); }
std::uint32_t Recorder::createBuffer(protocol::BufferKind kind, const void* data, std::uint32_t stride,
std::uint32_t elementSize, std::uint32_t count)
{
const std::uint32_t id = nextId_++;
const std::size_t size = data != nullptr ? std::size_t{elementSize} * count : 0;
if (std::byte* payload = add(protocol::Op::CreateBuffer, protocol::CreateBuffer{id, kind, elementSize, count}, size);
payload != nullptr && size != 0) {
pack(payload, data, stride, elementSize, count);
}
flushIfLarge();
return id;
}
void Recorder::updateBuffer(std::uint32_t id, const void* data, std::uint32_t stride, std::uint32_t elementSize,
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);
}
}
void Recorder::destroyBuffer(std::uint32_t id) { add(protocol::Op::DestroyBuffer, protocol::DestroyBuffer{id}); }
} // namespace t5de