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

+12
View File
@@ -0,0 +1,12 @@
# t5deRenderEngine.dll, the adapter: a renderer plugin for IMVU Classic (32-bit).
add_library(t5de_render_engine SHARED
log.cpp
passthrough_engine.cpp
plugin.cpp
plugin.def
renderer_process.cpp)
set_target_properties(t5de_render_engine PROPERTIES OUTPUT_NAME t5deRenderEngine)
target_link_libraries(t5de_render_engine PRIVATE t5de::imvu t5de::protocol t5de::options)
install(TARGETS t5de_render_engine RUNTIME DESTINATION .)
+39
View File
@@ -0,0 +1,39 @@
#include "log.hpp"
#include <share.h>
#include <windows.h>
#include <cstdio>
#include <filesystem>
#include <mutex>
namespace t5de::log {
namespace {
std::FILE* open()
{
wchar_t temp[MAX_PATH + 1];
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
if (length == 0 || length > MAX_PATH) {
return nullptr;
}
// Shared, so it can be read while the client runs.
return _wfsopen((std::filesystem::path(temp) / L"t5de-renderer.log").c_str(), L"w", _SH_DENYNO);
}
} // namespace
void write(std::string_view line)
{
static std::mutex mutex;
static std::FILE* file = open();
const std::scoped_lock lock(mutex);
if (file != nullptr) {
std::fwrite(line.data(), 1, line.size(), file);
std::fputc('\n', file);
std::fflush(file);
}
}
} // namespace t5de::log
+18
View File
@@ -0,0 +1,18 @@
#pragma once
// The renderer's log: %TEMP%\t5de-renderer.log, replaced each time the client starts.
#include <format>
#include <string_view>
#include <utility>
namespace t5de::log {
void write(std::string_view line);
template <class... Args>
void info(std::format_string<Args...> format, Args&&... args)
{
write(std::format(format, std::forward<Args>(args)...));
}
} // namespace t5de::log
+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
+76
View File
@@ -0,0 +1,76 @@
#pragma once
// A renderer that forwards every call to another one (IMVU's Direct3D engine) and logs what a frame is made of: proof
// that our declaration of the interface matches IMVU's, and a way to see how the client draws.
#include <array>
#include <cstdint>
#include <memory>
#include "renderer_process.hpp"
#include "t5de/imvu/render_engine.hpp"
namespace t5de {
class PassthroughEngine final : public imvu::PiRenderEngine {
public:
// renderer may be null.
PassthroughEngine(imvu::SharedPtr<imvu::PiRenderEngine> inner, std::unique_ptr<RendererProcess> renderer);
~PassthroughEngine() override;
void resizeFrameBuffer(imvu::Vec2i size) override;
void enable2dFrameBuffer() override;
void clear(std::uint32_t flags, imvu::Color color) override;
void setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection) override;
void setModelSpace(const imvu::Transform& model) override;
void setViewSpace(const imvu::Transform& view) override;
void setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1) override;
void setDepthTestingOn(bool on) override;
void setDepthWritingOn(bool on) override;
void setBlendMode(imvu::RenderBlendMode mode) override;
void setAlphaTestingOn(bool on) override;
void setAlphaTestValue(float value) override;
void setBlendingOn(bool on) override;
void setBackFaceCullingOn(bool on) override;
void setNormalInvertingOn(bool on) override;
void setLightingOn(bool on) override;
void setLightProperties(std::uint32_t index, const imvu::LightProperties& light) override;
void setFogOn(bool on) override;
void setFogAttributes(float start, float end, imvu::Color color) override;
void setTextureTransformMatrixIdentity(std::uint32_t stage) override;
void setTextureTransformMatrix(std::uint32_t stage, const imvu::TextureTransform& transform) override;
void setEnvironmentAmbient(imvu::Color color) override;
imvu::PiRenderTexture* newRenderTexture(const imvu::Pixmap& pixmap, std::uint32_t flags) override;
imvu::PiInternalIndexBuffer* newIndexBuffer(std::uint32_t count, const std::uint16_t* indices) override;
imvu::PiInternalVertexBuffer* newStaticVertexBuffer(const void* data, std::uint32_t stride,
std::uint32_t elementSize,
std::uint32_t elementCount) override;
imvu::PiInternalVertexBuffer* newDynamicVertexBuffer(std::uint32_t elementSize,
std::uint32_t elementCount) override;
imvu::SharedPtr<imvu::PiPixmapRenderTarget> newPixmapRenderTarget(imvu::Vec2i size) override;
imvu::SharedPtr<imvu::PiTextureRenderTarget> newTextureRenderTarget(imvu::Vec2i size) override;
void beginRendering(imvu::PiRenderTarget* target) override;
void endRendering() override;
void drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
imvu::PiInternalVertexBuffer* colors) override;
void drawLine(imvu::Vec3f from, imvu::Vec3f to, std::uint32_t color) override;
void drawPointSprites(const imvu::PointSpriteVector& sprites) override;
void 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) override;
// The interface's methods, by vtable slot (slot 0, the destructor, isn't counted).
static constexpr std::size_t slots = 35;
private:
void count(std::size_t slot);
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
std::unique_ptr<RendererProcess> renderer_;
std::array<std::uint32_t, slots> calls_{};
std::uint64_t frames_ = 0;
};
} // namespace t5de
+59
View File
@@ -0,0 +1,59 @@
// The renderer plugin's entry points, which SceneWindow.dll's createDLLRenderer looks up by name (exported
// undecorated by plugin.def). For now the renderer is the pass-through: IMVU's Direct3D engine, wrapped.
#include <windows.h>
#include <exception>
#include "log.hpp"
#include "passthrough_engine.hpp"
#include "renderer_process.hpp"
#include "t5de/imvu/render_engine.hpp"
namespace {
using namespace t5de;
// IMVU's Direct3D 9 engine, made by SceneWindow.dll (already loaded: it's what loads this plugin). Direct3D
// rather than IMVU's OpenGL engine, which needs a real graphics driver: Direct3D also works on Windows' basic
// display driver (virtual machines), and it's what most players use.
imvu::SharedPtr<imvu::PiRenderEngine> create_imvu_engine(imvu::SharedPtr<imvu::INativeWindow> window)
{
HMODULE scene_window = GetModuleHandleW(L"SceneWindow.dll");
auto create = scene_window == nullptr
? nullptr
: reinterpret_cast<imvu::CreateRenderer>(GetProcAddress(scene_window, imvu::createRendererName));
if (create == nullptr) {
log::info("SceneWindow.dll's createRenderer not found");
return {};
}
return create(std::move(window), imvu::RendererType::Direct3D);
}
} // namespace
extern "C" int __cdecl isRenderEngineSupported() { return 1; }
// Returns a C++ object from a C-linkage function: fine here, the caller is C++ built for the same ABI.
#pragma warning(suppress : 4190)
extern "C" imvu::SharedPtr<imvu::PiRenderEngine> __cdecl createRenderEngine(imvu::SharedPtr<imvu::INativeWindow> window)
{
try {
log::info("createRenderEngine: window {}", static_cast<void*>(window.get()));
HWND hwnd = window ? window->getPlatformWindow() : nullptr;
auto inner = create_imvu_engine(std::move(window));
if (!inner) {
return {};
}
auto renderer = hwnd != nullptr ? RendererProcess::start(hwnd) : nullptr;
return imvu::SharedPtr<imvu::PiRenderEngine>(
std::make_unique<PassthroughEngine>(std::move(inner), std::move(renderer)));
} catch (const std::exception& e) {
// Exceptions mustn't cross into IMVU's code (a different C++ runtime).
log::info("createRenderEngine failed: {}", e.what());
return {};
} catch (...) {
log::info("createRenderEngine failed");
return {};
}
}
+5
View File
@@ -0,0 +1,5 @@
; The names SceneWindow.dll's createDLLRenderer looks up, without C++ or __cdecl decoration.
LIBRARY t5deRenderEngine
EXPORTS
isRenderEngineSupported
createRenderEngine
+92
View File
@@ -0,0 +1,92 @@
#include "renderer_process.hpp"
#include <atomic>
#include <filesystem>
#include <format>
#include <string>
#include "log.hpp"
namespace t5de {
namespace {
// The folder this DLL is in: the client's program folder.
std::filesystem::path module_folder()
{
HMODULE self = nullptr;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&module_folder), &self);
wchar_t path[MAX_PATH];
GetModuleFileNameW(self, path, MAX_PATH);
return std::filesystem::path(path).parent_path();
}
} // namespace
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window)
{
static std::atomic<int> engines = 0;
const std::wstring name = std::format(L"Local\\t5de-renderer-{}-{}", GetCurrentProcessId(), engines++);
std::unique_ptr<RendererProcess> renderer(new RendererProcess());
renderer->mapping_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
sizeof(protocol::Channel), 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) {
log::info("renderer process: can't set up the channel: error {}", GetLastError());
return nullptr;
}
*renderer->channel_ = {.version = protocol::version,
.closing = 0,
.window = reinterpret_cast<std::uintptr_t>(window),
.frames = 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);
const auto exe = module_folder() / L"t5de-renderer.exe";
std::wstring command = std::format(L"\"{}\" --channel {} --parent {}", exe.wstring(), name, GetCurrentProcessId());
STARTUPINFOW startup{.cb = sizeof(STARTUPINFOW)};
PROCESS_INFORMATION process{};
if (!CreateProcessW(exe.c_str(), command.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup,
&process)) {
log::info("renderer process: can't start {}: error {}", exe.string(), GetLastError());
return nullptr;
}
CloseHandle(process.hThread);
renderer->process_ = process.hProcess;
log::info("renderer process: started {} (pid {}) on channel {}", exe.string(), process.dwProcessId,
std::filesystem::path(name).string());
return renderer;
}
RendererProcess::~RendererProcess()
{
if (channel_ != nullptr) {
channel_->closing = 1;
if (frameEvent_ != nullptr) {
SetEvent(frameEvent_);
}
UnmapViewOfFile(channel_);
}
for (HANDLE handle : {frameEvent_, mapping_, process_}) {
if (handle != nullptr) {
CloseHandle(handle);
}
}
}
void RendererProcess::frameDrawn()
{
++channel_->frames;
SetEvent(frameEvent_);
}
} // namespace t5de
+35
View File
@@ -0,0 +1,35 @@
#pragma once
// The renderer process (t5de-renderer.exe, 64-bit) an engine draws through: started with the engine, told about
// each frame through a shared memory channel (include/t5de/protocol/channel.hpp), and told to exit with it.
#include <windows.h>
#include <cstdint>
#include <memory>
#include "t5de/protocol/channel.hpp"
namespace t5de {
class RendererProcess {
public:
// Starts t5de-renderer.exe from this DLL's folder, drawing into window; null if it can't.
static std::unique_ptr<RendererProcess> start(HWND window);
RendererProcess(const RendererProcess&) = delete;
RendererProcess& operator=(const RendererProcess&) = delete;
~RendererProcess();
// The client drew a frame.
void frameDrawn();
private:
RendererProcess() = default;
HANDLE mapping_ = nullptr;
HANDLE frameEvent_ = nullptr;
HANDLE process_ = nullptr;
protocol::Channel* channel_ = nullptr;
};
} // namespace t5de