Add coop_mock_game: animated, frame-numbered A/V test game (DX11 + DX12)
A tiny test "game" for exercising the capture/audio/hook paths. Opens a normal window and renders an animated pattern -- moving bar + per-frame background so motion (and dropped frames) are obvious -- with a top-left block whose RGB encodes the exact frame number, so a capture test can decode it and detect dropped / duplicated / stale frames. Selectable backend (dx11 / dx12 today), behind a RenderBackend interface so OpenGL/Vulkan can be added. With audio args it also plays a configurable WASAPI tone (shared ToneSource), so it's a full A/V source with a window (unlike coop_tone). coop_mock_game.exe [dx11|dx12] [seconds] [rate] [channels] [bits] [pcm|float] Milestone 1 of the mock-game roadmap item (the stress-test suite follows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
116
tools/mock_game/render_dx11.cpp
Normal file
116
tools/mock_game/render_dx11.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
// DX11 backend for the mock game. Renders the animated pattern entirely with region
|
||||
// clears (ID3D11DeviceContext1::ClearView) so it needs no shaders: an animated full-screen
|
||||
// background, a moving vertical bar, and the top-left frame-counter block.
|
||||
#include "render_backend.hpp"
|
||||
|
||||
#include <d3d11_1.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
namespace coop::mock
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class Dx11Backend : public RenderBackend
|
||||
{
|
||||
public:
|
||||
bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) override
|
||||
{
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
const D3D_FEATURE_LEVEL levels[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
|
||||
ComPtr<ID3D11DeviceContext> ctx0;
|
||||
if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, levels,
|
||||
static_cast<UINT>(std::size(levels)), D3D11_SDK_VERSION,
|
||||
device_.GetAddressOf(), nullptr, ctx0.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (FAILED(ctx0.As(&ctx_))) // ClearView needs ID3D11DeviceContext1
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ComPtr<IDXGIDevice> dxgi_device;
|
||||
ComPtr<IDXGIAdapter> adapter;
|
||||
ComPtr<IDXGIFactory2> factory;
|
||||
if (FAILED(device_.As(&dxgi_device)) || FAILED(dxgi_device->GetAdapter(adapter.GetAddressOf())) ||
|
||||
FAILED(adapter->GetParent(IID_PPV_ARGS(factory.GetAddressOf()))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 desc = {};
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // UNORM (not sRGB) so the frame code is exact
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
desc.BufferCount = 2;
|
||||
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||
if (FAILED(factory->CreateSwapChainForHwnd(device_.Get(), hwnd, &desc, nullptr, nullptr,
|
||||
swap_.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
|
||||
|
||||
// D3D11 flip-model: GetBuffer(0) stays the live back buffer, so one RTV is reused.
|
||||
ComPtr<ID3D11Texture2D> back;
|
||||
if (FAILED(swap_->GetBuffer(0, IID_PPV_ARGS(back.GetAddressOf()))) ||
|
||||
FAILED(device_->CreateRenderTargetView(back.Get(), nullptr, rtv_.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void render_and_present(std::uint32_t frame) override
|
||||
{
|
||||
// Animated background: each channel sweeps at a different rate -> constant motion.
|
||||
const float bg[4] = {static_cast<float>((frame * 2) % 256) / 255.0f,
|
||||
static_cast<float>((frame * 3) % 256) / 255.0f,
|
||||
static_cast<float>((frame * 5) % 256) / 255.0f, 1.0f};
|
||||
ctx_->ClearRenderTargetView(rtv_.Get(), bg);
|
||||
|
||||
// Moving vertical bar (obvious motion; a dropped frame makes it jump).
|
||||
const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
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));
|
||||
const D3D11_RECT bar = {bx, 0, bx + 24, static_cast<LONG>(height_)};
|
||||
ctx_->ClearView(rtv_.Get(), white, &bar, 1);
|
||||
|
||||
// Frame-counter block (top-left): RGB encodes the exact frame number for the test.
|
||||
std::uint8_t r = 0, g = 0, b = 0;
|
||||
frame_to_rgb(frame, r, g, b);
|
||||
const float code[4] = {r / 255.0f, g / 255.0f, b / 255.0f, 1.0f};
|
||||
const D3D11_RECT block = {0, 0, static_cast<LONG>(kFrameBlock), static_cast<LONG>(kFrameBlock)};
|
||||
ctx_->ClearView(rtv_.Get(), code, &block, 1);
|
||||
|
||||
swap_->Present(1, 0); // vsync -> a game-like cadence
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* name() const override
|
||||
{
|
||||
return "dx11";
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint32_t width_ = 0;
|
||||
std::uint32_t height_ = 0;
|
||||
ComPtr<ID3D11Device> device_;
|
||||
ComPtr<ID3D11DeviceContext1> ctx_;
|
||||
ComPtr<IDXGISwapChain1> swap_;
|
||||
ComPtr<ID3D11RenderTargetView> rtv_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RenderBackend> create_dx11_backend()
|
||||
{
|
||||
return std::make_unique<Dx11Backend>();
|
||||
}
|
||||
|
||||
} // namespace coop::mock
|
||||
Reference in New Issue
Block a user