// dx11 screenshot capture. mirrors the d3d9 backend: copy the current // backbuffer into a staging texture, force alpha=255, write PNG via // stb_image_write, push to clipboard and notify. #include "d3d11_backend.h" #ifdef SPICE_D3D11 #include #include #include #include #include "d3d11_internal.h" #include "external/stb_image_write.h" #include "hooks/graphics/graphics.h" #include "misc/clipboard.h" #include "overlay/notifications.h" #include "util/fileutils.h" using d3d11_hooks::com_ptr; namespace { // copy the swapchain backbuffer into a CPU-readable staging texture and // flatten it into an RGBA8 buffer (BGRA backbuffers are swizzled, // alpha is forced to 255). bool copy_backbuffer_to_rgba(IDXGISwapChain *swapchain, ID3D11Device *device, ID3D11DeviceContext *context, std::vector &out, uint32_t &out_w, uint32_t &out_h) { ID3D11Texture2D *raw_bb = nullptr; if (FAILED(swapchain->GetBuffer(0, IID_PPV_ARGS(&raw_bb))) || !raw_bb) { return false; } com_ptr backbuffer(raw_bb); D3D11_TEXTURE2D_DESC desc {}; backbuffer->GetDesc(&desc); // MSAA backbuffers can't be CopyResource'd into a non-MS staging target. com_ptr resolved; ID3D11Texture2D *source = backbuffer.get(); if (desc.SampleDesc.Count > 1) { D3D11_TEXTURE2D_DESC rd = desc; rd.SampleDesc.Count = 1; rd.SampleDesc.Quality = 0; rd.Usage = D3D11_USAGE_DEFAULT; rd.BindFlags = D3D11_BIND_RENDER_TARGET; rd.CPUAccessFlags = 0; rd.MiscFlags = 0; ID3D11Texture2D *r = nullptr; if (FAILED(device->CreateTexture2D(&rd, nullptr, &r)) || !r) { return false; } resolved.reset(r); context->ResolveSubresource(resolved.get(), 0, backbuffer.get(), 0, desc.Format); source = resolved.get(); } D3D11_TEXTURE2D_DESC sd {}; sd.Width = desc.Width; sd.Height = desc.Height; sd.MipLevels = 1; sd.ArraySize = 1; sd.Format = desc.Format; sd.SampleDesc.Count = 1; sd.Usage = D3D11_USAGE_STAGING; sd.CPUAccessFlags = D3D11_CPU_ACCESS_READ; ID3D11Texture2D *raw_staging = nullptr; if (FAILED(device->CreateTexture2D(&sd, nullptr, &raw_staging)) || !raw_staging) { return false; } com_ptr staging(raw_staging); context->CopyResource(staging.get(), source); D3D11_MAPPED_SUBRESOURCE mapped {}; if (FAILED(context->Map(staging.get(), 0, D3D11_MAP_READ, 0, &mapped))) { return false; } // backbuffers from GetDesc are always fully-typed (never _TYPELESS). const bool is_bgra = desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; out.resize(static_cast(desc.Width) * desc.Height * 4); const uint8_t *src_base = reinterpret_cast(mapped.pData); for (uint32_t y = 0; y < desc.Height; ++y) { const uint8_t *row = src_base + static_cast(y) * mapped.RowPitch; uint8_t *dst = out.data() + static_cast(y) * desc.Width * 4; for (uint32_t x = 0; x < desc.Width; ++x) { dst[x * 4 + 0] = row[x * 4 + (is_bgra ? 2 : 0)]; dst[x * 4 + 1] = row[x * 4 + 1]; dst[x * 4 + 2] = row[x * 4 + (is_bgra ? 0 : 2)]; dst[x * 4 + 3] = 255; } } context->Unmap(staging.get(), 0); out_w = desc.Width; out_h = desc.Height; return true; } } // namespace namespace d3d11_hooks { void try_screenshot(IDXGISwapChain *swapchain) { if (!swapchain || !graphics_screenshot_consume()) { return; } auto file_path = graphics_screenshot_genpath(); if (file_path.empty()) { return; } ID3D11Device *raw_device = nullptr; if (FAILED(swapchain->GetDevice(IID_PPV_ARGS(&raw_device))) || !raw_device) { return; } com_ptr device(raw_device); ID3D11DeviceContext *raw_ctx = nullptr; device->GetImmediateContext(&raw_ctx); if (!raw_ctx) { return; } com_ptr context(raw_ctx); std::vector pixels; uint32_t w = 0, h = 0; if (!copy_backbuffer_to_rgba(swapchain, device.get(), context.get(), pixels, w, h)) { log_warning("graphics::d3d11", "screenshot: failed to capture backbuffer"); overlay::notifications::add( overlay::notifications::Severity::Error, "Screenshot failed to capture"); return; } log_info("graphics::d3d11", "saving screenshot to {}", file_path); if (stbi_write_png(file_path.c_str(), (int) w, (int) h, 4, pixels.data(), (int) w * 4)) { clipboard::copy_image(file_path); overlay::notifications::add( overlay::notifications::Severity::Success, fmt::format("Screenshot saved: {}", fileutils::basename(file_path))); } else { log_warning("graphics::d3d11", "screenshot: stbi_write_png failed"); overlay::notifications::add( overlay::notifications::Severity::Error, "Screenshot failed to save"); } } } #endif // SPICE_D3D11