Files
t5de-renderer/renderer/main.cpp
T
dhkatzandClaude Opus 5.5 9a7d7d2908 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]>
2026-10-28 05:17:03 -07:00

138 lines
5.0 KiB
C++

// t5de-renderer.exe: the renderer process. Started by the adapter (t5deRenderEngine.dll) for each of the client's
// 3D views; draws into a child window it places in the client's window, frame by frame as the client draws.
//
// For now a spike proving the architecture: a small box in the 3D view's corner, its color changing with each frame
// the client draws, drawn with Diligent (Direct3D 11) from this 64-bit process.
#include <windows.h>
#include <shellapi.h>
#include <cmath>
#include <cstdint>
#include <string>
#include <string_view>
#include "EngineFactoryD3D11.h"
#include "RefCntAutoPtr.hpp"
#include "t5de/protocol/channel.hpp"
namespace {
using namespace Diligent;
// The box: where in the client's 3D view, and how big.
constexpr int boxX = 16;
constexpr int boxY = 16;
constexpr int boxWidth = 240;
constexpr int boxHeight = 80;
struct Arguments {
std::wstring channel;
DWORD parent = 0;
};
Arguments parse_arguments()
{
Arguments arguments;
int count = 0;
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &count);
for (int i = 1; i + 1 < count; i += 2) {
const std::wstring_view name = argv[i];
if (name == L"--channel") {
arguments.channel = argv[i + 1];
} else if (name == L"--parent") {
arguments.parent = static_cast<DWORD>(std::wcstoul(argv[i + 1], nullptr, 10));
}
}
LocalFree(argv);
return arguments;
}
LRESULT CALLBACK window_procedure(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
{
if (message == WM_ERASEBKGND) {
return 1; // we draw all of it
}
return DefWindowProcW(window, message, wparam, lparam);
}
// A child of the client's window. Disabled, so mouse input goes to the client's window under it.
HWND create_child_window(HWND parent)
{
WNDCLASSEXW windowClass{.cbSize = sizeof(WNDCLASSEXW),
.lpfnWndProc = window_procedure,
.hInstance = GetModuleHandleW(nullptr),
.hCursor = LoadCursorW(nullptr, IDC_ARROW),
.lpszClassName = L"T5DERenderer"};
RegisterClassExW(&windowClass);
return CreateWindowExW(0, windowClass.lpszClassName, L"", WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CLIPSIBLINGS,
boxX, boxY, boxWidth, boxHeight, parent, nullptr, windowClass.hInstance, nullptr);
}
} // namespace
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
const Arguments arguments = parse_arguments();
if (arguments.channel.empty()) {
return 2;
}
HANDLE mapping = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, arguments.channel.c_str());
auto* channel = mapping == nullptr ? nullptr
: static_cast<t5de::protocol::Channel*>(MapViewOfFile(
mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(t5de::protocol::Channel)));
HANDLE frameEvent = OpenEventW(SYNCHRONIZE, FALSE, (arguments.channel + t5de::protocol::frameEventSuffix).c_str());
HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, arguments.parent);
if (channel == nullptr || frameEvent == nullptr || parent == nullptr || channel->version != t5de::protocol::version) {
return 3;
}
HWND window = create_child_window(reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window)));
if (window == nullptr) {
return 4;
}
IEngineFactoryD3D11* factory = GetEngineFactoryD3D11();
RefCntAutoPtr<IRenderDevice> device;
RefCntAutoPtr<IDeviceContext> context;
EngineD3D11CreateInfo engineInfo;
factory->CreateDeviceAndContextsD3D11(engineInfo, &device, &context);
if (!device) {
return 5;
}
RefCntAutoPtr<ISwapChain> swapChain;
SwapChainDesc swapChainDesc;
factory->CreateSwapChainD3D11(device, context, swapChainDesc, FullScreenModeDesc{}, Win32NativeWindow{window},
&swapChain);
if (!swapChain) {
return 6;
}
for (;;) {
const HANDLE handles[] = {frameEvent, parent};
const DWORD result = MsgWaitForMultipleObjects(2, handles, FALSE, 1000, QS_ALLINPUT);
MSG message;
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
DispatchMessageW(&message);
}
if (result == WAIT_OBJECT_0 + 1 || channel->closing != 0) {
break; // the client exited, or the engine is gone
}
if (result != WAIT_OBJECT_0) {
continue;
}
// T5DE gold, pulsing with the client's frames.
const float pulse = 0.5f + 0.5f * std::sin(static_cast<float>(channel->frames) * 0.1f);
const float color[] = {1.0f, 0.82f * pulse + 0.1f, 0.0f, 1.0f};
ITextureView* backBuffer = swapChain->GetCurrentBackBufferRTV();
context->SetRenderTargets(1, &backBuffer, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
context->ClearRenderTarget(backBuffer, color, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
swapChain->Present(0);
}
DestroyWindow(window);
return 0;
}