graphics: refactor screenshots (#860)
First commit - pure refactoring, no functional changes Second commit - clean up & minor bug fix.
This commit is contained in:
@@ -5,12 +5,8 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include <d3d9.h>
|
||||
#ifdef __GNUC__
|
||||
#include <d3dx9tex.h>
|
||||
#endif
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
@@ -19,28 +15,23 @@
|
||||
#include "games/popn/popn.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "launcher/options.h"
|
||||
#include "launcher/signal.h"
|
||||
#include "launcher/shutdown.h"
|
||||
#include "misc/clipboard.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "overlay/notifications.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/flags_helper.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/threadpool.h"
|
||||
|
||||
#include "d3d9_device.h"
|
||||
#include "d3d9_screenshot.h"
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
@@ -64,19 +55,6 @@
|
||||
return __ret; \
|
||||
} while (0)
|
||||
|
||||
#ifdef __GNUC__
|
||||
typedef decltype(D3DXSaveSurfaceToFileA) *D3DXSaveSurfaceToFileA_t;
|
||||
#else
|
||||
#define D3DXIFF_PNG ((DWORD) 3)
|
||||
|
||||
typedef HRESULT (WINAPI *D3DXSaveSurfaceToFileA_t)(
|
||||
LPCSTR pDestFile,
|
||||
DWORD DestFormat,
|
||||
LPDIRECT3DSURFACE9 pSrcSurface,
|
||||
CONST PALETTEENTRY *pSrcPalette,
|
||||
CONST RECT *pSrcRect);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 9 on 12
|
||||
*/
|
||||
@@ -100,8 +78,6 @@ typedef IDirect3D9* (WINAPI *Direct3DCreate9On12_t)(
|
||||
static void *D3D9_DIRECT3D_CREATE9_ADR = nullptr;
|
||||
static char D3D9_DIRECT3D_CREATE9_CONTENTS[16];
|
||||
|
||||
static bool ATTEMPTED_D3DX9_LOAD_LIBRARY = false;
|
||||
|
||||
// settings
|
||||
std::optional<UINT> D3D9_ADAPTER = std::nullopt;
|
||||
DWORD D3D9_BEHAVIOR_DISABLE = 0;
|
||||
@@ -1499,178 +1475,6 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
}
|
||||
}
|
||||
|
||||
static void save_capture(
|
||||
int screen,
|
||||
D3DFORMAT format,
|
||||
UINT width,
|
||||
UINT height,
|
||||
IDirect3DSurface9 *surface) {
|
||||
HRESULT hr;
|
||||
|
||||
// lock surface to be able to access the data
|
||||
D3DLOCKED_RECT finished_copy {};
|
||||
hr = surface->LockRect(&finished_copy, nullptr, 0);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to lock screenshot surface, hr={}", FMT_HRESULT(hr));
|
||||
graphics_capture_skip(screen);
|
||||
return;
|
||||
}
|
||||
|
||||
// copy pixel data
|
||||
size_t pitch = finished_copy.Pitch;
|
||||
auto data = reinterpret_cast<uint8_t *>(finished_copy.pBits);
|
||||
auto pixels = new uint8_t[width * height * 3];
|
||||
for (size_t row = 0; row < height; row++) {
|
||||
size_t offset_pixels = 0;
|
||||
size_t offset_row = row * width * 3;
|
||||
switch (format) {
|
||||
case D3DFMT_R8G8B8: {
|
||||
for (size_t offset = 0; offset < pitch; offset += 3) {
|
||||
auto cell = data + row * pitch + offset;
|
||||
auto pixel = &pixels[offset_row + offset_pixels];
|
||||
pixel[0] = cell[0];
|
||||
pixel[1] = cell[1];
|
||||
pixel[2] = cell[2];
|
||||
offset_pixels += 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case D3DFMT_X8R8G8B8:
|
||||
case D3DFMT_A8R8G8B8: {
|
||||
for (size_t offset = 0; offset < pitch; offset += 4) {
|
||||
auto cell = data + row * pitch + offset;
|
||||
auto pixel = &pixels[offset_row + offset_pixels];
|
||||
pixel[0] = cell[2];
|
||||
pixel[1] = cell[1];
|
||||
pixel[2] = cell[0];
|
||||
offset_pixels += 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case D3DFMT_X8B8G8R8:
|
||||
case D3DFMT_A8B8G8R8: {
|
||||
for (size_t offset = 0; offset < pitch; offset += 4) {
|
||||
auto cell = data + row * pitch + offset;
|
||||
auto pixel = &pixels[offset_row + offset_pixels];
|
||||
pixel[0] = cell[0];
|
||||
pixel[1] = cell[1];
|
||||
pixel[2] = cell[2];
|
||||
offset_pixels += 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
for (size_t offset = 0; offset < width; offset++) {
|
||||
auto pixel = &pixels[offset_row + offset_pixels];
|
||||
pixel[0] = 0;
|
||||
pixel[1] = 0;
|
||||
pixel[2] = 0;
|
||||
offset_pixels += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock surface
|
||||
hr = surface->UnlockRect();
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to unlock screenshot surface, hr={}", FMT_HRESULT(hr));
|
||||
graphics_capture_skip(screen);
|
||||
return;
|
||||
}
|
||||
|
||||
// enqueue
|
||||
graphics_capture_enqueue(screen, pixels, width, height);
|
||||
}
|
||||
|
||||
static void save_screenshot(const std::string &file_path, UINT height, IDirect3DSurface9 *surface) {
|
||||
HRESULT hr;
|
||||
|
||||
D3DLOCKED_RECT finished_copy {};
|
||||
hr = surface->LockRect(&finished_copy, nullptr, 0);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to lock screenshot surface, hr={}", FMT_HRESULT(hr));
|
||||
return;
|
||||
}
|
||||
|
||||
// set alpha channel to 255
|
||||
{
|
||||
auto pitch = finished_copy.Pitch;
|
||||
auto data = reinterpret_cast<uint8_t *>(finished_copy.pBits);
|
||||
|
||||
for (size_t i = 0; i < height; i++) {
|
||||
for (int j = 3; j < pitch; j += 4) {
|
||||
data[i * pitch + j] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr = surface->UnlockRect();
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "failed to unlock screenshot surface, hr={}", FMT_HRESULT(hr));
|
||||
return;
|
||||
}
|
||||
|
||||
// lazy load function
|
||||
static D3DXSaveSurfaceToFileA_t D3DXSaveSurfaceToFileA_ptr = nullptr;
|
||||
if (D3DXSaveSurfaceToFileA_ptr == nullptr) {
|
||||
D3DXSaveSurfaceToFileA_ptr = libutils::try_proc<D3DXSaveSurfaceToFileA_t>("D3DXSaveSurfaceToFileA");
|
||||
|
||||
// check if function was not found, likely because d3dx9 is not loaded
|
||||
if (!ATTEMPTED_D3DX9_LOAD_LIBRARY && D3DXSaveSurfaceToFileA_ptr == nullptr) {
|
||||
ATTEMPTED_D3DX9_LOAD_LIBRARY = true;
|
||||
|
||||
for (size_t i = 43; i >= 24; i--) {
|
||||
auto lib_name = fmt::format("d3dx9_{}.dll", i);
|
||||
auto d3dx9 = libutils::try_library(lib_name);
|
||||
|
||||
// Check if library was not found
|
||||
if (d3dx9 == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
D3DXSaveSurfaceToFileA_ptr = libutils::try_proc<D3DXSaveSurfaceToFileA_t>(
|
||||
d3dx9, "D3DXSaveSurfaceToFileA");
|
||||
|
||||
// Check if function was not found
|
||||
if (D3DXSaveSurfaceToFileA_ptr == nullptr) {
|
||||
FreeLibrary(d3dx9);
|
||||
d3dx9 = nullptr;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9", "found surface save function in '{}'", lib_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (D3DXSaveSurfaceToFileA_ptr != nullptr) {
|
||||
|
||||
// save to file
|
||||
log_info("graphics::d3d9", "saving screenshot to {}", file_path);
|
||||
auto hr = D3DXSaveSurfaceToFileA_ptr(file_path.c_str(), D3DXIFF_PNG, surface, nullptr, nullptr);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9", "Failed to save screenshot");
|
||||
overlay::notifications::add(
|
||||
overlay::notifications::Severity::Error,
|
||||
"Screenshot failed to save");
|
||||
return;
|
||||
}
|
||||
|
||||
// save to clipboard
|
||||
clipboard::copy_image(file_path);
|
||||
|
||||
overlay::notifications::add(
|
||||
overlay::notifications::Severity::Success,
|
||||
fmt::format("Screenshot saved: {}", fileutils::basename(file_path)));
|
||||
} else {
|
||||
log_warning("graphics::d3d9", "Direct3D save helper function not available");
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_d3d9_on_present(
|
||||
HWND hFocusWindow,
|
||||
IDirect3DDevice9 *device,
|
||||
@@ -1718,135 +1522,8 @@ void graphics_d3d9_on_present(
|
||||
wintouchemu::update();
|
||||
}
|
||||
|
||||
// check screenshot key
|
||||
static bool trigger_last = false;
|
||||
auto buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
if (buttons && (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons->at(games::OverlayButtons::Screenshot)))
|
||||
{
|
||||
if (!trigger_last) {
|
||||
graphics_screenshot_trigger();
|
||||
}
|
||||
trigger_last = true;
|
||||
} else {
|
||||
trigger_last = false;
|
||||
}
|
||||
|
||||
// process pending screenshot
|
||||
bool screenshot = false;
|
||||
bool capture = false;
|
||||
int capture_screen = 0;
|
||||
if ((screenshot = graphics_screenshot_consume())
|
||||
|| ((capture = graphics_capture_consume(&capture_screen)))) {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
// TODO: verify capture_screen is a valid swapchain
|
||||
|
||||
// get back buffer
|
||||
IDirect3DSurface9 *buffer = nullptr;
|
||||
if (SUB_SWAP_CHAIN != nullptr && capture_screen & 1) {
|
||||
hr = SUB_SWAP_CHAIN->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &buffer);
|
||||
} else {
|
||||
hr = device->GetBackBuffer(capture_screen, 0, D3DBACKBUFFER_TYPE_MONO, &buffer);
|
||||
}
|
||||
if (FAILED(hr) || buffer == nullptr) {
|
||||
log_warning("graphics::d3d9",
|
||||
"failed to get back buffer, hr={}",
|
||||
FMT_HRESULT(hr));
|
||||
if (capture) {
|
||||
graphics_capture_skip(capture_screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
D3DSURFACE_DESC desc {};
|
||||
hr = buffer->GetDesc(&desc);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9",
|
||||
"failed to acquire back buffer descriptor, hr={}",
|
||||
FMT_HRESULT(hr));
|
||||
buffer->Release();
|
||||
if (capture) {
|
||||
graphics_capture_skip(capture_screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: cache render targets
|
||||
IDirect3DSurface9 *temp_surface = nullptr;
|
||||
hr = device->CreateRenderTarget(
|
||||
desc.Width, desc.Height, desc.Format, desc.MultiSampleType,
|
||||
desc.MultiSampleQuality, TRUE, &temp_surface, nullptr);
|
||||
if (FAILED(hr) || temp_surface == nullptr) {
|
||||
log_warning("graphics::d3d9",
|
||||
"failed to acquire temporary surface, hr={}",
|
||||
FMT_HRESULT(hr));
|
||||
buffer->Release();
|
||||
if (capture) {
|
||||
graphics_capture_skip(capture_screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
hr = device->StretchRect(buffer, nullptr, temp_surface, nullptr, D3DTEXF_NONE);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("graphics::d3d9",
|
||||
"failed to copy back buffer contents, hr={}",
|
||||
FMT_HRESULT(hr));
|
||||
temp_surface->Release();
|
||||
buffer->Release();
|
||||
if (capture) {
|
||||
graphics_capture_skip(capture_screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// release original back buffer reference
|
||||
buffer->Release();
|
||||
|
||||
// function for storing the surface
|
||||
auto surface_process = [=]() {
|
||||
|
||||
// capture
|
||||
if (capture) {
|
||||
save_capture(capture_screen, desc.Format, desc.Width, desc.Height, temp_surface);
|
||||
}
|
||||
|
||||
// screenshot
|
||||
if (screenshot) {
|
||||
|
||||
// check where we can save it
|
||||
auto file_path = graphics_screenshot_genpath();
|
||||
if (!file_path.empty()) {
|
||||
|
||||
// write to file
|
||||
save_screenshot(file_path, desc.Height, temp_surface);
|
||||
}
|
||||
}
|
||||
|
||||
// release surface
|
||||
temp_surface->Release();
|
||||
};
|
||||
|
||||
// list of games that crash when running the screenshot processor on another thread
|
||||
static const robin_hood::unordered_set<std::string> THREAD_BAN {
|
||||
"JMA",
|
||||
#ifndef SPICE64
|
||||
"KFC",
|
||||
#endif
|
||||
"KMA",
|
||||
"KLP",
|
||||
"LMA",
|
||||
};
|
||||
|
||||
// run the save operation on another thread for supported games
|
||||
if (THREAD_BAN.contains(avs::game::MODEL)) {
|
||||
surface_process();
|
||||
} else {
|
||||
static auto pool = ThreadPool(2);
|
||||
pool.add(surface_process);
|
||||
}
|
||||
}
|
||||
graphics_d3d9_poll_screenshot_hotkey();
|
||||
graphics_d3d9_process_screenshot_and_capture(device, SUB_SWAP_CHAIN);
|
||||
}
|
||||
|
||||
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
||||
|
||||
Reference in New Issue
Block a user