Files
CoopAllTheThings/tools/mock_game/render_dx09.cpp
BlackMark 8a43d2f568 Mock game: render every backend UNCAPPED + assert a healthy present rate
The mock backends presented with vsync ("a game-like cadence") -- wrong for a
perf/stress fixture: it does trivial work on an RTX 4090, so it must run as fast
as it can. Vsync capped them to tens of fps (dx9 30, dx10 23, dx11 63, dx12 126),
which hid both capture-induced slowdowns and the hook-removal race. Uncapped now:

  dx9/dx10 INTERVAL_IMMEDIATE / Present(0,0) (BLT), dx11/dx12 ALLOW_TEARING +
  Present(0, ALLOW_TEARING) (flip), gl wglSwapIntervalEXT(0), vk IMMEDIATE/MAILBOX.

Measured no-hook: dx9 ~21000, dx10 ~2800, dx11 ~17000, dx12 ~12000, gl ~26000, vk
~24000 fps.

mock_game_test now adds a present-rate floor per backend (>= 300/s while
capturing): with the hook live every backend stays in the hundreds-thousands
(vk 13500, dx11 9000+, gl 1800, dx9/10 ~1000-1600, dx12 2500). This is the
dimension the frame-advance checks missed -- the Vulkan 144->3 FPS stall still
advanced frames -- so it catches a present-thread stall OR an accidental vsync.

The faster storm exposed the hook-removal UAF fixed in the previous commit.
README roadmap + lessons-learned updated (incl. correcting the old "reset makes
in-flight trampoline calls safe" claim). Full suite 21/21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:45:00 +02:00

119 lines
3.4 KiB
C++

// DX9 backend for the mock game. Renders the animated pattern with Clear + ColorFill (D3D9's
// built-in rect fill -- no shaders) and presents through a real D3D9 device, so the D3D9
// capture hook sees a genuine present. Two selectable modes: "dx9ex" (Direct3DCreate9Ex) and
// plain "dx9" (Direct3DCreate9), so the two D3D9 capture paths each have a matching game. The
// back buffer is the standard A8R8G8B8 (BGRA byte order); the capture side swizzles to RGBA.
#include "render_backend.hpp"
#include <d3d9.h>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
namespace coop::mock
{
namespace
{
D3DCOLOR opaque(std::uint8_t r, std::uint8_t g, std::uint8_t b)
{
return D3DCOLOR_ARGB(255, r, g, b);
}
class Dx9Backend : public RenderBackend
{
public:
explicit Dx9Backend(bool ex) : ex_(ex)
{
}
bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) override
{
width_ = width;
height_ = height;
D3DPRESENT_PARAMETERS pp = {};
pp.BackBufferWidth = width;
pp.BackBufferHeight = height;
pp.BackBufferFormat = D3DFMT_X8R8G8B8;
pp.BackBufferCount = 1;
pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
pp.hDeviceWindow = hwnd;
pp.Windowed = TRUE;
pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // uncapped: the mock must be fast
if (ex_)
{
ComPtr<IDirect3D9Ex> d3d;
if (FAILED(Direct3DCreate9Ex(D3D_SDK_VERSION, d3d.GetAddressOf())))
{
return false;
}
ComPtr<IDirect3DDevice9Ex> dev;
if (FAILED(d3d->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, nullptr,
dev.GetAddressOf())))
{
return false;
}
dev_ = dev; // IDirect3DDevice9Ex derives from IDirect3DDevice9
}
else
{
ComPtr<IDirect3D9> d3d(Direct3DCreate9(D3D_SDK_VERSION));
if (!d3d)
{
return false;
}
if (FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, dev_.GetAddressOf())))
{
return false;
}
}
return true;
}
void render_and_present(std::uint32_t frame) override
{
const D3DCOLOR bg = opaque(static_cast<std::uint8_t>((frame * 2) % 256),
static_cast<std::uint8_t>((frame * 3) % 256),
static_cast<std::uint8_t>((frame * 5) % 256));
dev_->Clear(0, nullptr, D3DCLEAR_TARGET, bg, 1.0f, 0);
ComPtr<IDirect3DSurface9> back;
if (SUCCEEDED(dev_->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, back.GetAddressOf())))
{
const LONG span = width_ > 24 ? static_cast<LONG>(width_ - 24) : 1;
const LONG bx = static_cast<LONG>((frame * 4) % static_cast<std::uint32_t>(span));
RECT bar = {bx, 0, bx + 24, static_cast<LONG>(height_)}; // moving vertical bar
dev_->ColorFill(back.Get(), &bar, opaque(255, 255, 255));
std::uint8_t r = 0, g = 0, b = 0;
frame_to_rgb(frame, r, g, b);
RECT block = {0, 0, static_cast<LONG>(kFrameBlock), static_cast<LONG>(kFrameBlock)};
dev_->ColorFill(back.Get(), &block, opaque(r, g, b)); // top-left frame-counter block
}
dev_->Present(nullptr, nullptr, nullptr, nullptr);
}
[[nodiscard]] const char* name() const override
{
return ex_ ? "dx9ex" : "dx9";
}
private:
bool ex_;
std::uint32_t width_ = 0;
std::uint32_t height_ = 0;
ComPtr<IDirect3DDevice9> dev_;
};
} // namespace
std::unique_ptr<RenderBackend> create_dx9_backend(bool ex)
{
return std::make_unique<Dx9Backend>(ex);
}
} // namespace coop::mock