Draw the client's scene in the renderer process

The adapter records everything the client draws (state, transforms, lights, fog, textures and buffers) as a command stream, sent in chunks through the shared memory channel, while IMVU's Direct3D renderer still draws the whole view. The renderer process draws it with Diligent, with shaders reproducing IMVU's Direct3D 9 fixed-function setup, over the right half of the 3D view to compare. It skips to the newest frame when it falls behind, and logs its errors and frame times.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-28 05:52:15 -07:00
1 parent 5c35c8b63a
commit 42fc55cb91
23 files changed
+2121 -472

No files matched your search

+48 -16
View File
@@ -1,6 +1,8 @@
#include "renderer_process.hpp"
#include <algorithm>
#include <atomic>
#include <cstring>
#include <filesystem>
#include <format>
#include <string>
@@ -11,6 +13,9 @@ namespace t5de {
namespace {
// How long to wait for the renderer process to take a chunk before giving up on it.
constexpr DWORD consumeTimeout = 5000;
// The folder this DLL is in: the client's program folder.
std::filesystem::path module_folder()
{
@@ -22,31 +27,39 @@ std::filesystem::path module_folder()
return std::filesystem::path(path).parent_path();
}
std::string narrow(const std::wstring& text) { return std::filesystem::path(text).string(); }
} // namespace
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window)
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::View view)
{
static std::atomic<int> engines = 0;
const std::wstring name = std::format(L"Local\\t5de-renderer-{}-{}", GetCurrentProcessId(), engines++);
constexpr DWORD size = sizeof(protocol::Channel) + protocol::channelCapacity;
std::unique_ptr<RendererProcess> renderer(new RendererProcess());
renderer->mapping_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
sizeof(protocol::Channel), name.c_str());
renderer->mapping_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size, name.c_str());
if (renderer->mapping_ == nullptr) {
log::info("renderer process: can't create the channel: error {}", GetLastError());
return nullptr;
}
renderer->channel_ = static_cast<protocol::Channel*>(
MapViewOfFile(renderer->mapping_, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(protocol::Channel)));
renderer->frameEvent_ = CreateEventW(nullptr, FALSE, FALSE, (name + protocol::frameEventSuffix).c_str());
if (renderer->channel_ == nullptr || renderer->frameEvent_ == nullptr) {
renderer->channel_ =
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) {
log::info("renderer process: can't set up the channel: error {}", GetLastError());
return nullptr;
}
renderer->data_ = reinterpret_cast<std::byte*>(renderer->channel_ + 1);
*renderer->channel_ = {.version = protocol::version,
.closing = 0,
.window = reinterpret_cast<std::uintptr_t>(window),
.frames = 0};
.frames = 0,
.view = view,
.capacity = protocol::channelCapacity,
.chunkSize = 0,
.chunkFlags = 0};
// The renderer's window goes on top of the client's 3D view: keep the client's drawing out of it.
SetWindowLongW(window, GWL_STYLE, GetWindowLongW(window, GWL_STYLE) | WS_CLIPCHILDREN);
@@ -62,8 +75,7 @@ std::unique_ptr<RendererProcess> RendererProcess::start(HWND window)
}
CloseHandle(process.hThread);
renderer->process_ = process.hProcess;
log::info("renderer process: started {} (pid {}) on channel {}", exe.string(), process.dwProcessId,
std::filesystem::path(name).string());
log::info("renderer process: started {} (pid {}) on channel {}", exe.string(), process.dwProcessId, narrow(name));
return renderer;
}
@@ -71,22 +83,42 @@ RendererProcess::~RendererProcess()
{
if (channel_ != nullptr) {
channel_->closing = 1;
if (frameEvent_ != nullptr) {
SetEvent(frameEvent_);
if (ready_ != nullptr) {
SetEvent(ready_);
}
UnmapViewOfFile(channel_);
}
for (HANDLE handle : {frameEvent_, mapping_, process_}) {
for (HANDLE handle : {ready_, consumed_, mapping_, process_}) {
if (handle != nullptr) {
CloseHandle(handle);
}
}
}
void RendererProcess::frameDrawn()
bool RendererProcess::send(std::span<const std::byte> commands, bool endsFrame)
{
++channel_->frames;
SetEvent(frameEvent_);
if (!alive_) {
return false;
}
do {
const HANDLE waits[] = {consumed_, process_};
const DWORD result = WaitForMultipleObjects(2, waits, FALSE, consumeTimeout);
if (result != WAIT_OBJECT_0) {
log::info("renderer process: {}; giving up on it", result == WAIT_OBJECT_0 + 1 ? "exited" : "not responding");
alive_ = false;
return false;
}
const std::size_t size = std::min<std::size_t>(commands.size(), channel_->capacity);
std::memcpy(data_, commands.data(), size);
commands = commands.subspan(size);
channel_->chunkSize = static_cast<std::uint32_t>(size);
channel_->chunkFlags = endsFrame && commands.empty() ? protocol::chunkEndsFrame : 0;
if (endsFrame && commands.empty()) {
++channel_->frames;
}
SetEvent(ready_);
} while (!commands.empty());
return true;
}
} // namespace t5de