// 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 #include #include #include 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(frame & 0xFF); g = static_cast((frame >> 8) & 0xFF); b = static_cast((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(r) | (static_cast(g) << 8) | (static_cast(b) << 16); } // Size (px) of the top-left frame-counter block the test samples. inline constexpr std::uint32_t kFrameBlock = 64; // Width (px) of the moving vertical bar. inline constexpr std::uint32_t kBarWidth = 24; // The animated pattern the mock renders for one frame, computed once so every backend draws the // IDENTICAL image (the capture test compares backends). Each backend consumes these values with its // own API's clear/fill calls: an animated full-screen background, a kBarWidth-wide full-height white // bar at bar_x, and the top-left kFrameBlock-square frame-counter block whose colour encodes `frame`. struct FramePattern { std::uint8_t bg_r, bg_g, bg_b; // background; each channel sweeps at a different rate -> motion std::uint32_t bar_x; // left edge of the moving bar std::uint8_t code_r, code_g, code_b; // frame-counter block colour (frame_to_rgb) }; inline FramePattern frame_pattern(std::uint32_t frame, std::uint32_t width) { FramePattern p{}; p.bg_r = static_cast((frame * 2) % 256); p.bg_g = static_cast((frame * 3) % 256); p.bg_b = static_cast((frame * 5) % 256); const std::uint32_t span = width > kBarWidth ? width - kBarWidth : 1; p.bar_x = (frame * 4) % span; frame_to_rgb(frame, p.code_r, p.code_g, p.code_b); return p; } 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 create(const std::string& name); }; // Per-backend factories (defined in their own TUs; create() dispatches to them). std::unique_ptr create_dx11_backend(); std::unique_ptr create_dx12_backend(); std::unique_ptr create_dx10_backend(); std::unique_ptr create_dx9_backend(bool ex); // ex: D3D9Ex vs plain D3D9 std::unique_ptr create_gl_backend(); std::unique_ptr create_vk_backend(); } // namespace coop::mock