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]>
This commit is contained in:
1 parent
fa883892db
commit
6e4fe589e5
18 files changed
+523
-58
No files matched your search
+32
-9
@@ -232,39 +232,62 @@ imvu::PiInternalVertexBuffer* Engine::newDynamicVertexBuffer(std::uint32_t eleme
|
||||
imvu::SharedPtr<imvu::PiPixmapRenderTarget> Engine::newPixmapRenderTarget(imvu::Vec2i size)
|
||||
{
|
||||
log::info("engine: frame {}: a {}x{} pixmap render target", frames_, size.x, size.y);
|
||||
return inner_->newPixmapRenderTarget(size);
|
||||
if (!recorder_->active() || size.x <= 0 || size.y <= 0) {
|
||||
return inner_->newPixmapRenderTarget(size);
|
||||
}
|
||||
return imvu::SharedPtr<PixmapTarget>(std::make_unique<PixmapTarget>(recorder_, size));
|
||||
}
|
||||
|
||||
imvu::SharedPtr<imvu::PiTextureRenderTarget> Engine::newTextureRenderTarget(imvu::Vec2i size)
|
||||
{
|
||||
log::info("engine: frame {}: a {}x{} texture render target", frames_, size.x, size.y);
|
||||
return inner_->newTextureRenderTarget(size);
|
||||
if (!recorder_->active() || size.x <= 0 || size.y <= 0) {
|
||||
return inner_->newTextureRenderTarget(size);
|
||||
}
|
||||
return imvu::SharedPtr<TextureTarget>(std::make_unique<TextureTarget>(recorder_, size));
|
||||
}
|
||||
|
||||
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
||||
{
|
||||
inner_->beginRendering(target);
|
||||
// Decided per frame: frames into render targets, and frames while the renderer process doesn't cover the view, are
|
||||
// drawn by the client's renderer.
|
||||
const bool takenOver = target == nullptr && recorder_->covering();
|
||||
// Frames into our render targets are drawn by the renderer process only; the client's renderer doesn't know them.
|
||||
ourTarget_ = ours(target) ? target : nullptr;
|
||||
if (ourTarget_ == nullptr) {
|
||||
inner_->beginRendering(target);
|
||||
}
|
||||
// Decided per frame: frames into the client renderer's render targets, and frames while the renderer process
|
||||
// doesn't cover the view, are drawn by the client's renderer.
|
||||
const bool takenOver = ourTarget_ != nullptr || (target == nullptr && recorder_->covering());
|
||||
if (takenOver != takenOver_ && target == nullptr) {
|
||||
log::info("engine: frame {}: {}", frames_, takenOver ? "drawn by the renderer process" : "drawn by the client's renderer");
|
||||
}
|
||||
takenOver_ = takenOver;
|
||||
if (target != nullptr && foreignLogged_ < 20) {
|
||||
++foreignLogged_;
|
||||
log::info("engine: frame {}: drawing into a render target ({})", frames_, static_cast<void*>(target));
|
||||
log::info("engine: frame {}: drawing into {} render target ({})", frames_,
|
||||
ourTarget_ != nullptr ? "our" : "the client renderer's", static_cast<void*>(target));
|
||||
}
|
||||
recorder_->add(Op::BeginFrame, protocol::BeginFrame{target != nullptr});
|
||||
protocol::BeginFrame begin{target == nullptr ? 0 : protocol::foreignTarget, 0};
|
||||
if (const auto* pixmap = dynamic_cast<const PixmapTarget*>(ourTarget_)) {
|
||||
begin = {pixmap->id(), 1};
|
||||
} else if (const auto* texture = dynamic_cast<const TextureTarget*>(ourTarget_)) {
|
||||
begin = {texture->id(), 0};
|
||||
}
|
||||
recorder_->add(Op::BeginFrame, begin);
|
||||
}
|
||||
|
||||
void Engine::endRendering()
|
||||
{
|
||||
inner_->endRendering();
|
||||
if (ourTarget_ == nullptr) {
|
||||
inner_->endRendering();
|
||||
}
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
recorder_->add(Op::EndFrame, protocol::EndFrame{static_cast<std::uint64_t>(now.QuadPart)});
|
||||
recorder_->flush(true);
|
||||
if (auto* pixmap = dynamic_cast<PixmapTarget*>(ourTarget_)) {
|
||||
pixmap->setPixels(recorder_->readBack());
|
||||
}
|
||||
ourTarget_ = nullptr;
|
||||
++frames_;
|
||||
if (frames_ % 300 == 0 && statsLogged_ < 6) {
|
||||
++statsLogged_;
|
||||
|
||||
@@ -73,6 +73,7 @@ private:
|
||||
std::shared_ptr<Recorder> recorder_;
|
||||
std::uint64_t frames_ = 0;
|
||||
bool takenOver_ = false; // this frame is drawn by the renderer process only
|
||||
imvu::PiRenderTarget* ourTarget_ = nullptr; // this frame's render target, if it's one of ours
|
||||
int foreignLogged_ = 0; // diagnostics: textures the client renderer made, render targets
|
||||
|
||||
// Diagnostics: what the client draws, summed over 300 frames and logged (the first few times).
|
||||
|
||||
@@ -8,12 +8,20 @@ namespace {
|
||||
|
||||
using Getter = unsigned(__thiscall*)(const imvu::Pixmap*);
|
||||
using PixelsGetter = std::uint32_t*(__thiscall*)(const imvu::Pixmap*);
|
||||
using SizeConstructor = imvu::Pixmap*(__thiscall*)(void*, int, int);
|
||||
using DefaultConstructor = imvu::Pixmap*(__thiscall*)(void*);
|
||||
using CopyConstructor = imvu::Pixmap*(__thiscall*)(void*, const imvu::Pixmap*);
|
||||
using Destructor = void(__thiscall*)(imvu::Pixmap*);
|
||||
|
||||
struct Methods {
|
||||
Getter width = nullptr;
|
||||
Getter height = nullptr;
|
||||
Getter rowBytes = nullptr;
|
||||
PixelsGetter pixels = nullptr;
|
||||
SizeConstructor construct = nullptr;
|
||||
DefaultConstructor constructEmpty = nullptr;
|
||||
CopyConstructor copy = nullptr;
|
||||
Destructor destroy = nullptr;
|
||||
};
|
||||
|
||||
const Methods& methods()
|
||||
@@ -25,6 +33,10 @@ const Methods& methods()
|
||||
found.height = reinterpret_cast<Getter>(GetProcAddress(module, "?height@Pixmap@@QBEIXZ"));
|
||||
found.rowBytes = reinterpret_cast<Getter>(GetProcAddress(module, "?rowBytes@Pixmap@@QBEIXZ"));
|
||||
found.pixels = reinterpret_cast<PixelsGetter>(GetProcAddress(module, "?pixels@Pixmap@@QBEPAIXZ"));
|
||||
found.construct = reinterpret_cast<SizeConstructor>(GetProcAddress(module, "??0Pixmap@@QAE@HH@Z"));
|
||||
found.constructEmpty = reinterpret_cast<DefaultConstructor>(GetProcAddress(module, "??0Pixmap@@QAE@XZ"));
|
||||
found.copy = reinterpret_cast<CopyConstructor>(GetProcAddress(module, "??0Pixmap@@QAE@ABV0@@Z"));
|
||||
found.destroy = reinterpret_cast<Destructor>(GetProcAddress(module, "??1Pixmap@@QAE@XZ"));
|
||||
}
|
||||
return found;
|
||||
}();
|
||||
@@ -42,4 +54,30 @@ std::optional<PixmapPixels> pixmap_pixels(const imvu::Pixmap& pixmap)
|
||||
return PixmapPixels{m.width(&pixmap), m.height(&pixmap), m.rowBytes(&pixmap), m.pixels(&pixmap)};
|
||||
}
|
||||
|
||||
OwnedPixmap::OwnedPixmap(int width, int height)
|
||||
{
|
||||
const Methods& m = methods();
|
||||
if (m.construct != nullptr && m.constructEmpty != nullptr && m.copy != nullptr && m.destroy != nullptr) {
|
||||
m.construct(storage_, width, height);
|
||||
valid_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
OwnedPixmap::~OwnedPixmap()
|
||||
{
|
||||
if (valid_) {
|
||||
methods().destroy(reinterpret_cast<imvu::Pixmap*>(storage_));
|
||||
}
|
||||
}
|
||||
|
||||
imvu::Pixmap* OwnedPixmap::copyTo(imvu::Pixmap* result) const
|
||||
{
|
||||
const Methods& m = methods();
|
||||
if (valid_) {
|
||||
return m.copy(result, &get());
|
||||
}
|
||||
// Without pixmap.dll's methods there's nothing to construct one with (IMVU's own code would have failed already).
|
||||
return m.constructEmpty != nullptr ? m.constructEmpty(result) : result;
|
||||
}
|
||||
|
||||
} // namespace t5de
|
||||
+26
-2
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
// Reading IMVU's images (Pixmap) through pixmap.dll's exported methods, rather than depending on their layout.
|
||||
// IMVU's images (Pixmap), through pixmap.dll's exported methods rather than depending on their layout (only their
|
||||
// size, for making our own).
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
@@ -12,10 +14,32 @@ struct PixmapPixels {
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint32_t rowBytes;
|
||||
const std::uint32_t* pixels; // 0xAARRGGBB, rows rowBytes apart
|
||||
std::uint32_t* pixels; // 0xAARRGGBB, rows rowBytes apart
|
||||
};
|
||||
|
||||
// Null if pixmap.dll's methods can't be found.
|
||||
std::optional<PixmapPixels> pixmap_pixels(const imvu::Pixmap& pixmap);
|
||||
|
||||
// A Pixmap of our own, made and destroyed by pixmap.dll.
|
||||
class OwnedPixmap {
|
||||
public:
|
||||
// Width by height; invalid if pixmap.dll's methods can't be found.
|
||||
OwnedPixmap(int width, int height);
|
||||
OwnedPixmap(const OwnedPixmap&) = delete;
|
||||
OwnedPixmap& operator=(const OwnedPixmap&) = delete;
|
||||
~OwnedPixmap();
|
||||
|
||||
[[nodiscard]] bool valid() const { return valid_; }
|
||||
[[nodiscard]] const imvu::Pixmap& get() const { return *reinterpret_cast<const imvu::Pixmap*>(storage_); }
|
||||
|
||||
// Constructs a copy in result (raw memory for a Pixmap, as for a Pixmap returned by value); an empty Pixmap if
|
||||
// this one isn't valid.
|
||||
imvu::Pixmap* copyTo(imvu::Pixmap* result) const;
|
||||
|
||||
private:
|
||||
// sizeof(Pixmap) in pixmap.dll: its constructors set 7 fields.
|
||||
alignas(4) std::byte storage_[28];
|
||||
bool valid_ = false;
|
||||
};
|
||||
|
||||
} // namespace t5de
|
||||
@@ -69,6 +69,18 @@ void Recorder::addPixels(protocol::Op op, std::uint32_t id, const imvu::Pixmap&
|
||||
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_++;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include "renderer_process.hpp"
|
||||
#include "t5de/imvu/types.hpp"
|
||||
@@ -37,6 +38,10 @@ public:
|
||||
void flushIfLarge();
|
||||
|
||||
std::uint32_t createTexture(const imvu::Pixmap& pixmap, std::uint32_t flags);
|
||||
// Destroyed with destroyTexture.
|
||||
std::uint32_t createRenderTarget(std::uint32_t width, std::uint32_t height);
|
||||
// After flushing a frame that reads its render target back: its pixels (RendererProcess::readBack).
|
||||
std::span<const std::byte> readBack();
|
||||
void updateTexture(std::uint32_t id, const imvu::Pixmap& pixmap);
|
||||
void destroyTexture(std::uint32_t id);
|
||||
|
||||
|
||||
@@ -72,7 +72,9 @@ std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::V
|
||||
static_cast<protocol::Channel*>(MapViewOfFile(renderer->mapping_, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
||||
renderer->ready_ = CreateEventW(nullptr, FALSE, FALSE, (name + protocol::readyEventSuffix).c_str());
|
||||
renderer->consumed_ = CreateEventW(nullptr, FALSE, TRUE, (name + protocol::consumedEventSuffix).c_str());
|
||||
if (renderer->channel_ == nullptr || renderer->ready_ == nullptr || renderer->consumed_ == nullptr) {
|
||||
renderer->read_ = CreateEventW(nullptr, FALSE, FALSE, (name + protocol::readEventSuffix).c_str());
|
||||
if (renderer->channel_ == nullptr || renderer->ready_ == nullptr || renderer->consumed_ == nullptr
|
||||
|| renderer->read_ == nullptr) {
|
||||
log::info("renderer process: can't set up the channel: error {}", GetLastError());
|
||||
return nullptr;
|
||||
}
|
||||
@@ -86,7 +88,7 @@ std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::V
|
||||
.chunkSize = 0,
|
||||
.chunkFlags = 0,
|
||||
.covering = 0,
|
||||
.reserved = 0};
|
||||
.readSize = 0};
|
||||
|
||||
// The renderer's window goes on top of the client's 3D view, as a child window. The client's window mustn't clip
|
||||
// its children (WS_CLIPCHILDREN, which the client creates it with): the client draws a frame when its window gets
|
||||
@@ -117,7 +119,7 @@ RendererProcess::~RendererProcess()
|
||||
}
|
||||
UnmapViewOfFile(channel_);
|
||||
}
|
||||
for (HANDLE handle : {ready_, consumed_, mapping_, process_}) {
|
||||
for (HANDLE handle : {ready_, consumed_, read_, mapping_, process_}) {
|
||||
if (handle != nullptr) {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
@@ -150,4 +152,20 @@ bool RendererProcess::send(std::span<const std::byte> commands, bool endsFrame)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::span<const std::byte> RendererProcess::readBack()
|
||||
{
|
||||
if (!alive_) {
|
||||
return {};
|
||||
}
|
||||
const HANDLE waits[] = {read_, process_};
|
||||
const DWORD result = WaitForMultipleObjects(2, waits, FALSE, consumeTimeout);
|
||||
if (result != WAIT_OBJECT_0) {
|
||||
log::info("renderer process: {} reading back a frame; giving up on it",
|
||||
result == WAIT_OBJECT_0 + 1 ? "exited" : "not responding");
|
||||
alive_ = false;
|
||||
return {};
|
||||
}
|
||||
return {data_, std::min<std::size_t>(channel_->readSize, channel_->capacity)};
|
||||
}
|
||||
|
||||
} // namespace t5de
|
||||
@@ -25,6 +25,10 @@ public:
|
||||
// process is gone or stopped taking them (it's then given up on).
|
||||
bool send(std::span<const std::byte> commands, bool endsFrame);
|
||||
|
||||
// After sending a frame that reads its render target back: waits for the pixels (protocol::Channel), valid until
|
||||
// the next send. Empty if the renderer process couldn't, or is gone.
|
||||
std::span<const std::byte> readBack();
|
||||
|
||||
[[nodiscard]] bool alive() const { return alive_; }
|
||||
|
||||
// Whether the renderer process covers the whole 3D view (protocol::Channel::covering).
|
||||
@@ -36,6 +40,7 @@ private:
|
||||
HANDLE mapping_ = nullptr;
|
||||
HANDLE ready_ = nullptr;
|
||||
HANDLE consumed_ = nullptr;
|
||||
HANDLE read_ = nullptr;
|
||||
HANDLE process_ = nullptr;
|
||||
protocol::Channel* channel_ = nullptr;
|
||||
std::byte* data_ = nullptr;
|
||||
|
||||
+90
-7
@@ -1,8 +1,13 @@
|
||||
#include "resources.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
namespace t5de {
|
||||
|
||||
namespace {
|
||||
@@ -48,28 +53,48 @@ Texture::Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorde
|
||||
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
|
||||
recorder_->destroyTexture(id_);
|
||||
if (ownsId_) {
|
||||
recorder_->destroyTexture(id_);
|
||||
}
|
||||
}
|
||||
|
||||
imvu::Vec2i Texture::size() const { return inner_->size(); }
|
||||
imvu::Vec2i Texture::size() const { return inner_ != nullptr ? inner_->size() : size_; }
|
||||
|
||||
void Texture::updateRegions(const imvu::Pixmap& pixmap, const imvu::CompoundRegion& regions)
|
||||
{
|
||||
inner_->updateRegions(pixmap, regions);
|
||||
recorder_->updateTexture(id_, pixmap); // all of it: the regions' layout isn't known
|
||||
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)
|
||||
{
|
||||
inner_->update(pixmap);
|
||||
recorder_->updateTexture(id_, pixmap);
|
||||
if (inner_ != nullptr) {
|
||||
inner_->update(pixmap);
|
||||
recorder_->updateTexture(id_, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
imvu::Pixmap* Texture::read(imvu::Pixmap* result) const { return inner_->read(result); }
|
||||
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)
|
||||
@@ -105,4 +130,62 @@ void VertexBuffer::update(const void* data, std::uint32_t stride, std::uint32_t
|
||||
|
||||
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
|
||||
+48
-2
@@ -3,8 +3,12 @@
|
||||
// client renderer still draws with) and mirrors it, by id, to the renderer process. The client deletes them through
|
||||
// their virtual destructors.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include "pixmap.hpp"
|
||||
|
||||
#include "recorder.hpp"
|
||||
#include "t5de/imvu/resources.hpp"
|
||||
@@ -14,6 +18,9 @@ namespace t5de {
|
||||
class Texture final : public imvu::PiRenderTexture {
|
||||
public:
|
||||
Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id);
|
||||
// What's been drawn into one of our texture render targets (TextureTarget): no texture of the client renderer's,
|
||||
// and the id stays the target's.
|
||||
Texture(std::shared_ptr<Recorder> recorder, std::uint32_t targetId, imvu::Vec2i size);
|
||||
~Texture() override;
|
||||
|
||||
imvu::Vec2i size() const override;
|
||||
@@ -28,6 +35,44 @@ private:
|
||||
imvu::PiRenderTexture* inner_;
|
||||
std::shared_ptr<Recorder> recorder_;
|
||||
std::uint32_t id_;
|
||||
imvu::Vec2i size_{};
|
||||
bool ownsId_ = true;
|
||||
};
|
||||
|
||||
// Render targets the renderer process draws (made while it runs; otherwise the client renderer makes its own).
|
||||
class PixmapTarget final : public imvu::PiPixmapRenderTarget {
|
||||
public:
|
||||
PixmapTarget(std::shared_ptr<Recorder> recorder, imvu::Vec2i size);
|
||||
~PixmapTarget() override;
|
||||
|
||||
imvu::Pixmap* getPixmap(imvu::Pixmap* result) const override;
|
||||
|
||||
// Copies a frame's pixels, read back from the renderer process (rows packed, 0xAARRGGBB), into the pixmap.
|
||||
void setPixels(std::span<const std::byte> pixels);
|
||||
|
||||
[[nodiscard]] std::uint32_t id() const { return id_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Recorder> recorder_;
|
||||
std::uint32_t id_;
|
||||
imvu::Vec2i size_;
|
||||
OwnedPixmap pixmap_;
|
||||
};
|
||||
|
||||
class TextureTarget final : public imvu::PiTextureRenderTarget {
|
||||
public:
|
||||
TextureTarget(std::shared_ptr<Recorder> recorder, imvu::Vec2i size);
|
||||
~TextureTarget() override;
|
||||
|
||||
imvu::TextureArea* area(imvu::TextureArea* result) const override;
|
||||
imvu::SharedPtr<imvu::PiRenderTexture>* texture(imvu::SharedPtr<imvu::PiRenderTexture>* result) const override;
|
||||
|
||||
[[nodiscard]] std::uint32_t id() const { return id_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Recorder> recorder_;
|
||||
std::uint32_t id_;
|
||||
imvu::Vec2i size_;
|
||||
};
|
||||
|
||||
class IndexBuffer final : public imvu::PiInternalIndexBuffer {
|
||||
@@ -62,8 +107,9 @@ private:
|
||||
std::uint32_t id_;
|
||||
};
|
||||
|
||||
// Whether an object is one of ours (live Texture, IndexBuffer or VertexBuffer): the client can also hand the engine
|
||||
// objects the client renderer made itself (render targets' textures), which must reach it unchanged.
|
||||
// Whether an object is one of ours (live Texture, IndexBuffer, VertexBuffer, PixmapTarget or TextureTarget): the client
|
||||
// can also hand the engine objects the client renderer made itself (its render targets, and their textures), which
|
||||
// must reach it unchanged.
|
||||
bool ours(const void* object);
|
||||
|
||||
// The client renderer's object, and our id, for what the client passes in: ours unwrapped, others unchanged (id 0).
|
||||
|
||||
Reference in new issue
Block a user