Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs, Allman functions) over every source file so the tree is formatter-clean. Whitespace only -- no behavior change; full x64 + x86 suites pass. Also set SortIncludes: false in .clang-format. Windows include order is load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h / dinput.h; winsock2.h must precede windows.h), and the default alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build break. Leaving order alone keeps the manual, correct grouping.
91 lines
4.0 KiB
C++
91 lines
4.0 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;
|
|
|
|
// 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<std::uint8_t>((frame * 2) % 256);
|
|
p.bg_g = static_cast<std::uint8_t>((frame * 3) % 256);
|
|
p.bg_b = static_cast<std::uint8_t>((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<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
|