diff --git a/tools/mock_game/CMakeLists.txt b/tools/mock_game/CMakeLists.txt index b418507..338b658 100644 --- a/tools/mock_game/CMakeLists.txt +++ b/tools/mock_game/CMakeLists.txt @@ -5,12 +5,13 @@ add_executable(coop_mock_game render_backend.cpp render_dx11.cpp render_dx12.cpp - render_dx10.cpp) + render_dx10.cpp + render_dx09.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 d3d10 dxgi ole32) +target_link_libraries(coop_mock_game PRIVATE d3d11 d3d12 d3d10 d3d9 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 4be4e6b..6948235 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 [dx10|dx11|dx12] [seconds] [rate] [channels] [bits] [pcm|float] +// coop_mock_game.exe [dx9|dx9ex|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 e7e7510..a7a0ae0 100644 --- a/tools/mock_game/render_backend.cpp +++ b/tools/mock_game/render_backend.cpp @@ -17,6 +17,14 @@ std::unique_ptr RenderBackend::create(const std::string& name) { return create_dx10_backend(); } + if (name == "dx9ex") + { + return create_dx9_backend(/*ex=*/true); + } + if (name == "dx9") + { + return create_dx9_backend(/*ex=*/false); + } return nullptr; } diff --git a/tools/mock_game/render_backend.hpp b/tools/mock_game/render_backend.hpp index 9219516..568fe9f 100644 --- a/tools/mock_game/render_backend.hpp +++ b/tools/mock_game/render_backend.hpp @@ -61,5 +61,6 @@ public: 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 } // namespace coop::mock diff --git a/tools/mock_game/render_dx09.cpp b/tools/mock_game/render_dx09.cpp new file mode 100644 index 0000000..2d8d847 --- /dev/null +++ b/tools/mock_game/render_dx09.cpp @@ -0,0 +1,118 @@ +// DX9 backend for the mock game. Renders the animated pattern with Clear + ColorFill (D3D9's +// built-in rect fill -- no shaders) and presents through a real D3D9 device, so the D3D9 +// capture hook sees a genuine present. Two selectable modes: "dx9ex" (Direct3DCreate9Ex) and +// plain "dx9" (Direct3DCreate9), so the two D3D9 capture paths each have a matching game. The +// back buffer is the standard A8R8G8B8 (BGRA byte order); the capture side swizzles to RGBA. +#include "render_backend.hpp" + +#include +#include + +using Microsoft::WRL::ComPtr; + +namespace coop::mock +{ +namespace +{ +D3DCOLOR opaque(std::uint8_t r, std::uint8_t g, std::uint8_t b) +{ + return D3DCOLOR_ARGB(255, r, g, b); +} + +class Dx9Backend : public RenderBackend +{ +public: + explicit Dx9Backend(bool ex) : ex_(ex) + { + } + + bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) override + { + width_ = width; + height_ = height; + + D3DPRESENT_PARAMETERS pp = {}; + pp.BackBufferWidth = width; + pp.BackBufferHeight = height; + pp.BackBufferFormat = D3DFMT_X8R8G8B8; + pp.BackBufferCount = 1; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.hDeviceWindow = hwnd; + pp.Windowed = TRUE; + pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; // vsync -> a game-like cadence + + if (ex_) + { + ComPtr d3d; + if (FAILED(Direct3DCreate9Ex(D3D_SDK_VERSION, d3d.GetAddressOf()))) + { + return false; + } + ComPtr dev; + if (FAILED(d3d->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, + D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, nullptr, + dev.GetAddressOf()))) + { + return false; + } + dev_ = dev; // IDirect3DDevice9Ex derives from IDirect3DDevice9 + } + else + { + ComPtr d3d(Direct3DCreate9(D3D_SDK_VERSION)); + if (!d3d) + { + return false; + } + if (FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, + D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, dev_.GetAddressOf()))) + { + return false; + } + } + return true; + } + + void render_and_present(std::uint32_t frame) override + { + const D3DCOLOR bg = opaque(static_cast((frame * 2) % 256), + static_cast((frame * 3) % 256), + static_cast((frame * 5) % 256)); + dev_->Clear(0, nullptr, D3DCLEAR_TARGET, bg, 1.0f, 0); + + ComPtr back; + if (SUCCEEDED(dev_->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, back.GetAddressOf()))) + { + const LONG span = width_ > 24 ? static_cast(width_ - 24) : 1; + const LONG bx = static_cast((frame * 4) % static_cast(span)); + RECT bar = {bx, 0, bx + 24, static_cast(height_)}; // moving vertical bar + dev_->ColorFill(back.Get(), &bar, opaque(255, 255, 255)); + + std::uint8_t r = 0, g = 0, b = 0; + frame_to_rgb(frame, r, g, b); + RECT block = {0, 0, static_cast(kFrameBlock), static_cast(kFrameBlock)}; + dev_->ColorFill(back.Get(), &block, opaque(r, g, b)); // top-left frame-counter block + } + + dev_->Present(nullptr, nullptr, nullptr, nullptr); + } + + [[nodiscard]] const char* name() const override + { + return ex_ ? "dx9ex" : "dx9"; + } + +private: + bool ex_; + std::uint32_t width_ = 0; + std::uint32_t height_ = 0; + ComPtr dev_; +}; +} // namespace + +std::unique_ptr create_dx9_backend(bool ex) +{ + return std::make_unique(ex); +} + +} // namespace coop::mock