Add the renderer process

The renderer becomes two programs: the adapter (t5deRenderEngine.dll, 32-bit, in IMVU's process) and a 64-bit renderer process (t5de-renderer.exe) drawing with Diligent Engine, fetched with FetchContent and pinned. For now a spike: the adapter starts the renderer process for each engine and signals each frame through a shared memory channel, and the renderer process draws a box into a child window in the client's 3D view.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-28 05:17:03 -07:00
1 parent 0d126574bf
commit 9a7d7d2908
18 files changed
+491 -57

No files matched your search

+395
View File
@@ -0,0 +1,395 @@
#include "passthrough_engine.hpp"
#include <windows.h>
#include <atomic>
#include <string>
#include "log.hpp"
namespace t5de {
namespace {
constexpr std::array<const char*, PassthroughEngine::slots> names = {
"~PiRenderEngine",
"resizeFrameBuffer",
"enable2dFrameBuffer",
"clear",
"setProjection",
"setModelSpace",
"setViewSpace",
"setTextures",
"setDepthTestingOn",
"setDepthWritingOn",
"setBlendMode",
"setAlphaTestingOn",
"setAlphaTestValue",
"setBlendingOn",
"setBackFaceCullingOn",
"setNormalInvertingOn",
"setLightingOn",
"setLightProperties",
"setFogOn",
"setFogAttributes",
"setTextureTransformMatrixIdentity",
"setTextureTransformMatrix",
"setEnvironmentAmbient",
"newRenderTexture",
"newIndexBuffer",
"newStaticVertexBuffer",
"newDynamicVertexBuffer",
"newPixmapRenderTarget",
"newTextureRenderTarget",
"beginRendering",
"endRendering",
"drawTriangles",
"drawLine",
"drawPointSprites",
"drawTrianglesFromMemory",
};
// Frames whose calls are logged: the first few, then one a minute or so at 30 FPS.
bool logged(std::uint64_t frame) { return frame < 5 || frame % 1800 == 0; }
// Whether to log a call's arguments: the first few calls of each method, in the whole process. Enough to check that
// our layouts of IMVU's types read sensible values.
bool sampled(std::size_t slot)
{
static std::array<std::atomic<int>, PassthroughEngine::slots> calls{};
return calls[slot]++ < 3;
}
std::string text(const imvu::Vec3f& v) { return std::format("({:.3f}, {:.3f}, {:.3f})", v.x, v.y, v.z); }
std::string text(const imvu::Transform& t)
{
const auto& m = t.linear;
return std::format("[{:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f}] + {}", m[0][0], m[0][1],
m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], text(t.translation));
}
// A pixmap's size, from pixmap.dll's exported Pixmap::width and Pixmap::height.
imvu::Vec2i pixmap_size(const imvu::Pixmap& pixmap)
{
using Getter = unsigned(__thiscall*)(const imvu::Pixmap*);
struct Getters {
Getter width;
Getter height;
};
static const Getters getters = [] {
HMODULE module = GetModuleHandleW(L"pixmap.dll");
if (module == nullptr) {
return Getters{};
}
return Getters{reinterpret_cast<Getter>(GetProcAddress(module, "?width@Pixmap@@QBEIXZ")),
reinterpret_cast<Getter>(GetProcAddress(module, "?height@Pixmap@@QBEIXZ"))};
}();
if (getters.width == nullptr || getters.height == nullptr) {
return {-1, -1};
}
return {static_cast<int>(getters.width(&pixmap)), static_cast<int>(getters.height(&pixmap))};
}
} // namespace
PassthroughEngine::PassthroughEngine(imvu::SharedPtr<imvu::PiRenderEngine> inner,
std::unique_ptr<RendererProcess> renderer)
: inner_(std::move(inner)), renderer_(std::move(renderer))
{
log::info("passthrough: wrapping the engine at {}", static_cast<void*>(inner_.get()));
}
PassthroughEngine::~PassthroughEngine() { log::info("passthrough: destroyed after {} frames", frames_); }
void PassthroughEngine::count(std::size_t slot) { ++calls_[slot]; }
void PassthroughEngine::resizeFrameBuffer(imvu::Vec2i size)
{
log::info("resizeFrameBuffer({}, {})", size.x, size.y);
count(1);
inner_->resizeFrameBuffer(size);
}
void PassthroughEngine::enable2dFrameBuffer()
{
count(2);
inner_->enable2dFrameBuffer();
}
void PassthroughEngine::clear(std::uint32_t flags, imvu::Color color)
{
if (sampled(3)) {
log::info("clear(flags {}, color {:08x})", flags, color);
}
count(3);
inner_->clear(flags, color);
}
void PassthroughEngine::setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection)
{
if (sampled(4)) {
const auto& m = projection.m;
log::info("setProjection({}x{}, [{:.3f} {:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f} {:.3f}; "
"{:.3f} {:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f} {:.3f}])",
viewport.x, viewport.y, m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2],
m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], m[3][0], m[3][1], m[3][2], m[3][3]);
}
count(4);
inner_->setProjection(viewport, projection);
}
void PassthroughEngine::setModelSpace(const imvu::Transform& model)
{
if (sampled(5)) {
log::info("setModelSpace({})", text(model));
}
count(5);
inner_->setModelSpace(model);
}
void PassthroughEngine::setViewSpace(const imvu::Transform& view)
{
if (sampled(6)) {
log::info("setViewSpace({})", text(view));
}
count(6);
inner_->setViewSpace(view);
}
void PassthroughEngine::setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1)
{
count(7);
inner_->setTextures(texture0, texture1);
}
void PassthroughEngine::setDepthTestingOn(bool on)
{
count(8);
inner_->setDepthTestingOn(on);
}
void PassthroughEngine::setDepthWritingOn(bool on)
{
count(9);
inner_->setDepthWritingOn(on);
}
void PassthroughEngine::setBlendMode(imvu::RenderBlendMode mode)
{
if (sampled(10)) {
log::info("setBlendMode({})", static_cast<int>(mode));
}
count(10);
inner_->setBlendMode(mode);
}
void PassthroughEngine::setAlphaTestingOn(bool on)
{
count(11);
inner_->setAlphaTestingOn(on);
}
void PassthroughEngine::setAlphaTestValue(float value)
{
if (sampled(12)) {
log::info("setAlphaTestValue({:.3f})", value);
}
count(12);
inner_->setAlphaTestValue(value);
}
void PassthroughEngine::setBlendingOn(bool on)
{
count(13);
inner_->setBlendingOn(on);
}
void PassthroughEngine::setBackFaceCullingOn(bool on)
{
count(14);
inner_->setBackFaceCullingOn(on);
}
void PassthroughEngine::setNormalInvertingOn(bool on)
{
count(15);
inner_->setNormalInvertingOn(on);
}
void PassthroughEngine::setLightingOn(bool on)
{
count(16);
inner_->setLightingOn(on);
}
void PassthroughEngine::setLightProperties(std::uint32_t index, const imvu::LightProperties& light)
{
if (sampled(17)) {
log::info("setLightProperties({}, enabled {}, diffuse {:08x}, vector {}, type {})", index,
light.enabled, light.diffuse, text(light.vector), static_cast<int>(light.type));
}
count(17);
inner_->setLightProperties(index, light);
}
void PassthroughEngine::setFogOn(bool on)
{
count(18);
inner_->setFogOn(on);
}
void PassthroughEngine::setFogAttributes(float start, float end, imvu::Color color)
{
if (sampled(19)) {
log::info("setFogAttributes(start {:.3f}, end {:.3f}, color {:08x})", start, end, color);
}
count(19);
inner_->setFogAttributes(start, end, color);
}
void PassthroughEngine::setTextureTransformMatrixIdentity(std::uint32_t stage)
{
count(20);
inner_->setTextureTransformMatrixIdentity(stage);
}
void PassthroughEngine::setTextureTransformMatrix(std::uint32_t stage, const imvu::TextureTransform& transform)
{
if (sampled(21)) {
const auto& m = transform.m;
log::info("setTextureTransformMatrix({}, [{:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}])", stage, m[0],
m[1], m[2], m[3], m[4], m[5]);
}
count(21);
inner_->setTextureTransformMatrix(stage, transform);
}
void PassthroughEngine::setEnvironmentAmbient(imvu::Color color)
{
if (sampled(22)) {
log::info("setEnvironmentAmbient({:08x})", color);
}
count(22);
inner_->setEnvironmentAmbient(color);
}
imvu::PiRenderTexture* PassthroughEngine::newRenderTexture(const imvu::Pixmap& pixmap, std::uint32_t flags)
{
if (sampled(23)) {
const auto size = pixmap_size(pixmap);
log::info("newRenderTexture({}x{} pixmap, flags {:x})", size.x, size.y, flags);
}
count(23);
return inner_->newRenderTexture(pixmap, flags);
}
imvu::PiInternalIndexBuffer* PassthroughEngine::newIndexBuffer(std::uint32_t indexCount, const std::uint16_t* indices)
{
if (sampled(24)) {
log::info("newIndexBuffer({} indices)", indexCount);
}
count(24);
return inner_->newIndexBuffer(indexCount, indices);
}
imvu::PiInternalVertexBuffer* PassthroughEngine::newStaticVertexBuffer(const void* data, std::uint32_t stride,
std::uint32_t elementSize, std::uint32_t elementCount)
{
if (sampled(25)) {
log::info("newStaticVertexBuffer(stride {}, element size {}, {} elements)", stride, elementSize,
elementCount);
}
count(25);
return inner_->newStaticVertexBuffer(data, stride, elementSize, elementCount);
}
imvu::PiInternalVertexBuffer* PassthroughEngine::newDynamicVertexBuffer(std::uint32_t elementSize, std::uint32_t elementCount)
{
if (sampled(26)) {
log::info("newDynamicVertexBuffer(element size {}, {} elements)", elementSize, elementCount);
}
count(26);
return inner_->newDynamicVertexBuffer(elementSize, elementCount);
}
imvu::SharedPtr<imvu::PiPixmapRenderTarget> PassthroughEngine::newPixmapRenderTarget(imvu::Vec2i size)
{
log::info("newPixmapRenderTarget({}, {})", size.x, size.y);
count(27);
return inner_->newPixmapRenderTarget(size);
}
imvu::SharedPtr<imvu::PiTextureRenderTarget> PassthroughEngine::newTextureRenderTarget(imvu::Vec2i size)
{
log::info("newTextureRenderTarget({}, {})", size.x, size.y);
count(28);
return inner_->newTextureRenderTarget(size);
}
void PassthroughEngine::beginRendering(imvu::PiRenderTarget* target)
{
count(29);
inner_->beginRendering(target);
}
void PassthroughEngine::endRendering()
{
count(30);
inner_->endRendering();
if (renderer_) {
renderer_->frameDrawn();
}
if (logged(frames_)) {
std::string line = std::format("frame {}:", frames_);
for (std::size_t slot = 1; slot < slots; ++slot) {
if (calls_[slot] != 0) {
line += std::format(" {} x{},", names[slot], calls_[slot]);
}
}
line.pop_back();
log::write(line);
}
calls_ = {};
++frames_;
}
void PassthroughEngine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
imvu::PiInternalVertexBuffer* colors)
{
count(31);
inner_->drawTriangles(indices, positions, normals, texCoords, colors);
}
void PassthroughEngine::drawLine(imvu::Vec3f from, imvu::Vec3f to, std::uint32_t color)
{
if (sampled(32)) {
log::info("drawLine({}, {}, {:08x})", text(from), text(to), color);
}
count(32);
inner_->drawLine(from, to, color);
}
void PassthroughEngine::drawPointSprites(const imvu::PointSpriteVector& sprites)
{
count(33);
inner_->drawPointSprites(sprites);
}
void PassthroughEngine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t indexCount,
const std::uint16_t* indices,
const imvu::VertexAttributeArray<imvu::Vec3f>& positions,
const imvu::VertexAttributeArray<imvu::Vec3f>& normals,
const imvu::VertexAttributeArray<imvu::Vec2f>& texCoords,
const imvu::VertexAttributeArray<imvu::Color>& colors)
{
if (sampled(34)) {
log::info("drawTrianglesFromMemory({} vertices, {} indices; positions {} stride {}, colors {} stride {})",
vertexCount, indexCount, positions.count, positions.stride, colors.count, colors.stride);
}
count(34);
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
}
} // namespace t5de