Draw the client's scene in the renderer process

The adapter records everything the client draws (state, transforms, lights, fog, textures and buffers) as a command stream, sent in chunks through the shared memory channel, while IMVU's Direct3D renderer still draws the whole view. The renderer process draws it with Diligent, with shaders reproducing IMVU's Direct3D 9 fixed-function setup, over the right half of the 3D view to compare. It skips to the newest frame when it falls behind, and logs its errors and frame times.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-28 05:52:15 -07:00
1 parent 5c35c8b63a
commit 42fc55cb91
23 files changed
+2121 -472

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
#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;
}
process_->send(writer_.bytes(), endsFrame);
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::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;
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