Fix particle sizes, and add tools for comparing renderers

Particles: Direct3D 9's size limit evidently isn't the documented 64 pixels; unclamped (up to 2048) they match the client's renderer. A compare view shows the left half's scene over the right half, both renderers side by side; %TEMP%\t5de-renderer-view.txt switches views and %TEMP%\t5de-renderer-shaders\fixed_function.hlsl replaces the shaders while the client runs. The renderer process runs from a copy, so the program can be replaced meanwhile. The adapter recognizes only the textures and buffers it made (others reach the client renderer unchanged) and logs the others and render targets.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-28 06:43:34 -07:00
1 parent 1a45bf9a86
commit 299cd8c312
11 files changed
+239 -28

No files matched your search

+95 -13
View File
@@ -84,15 +84,90 @@ HWND create_child_window(HWND parent)
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)
// The view, as the adapter set it, or as %TEMP%\t5de-renderer-view.txt says while the client runs (off, split, full
// or compare): for comparing the renderers without restarting the client. Read every second or so.
View current_view(View adapter)
{
RECT client{};
GetClientRect(parent, &client);
if (view == View::Split) {
client.left = client.right / 2;
static View view = adapter;
static ULONGLONG checked = 0;
if (GetTickCount64() - checked < 1000) {
return view;
}
return client;
checked = GetTickCount64();
view = adapter;
wchar_t temp[MAX_PATH + 1];
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
if (length == 0 || length > MAX_PATH) {
return view;
}
const HANDLE file = CreateFileW((std::filesystem::path(temp) / L"t5de-renderer-view.txt").c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) {
return view;
}
char text[16] = {};
DWORD read = 0;
ReadFile(file, text, sizeof(text) - 1, &read, nullptr);
CloseHandle(file);
const std::string_view setting(text, std::strcspn(text, "\r\n "));
view = setting == "off" ? View::Off
: setting == "split" ? View::Split
: setting == "full" ? View::Full
: setting == "compare" ? View::Compare
: adapter;
return view;
}
// Shader changes to try while the client runs: %TEMP%\t5de-renderer-shaders\fixed_function.hlsl, if there is
// one, replaces the built-in shaders, and is read again when it changes (checked every second or so).
void reload_shaders(t5de::Scene& scene)
{
static ULONGLONG checked = 0;
static std::filesystem::file_time_type loaded{};
if (GetTickCount64() - checked < 1000) {
return;
}
checked = GetTickCount64();
std::error_code error;
const auto path = std::filesystem::temp_directory_path(error) / L"t5de-renderer-shaders" / L"fixed_function.hlsl";
const auto modified = std::filesystem::last_write_time(path, error);
if (error || modified == loaded) {
return;
}
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) {
return;
}
std::string source(static_cast<std::size_t>(std::filesystem::file_size(path, error)), ' ');
DWORD read = 0;
ReadFile(file, source.data(), static_cast<DWORD>(source.size()), &read, nullptr);
CloseHandle(file);
source.resize(read);
loaded = modified;
scene.setShaderSource(std::move(source));
info("shaders reloaded from {}", path.string());
}
// Where the child window goes in the client's 3D view, and where in the 3D view its scene starts (offset).
struct Placement {
RECT window{};
int offset = 0;
};
Placement placement(HWND parent, View view)
{
Placement place;
GetClientRect(parent, &place.window);
if (view == View::Split || view == View::Compare) {
place.window.left = place.window.right / 2;
}
place.offset = view == View::Compare ? 0 : place.window.left;
if (view == View::Off) {
place.window = {};
}
return place;
}
} // namespace
@@ -203,18 +278,25 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
}
// Follow the client's 3D view's size.
const RECT place = placement(clientWindow, channel->view);
const auto [place, offset] = placement(clientWindow, current_view(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));
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0) {
placed = place;
info("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
if (width > 0 && height > 0) {
SetWindowPos(window, nullptr, place.left, place.top, width, height,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
info("drawing {}x{} at {} in the client's 3D view, its scene from {}", width, height, place.left, offset);
} else {
ShowWindow(window, SW_HIDE);
info("hidden");
}
}
reload_shaders(scene);
LARGE_INTEGER start, end;
QueryPerformanceCounter(&start);
if (scene.execute(std::span(frame).first(complete), place.left, width > 0 && height > 0)) {
if (scene.execute(std::span(frame).first(complete), offset, width > 0 && height > 0)) {
swapChain->Present(0);
QueryPerformanceCounter(&end);
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
+32 -2
View File
@@ -1,5 +1,8 @@
#include "scene.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <string>
@@ -56,7 +59,7 @@ constexpr std::uint32_t hasColors = 4;
} // namespace
Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapChain)
: device_(device), context_(context), swapChain_(swapChain)
: device_(device), context_(context), swapChain_(swapChain), shaderSource_(fixedFunctionHlsl)
{
BufferDesc constants;
constants.Name = "Constants";
@@ -86,6 +89,13 @@ Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapCha
constants_.flags[2] = 127; // the client's alpha reference
}
void Scene::setShaderSource(std::string source)
{
shaderSource_ = std::move(source);
shaders_.clear();
pipelines_.clear();
}
bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
{
offsetX_ = offsetX;
@@ -546,6 +556,26 @@ void Scene::drawPointSprites(const protocol::Command& command)
constants_.ambient[1], constants_.ambient[2], constants_.texTransform0[0], constants_.texTransform0[1],
constants_.texTransform0[2], constants_.texTransform0[4], constants_.texTransform0[5],
constants_.texTransform0[6]);
// The first sprite: where it is, seen from the camera, and how big it's drawn.
float point[4] = {0, 0, 0, 1};
std::uint32_t color = 0;
std::memcpy(point, payload.data(), 12);
std::memcpy(&color, payload.data() + 12, 4);
const auto transform = [](const float (&p)[4], const float (&m)[16]) {
std::array<float, 4> out{};
for (int column = 0; column < 4; ++column) {
out[column] = p[0] * m[column] + p[1] * m[4 + column] + p[2] * m[8 + column] + p[3] * m[12 + column];
}
return out;
};
const auto world = transform(point, constants_.world);
const float worldPoint[4] = {world[0], world[1], world[2], world[3]};
const auto view = transform(worldPoint, constants_.view);
const float distance = std::sqrt(view[0] * view[0] + view[1] * view[1] + view[2] * view[2]);
log::info(" first sprite at ({:.2f} {:.2f} {:.2f}), color {:08x}, from the camera ({:.2f} {:.2f} {:.2f}): "
"distance {:.2f}, {:.1f} pixels across (viewport height {})",
point[0], point[1], point[2], color, view[0], view[1], view[2], distance,
std::clamp(constants_.viewport[1] / std::max(distance, 1e-4f), 1.0f, 2048.0f), constants_.viewport[1]);
}
IBuffer* buffers[] = {buffer};
context_->SetVertexBuffers(0, 1, buffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
@@ -565,7 +595,7 @@ const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textur
const std::string source = std::string("#define HAS_NORMALS ") + ((streams & hasNormals) != 0 ? "1" : "0") +
"\n#define HAS_TEX_COORDS " + ((streams & hasTexCoords) != 0 ? "1" : "0") +
"\n#define HAS_COLORS " + ((streams & hasColors) != 0 ? "1" : "0") +
"\n#define TEXTURES " + std::to_string(textures) + "\n" + fixedFunctionHlsl;
"\n#define TEXTURES " + std::to_string(textures) + "\n" + shaderSource_;
Shaders created;
ShaderCreateInfo info;
info.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
+5
View File
@@ -6,6 +6,7 @@
#include <cstddef>
#include <cstdint>
#include <span>
#include <string>
#include <unordered_map>
#include "EngineFactoryD3D11.h"
@@ -23,6 +24,9 @@ public:
// present).
bool execute(std::span<const std::byte> commands, int offsetX, bool draw);
// Replaces the shaders' source (the built-in one by default): for trying changes while the client runs.
void setShaderSource(std::string source);
// When the adapter sent the last frame carried out (QueryPerformanceCounter).
[[nodiscard]] std::uint64_t lastSentAt() const { return lastSentAt_; }
@@ -111,6 +115,7 @@ private:
std::unordered_map<std::uint32_t, Buffer> buffers_;
std::unordered_map<std::uint32_t, Shaders> shaders_;
std::unordered_map<std::uint32_t, Pipeline> pipelines_;
std::string shaderSource_;
Constants constants_{};
State state_;
+1 -1
View File
@@ -101,7 +101,7 @@ void vs_sprite(in SpriteInput input, out PSInput output)
{
float4 view = mul(mul(float4(input.Position, 1.0), g_World), g_View);
float4 clip = mul(view, g_Projection);
float size = clamp(g_Viewport.y / max(length(view.xyz), 1e-4), 1.0, 64.0);
float size = clamp(g_Viewport.y / max(length(view.xyz), 1e-4), 1.0, 2048.0);
float2 corner = float2((input.Corner & 1) != 0 ? 1.0 : -1.0, (input.Corner & 2) != 0 ? -1.0 : 1.0);
clip.xy += corner * size / g_Viewport.xy * clip.w; // half the size each way, in clip space
output.Position = clip;