# include "renderer.h" # include "setup.h" # if RENDERER(IMGUI_DX11) # include "platform.h" # if PLATFORM(WINDOWS) # define NOMINMAX # define WIN32_LEAN_AND_MEAN # include # endif # include # include "imgui_impl_dx11.h" # include # include # include # include # include # define STB_IMAGE_WRITE_IMPLEMENTATION # include "../../external/stb_image_latest/stb_image_write.h" struct RendererDX11 final : Renderer { bool Create(Platform& platform) override; void Destroy() override; void NewFrame() override; void RenderDrawData(ImDrawData* drawData) override; void Clear(const ImVec4& color) override; void Present() override; void Resize(int width, int height) override; ImTextureID CreateTexture(const void* data, int width, int height) override; void DestroyTexture(ImTextureID texture) override; int GetTextureWidth(ImTextureID texture) override; int GetTextureHeight(ImTextureID texture) override; bool TakeScreenshot(const char* filename) override; HRESULT CreateDeviceD3D(HWND hWnd); void CleanupDeviceD3D(); void CreateRenderTarget(); void CleanupRenderTarget(); Platform* m_Platform = nullptr; ID3D11Device* m_device = nullptr; ID3D11DeviceContext* m_deviceContext = nullptr; IDXGISwapChain* m_swapChain = nullptr; ID3D11RenderTargetView* m_mainRenderTargetView = nullptr; }; std::unique_ptr CreateRenderer() { return std::make_unique(); } bool RendererDX11::Create(Platform& platform) { m_Platform = &platform; auto hr = CreateDeviceD3D(reinterpret_cast(platform.GetMainWindowHandle())); if (FAILED(hr)) return false; if (!ImGui_ImplDX11_Init(m_device, m_deviceContext)) { CleanupDeviceD3D(); return false; } m_Platform->SetRenderer(this); return true; } void RendererDX11::Destroy() { if (!m_Platform) return; m_Platform->SetRenderer(nullptr); ImGui_ImplDX11_Shutdown(); CleanupDeviceD3D(); } void RendererDX11::NewFrame() { ImGui_ImplDX11_NewFrame(); } void RendererDX11::RenderDrawData(ImDrawData* drawData) { ImGui_ImplDX11_RenderDrawData(drawData); } void RendererDX11::Clear(const ImVec4& color) { m_deviceContext->ClearRenderTargetView(m_mainRenderTargetView, (float*)&color.x); } void RendererDX11::Present() { m_swapChain->Present(1, 0); } void RendererDX11::Resize(int width, int height) { ImGui_ImplDX11_InvalidateDeviceObjects(); CleanupRenderTarget(); m_swapChain->ResizeBuffers(0, (UINT)width, (UINT)height, DXGI_FORMAT_UNKNOWN, 0); CreateRenderTarget(); } HRESULT RendererDX11::CreateDeviceD3D(HWND hWnd) { // Setup swap chain DXGI_SWAP_CHAIN_DESC sd; { ZeroMemory(&sd, sizeof(sd)); sd.BufferCount = 2; sd.BufferDesc.Width = 0; sd.BufferDesc.Height = 0; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 60; sd.BufferDesc.RefreshRate.Denominator = 1; sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; } UINT createDeviceFlags = 0; //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; D3D_FEATURE_LEVEL featureLevel; const D3D_FEATURE_LEVEL featureLevelArray[1] = { D3D_FEATURE_LEVEL_11_0, }; if (D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 1, D3D11_SDK_VERSION, &sd, &m_swapChain, &m_device, &featureLevel, &m_deviceContext) != S_OK) return E_FAIL; CreateRenderTarget(); return S_OK; } void RendererDX11::CleanupDeviceD3D() { CleanupRenderTarget(); if (m_swapChain) { m_swapChain->Release(); m_swapChain = nullptr; } if (m_deviceContext) { m_deviceContext->Release(); m_deviceContext = nullptr; } if (m_device) { m_device->Release(); m_device = nullptr; } } void RendererDX11::CreateRenderTarget() { DXGI_SWAP_CHAIN_DESC sd; m_swapChain->GetDesc(&sd); // Create the render target ID3D11Texture2D* pBackBuffer; D3D11_RENDER_TARGET_VIEW_DESC render_target_view_desc; ZeroMemory(&render_target_view_desc, sizeof(render_target_view_desc)); render_target_view_desc.Format = sd.BufferDesc.Format; render_target_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); m_device->CreateRenderTargetView(pBackBuffer, &render_target_view_desc, &m_mainRenderTargetView); m_deviceContext->OMSetRenderTargets(1, &m_mainRenderTargetView, nullptr); pBackBuffer->Release(); } void RendererDX11::CleanupRenderTarget() { if (m_mainRenderTargetView) { m_mainRenderTargetView->Release(); m_mainRenderTargetView = nullptr; } } ImTextureID RendererDX11::CreateTexture(const void* data, int width, int height) { return ImGui_CreateTexture(data, width, height); } void RendererDX11::DestroyTexture(ImTextureID texture) { return ImGui_DestroyTexture(texture); } int RendererDX11::GetTextureWidth(ImTextureID texture) { return ImGui_GetTextureWidth(texture); } int RendererDX11::GetTextureHeight(ImTextureID texture) { return ImGui_GetTextureHeight(texture); } static void GpuFinish(ID3D11Device* device, ID3D11DeviceContext* ctx) { ID3D11Query* query = nullptr; D3D11_QUERY_DESC qd = { D3D11_QUERY_EVENT, 0 }; device->CreateQuery(&qd, &query); ctx->End(query); while (ctx->GetData(query, nullptr, 0, 0) == S_FALSE) { /* spin */ } query->Release(); } bool RendererDX11::TakeScreenshot(const char* filename) { // Generate filename if not provided std::string fname; if (!filename) { time_t now = time(nullptr); char buffer[64]; strftime(buffer, sizeof(buffer), "screenshot_%Y%m%d_%H%M%S.png", localtime(&now)); fname = buffer; } else fname = filename; // Flush any pending GPU work before capturing m_deviceContext->Flush(); // Get back buffer ID3D11Texture2D* backBuffer = nullptr; HRESULT hr = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer); if (FAILED(hr) || !backBuffer) return false; // Get description D3D11_TEXTURE2D_DESC desc; backBuffer->GetDesc(&desc); // Create staging texture for CPU readback D3D11_TEXTURE2D_DESC stagingDesc = desc; stagingDesc.BindFlags = 0; stagingDesc.MiscFlags = 0; stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; stagingDesc.Usage = D3D11_USAGE_STAGING; ID3D11Texture2D* stagingTexture = nullptr; hr = m_device->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture); if (FAILED(hr) || !stagingTexture) { backBuffer->Release(); return false; } // Copy back buffer to staging m_deviceContext->CopyResource(stagingTexture, backBuffer); m_deviceContext->Flush(); // Wait for GPU to finish GpuFinish(m_device, m_deviceContext); backBuffer->Release(); // Map and read D3D11_MAPPED_SUBRESOURCE mapped = {}; hr = m_deviceContext->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mapped); if (FAILED(hr)) { stagingTexture->Release(); return false; } // Check format const bool isBGRA = (desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB); const bool isRGBA = (desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB); if (!isBGRA && !isRGBA) { m_deviceContext->Unmap(stagingTexture, 0); stagingTexture->Release(); return false; } const int w = (int)desc.Width; const int h = (int)desc.Height; std::vector rgba(w * h * 4); // Copy row by row and convert BGRA to RGBA if needed // DirectX11 render targets are typically already in the correct orientation (top-to-bottom) for (int y = 0; y < h; ++y) { const unsigned char* srcRow = (const unsigned char*)mapped.pData + y * mapped.RowPitch; unsigned char* dstRow = rgba.data() + y * w * 4; for (int x = 0; x < w; ++x) { const unsigned char* p = srcRow + x * 4; // DXGI_FORMAT_R8G8B8A8_UNORM is actually BGRA in memory! // Convert BGRA to RGBA dstRow[x * 4 + 0] = p[2]; // R <- B dstRow[x * 4 + 1] = p[1]; // G <- G dstRow[x * 4 + 2] = p[0]; // B <- R dstRow[x * 4 + 3] = p[3]; // A <- A } } m_deviceContext->Unmap(stagingTexture, 0); stagingTexture->Release(); // Write PNG int ok = stbi_write_png(fname.c_str(), w, h, 4, rgba.data(), w * 4); return ok != 0; } # endif // RENDERER(IMGUI_DX11)