From 3c28a8794b1ba787bed9416b3b3220d917daa16a Mon Sep 17 00:00:00 2001 From: dhkatz Date: Wed, 28 Oct 2026 04:28:26 -0700 Subject: [PATCH] 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 --- README.md | 6 ++--- docs/interface.md | 22 +++++++++++++--- include/t5de/imvu/render_engine.hpp | 12 +++++++++ src/log.cpp | 6 ++--- src/passthrough_engine.hpp | 2 +- src/plugin.cpp | 39 +++++++++++------------------ 6 files changed, 52 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 1de4097..8a4be95 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/docs/interface.md b/docs/interface.md index 7082b94..78fe116 100644 --- a/docs/interface.md +++ b/docs/interface.md @@ -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. diff --git a/include/t5de/imvu/render_engine.hpp b/include/t5de/imvu/render_engine.hpp index 7d0e997..9d6f563 100644 --- a/include/t5de/imvu/render_engine.hpp +++ b/include/t5de/imvu/render_engine.hpp @@ -112,4 +112,16 @@ static_assert(sizeof(PiRenderEngine) == 12, "vtable pointer, magic, size"); using IsRenderEngineSupported = int(__cdecl*)(); using CreateRenderEngine = SharedPtr(__cdecl*)(SharedPtr 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, RendererType), exported by this name. +using CreateRenderer = SharedPtr(__cdecl*)(SharedPtr 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 diff --git a/src/log.cpp b/src/log.cpp index 1572b07..57871d3 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,5 +1,6 @@ #include "log.hpp" +#include #include #include @@ -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 diff --git a/src/passthrough_engine.hpp b/src/passthrough_engine.hpp index 8480738..f5b5638 100644 --- a/src/passthrough_engine.hpp +++ b/src/passthrough_engine.hpp @@ -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 diff --git a/src/plugin.cpp b/src/plugin.cpp index a288ac7..6b6b4b3 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -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 #include -#include #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(&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 create_imvu_engine(imvu::SharedPtr 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(GetProcAddress(scene_window, imvu::createRendererName)); + if (create == nullptr) { + log::info("SceneWindow.dll's createRenderer not found"); return {}; } - auto supported = reinterpret_cast(GetProcAddress(module, "isRenderEngineSupported")); - auto create = reinterpret_cast(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 __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 {}; } }