Files
t5de-renderer/adapter/resources.cpp
T
dhkatzandClaude Opus 5.5 065c7e9300
CI / build (push) Successful in 1m15s
Never make the client wait for drawing, or hold up its steps
The renderer process now takes each view's chunks on a thread of its own as
they come, and draws the newest frame on its main thread: the client no longer
waits for the previous frame to be drawn (on a VM, 22 of a frame's 25 ms).

The adapter records the client's steps (SceneViewer::step, by replacing its
entry in the exported IFrameListener vtable, after checking it), and the frame
pacer doesn't ask for a frame that wouldn't end before the next step is due: a
late step made the client catch up two at once, which showed as hitches. While
the renderer process covers the view, the client's renderer doesn't begin or
present frames, which could wait for the display.

The engine's stats now include the time in the client renderer's methods.

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

193 lines
5.8 KiB
C++

#include "resources.hpp"
#include <algorithm>
#include <cstring>
#include <mutex>
#include <new>
#include <unordered_set>
#include "inner_timing.hpp"
#include "log.hpp"
namespace t5de {
namespace {
// Our live resources, by address.
struct Registry {
std::mutex mutex;
std::unordered_set<const void*> objects;
};
Registry& registry()
{
static Registry instance;
return instance;
}
void add(const void* object)
{
const std::scoped_lock lock(registry().mutex);
registry().objects.insert(object);
}
void remove(const void* object)
{
const std::scoped_lock lock(registry().mutex);
registry().objects.erase(object);
}
} // namespace
bool ours(const void* object)
{
if (object == nullptr) {
return false;
}
const std::scoped_lock lock(registry().mutex);
return registry().objects.contains(object);
}
Texture::Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
: inner_(inner), recorder_(std::move(recorder)), id_(id)
{
add(static_cast<imvu::PiRenderTexture*>(this));
}
Texture::Texture(std::shared_ptr<Recorder> recorder, std::uint32_t targetId, imvu::Vec2i size)
: inner_(nullptr), recorder_(std::move(recorder)), id_(targetId), size_(size), ownsId_(false)
{
add(static_cast<imvu::PiRenderTexture*>(this));
}
Texture::~Texture()
{
remove(static_cast<imvu::PiRenderTexture*>(this));
delete inner_; // through its virtual destructor: IMVU's code frees it
if (ownsId_) {
recorder_->destroyTexture(id_);
}
}
imvu::Vec2i Texture::size() const { return inner_ != nullptr ? inner_->size() : size_; }
void Texture::updateRegions(const imvu::Pixmap& pixmap, const imvu::CompoundRegion& regions)
{
if (inner_ != nullptr) {
{ const InnerCall timed; inner_->updateRegions(pixmap, regions); }
recorder_->updateTexture(id_, pixmap); // all of it: the regions' layout isn't known
}
}
void Texture::update(const imvu::Pixmap& pixmap)
{
if (inner_ != nullptr) {
{ const InnerCall timed; inner_->update(pixmap); }
recorder_->updateTexture(id_, pixmap);
}
}
imvu::Pixmap* Texture::read(imvu::Pixmap* result) const
{
if (inner_ != nullptr) {
return inner_->read(result);
}
// A render target's texture: not read back (not seen used); an image of its size.
log::info("texture: a render target's texture read; not supported, returning a blank image");
return OwnedPixmap(size_.x, size_.y).copyTo(result);
}
IndexBuffer::IndexBuffer(imvu::PiInternalIndexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
: inner_(inner), recorder_(std::move(recorder)), id_(id)
{
add(static_cast<imvu::PiInternalIndexBuffer*>(this));
}
IndexBuffer::~IndexBuffer()
{
remove(static_cast<imvu::PiInternalIndexBuffer*>(this));
delete inner_;
recorder_->destroyBuffer(id_);
}
VertexBuffer::VertexBuffer(imvu::PiInternalVertexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
: inner_(inner), recorder_(std::move(recorder)), id_(id)
{
add(static_cast<imvu::PiInternalVertexBuffer*>(this));
}
VertexBuffer::~VertexBuffer()
{
remove(static_cast<imvu::PiInternalVertexBuffer*>(this));
delete inner_;
recorder_->destroyBuffer(id_);
}
void VertexBuffer::update(const void* data, std::uint32_t stride, std::uint32_t elementSize, std::uint32_t elementCount)
{
{ const InnerCall timed; inner_->update(data, stride, elementSize, elementCount); }
recorder_->updateBuffer(id_, data, stride, elementSize, elementCount);
}
void VertexBuffer::unknown2() { inner_->unknown2(); }
PixmapTarget::PixmapTarget(std::shared_ptr<Recorder> recorder, imvu::Vec2i size)
: recorder_(std::move(recorder)),
id_(recorder_->createRenderTarget(static_cast<std::uint32_t>(size.x), static_cast<std::uint32_t>(size.y))),
size_(size), pixmap_(size.x, size.y)
{
add(static_cast<imvu::PiRenderTarget*>(this));
}
PixmapTarget::~PixmapTarget()
{
remove(static_cast<imvu::PiRenderTarget*>(this));
recorder_->destroyTexture(id_);
}
imvu::Pixmap* PixmapTarget::getPixmap(imvu::Pixmap* result) const { return pixmap_.copyTo(result); }
void PixmapTarget::setPixels(std::span<const std::byte> pixels)
{
const auto into = pixmap_.valid() ? pixmap_pixels(pixmap_.get()) : std::nullopt;
const auto width = static_cast<std::uint32_t>(size_.x);
const auto height = static_cast<std::uint32_t>(size_.y);
const std::size_t rowSize = std::size_t{width} * 4;
if (!into || into->pixels == nullptr || pixels.size() < rowSize * height) {
log::info("render target: {}x{} frame not read back ({} bytes)", width, height, pixels.size());
return;
}
const std::size_t copied = std::min<std::size_t>(rowSize, std::size_t{into->width} * 4);
auto* rows = reinterpret_cast<std::byte*>(into->pixels);
for (std::uint32_t y = 0; y < std::min(into->height, height); ++y) {
std::memcpy(rows + std::size_t{y} * into->rowBytes, pixels.data() + y * rowSize, copied);
}
}
TextureTarget::TextureTarget(std::shared_ptr<Recorder> recorder, imvu::Vec2i size)
: recorder_(std::move(recorder)),
id_(recorder_->createRenderTarget(static_cast<std::uint32_t>(size.x), static_cast<std::uint32_t>(size.y))),
size_(size)
{
add(static_cast<imvu::PiRenderTarget*>(this));
}
TextureTarget::~TextureTarget()
{
remove(static_cast<imvu::PiRenderTarget*>(this));
recorder_->destroyTexture(id_);
}
imvu::TextureArea* TextureTarget::area(imvu::TextureArea* result) const
{
return new (result) imvu::TextureArea{0, 0, 1, 1};
}
imvu::SharedPtr<imvu::PiRenderTexture>* TextureTarget::texture(imvu::SharedPtr<imvu::PiRenderTexture>* result) const
{
return new (result) imvu::SharedPtr<imvu::PiRenderTexture>(
std::unique_ptr<imvu::PiRenderTexture>(new Texture(recorder_, id_, size_)));
}
} // namespace t5de