Files
CoopAllTheThings/tools/mock_game/render_dx09.cpp
BlackMark 9a736cf477 Give the mock-game backends one source of truth for the test pattern
The animated pattern math (background sweep, moving 24px bar, frame-
counter block) was copy-pasted across all five clear-based backends
(dx9/dx10/dx11/dx12/gl). The backends MUST render an identical image --
the capture test decodes the same frame-counter block from each -- so a
drift between copies would be a silent test hazard.

Factor it into one frame_pattern() helper (plus kBarWidth) in
render_backend.hpp; each backend now consumes the computed bg/bar_x/code
values with its own API's clear/fill. mock_game_test still decodes every
backend correctly.
2026-07-12 09:35:45 +02:00

114 lines
3.2 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 FramePattern p = frame_pattern(frame, width_);
dev_->Clear(0, nullptr, D3DCLEAR_TARGET, opaque(p.bg_r, p.bg_g, p.bg_b), 1.0f, 0);
ComPtr<IDirect3DSurface9> back;
if (SUCCEEDED(dev_->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, back.GetAddressOf())))
{
const LONG bx = static_cast<LONG>(p.bar_x);
RECT bar = {bx, 0, bx + static_cast<LONG>(kBarWidth), static_cast<LONG>(height_)}; // moving vertical bar
dev_->ColorFill(back.Get(), &bar, opaque(255, 255, 255));
RECT block = {0, 0, static_cast<LONG>(kFrameBlock), static_cast<LONG>(kFrameBlock)};
dev_->ColorFill(back.Get(), &block, opaque(p.code_r, p.code_g, p.code_b)); // top-left frame 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