Wrap IMVU's Direct3D renderer in the pass-through

IMVU's OpenGL renderer needs a real graphics driver; Direct3D also runs on Windows' basic display driver and is what most players use. The log can now be read while the client runs. Verified in rooms: every call goes through.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-28 04:28:26 -07:00
1 parent dc643d271b
commit 3c28a8794b
6 files changed
+52 -35

No files matched your search

+3 -3
View File
@@ -3,7 +3,7 @@
A new 3D renderer for the IMVU Classic client, as a renderer plugin the client loads like its own OpenGL renderer.
Part of [T5DE](https://git.t5de.dev/t5de/t5de); unofficial, not affiliated with IMVU.
For now it's a pass-through: it wraps IMVU's OpenGL renderer and logs what each frame draws, to prove our
For now it's a pass-through: it wraps IMVU's Direct3D renderer and logs what each frame draws, to prove our
declarations of IMVU's interface (`docs/interface.md`).
## Building
@@ -20,6 +20,6 @@ That configures, builds `build/windows-x86/Release/t5deRenderEngine.dll` and run
## Trying it
Copy `t5deRenderEngine.dll` into a T5DE client folder, and have the client load it in place of
`glRenderEngine.dll` (a T5DE patch, to come), then choose OpenGL in Settings > Advanced. The log is
Copy `t5deRenderEngine.dll` into a T5DE client folder built with the `t5de-renderer` patch, which has the OpenGL
setting load it in place of `glRenderEngine.dll`, then choose OpenGL in Settings > Advanced. The log is
`%TEMP%\t5de-renderer.log`.
+19 -3
View File
@@ -79,9 +79,25 @@ virtual methods, IMVU's code only uses an engine through helpers that call its v
ticks; `runApp` waits out `zero.dll`'s `millisecondsPerFrame`). Shorter ticks speed animations up. Smooth high
frame rates need in-between poses: hooks in `SceneWindow.dll`'s animation update.
## What the client draws
From the pass-through (Direct3D, client 554.8, a few rooms):
- A room frame: about 146 `drawTriangles`, each with its own `setModelSpace` (131) and `setTextures` (138), with
per-object alpha test, culling, normal inverting and blending, 252 texture-transform resets, 8 light updates; no
`drawLine`, `drawPointSprites` or `drawTrianglesFromMemory` in those rooms.
- Every frame creates index and vertex buffers (4 and 12 in a room, with nothing new loading): most likely avatars,
skinned on the CPU and uploaded again each frame.
- Each 3D window, and each room change, creates a new engine (`createRenderEngine`), so engines start and stop
often; frame buffers are resized freely (up to 3440x1272 here).
- IMVU's OpenGL engine needs a real graphics driver (`ARB_multitexture`); on Windows' basic display driver
(virtual machines) it throws and the client falls back. Its exceptions are `std::exception`s our code can
catch.
## Plan
1. Pass-through plugin (this repository's first version): wraps IMVU's OpenGL engine and logs what each frame
calls, proving our declarations.
1. Pass-through plugin (done, verified in rooms): wraps IMVU's Direct3D engine, made with `SceneWindow.dll`'s
exported `createRenderer`, and logs what each frame calls, proving our declarations.
2. Work out the layouts above.
3. A renderer reproducing the current look on Direct3D 11 or OpenGL 4, then the improvements.
3. A renderer reproducing the current look on Direct3D 11 (it also runs in software, on WARP, where OpenGL can't),
then the improvements.
+12
View File
@@ -112,4 +112,16 @@ static_assert(sizeof(PiRenderEngine) == 12, "vtable pointer, magic, size");
using IsRenderEngineSupported = int(__cdecl*)();
using CreateRenderEngine = SharedPtr<PiRenderEngine>(__cdecl*)(SharedPtr<INativeWindow> window);
// The renderers SceneWindow.dll's createRenderer makes (values from _avatarwindow.pyd's RendererType).
enum class RendererType : int {
Direct3D = 1, // RenderEngineDx, Direct3D 9
OpenGL = 2, // the plugin glRenderEngine.dll
SwiftShader = 9, // RenderEngineDx on swiftshader.dll
};
// SceneWindow.dll's createRenderer(boost::shared_ptr<INativeWindow>, RendererType), exported by this name.
using CreateRenderer = SharedPtr<PiRenderEngine>(__cdecl*)(SharedPtr<INativeWindow> window, RendererType type);
inline constexpr const char* createRendererName =
"?createRenderer@@YA?AV?$shared_ptr@VPiRenderEngine@@@boost@@V?$shared_ptr@VINativeWindow@@@2@W4RendererType@@@Z";
} // namespace t5de::imvu
+3 -3
View File
@@ -1,5 +1,6 @@
#include "log.hpp"
#include <share.h>
#include <windows.h>
#include <cstdio>
@@ -17,9 +18,8 @@ std::FILE* open()
if (length == 0 || length > MAX_PATH) {
return nullptr;
}
std::FILE* file = nullptr;
_wfopen_s(&file, (std::filesystem::path(temp) / L"t5de-renderer.log").c_str(), L"w");
return file;
// 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
+1 -1
View File
@@ -1,5 +1,5 @@
#pragma once
// A renderer that forwards every call to another one (IMVU's OpenGL engine) and logs what a frame is made of: proof
// 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>
+14 -25
View File
@@ -1,10 +1,9 @@
// 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 OpenGL engine, wrapped.
// undecorated by plugin.def). For now the renderer is the pass-through: IMVU's Direct3D engine, wrapped.
#include <windows.h>
#include <exception>
#include <filesystem>
#include "log.hpp"
#include "passthrough_engine.hpp"
@@ -14,33 +13,20 @@ namespace {
using namespace t5de;
// The folder this DLL is in: the client's program folder.
std::filesystem::path program_folder()
{
HMODULE self = nullptr;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&program_folder), &self);
wchar_t path[MAX_PATH];
GetModuleFileNameW(self, path, MAX_PATH);
return std::filesystem::path(path).parent_path();
}
// IMVU's OpenGL engine, from the plugin in the program folder (never unloaded: its engines use its code).
// 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)
{
const auto dll = program_folder() / L"glRenderEngine.dll";
HMODULE module = LoadLibraryW(dll.c_str());
if (module == nullptr) {
log::info("can't load {}: error {}", dll.string(), GetLastError());
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 {};
}
auto supported = reinterpret_cast<imvu::IsRenderEngineSupported>(GetProcAddress(module, "isRenderEngineSupported"));
auto create = reinterpret_cast<imvu::CreateRenderEngine>(GetProcAddress(module, "createRenderEngine"));
if (supported == nullptr || create == nullptr || supported() == 0) {
log::info("{} isn't a usable renderer", dll.string());
return {};
}
return create(std::move(window));
return create(std::move(window), imvu::RendererType::Direct3D);
}
} // namespace
@@ -62,5 +48,8 @@ extern "C" imvu::SharedPtr<imvu::PiRenderEngine> __cdecl createRenderEngine(imvu
// 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 {};
}
}