CI / build (push) Successful in 48s
While the renderer process runs, the adapter makes the client's render targets itself and the renderer process draws frames into them: texture targets are drawn with like any texture, and a pixmap target's frame is read back through the channel into its Pixmap (protocol version 4: BeginFrame names its target, CreateRenderTarget, Channel::readSize and the -read event). Frames into render targets are drawn even while the window is hidden. The client uses pixmap targets for snapshots (outfit pictures, photos), checked to look right. The render target interfaces are declared from IMVU's vtables. Co-Authored-By: Claude Opus 5.5 <[email protected]>
124 lines
4.0 KiB
C++
124 lines
4.0 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;
|
|
}
|
|
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::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;
|
|
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
|