// DX12 backend for the mock game. Renders the same animated pattern as the DX11 backend // (animated background + moving bar + top-left frame-counter block) so the capture tests // can verify the DX12 capture path against an identical, frame-numbered source. #include "render_backend.hpp" #include #include #include using Microsoft::WRL::ComPtr; namespace coop::mock { namespace { constexpr UINT kBackBuffers = 3; // DX12 rotates these explicitly -- the case the capture must get right class Dx12Backend : public RenderBackend { public: bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) override { width_ = width; height_ = height; if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(device_.GetAddressOf())))) { return false; } D3D12_COMMAND_QUEUE_DESC qd = {}; qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; if (FAILED(device_->CreateCommandQueue(&qd, IID_PPV_ARGS(queue_.GetAddressOf())))) { return false; } ComPtr factory; if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())))) { return false; } DXGI_SWAP_CHAIN_DESC1 desc = {}; desc.Width = width; desc.Height = height; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = kBackBuffers; desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; ComPtr sc1; if (FAILED(factory->CreateSwapChainForHwnd(queue_.Get(), hwnd, &desc, nullptr, nullptr, sc1.GetAddressOf())) || FAILED(sc1.As(&swap_))) { return false; } factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); D3D12_DESCRIPTOR_HEAP_DESC hd = {}; hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; hd.NumDescriptors = kBackBuffers; if (FAILED(device_->CreateDescriptorHeap(&hd, IID_PPV_ARGS(rtv_heap_.GetAddressOf())))) { return false; } rtv_stride_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); D3D12_CPU_DESCRIPTOR_HANDLE h = rtv_heap_->GetCPUDescriptorHandleForHeapStart(); for (UINT i = 0; i < kBackBuffers; ++i) { if (FAILED(swap_->GetBuffer(i, IID_PPV_ARGS(targets_[i].GetAddressOf())))) { return false; } device_->CreateRenderTargetView(targets_[i].Get(), nullptr, h); rtv_handles_[i] = h; h.ptr += rtv_stride_; if (FAILED(device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(allocs_[i].GetAddressOf())))) { return false; } } if (FAILED(device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocs_[0].Get(), nullptr, IID_PPV_ARGS(list_.GetAddressOf())))) { return false; } list_->Close(); if (FAILED(device_->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence_.GetAddressOf())))) { return false; } fence_event_ = CreateEventW(nullptr, FALSE, FALSE, nullptr); return fence_event_ != nullptr; } void render_and_present(std::uint32_t frame) override { const UINT idx = swap_->GetCurrentBackBufferIndex(); allocs_[idx]->Reset(); list_->Reset(allocs_[idx].Get(), nullptr); transition(targets_[idx].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); // Animated background (full RT clear). const float bg[4] = {static_cast((frame * 2) % 256) / 255.0f, static_cast((frame * 3) % 256) / 255.0f, static_cast((frame * 5) % 256) / 255.0f, 1.0f}; list_->ClearRenderTargetView(rtv_handles_[idx], bg, 0, nullptr); // Moving vertical bar. const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; const LONG span = width_ > 24 ? static_cast(width_ - 24) : 1; const LONG bx = static_cast((frame * 4) % static_cast(span)); const D3D12_RECT bar = {bx, 0, bx + 24, static_cast(height_)}; list_->ClearRenderTargetView(rtv_handles_[idx], white, 1, &bar); // Frame-counter block (top-left). 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 D3D12_RECT block = {0, 0, static_cast(kFrameBlock), static_cast(kFrameBlock)}; list_->ClearRenderTargetView(rtv_handles_[idx], code, 1, &block); transition(targets_[idx].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); list_->Close(); ID3D12CommandList* lists[] = {list_.Get()}; queue_->ExecuteCommandLists(1, lists); swap_->Present(1, 0); wait_for_gpu(); // simple per-frame sync (mock game: correctness over throughput) } [[nodiscard]] const char* name() const override { return "dx12"; } ~Dx12Backend() override { if (fence_ != nullptr) { wait_for_gpu(); } if (fence_event_ != nullptr) { CloseHandle(fence_event_); } } private: void transition(ID3D12Resource* res, D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) { D3D12_RESOURCE_BARRIER b = {}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; b.Transition.pResource = res; b.Transition.StateBefore = from; b.Transition.StateAfter = to; b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; list_->ResourceBarrier(1, &b); } void wait_for_gpu() { const UINT64 v = ++fence_value_; queue_->Signal(fence_.Get(), v); if (fence_->GetCompletedValue() < v) { fence_->SetEventOnCompletion(v, fence_event_); WaitForSingleObject(fence_event_, INFINITE); } } std::uint32_t width_ = 0; std::uint32_t height_ = 0; ComPtr device_; ComPtr queue_; ComPtr swap_; ComPtr rtv_heap_; UINT rtv_stride_ = 0; ComPtr targets_[kBackBuffers]; D3D12_CPU_DESCRIPTOR_HANDLE rtv_handles_[kBackBuffers] = {}; ComPtr allocs_[kBackBuffers]; ComPtr list_; ComPtr fence_; UINT64 fence_value_ = 0; HANDLE fence_event_ = nullptr; }; } // namespace std::unique_ptr create_dx12_backend() { return std::make_unique(); } } // namespace coop::mock