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]>
192 lines
5.7 KiB
C++
192 lines
5.7 KiB
C++
#include "resources.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <new>
|
|
#include <unordered_set>
|
|
|
|
#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) {
|
|
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) {
|
|
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)
|
|
{
|
|
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
|