Files
CoopAllTheThings/tools/mock_game/render_backend.hpp
BlackMark 49daa1d81f M2(Vulkan): Vulkan mock-game backend + volk/Vulkan-Headers submodules
Add render_vk.cpp (selectable as `vk`): brings up a real Vulkan
instance/device/swap chain via volk (which dlopens vulkan-1.dll -- the
loader-bypass case the capture hook must handle) and clears the swap-chain image
to the frame-counter colour each frame with vkCmdClearColorImage (no pipeline,
no shaders, no SPIR-V) and presents. The whole image encodes the frame number,
so it animates and stale frames are detectable.

Adds the official Khronos Vulkan-Headers + zeux/volk submodules and a
coop_require_submodule() CMake helper that fails with a clear "git submodule
update --init --recursive" message rather than auto-cloning. volk is pinned to
the project's dynamic CRT (no LNK4098).

mock_game_test gets a vk liveness check (its present pointer is cached at init,
so late injection can't hook it -- the capture path needs the early-load path,
to come). 16/16 ctest, skips cleanly without a Vulkan driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:12:55 +02:00

69 lines
2.8 KiB
C++

// Pluggable render backend for the mock game (a test fixture, not a shipped component).
//
// The mock game renders an *animated* (never static) pattern so dropped / duplicated /
// torn / stale frames are obvious both to the eye and to an automated capture test: a
// moving bar + a per-frame background colour give visible motion, and a small top-left
// "frame-counter" block encodes the exact frame number in its RGB so a test can decode it
// from a captured pixel and assert the sequence advances (the DX12 rotating-backbuffer bug
// showed up as a stuck/repeating counter). The backend is selectable (DX11 / DX12 today)
// and structured so OpenGL / Vulkan can be added by implementing this interface.
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <windows.h>
namespace coop::mock
{
// Encode a frame counter into an RGB triple (and back). R/G/B are the low 24 bits, so it
// is unambiguous for ~16M frames. The swap chain is UNORM (not sRGB), so the bytes survive
// a capture copy exactly. The capture test samples the frame-counter block and decodes it.
inline void frame_to_rgb(std::uint32_t frame, std::uint8_t& r, std::uint8_t& g, std::uint8_t& b)
{
r = static_cast<std::uint8_t>(frame & 0xFF);
g = static_cast<std::uint8_t>((frame >> 8) & 0xFF);
b = static_cast<std::uint8_t>((frame >> 16) & 0xFF);
}
inline std::uint32_t rgb_to_frame(std::uint8_t r, std::uint8_t g, std::uint8_t b)
{
return static_cast<std::uint32_t>(r) | (static_cast<std::uint32_t>(g) << 8) |
(static_cast<std::uint32_t>(b) << 16);
}
// Size (px) of the top-left frame-counter block the test samples.
inline constexpr std::uint32_t kFrameBlock = 64;
class RenderBackend
{
public:
virtual ~RenderBackend() = default;
// Bring up the device + swap chain on `hwnd` at the given client size. False on failure.
virtual bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) = 0;
// Draw frame `frame` (animated background + moving bar + the frame-counter block) and
// present it. One call per displayed frame.
virtual void render_and_present(std::uint32_t frame) = 0;
// Human-readable backend name (e.g. "dx11"), for logging.
[[nodiscard]] virtual const char* name() const = 0;
// Factory: returns a backend for `name` ("dx11" / "dx12"), or nullptr if unknown /
// unavailable on this machine.
static std::unique_ptr<RenderBackend> create(const std::string& name);
};
// Per-backend factories (defined in their own TUs; create() dispatches to them).
std::unique_ptr<RenderBackend> create_dx11_backend();
std::unique_ptr<RenderBackend> create_dx12_backend();
std::unique_ptr<RenderBackend> create_dx10_backend();
std::unique_ptr<RenderBackend> create_dx9_backend(bool ex); // ex: D3D9Ex vs plain D3D9
std::unique_ptr<RenderBackend> create_gl_backend();
std::unique_ptr<RenderBackend> create_vk_backend();
} // namespace coop::mock