mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
b3d8d0aea9
## Link to GitHub Issue or related Pull Request, if one exists Fixes #134 ## Description of change Add ImGui DX11 backend implementation, and integrate into the spice overlay. The tricky part is that some of the Unity games: 1. Launch multiple windows, and 2. Can be resized (even full screen games launch at a certain resolution and then expand to fit desktop resolution) 3. lazy load DX11 DLLs This PR also adds screenshot functionality, but screen resize is not implemented. ## Testing Seems to be working for most games. Need to do final tests for: - [x] CCJ (attract movie plays as well) - [x] Busou Shinki (title movie plays) - [x] MFG - [x] QuizKnock - [x] Polaris Chord - [x] check dx9 for regression
166 lines
5.4 KiB
C++
166 lines
5.4 KiB
C++
// 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 <vector>
|
|
|
|
#include <windows.h>
|
|
#include <d3d11.h>
|
|
#include <dxgi.h>
|
|
|
|
#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<uint8_t> &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<ID3D11Texture2D> 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<ID3D11Texture2D> 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<ID3D11Texture2D> 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<size_t>(desc.Width) * desc.Height * 4);
|
|
const uint8_t *src_base = reinterpret_cast<const uint8_t *>(mapped.pData);
|
|
for (uint32_t y = 0; y < desc.Height; ++y) {
|
|
const uint8_t *row = src_base + static_cast<size_t>(y) * mapped.RowPitch;
|
|
uint8_t *dst = out.data() + static_cast<size_t>(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<ID3D11Device> device(raw_device);
|
|
ID3D11DeviceContext *raw_ctx = nullptr;
|
|
device->GetImmediateContext(&raw_ctx);
|
|
if (!raw_ctx) {
|
|
return;
|
|
}
|
|
com_ptr<ID3D11DeviceContext> context(raw_ctx);
|
|
|
|
std::vector<uint8_t> 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
|