diff --git a/tools/mock_game/CMakeLists.txt b/tools/mock_game/CMakeLists.txt index 8368f62..b418507 100644 --- a/tools/mock_game/CMakeLists.txt +++ b/tools/mock_game/CMakeLists.txt @@ -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. diff --git a/tools/mock_game/main.cpp b/tools/mock_game/main.cpp index ed7c8f0..4be4e6b 100644 --- a/tools/mock_game/main.cpp +++ b/tools/mock_game/main.cpp @@ -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 diff --git a/tools/mock_game/render_backend.cpp b/tools/mock_game/render_backend.cpp index 67f78c3..e7e7510 100644 --- a/tools/mock_game/render_backend.cpp +++ b/tools/mock_game/render_backend.cpp @@ -13,6 +13,10 @@ std::unique_ptr RenderBackend::create(const std::string& name) { return create_dx12_backend(); } + if (name == "dx10") + { + return create_dx10_backend(); + } return nullptr; } diff --git a/tools/mock_game/render_backend.hpp b/tools/mock_game/render_backend.hpp index 821f5ab..9219516 100644 --- a/tools/mock_game/render_backend.hpp +++ b/tools/mock_game/render_backend.hpp @@ -60,5 +60,6 @@ public: // 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(); } // namespace coop::mock diff --git a/tools/mock_game/render_dx10.cpp b/tools/mock_game/render_dx10.cpp new file mode 100644 index 0000000..5df634e --- /dev/null +++ b/tools/mock_game/render_dx10.cpp @@ -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 + +#include +#include +#include + +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(r) | (static_cast(g) << 8) | + (static_cast(b) << 16) | (static_cast(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(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((frame * 2) % 256), + static_cast((frame * 3) % 256), + static_cast((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(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(width_ * 4), 0); + ComPtr 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 px_; + ComPtr device_; + ComPtr swap_; + ComPtr scratch_; +}; +} // namespace + +std::unique_ptr create_dx10_backend() +{ + return std::make_unique(); +} + +} // namespace coop::mock