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:
1 parent
5c35c8b63a
commit
42fc55cb91
23 files changed
+2121
-472
No files matched your search
+132
-25
@@ -1,31 +1,63 @@
|
||||
// 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.
|
||||
// 3D views: receives the client's drawing as commands through a shared memory channel, and draws it with Diligent
|
||||
// into a child window it places in the client's window (over the right half, for now, to compare with the client's
|
||||
// own renderer on the left).
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <share.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "EngineFactoryD3D11.h"
|
||||
#include "RefCntAutoPtr.hpp"
|
||||
#include "scene.hpp"
|
||||
#include "t5de/protocol/channel.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Diligent;
|
||||
using t5de::protocol::Channel;
|
||||
using t5de::protocol::View;
|
||||
|
||||
// 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;
|
||||
// The renderer processes' log: %TEMP%\t5de-renderer-process.log, shared by all of them (lines start with the pid).
|
||||
void write_log(std::string_view line)
|
||||
{
|
||||
static std::FILE* file = [] {
|
||||
wchar_t temp[MAX_PATH + 1];
|
||||
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
|
||||
return length == 0 || length > MAX_PATH
|
||||
? nullptr
|
||||
: _wfsopen((std::filesystem::path(temp) / L"t5de-renderer-process.log").c_str(), L"a", _SH_DENYNO);
|
||||
}();
|
||||
if (file != nullptr) {
|
||||
std::fprintf(file, "%lu: %.*s\n", GetCurrentProcessId(), static_cast<int>(line.size()), line.data());
|
||||
std::fflush(file);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void log(std::format_string<Args...> format, Args&&... args)
|
||||
{
|
||||
write_log(std::format(format, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// Diligent's messages (shader compilation errors, invalid states, ...), into the log.
|
||||
void DILIGENT_CALL_TYPE diligent_message(DEBUG_MESSAGE_SEVERITY severity, const Char* message, const Char* function,
|
||||
const Char*, int)
|
||||
{
|
||||
static constexpr const char* severities[] = {"info", "warning", "error", "fatal error"};
|
||||
log("Diligent {}: {}{}{}", severities[severity < 4 ? severity : 3], function != nullptr ? function : "",
|
||||
function != nullptr ? ": " : "", message != nullptr ? message : "");
|
||||
}
|
||||
|
||||
struct Arguments {
|
||||
std::wstring channel;
|
||||
@@ -67,7 +99,18 @@ HWND create_child_window(HWND parent)
|
||||
.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);
|
||||
0, 0, 1, 1, parent, nullptr, windowClass.hInstance, nullptr);
|
||||
}
|
||||
|
||||
// Where the child window goes in the client's 3D view.
|
||||
RECT placement(HWND parent, View view)
|
||||
{
|
||||
RECT client{};
|
||||
GetClientRect(parent, &client);
|
||||
if (view == View::Split) {
|
||||
client.left = client.right / 2;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -80,37 +123,65 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
}
|
||||
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());
|
||||
: static_cast<Channel*>(MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0));
|
||||
HANDLE ready = OpenEventW(SYNCHRONIZE, FALSE, (arguments.channel + t5de::protocol::readyEventSuffix).c_str());
|
||||
HANDLE consumed =
|
||||
OpenEventW(EVENT_MODIFY_STATE, FALSE, (arguments.channel + t5de::protocol::consumedEventSuffix).c_str());
|
||||
HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, arguments.parent);
|
||||
if (channel == nullptr || frameEvent == nullptr || parent == nullptr || channel->version != t5de::protocol::version) {
|
||||
if (channel == nullptr || ready == nullptr || consumed == nullptr || parent == nullptr
|
||||
|| channel->version != t5de::protocol::version) {
|
||||
return 3;
|
||||
}
|
||||
const auto* data = reinterpret_cast<const std::byte*>(channel + 1);
|
||||
|
||||
HWND window = create_child_window(reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window)));
|
||||
const HWND clientWindow = reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window));
|
||||
const HWND window = create_child_window(clientWindow);
|
||||
if (window == nullptr) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
log("started for window {:#x}, view {}", channel->window, static_cast<int>(channel->view));
|
||||
IEngineFactoryD3D11* factory = GetEngineFactoryD3D11();
|
||||
factory->SetMessageCallback(diligent_message);
|
||||
RefCntAutoPtr<IRenderDevice> device;
|
||||
RefCntAutoPtr<IDeviceContext> context;
|
||||
EngineD3D11CreateInfo engineInfo;
|
||||
factory->CreateDeviceAndContextsD3D11(engineInfo, &device, &context);
|
||||
if (!device) {
|
||||
log("can't create a Direct3D 11 device");
|
||||
return 5;
|
||||
}
|
||||
RefCntAutoPtr<ISwapChain> swapChain;
|
||||
SwapChainDesc swapChainDesc;
|
||||
swapChainDesc.ColorBufferFormat = TEX_FORMAT_RGBA8_UNORM; // no sRGB conversion, like the client's renderer
|
||||
swapChainDesc.DepthBufferFormat = TEX_FORMAT_D32_FLOAT;
|
||||
factory->CreateSwapChainD3D11(device, context, swapChainDesc, FullScreenModeDesc{}, Win32NativeWindow{window},
|
||||
&swapChain);
|
||||
if (!swapChain) {
|
||||
log("can't create a swap chain");
|
||||
return 6;
|
||||
}
|
||||
t5de::Scene scene(device, context, swapChain);
|
||||
|
||||
// Takes the chunk waiting in the channel (after "ready"), and lets the adapter send the next: appends it to
|
||||
// frame, and returns whether it ends a frame.
|
||||
std::vector<std::byte> frame;
|
||||
const auto take_chunk = [&] {
|
||||
const std::uint32_t size = channel->chunkSize <= channel->capacity ? channel->chunkSize : 0;
|
||||
const bool endsFrame = (channel->chunkFlags & t5de::protocol::chunkEndsFrame) != 0;
|
||||
frame.insert(frame.end(), data, data + size);
|
||||
SetEvent(consumed);
|
||||
return endsFrame;
|
||||
};
|
||||
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
double drawTime = 0;
|
||||
int drawn = 0;
|
||||
int skipped = 0;
|
||||
RECT placed{};
|
||||
for (;;) {
|
||||
const HANDLE handles[] = {frameEvent, parent};
|
||||
const HANDLE handles[] = {ready, parent};
|
||||
const DWORD result = MsgWaitForMultipleObjects(2, handles, FALSE, 1000, QS_ALLINPUT);
|
||||
MSG message;
|
||||
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||
@@ -119,19 +190,55 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
if (result == WAIT_OBJECT_0 + 1 || channel->closing != 0) {
|
||||
break; // the client exited, or the engine is gone
|
||||
}
|
||||
if (result != WAIT_OBJECT_0) {
|
||||
if (result != WAIT_OBJECT_0 || !take_chunk()) {
|
||||
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);
|
||||
// If the client has sent more frames meanwhile (drawing fell behind), catch up: carry out the older ones'
|
||||
// changes without drawing them, and draw only the newest. (An incomplete frame stays for the next round.)
|
||||
std::size_t complete = frame.size();
|
||||
while (WaitForSingleObject(ready, 0) == WAIT_OBJECT_0) {
|
||||
bool ended = take_chunk();
|
||||
while (!ended && WaitForSingleObject(ready, 1000) == WAIT_OBJECT_0) {
|
||||
ended = take_chunk();
|
||||
}
|
||||
if (!ended) {
|
||||
break;
|
||||
}
|
||||
scene.execute(std::span(frame).first(complete), 0, false);
|
||||
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||
complete = frame.size();
|
||||
++skipped;
|
||||
}
|
||||
|
||||
// Follow the client's 3D view's size.
|
||||
const RECT place = placement(clientWindow, channel->view);
|
||||
const int width = place.right - place.left;
|
||||
const int height = place.bottom - place.top;
|
||||
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0 && width > 0 && height > 0) {
|
||||
SetWindowPos(window, nullptr, place.left, place.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
|
||||
placed = place;
|
||||
log("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
|
||||
}
|
||||
LARGE_INTEGER start, end;
|
||||
QueryPerformanceCounter(&start);
|
||||
if (scene.execute(std::span(frame).first(complete), place.left, width > 0 && height > 0)) {
|
||||
swapChain->Present(0);
|
||||
QueryPerformanceCounter(&end);
|
||||
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
||||
if (++drawn == 300) {
|
||||
log("last 300 frames: {:.1f} ms each to draw and present, {} skipped to catch up", drawTime * 1000 / drawn,
|
||||
skipped);
|
||||
drawTime = 0;
|
||||
drawn = 0;
|
||||
skipped = 0;
|
||||
}
|
||||
}
|
||||
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||
}
|
||||
|
||||
log("exiting");
|
||||
DestroyWindow(window);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user