spicetools/hooks/graphics/backends/d3d9/d3d9_backend.cpp

1350 lines
46 KiB
C++

#include "d3d9_backend.h"
#include <cassert>
#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 "games/iidx/iidx.h"
#include "games/sdvx/sdvx.h"
#include "games/io.h"
#include "hooks/graphics/graphics.h"
#include "launcher/launcher.h"
#include "launcher/options.h"
#include "launcher/shutdown.h"
#include "misc/clipboard.h"
#include "misc/eamuse.h"
#include "misc/wintouchemu.h"
#include "overlay/overlay.h"
#include "util/detour.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"
#ifdef min
#undef min
#endif
#define CHECK_RESULT(x) \
do { \
HRESULT __ret = (x); \
if (GRAPHICS_LOG_HRESULT && FAILED(__ret)) [[unlikely]] { \
log_warning("graphics::d3d9", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(__ret)); \
} \
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
*/
#define MAX_D3D9ON12_QUEUES 2
typedef struct _D3D9ON12_ARGS {
BOOL Enable9On12;
IUnknown *pD3D12Device;
IUnknown *ppD3D12Queues[MAX_D3D9ON12_QUEUES];
UINT NumQueues;
UINT NodeMask;
} D3D9ON12_ARGS;
typedef HRESULT (WINAPI *Direct3DCreate9On12Ex_t)(
UINT SDKVersion, D3D9ON12_ARGS *pOverrideList,
UINT NumOverrideEntries, IDirect3D9Ex** ppOutputInterface);
typedef IDirect3D9* (WINAPI *Direct3DCreate9On12_t)(
UINT SDKVersion, D3D9ON12_ARGS *pOverrideList, UINT NumOverrideEntries);
// state
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;
static decltype(Direct3DCreate9) *Direct3DCreate9_orig = nullptr;
static decltype(Direct3DCreate9Ex) *Direct3DCreate9Ex_orig = nullptr;
static Direct3DCreate9On12_t Direct3DCreate9On12_orig = nullptr;
static Direct3DCreate9On12Ex_t Direct3DCreate9On12Ex_orig = nullptr;
static bool ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = false;
static IDirect3DSwapChain9 *SUB_SWAP_CHAIN = nullptr;
static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRESENT_PARAMETERS *present_params);
static std::string behavior2s(DWORD behavior_flags) {
FLAGS_START(behavior_flags);
FLAG(behavior_flags, D3DCREATE_FPU_PRESERVE);
FLAG(behavior_flags, D3DCREATE_MULTITHREADED);
FLAG(behavior_flags, D3DCREATE_PUREDEVICE);
FLAG(behavior_flags, D3DCREATE_SOFTWARE_VERTEXPROCESSING);
FLAG(behavior_flags, D3DCREATE_HARDWARE_VERTEXPROCESSING);
FLAG(behavior_flags, D3DCREATE_MIXED_VERTEXPROCESSING);
FLAG(behavior_flags, D3DCREATE_DISABLE_DRIVER_MANAGEMENT);
FLAG(behavior_flags, D3DCREATE_ADAPTERGROUP_DEVICE);
FLAG(behavior_flags, D3DCREATE_DISABLE_DRIVER_MANAGEMENT_EX);
FLAG(behavior_flags, D3DCREATE_NOWINDOWCHANGES);
FLAG(behavior_flags, D3DCREATE_DISABLE_PSGP_THREADING);
FLAG(behavior_flags, D3DCREATE_ENABLE_PRESENTSTATS);
FLAG(behavior_flags, D3DCREATE_DISABLE_PRINTSCREEN);
FLAG(behavior_flags, D3DCREATE_SCREENSAVER);
FLAGS_END(behavior_flags);
}
static std::string format2s(D3DFORMAT format) {
switch (format) {
ENUM_VARIANT(D3DFMT_UNKNOWN);
ENUM_VARIANT(D3DFMT_R8G8B8);
ENUM_VARIANT(D3DFMT_A8R8G8B8);
ENUM_VARIANT(D3DFMT_X8R8G8B8);
ENUM_VARIANT(D3DFMT_R5G6B5);
ENUM_VARIANT(D3DFMT_X1R5G5B5);
ENUM_VARIANT(D3DFMT_A1R5G5B5);
ENUM_VARIANT(D3DFMT_A4R4G4B4);
ENUM_VARIANT(D3DFMT_R3G3B2);
ENUM_VARIANT(D3DFMT_A8);
ENUM_VARIANT(D3DFMT_A8R3G3B2);
ENUM_VARIANT(D3DFMT_X4R4G4B4);
ENUM_VARIANT(D3DFMT_A2B10G10R10);
ENUM_VARIANT(D3DFMT_A8B8G8R8);
ENUM_VARIANT(D3DFMT_X8B8G8R8);
ENUM_VARIANT(D3DFMT_G16R16);
ENUM_VARIANT(D3DFMT_A2R10G10B10);
ENUM_VARIANT(D3DFMT_A16B16G16R16);
ENUM_VARIANT(D3DFMT_A8P8);
ENUM_VARIANT(D3DFMT_P8);
ENUM_VARIANT(D3DFMT_L8);
ENUM_VARIANT(D3DFMT_A8L8);
ENUM_VARIANT(D3DFMT_A4L4);
ENUM_VARIANT(D3DFMT_V8U8);
ENUM_VARIANT(D3DFMT_L6V5U5);
ENUM_VARIANT(D3DFMT_X8L8V8U8);
ENUM_VARIANT(D3DFMT_Q8W8V8U8);
ENUM_VARIANT(D3DFMT_V16U16);
ENUM_VARIANT(D3DFMT_A2W10V10U10);
ENUM_VARIANT(D3DFMT_UYVY);
ENUM_VARIANT(D3DFMT_YUY2);
ENUM_VARIANT(D3DFMT_DXT1);
ENUM_VARIANT(D3DFMT_DXT2);
ENUM_VARIANT(D3DFMT_DXT3);
ENUM_VARIANT(D3DFMT_DXT4);
ENUM_VARIANT(D3DFMT_DXT5);
ENUM_VARIANT(D3DFMT_MULTI2_ARGB8);
ENUM_VARIANT(D3DFMT_G8R8_G8B8);
ENUM_VARIANT(D3DFMT_R8G8_B8G8);
ENUM_VARIANT(D3DFMT_D16_LOCKABLE);
ENUM_VARIANT(D3DFMT_D32);
ENUM_VARIANT(D3DFMT_D15S1);
ENUM_VARIANT(D3DFMT_D24S8);
ENUM_VARIANT(D3DFMT_D24X8);
ENUM_VARIANT(D3DFMT_D24X4S4);
ENUM_VARIANT(D3DFMT_D16);
ENUM_VARIANT(D3DFMT_L16);
ENUM_VARIANT(D3DFMT_D32F_LOCKABLE);
ENUM_VARIANT(D3DFMT_D24FS8);
ENUM_VARIANT(D3DFMT_D32_LOCKABLE);
ENUM_VARIANT(D3DFMT_S8_LOCKABLE);
ENUM_VARIANT(D3DFMT_VERTEXDATA);
ENUM_VARIANT(D3DFMT_INDEX16);
ENUM_VARIANT(D3DFMT_INDEX32);
ENUM_VARIANT(D3DFMT_Q16W16V16U16);
ENUM_VARIANT(D3DFMT_R16F);
ENUM_VARIANT(D3DFMT_G16R16F);
ENUM_VARIANT(D3DFMT_A16B16G16R16F);
ENUM_VARIANT(D3DFMT_R32F);
ENUM_VARIANT(D3DFMT_G32R32F);
ENUM_VARIANT(D3DFMT_A32B32G32R32F);
ENUM_VARIANT(D3DFMT_CxV8U8);
ENUM_VARIANT(D3DFMT_A1);
ENUM_VARIANT(D3DFMT_A2B10G10R10_XR_BIAS);
ENUM_VARIANT(D3DFMT_BINARYBUFFER);
default:
return fmt::to_string(format);
}
}
static std::string presentation_interval2s(UINT presentation_interval) {
FLAGS_START(presentation_interval);
FLAG(presentation_interval, D3DPRESENT_INTERVAL_DEFAULT);
FLAG(presentation_interval, D3DPRESENT_INTERVAL_ONE);
FLAG(presentation_interval, D3DPRESENT_INTERVAL_TWO);
FLAG(presentation_interval, D3DPRESENT_INTERVAL_THREE);
FLAG(presentation_interval, D3DPRESENT_INTERVAL_FOUR);
FLAG(presentation_interval, D3DPRESENT_INTERVAL_IMMEDIATE);
FLAGS_END(presentation_interval);
}
static bool is_dx9_on_12_enabled() {
switch (GRAPHICS_9_ON_12_STATE) {
case DX9ON12_FORCE_OFF:
log_info("graphics::d3d9", "DirectX 9on12: forced OFF by user (-dx9on12)");
return false;
case DX9ON12_FORCE_ON:
log_info("graphics::d3d9", "DirectX 9on12: forced ON by user (-9on12 or -dx9on12)");
return true;
case DX9ON12_AUTO:
default:
if (GRAPHICS_9_ON_12_REQUESTED_BY_GAME) {
log_info(
"graphics::d3d9",
"DirectX 9on12: enabled automatically for current game (tip: use -dx9on12 to force on or off)");
return true;
} else {
log_info(
"graphics::d3d9",
"DirectX 9on12: disabled by default, using DX9 (tip: use -dx9on12 to force on or off)");
return false;
}
}
}
static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
log_misc("graphics::d3d9", "Direct3DCreate9 hook hit");
// remove hook
if (D3D9_DIRECT3D_CREATE9_ADR) {
detour::inline_restore(D3D9_DIRECT3D_CREATE9_ADR, D3D9_DIRECT3D_CREATE9_CONTENTS);
if (Direct3DCreate9_orig == nullptr) {
Direct3DCreate9_orig = reinterpret_cast<decltype(Direct3DCreate9) *>(D3D9_DIRECT3D_CREATE9_ADR);
}
}
// create interface
IDirect3D9 *value;
if (is_dx9_on_12_enabled()) {
if (!Direct3DCreate9On12_orig) {
log_fatal("graphics::d3d9", "unable to find Direct3DCreate9On12");
} else {
D3D9ON12_ARGS args = {};
args.Enable9On12 = TRUE;
value = Direct3DCreate9On12_orig(SDKVersion, &args, 1);
}
} else {
value = Direct3DCreate9_orig(SDKVersion);
}
if (value == nullptr) {
log_warning("graphics::d3d9", "failed to create Direct3D interface for {}", SDKVersion);
return value;
}
value = new WrappedIDirect3D9(value);
// add hook
if (D3D9_DIRECT3D_CREATE9_ADR) {
detour::inline_noprotect((void *) Direct3DCreate9_hook, D3D9_DIRECT3D_CREATE9_ADR);
}
// return modified interface
return value;
}
static HRESULT WINAPI Direct3DCreate9Ex_hook(UINT SDKVersion, IDirect3D9Ex **d3d9ex) {
log_misc("graphics::d3d9", "Direct3DCreate9Ex hook hit");
// call original
HRESULT result;
if (is_dx9_on_12_enabled()) {
if (!Direct3DCreate9On12Ex_orig) {
log_fatal("graphics::d3d9", "unable to find Direct3DCreate9On12Ex");
} else {
D3D9ON12_ARGS args = {};
args.Enable9On12 = TRUE;
result = Direct3DCreate9On12Ex_orig(SDKVersion, &args, 1, d3d9ex);
}
} else {
result = Direct3DCreate9Ex_orig(SDKVersion, d3d9ex);
}
if (FAILED(result) || *d3d9ex == nullptr) {
log_warning("graphics::d3d9", "failed to create Direct3D interface for {}, hr={}",
SDKVersion,
FMT_HRESULT(result));
return result;
}
*d3d9ex = new WrappedIDirect3D9(*d3d9ex);
// return original result
return result;
}
void graphics_d3d9_init() {
log_info("graphics::d3d9", "initializing");
// DX9 inline hooks
HMODULE d3d9 = libutils::try_module("d3d9.dll");
if (!d3d9) {
log_info("graphics::d3d9", "skipping inline hooks");
} else {
// 9 on 12
Direct3DCreate9On12_orig = (Direct3DCreate9On12_t) libutils::try_proc(d3d9, "Direct3DCreate9On12");
Direct3DCreate9On12Ex_orig = (Direct3DCreate9On12Ex_t) libutils::try_proc(d3d9, "Direct3DCreate9On12Ex");
// inline hooks
D3D9_DIRECT3D_CREATE9_ADR = (void *) libutils::get_proc(d3d9, "Direct3DCreate9");
detour::inline_preserve(
reinterpret_cast<void *>(Direct3DCreate9_hook),
D3D9_DIRECT3D_CREATE9_ADR,
D3D9_DIRECT3D_CREATE9_CONTENTS);
}
// DX9 IAT hooks
Direct3DCreate9_orig = detour::iat_try("Direct3DCreate9", Direct3DCreate9_hook);
Direct3DCreate9Ex_orig = detour::iat_try("Direct3DCreate9Ex", Direct3DCreate9Ex_hook);
}
/*
* IUnknown
*/
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::QueryInterface(
REFIID riid,
void **ppvObj)
{
if (ppvObj == nullptr) {
return E_POINTER;
}
if (riid == IID_WrappedIDirect3D9 ||
riid == IID_IDirect3D9 ||
riid == IID_IDirect3D9Ex)
{
// update to IDirect3DDevice9Ex interface
if (!is_d3d9ex && riid == IID_IDirect3D9Ex) {
IDirect3D9Ex *ex = nullptr;
HRESULT ret = pReal->QueryInterface(IID_PPV_ARGS(&ex));
if (FAILED(ret) || ex == nullptr) {
if (ret != E_NOINTERFACE) {
log_warning("graphics::d3d9",
"failed to upgrade to IDirect3DDevice9Ex, hr={}",
FMT_HRESULT(ret));
}
return E_NOINTERFACE;
}
pReal->Release();
pReal = ex;
is_d3d9ex = true;
}
this->AddRef();
*ppvObj = this;
return S_OK;
}
return pReal->QueryInterface(riid, ppvObj);
}
ULONG STDMETHODCALLTYPE WrappedIDirect3D9::AddRef() {
return pReal->AddRef();
}
ULONG STDMETHODCALLTYPE WrappedIDirect3D9::Release() {
ULONG refs = this->pReal != nullptr ? this->pReal->Release() : 0;
if (refs == 0) {
delete this;
}
return refs;
}
/*
* IDirect3D9
*/
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pInitializeFunction) {
CHECK_RESULT(pReal->RegisterSoftwareDevice(pInitializeFunction));
}
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
return pReal->GetAdapterCount();
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
UINT Adapter,
DWORD Flags,
D3DADAPTER_IDENTIFIER9 *pIdentifier)
{
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
}
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
return pReal->GetAdapterModeCount(Adapter, Format);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
UINT Adapter,
D3DFORMAT Format,
UINT Mode,
D3DDISPLAYMODE *pMode)
{
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
if (SUCCEEDED(ret) && pMode) {
/*
log_misc("graphics::d3d9", "IDirect3D9::EnumAdapterMode({}, {}, {}) => {}x{} @ {} Hz ({})",
Adapter,
format2s(Format),
Mode,
pMode->Width,
pMode->Height,
pMode->RefreshRate,
format2s(pMode->Format));
*/
bool modified = false;
auto width = pMode->Width;
auto height = pMode->Height;
auto refresh = pMode->RefreshRate;
if (avs::game::is_model("LDJ")) {
if (Mode == 0 && games::iidx::TDJ_MODE) {
if (games::iidx::is_tdj_fhd()) {
log_misc("graphics::d3d9", "overriding mode 0 to 1920x1080 @ 120 Hz (for TDJ FHD)");
pMode->Width = 1920;
pMode->Height = 1080;
pMode->RefreshRate = 120;
modified = true;
} else {
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz (for TDJ HD)");
pMode->Width = 1280;
pMode->Height = 720;
pMode->RefreshRate = 120;
modified = true;
}
}
// For whatever reason, TDJ FHD mode prefers to pick lower (~60Hz) resolutions instead
// of 1080p@120Hz. Remove them here and try to force 120+ Hz.
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
// zero out display mode for bad entries
// - skip non-native resolutions
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
if (!modified &&
(width > height) &&
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
log_misc(
"graphics::d3d9", "swapping width and height for mode {}, {}x{} @ {}Hz (-autoorientation)",
Mode, width, height, refresh);
pMode->Height = width;
pMode->Width = height;
modified = true;
}
}
CHECK_RESULT(ret);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE *pMode) {
CHECK_RESULT(pReal->GetAdapterDisplayMode(Adapter, pMode));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceType(
UINT iAdapter,
D3DDEVTYPE DevType,
D3DFORMAT DisplayFormat,
D3DFORMAT BackBufferFormat,
BOOL bWindowed)
{
CHECK_RESULT(pReal->CheckDeviceType(iAdapter, DevType, DisplayFormat, BackBufferFormat, bWindowed));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT AdapterFormat,
DWORD Usage,
D3DRESOURCETYPE RType,
D3DFORMAT CheckFormat)
{
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceMultiSampleType(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT SurfaceFormat,
BOOL Windowed,
D3DMULTISAMPLE_TYPE MultiSampleType,
DWORD *pQualityLevels)
{
CHECK_RESULT(pReal->CheckDeviceMultiSampleType(
Adapter,
DeviceType,
SurfaceFormat,
Windowed,
MultiSampleType,
pQualityLevels));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDepthStencilMatch(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT AdapterFormat,
D3DFORMAT RenderTargetFormat,
D3DFORMAT DepthStencilFormat)
{
CHECK_RESULT(pReal->CheckDepthStencilMatch(
Adapter,
DeviceType,
AdapterFormat,
RenderTargetFormat,
DepthStencilFormat));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormatConversion(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT SourceFormat,
D3DFORMAT TargetFormat)
{
CHECK_RESULT(pReal->CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9 *pCaps) {
log_misc("graphics::d3d9", "IDirect3D9::GetDeviceCaps hook hit");
if (!pCaps) {
log_warning("graphics::d3d9", "NULL pointer passed in for required parameter");
return D3DERR_INVALIDCALL;
}
// SDVX uses `NumberOfAdaptersInGroup` to allocate a vector and the Microsoft documentation states:
// "The value will be 0 for a subordinate adapter of a multihead card. Each card can have at most one
// master, but may have many subordinates." Therefore, this must point to the master adapter.
if (Adapter == 0 && D3D9_ADAPTER.has_value()) {
Adapter = D3D9_ADAPTER.value();
// Get the master adapter ordinal
HRESULT result = this->pReal->GetDeviceCaps(Adapter, DeviceType, pCaps);
if (FAILED(result)) {
log_warning("graphics::d3d9", "GetDeviceCaps failed, hr={}", FMT_HRESULT(result));
return result;
}
// Now get the device caps for the master adapter
Adapter = pCaps->MasterAdapterOrdinal;
}
HRESULT ret = this->pReal->GetDeviceCaps(Adapter, DeviceType, pCaps);
if (FAILED(ret)) {
return ret;
}
if (avs::game::is_model("LDJ")) {
if (!GRAPHICS_WINDOWED) {
// use 2 so that the subscreen overlay can be drawn in full screen with single monitor
pCaps->NumberOfAdaptersInGroup = 2;
}
// in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) {
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) {
// user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1;
} else {
// in both full screen and windowed mode, use 2 so that the game draws the subscreen
// (if this is 1, the game won't even draw the second window, causing subscreen overlay to not work)
pCaps->NumberOfAdaptersInGroup = 2;
}
} else if (avs::game::is_model({"NBT", "PAN"})) {
// beatstream, nostalgia
if (GRAPHICS_WINDOWED) {
pCaps->NumberOfAdaptersInGroup = std::min(pCaps->NumberOfAdaptersInGroup, 1u);
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
pCaps->NumberOfAdaptersInGroup = 1;
}
}
return ret;
}
HMONITOR STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterMonitor(UINT Adapter) {
return pReal->GetAdapterMonitor(Adapter);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 **ppReturnedDeviceInterface)
{
log_misc("graphics::d3d9", "IDirect3D9::CreateDevice hook hit ({}, {}, {}, {}, {}, {})",
Adapter,
DeviceType,
fmt::ptr(hFocusWindow),
behavior2s(BehaviorFlags),
fmt::ptr(pPresentationParameters),
fmt::ptr(ppReturnedDeviceInterface));
// check parameters
if (!pPresentationParameters || !ppReturnedDeviceInterface) {
log_warning("graphics::d3d9", "NULL pointer passed in for required parameter");
return D3DERR_INVALIDCALL;
}
DWORD orig_behavior_flags = BehaviorFlags;
size_t num_adapters = 1;
// behavior flags
if (D3D9_BEHAVIOR_DISABLE) {
BehaviorFlags &= ~D3D9_BEHAVIOR_DISABLE;
}
// when rendering via software, disable pure device
if (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) {
BehaviorFlags &= ~D3DCREATE_PUREDEVICE;
}
// override adapter used
if (D3D9_ADAPTER.has_value()) {
Adapter = D3D9_ADAPTER.value();
}
// get number of adapters for info dump
if (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE) {
D3DCAPS9 device_caps {};
if (SUCCEEDED(this->pReal->GetDeviceCaps(Adapter, DeviceType, &device_caps))) {
num_adapters = device_caps.NumberOfAdaptersInGroup;
}
}
// dump presentation parameters
for (size_t i = 0; i < num_adapters; i++) {
const auto *params = &pPresentationParameters[i];
log_info("graphics::d3d9",
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
"Format: {}, BackBufferCount: {}, MultiSampleType: {}, MultiSampleQuality: {}, "
"SwapEffect: {}, Windowed: {}, EnableAutoDepthStencil: {}, AutoDepthStencilFormat: {}, "
"Flags: {}, FullScreen_RefreshRateInHz: {}, PresentationInterval: {}",
i,
params->BackBufferWidth,
params->BackBufferHeight,
format2s(params->BackBufferFormat),
params->BackBufferCount,
params->MultiSampleType,
params->MultiSampleQuality,
params->SwapEffect,
params->Windowed,
params->EnableAutoDepthStencil,
format2s(params->AutoDepthStencilFormat),
params->Flags,
params->FullScreen_RefreshRateInHz,
presentation_interval2s(params->PresentationInterval));
}
// set windowed
if (GRAPHICS_WINDOWED) {
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
} else if (GRAPHICS_FORCE_REFRESH > 0) {
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz (-graphics-force-refresh option)",
pPresentationParameters->FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH);
pPresentationParameters->FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH;
} else if (pPresentationParameters->FullScreen_RefreshRateInHz == 0) {
log_warning(
"graphics::d3d9",
"This game sets FullScreen_RefreshRateInHz to 0, which means it will boot with whatever "
"refresh rate you have set in the desktop. If the game is launching at the wrong Hz, "
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
}
// force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
}
// call original
HRESULT ret = this->pReal->CreateDevice(
Adapter,
DeviceType,
hFocusWindow,
BehaviorFlags,
pPresentationParameters,
ppReturnedDeviceInterface);
// check for error
if (ret != D3D_OK) {
// log error
log_info("graphics::d3d9", "IDirect3D9::CreateDevice failed, hr={}", FMT_HRESULT(ret));
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
graphics_hook_window(hFocusWindow, pPresentationParameters);
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
}
// return result
return ret;
}
/*
* IDirect3D9Ex
*/
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCountEx(UINT Adapter, const D3DDISPLAYMODEFILTER *pFilter) {
assert(is_d3d9ex);
return static_cast<IDirect3D9Ex *>(pReal)->GetAdapterModeCountEx(Adapter, pFilter);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModesEx(
UINT Adapter,
const D3DDISPLAYMODEFILTER *pFilter,
UINT Mode,
D3DDISPLAYMODEEX *pMode)
{
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->EnumAdapterModesEx(Adapter, pFilter, Mode, pMode));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterDisplayModeEx(
UINT Adapter,
D3DDISPLAYMODEEX *pMode,
D3DDISPLAYROTATION *pRotation)
{
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->GetAdapterDisplayModeEx(Adapter, pMode, pRotation));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
D3DDISPLAYMODEEX *pFullscreenDisplayMode,
IDirect3DDevice9Ex **ppReturnedDeviceInterface)
{
assert(is_d3d9ex);
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit ({}, {}, {}, {}, {}, {})",
Adapter,
DeviceType,
fmt::ptr(hFocusWindow),
behavior2s(BehaviorFlags),
fmt::ptr(pPresentationParameters),
fmt::ptr(ppReturnedDeviceInterface));
// check parameters
if (!pPresentationParameters || !ppReturnedDeviceInterface) {
log_warning("graphics::d3d9", "NULL pointer passed in required parameter");
return D3DERR_INVALIDCALL;
}
DWORD orig_behavior_flags = BehaviorFlags;
size_t num_adapters = 1;
// behavior flags
if (D3D9_BEHAVIOR_DISABLE) {
BehaviorFlags &= ~D3D9_BEHAVIOR_DISABLE;
}
// when rendering via software, disable pure device
if (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) {
BehaviorFlags &= ~D3DCREATE_PUREDEVICE;
}
// override adapter used
if (D3D9_ADAPTER.has_value()) {
Adapter = D3D9_ADAPTER.value();
}
// get number of adapters for info dump
if (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE) {
D3DCAPS9 device_caps {};
if (SUCCEEDED(this->pReal->GetDeviceCaps(Adapter, DeviceType, &device_caps))) {
num_adapters = device_caps.NumberOfAdaptersInGroup;
}
}
for (size_t i = 0; i < num_adapters; i++) {
const auto *params = &pPresentationParameters[i];
log_info("graphics::d3d9",
"D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
"Format: {}, BackBufferCount: {}, MultiSampleType: {}, MultiSampleQuality: {}, "
"SwapEffect: {}, Windowed: {}, EnableAutoDepthStencil: {}, AutoDepthStencilFormat: {}, "
"Flags: {}, FullScreen_RefreshRateInHz: {}, PresentationInterval: {}",
i,
params->BackBufferWidth,
params->BackBufferHeight,
format2s(params->BackBufferFormat),
params->BackBufferCount,
params->MultiSampleType,
params->MultiSampleQuality,
params->SwapEffect,
params->Windowed,
params->EnableAutoDepthStencil,
format2s(params->AutoDepthStencilFormat),
params->Flags,
params->FullScreen_RefreshRateInHz,
presentation_interval2s(params->PresentationInterval));
}
if (pFullscreenDisplayMode) {
for (size_t i = 0; i < num_adapters; i++) {
const auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
log_info("graphics::d3d9",
"D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, "
"Format: {}, ScanLineOrdering: {}",
i,
fullscreen_display_mode->Width,
fullscreen_display_mode->Height,
fullscreen_display_mode->RefreshRate,
format2s(fullscreen_display_mode->Format),
fullscreen_display_mode->ScanLineOrdering);
}
}
// set windowed
//
// note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode.
if (GRAPHICS_WINDOWED) {
if (avs::game::is_model({"LDJ", "KFC"}) && (BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
log_misc("graphics::d3d9", "disabling adapter group device in windowed mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
}
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
pFullscreenDisplayMode = nullptr;
} else if (GRAPHICS_FORCE_REFRESH > 0) {
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz",
pPresentationParameters->FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH);
pPresentationParameters->FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH;
if (pFullscreenDisplayMode) {
pFullscreenDisplayMode->RefreshRate = GRAPHICS_FORCE_REFRESH;
}
}
// force single adapter
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
}
// call original
HRESULT result = static_cast<IDirect3D9Ex *>(this->pReal)->CreateDeviceEx(
Adapter,
DeviceType,
hFocusWindow,
BehaviorFlags,
pPresentationParameters,
pFullscreenDisplayMode,
ppReturnedDeviceInterface);
// check for error
if (result != D3D_OK) {
// log error
log_warning("graphics::d3d9", "CreateDeviceEx failed, hr={}", FMT_HRESULT(result));
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
graphics_hook_window(hFocusWindow, pPresentationParameters);
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
// initialize sub screen if IIDX/SDVX requested a multi-head context
if (avs::game::is_model({"LDJ", "KFC"}) && (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
}
}
return result;
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterLUID(UINT Adapter, LUID *pLUID) {
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->GetAdapterLUID(Adapter, pLUID));
}
// Create swap chain for TDJ sub screen if needed
//
// The sub screen swap chain should be created if:
// - Running windowed with `NumberOfAdaptersInGroup >= 2` (game expects implicit swap chain to exist)
// - Running fullscreen with `NumberOfAdaptersInGroup < 2` (overridden `GetDeviceCaps` structure)
static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRESENT_PARAMETERS *present_params) {
D3DCAPS9 caps {};
HRESULT hr = device->GetDeviceCaps(&caps);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get device caps, hr={}", FMT_HRESULT(hr));
return;
}
// check if sub window swap chain is needed
/*
if (GRAPHICS_WINDOWED && caps.NumberOfAdaptersInGroup < 2) {
log_info("graphics::d3d9", "skipping swap chain creation, running windowed with less than two monitors");
return;
}
if (!GRAPHICS_WINDOWED && caps.NumberOfAdaptersInGroup >= 2) {
log_info("graphics::d3d9", "skipping swap chain creation, running fullscreen with two or more monitors");
return;
}
*/
if (GRAPHICS_WINDOWED) {
log_info("graphics::d3d9", "creating additional swap chain");
present_params->Windowed = true;
present_params->FullScreen_RefreshRateInHz = 0;
// calling `WrappedIDirect3DDevice9::CreateAdditionalSwapChain` triggers special handling for
// LDJ calling `GetSwapChain`
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr));
}
} else {
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
} else {
return;
}
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr));
}
}
}
IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
if (SUB_SWAP_CHAIN == nullptr) {
return nullptr;
}
IDirect3DSurface9 *surface = nullptr;
HRESULT hr = SUB_SWAP_CHAIN->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &surface);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get back buffer of LDJ sub screen, hr={}", FMT_HRESULT(hr));
return nullptr;
}
return surface;
}
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true;
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning(
"graphics::d3d9",
"failed to acquire sub screeen swap chain! hr={}",
FMT_HRESULT(hr));
return;
}
}
if (SUB_SWAP_CHAIN != nullptr) {
wintouchemu::update();
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) {
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
}
}
}
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");
return;
}
// save to clipboard
clipboard::copy_image(file_path);
} else {
log_warning("graphics::d3d9", "Direct3D save helper function not available");
}
}
void graphics_d3d9_on_present(
HWND hFocusWindow,
IDirect3DDevice9 *device,
IDirect3DDevice9 *wrapped_device) {
// Do overlay init as many d3d9 hooks create a dummy instance to get vtable offsets and never
// call `Present`. This avoids race conditions on `IDirect3D9::CreateDevice` like with
// `dx9osd.dll` for pfreepanic.
if (!overlay::OVERLAY) {
IDirect3D9 *d3d = nullptr;
if (SUCCEEDED(device->GetDirect3D(&d3d)) && d3d != nullptr) {
overlay::create_d3d9(hFocusWindow, d3d, device);
d3d->Release();
}
} else if (overlay::OVERLAY->uses_device(device) && SUCCEEDED(device->BeginScene())) {
// render overlay
overlay::OVERLAY->update();
overlay::OVERLAY->new_frame();
overlay::OVERLAY->render();
device->EndScene();
}
// for IIDX TDJ / SDVX UFC, handle subscreen
const bool is_vm =
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
if (is_vm || is_tdj) {
graphics_d3d9_ldj_on_present(wrapped_device);
}
// 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));
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();
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();
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();
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);
}
}
}