M2: DX10 mock-game render backend
Add render_dx10.cpp (selectable as `dx10`): composes the animated pattern (background + moving bar + top-left frame-counter block) on the CPU each frame and CopyResource's it into a real D3D10 DXGI swap-chain back buffer (DX10 has no rect-clear; no shaders). Smoke-verified: `coop_mock_game dx10 2` presents frames and exits cleanly. The frame-accurate decode-through-the-hook assertion lands with the DX10 capture commit next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
# CoopMockGame -- a tiny test "game" with an animated, frame-numbered render (DX11/DX12)
|
||||
# CoopMockGame -- a tiny test "game" with an animated, frame-numbered render (DX10/DX11/DX12)
|
||||
# and a configurable WASAPI tone, used by the capture/audio/hook stress tests. Not shipped.
|
||||
add_executable(coop_mock_game
|
||||
main.cpp
|
||||
render_backend.cpp
|
||||
render_dx11.cpp
|
||||
render_dx12.cpp)
|
||||
render_dx12.cpp
|
||||
render_dx10.cpp)
|
||||
|
||||
# Reuses the shared ToneSource (also used by coop_tone + the audio hook self-test).
|
||||
target_include_directories(coop_mock_game PRIVATE ${CMAKE_SOURCE_DIR}/tools/audio_tone)
|
||||
|
||||
target_link_libraries(coop_mock_game PRIVATE d3d11 d3d12 dxgi ole32)
|
||||
target_link_libraries(coop_mock_game PRIVATE d3d11 d3d12 d3d10 dxgi ole32)
|
||||
set_target_properties(coop_mock_game PROPERTIES OUTPUT_NAME "coop_mock_game")
|
||||
|
||||
# Test fixture -> stage next to the tests (alongside coop_tone), not in the deployable root.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// CoopMockGame -- a tiny test "game" used to exercise the capture + audio + hook paths.
|
||||
//
|
||||
// coop_mock_game.exe [dx11|dx12] [seconds] [rate] [channels] [bits] [pcm|float]
|
||||
// coop_mock_game.exe [dx10|dx11|dx12] [seconds] [rate] [channels] [bits] [pcm|float]
|
||||
//
|
||||
// It opens a normal visible window and renders an animated, frame-numbered pattern (see
|
||||
// render_backend.hpp): a moving bar + per-frame background colour make motion obvious, and
|
||||
|
||||
@@ -13,6 +13,10 @@ std::unique_ptr<RenderBackend> RenderBackend::create(const std::string& name)
|
||||
{
|
||||
return create_dx12_backend();
|
||||
}
|
||||
if (name == "dx10")
|
||||
{
|
||||
return create_dx10_backend();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,5 +60,6 @@ public:
|
||||
// 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();
|
||||
|
||||
} // namespace coop::mock
|
||||
|
||||
129
tools/mock_game/render_dx10.cpp
Normal file
129
tools/mock_game/render_dx10.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
// DX10 backend for the mock game. DX10 has no rect-clear (ClearView), so it composes the
|
||||
// animated pattern (background + moving bar + top-left frame-counter block) into a CPU buffer
|
||||
// each frame, uploads it into a scratch texture, and CopyResource's that into the swap-chain
|
||||
// back buffer -- no shaders. It presents through a real D3D10 DXGI swap chain, so the capture
|
||||
// hook sees a genuine D3D10 present.
|
||||
#include "render_backend.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <d3d10.h>
|
||||
#include <dxgi.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
namespace coop::mock
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::uint32_t pack(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a = 255)
|
||||
{
|
||||
// R8G8B8A8_UNORM byte order: R in the low byte.
|
||||
return static_cast<std::uint32_t>(r) | (static_cast<std::uint32_t>(g) << 8) |
|
||||
(static_cast<std::uint32_t>(b) << 16) | (static_cast<std::uint32_t>(a) << 24);
|
||||
}
|
||||
|
||||
class Dx10Backend : public RenderBackend
|
||||
{
|
||||
public:
|
||||
bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) override
|
||||
{
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
px_.resize(static_cast<std::size_t>(width) * height);
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC desc = {};
|
||||
desc.BufferDesc.Width = width;
|
||||
desc.BufferDesc.Height = height;
|
||||
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // UNORM (not sRGB) so the frame code is exact
|
||||
desc.BufferDesc.RefreshRate.Numerator = 60;
|
||||
desc.BufferDesc.RefreshRate.Denominator = 1;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
desc.BufferCount = 2;
|
||||
desc.OutputWindow = hwnd;
|
||||
desc.Windowed = TRUE;
|
||||
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // blt model: GetBuffer(0) is the back buffer
|
||||
|
||||
if (FAILED(D3D10CreateDeviceAndSwapChain(nullptr, D3D10_DRIVER_TYPE_HARDWARE, nullptr, 0,
|
||||
D3D10_SDK_VERSION, &desc, swap_.GetAddressOf(),
|
||||
device_.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scratch texture matching the back buffer; we upload the CPU frame into it then
|
||||
// CopyResource it into the back buffer (DX10 can't fill a sub-rect of an RTV directly).
|
||||
D3D10_TEXTURE2D_DESC td = {};
|
||||
td.Width = width;
|
||||
td.Height = height;
|
||||
td.MipLevels = 1;
|
||||
td.ArraySize = 1;
|
||||
td.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
td.SampleDesc.Count = 1;
|
||||
td.Usage = D3D10_USAGE_DEFAULT;
|
||||
td.BindFlags = D3D10_BIND_SHADER_RESOURCE;
|
||||
return SUCCEEDED(device_->CreateTexture2D(&td, nullptr, scratch_.GetAddressOf()));
|
||||
}
|
||||
|
||||
void render_and_present(std::uint32_t frame) override
|
||||
{
|
||||
const std::uint32_t bg = pack(static_cast<std::uint8_t>((frame * 2) % 256),
|
||||
static_cast<std::uint8_t>((frame * 3) % 256),
|
||||
static_cast<std::uint8_t>((frame * 5) % 256));
|
||||
const std::uint32_t whitepx = pack(255, 255, 255);
|
||||
std::uint8_t fr = 0, fg = 0, fb = 0;
|
||||
frame_to_rgb(frame, fr, fg, fb);
|
||||
const std::uint32_t code = pack(fr, fg, fb);
|
||||
|
||||
const std::uint32_t span = width_ > 24 ? width_ - 24 : 1;
|
||||
const std::uint32_t bx = (frame * 4) % span; // moving vertical bar
|
||||
for (std::uint32_t y = 0; y < height_; ++y)
|
||||
{
|
||||
std::uint32_t* row = px_.data() + static_cast<std::size_t>(y) * width_;
|
||||
for (std::uint32_t x = 0; x < width_; ++x)
|
||||
{
|
||||
std::uint32_t c = bg;
|
||||
if (x >= bx && x < bx + 24)
|
||||
{
|
||||
c = whitepx;
|
||||
}
|
||||
if (x < kFrameBlock && y < kFrameBlock)
|
||||
{
|
||||
c = code; // top-left frame-counter block
|
||||
}
|
||||
row[x] = c;
|
||||
}
|
||||
}
|
||||
|
||||
device_->UpdateSubresource(scratch_.Get(), 0, nullptr, px_.data(), static_cast<UINT>(width_ * 4), 0);
|
||||
ComPtr<ID3D10Texture2D> back;
|
||||
if (SUCCEEDED(swap_->GetBuffer(0, IID_PPV_ARGS(back.GetAddressOf()))))
|
||||
{
|
||||
device_->CopyResource(back.Get(), scratch_.Get());
|
||||
}
|
||||
swap_->Present(1, 0); // vsync -> a game-like cadence
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* name() const override
|
||||
{
|
||||
return "dx10";
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint32_t width_ = 0;
|
||||
std::uint32_t height_ = 0;
|
||||
std::vector<std::uint32_t> px_;
|
||||
ComPtr<ID3D10Device> device_;
|
||||
ComPtr<IDXGISwapChain> swap_;
|
||||
ComPtr<ID3D10Texture2D> scratch_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RenderBackend> create_dx10_backend()
|
||||
{
|
||||
return std::make_unique<Dx10Backend>();
|
||||
}
|
||||
|
||||
} // namespace coop::mock
|
||||
Reference in New Issue
Block a user