Files
spice2x-r3d/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp
T
bicarusandGitHub 8b2f38307b graphics: rewrite screenshot and api capture image processing (#870)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Significantly speeds up API screen capture and D3D9 screenshots saving.
Two reasons for doing this:

1. We now have a 4K game (GITADORA) and existing capture code was taking
multiple seconds.
2. Renewed user interest on streaming as we have a couple more companion
apps in active development.

**API screen capture (streaming), 1280x720:** 14.3ms -> 6.3ms per frame.
Back buffer copies go to pooled `D3DPOOL_SYSTEMMEM` surfaces via
`GetRenderTargetData` instead of allocating a lockable render target
every frame, and TooJpeg is replaced with libjpeg-turbo (encode 9.8ms ->
3.0ms). MSAA remains unsupported

**Screenshots for GITADORA arena model, across 4 screens with one of
them 4K**: 4068ms -> 124ms. `D3DXSaveSurfaceToFileA` is replaced with
fpng (encode 4043ms -> 76ms) and the screens encode in parallel.
Dropping D3DX also removes the `d3dx9_43.dll` ... `d3dx9_24.dll` probing
loop, so screenshots no longer fail outright on machines with no D3DX9
runtime installed.

Screenshot surfaces are read on the present thread, so no D3D call
reaches another thread for screenshots. This fixes a hang in DDR X2
introduced earlier in the branch: its device has no internal locking,
and reading the surface on a pool thread while the present thread sat
inside `GetRenderTargetData` left the game's own render thread
deadlocked.


## Testing

- **GITADORA** (arena model, D3D9Ex, 4K main plus three subscreens,
windowed) with
`-screenshotsub`: three sets of four screenshots, images verified
correct. Completion
order differs between sets, so the screens really are encoding in
parallel.
- **LovePlus** (KLP, plain D3D9, 768x1360): covers the inline path used
by games whose
  image processing must not leave the present thread. 
- **API screen capture** through a companion app: live video correct
throughout.
- **Print Screen** bound as the screenshot key: the clipboard copy
succeeded on every shot.
- Quitting the game after capturing leaves no `IDirect3DDevice9`
reference count warning,
  so the pooled readback surfaces are released along with the device.
2026-08-18 00:22:45 -07:00

2383 lines
75 KiB
C++

#include "d3d9_device.h"
#include <algorithm>
#include <cassert>
#include <climits>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "util/flags_helper.h"
#include "util/utils.h"
#include "cfg/screen_resize.h"
#include "d3d9_backend.h"
#include "d3d9_live2d.h"
#include "d3d9_readback.h"
#include "d3d9_texture.h"
#ifndef SPICE64
#include "shaders/vertex_shader.h"
#endif
// maps arena's cached additional swap chains (SMALL, LEFT, RIGHT) to screen numbers.
// MAIN is the implicit swap chain, is not in those slots, and is always screen 0.
// screen 1 is the subscreen for every other game, so SMALL takes that number here too.
static constexpr int GFDM_ARENA_SLOT_SCREENS[] { 1, 2, 3 };
#define CHECK_RESULT_FMT(x, fmt, ...) \
HRESULT __ret = (x); \
if (GRAPHICS_LOG_HRESULT && FAILED(__ret)) [[unlikely]] { \
log_warning("graphics::d3d9", "{} failed, hr={} " fmt, __FUNCTION__, FMT_HRESULT(__ret), ## __VA_ARGS__); \
} \
return __ret
#define CHECK_RESULT(x) \
HRESULT __ret = (x); \
if (GRAPHICS_LOG_HRESULT && FAILED(__ret)) [[unlikely]] { \
log_warning("graphics::d3d9", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(__ret)); \
} \
return __ret
// TODO: At some point, support a manual `Reset` call by reloading all textures
constexpr bool CUSTOM_RESET = false;
constexpr D3DFORMAT D3DFMT_DF24 = static_cast<D3DFORMAT>(MAKEFOURCC('D', 'F', '2', '4'));
auto format_as(D3DPOOL f) {
return fmt::underlying(f);
}
auto format_as(D3DFORMAT f) {
return fmt::underlying(f);
}
std::string usage2s(DWORD dwUsage) {
FLAGS_START(dwUsage);
FLAG(dwUsage, D3DUSAGE_RENDERTARGET);
FLAG(dwUsage, D3DUSAGE_DEPTHSTENCIL);
FLAG(dwUsage, D3DUSAGE_WRITEONLY);
FLAG(dwUsage, D3DUSAGE_SOFTWAREPROCESSING);
FLAG(dwUsage, D3DUSAGE_DONOTCLIP);
FLAG(dwUsage, D3DUSAGE_POINTS);
FLAG(dwUsage, D3DUSAGE_RTPATCHES);
FLAG(dwUsage, D3DUSAGE_NPATCHES);
FLAG(dwUsage, D3DUSAGE_DYNAMIC);
FLAG(dwUsage, D3DUSAGE_AUTOGENMIPMAP);
FLAG(dwUsage, D3DUSAGE_DMAP);
FLAGS_END(dwUsage);
}
std::string pool2s(D3DPOOL Pool) {
switch (Pool) {
ENUM_VARIANT(D3DPOOL_DEFAULT);
ENUM_VARIANT(D3DPOOL_MANAGED);
ENUM_VARIANT(D3DPOOL_SYSTEMMEM);
ENUM_VARIANT(D3DPOOL_SCRATCH);
default:
return fmt::format("Pool(0x{:08x})", Pool);
}
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::QueryInterface(
REFIID riid,
void **ppvObj)
{
if (ppvObj == nullptr) {
return E_POINTER;
}
if ((riid == IID_IUnknown && is_gfdm_two_head_exclusive()) ||
riid == IID_WrappedIDirect3DDevice9 ||
riid == IID_IDirect3DDevice9 ||
riid == IID_IDirect3DDevice9Ex)
{
// update to IDirect3DDevice9Ex interface
if (!is_d3d9ex && riid == IID_IDirect3DDevice9Ex) {
IDirect3DDevice9Ex *deviceex = nullptr;
HRESULT ret = pReal->QueryInterface(IID_PPV_ARGS(&deviceex));
if (FAILED(ret) || deviceex == nullptr) {
if (ret != E_NOINTERFACE) {
log_warning("graphics::d3d9",
"failed to upgrade to IDirect3DDevice9Ex, hr={}",
FMT_HRESULT(ret));
}
return E_NOINTERFACE;
}
pReal->Release();
pReal = deviceex;
is_d3d9ex = true;
}
this->AddRef();
*ppvObj = this;
return S_OK;
}
return pReal->QueryInterface(riid, ppvObj);
}
ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::AddRef() {
this->refs++;
return pReal->AddRef();
}
ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() {
// get what this thread thinks the reference count is
auto local_refs = --this->refs;
// release owned objects if there are no more references
if (local_refs == 0) {
if (this->main_swapchain) {
this->main_swapchain->Release();
this->main_swapchain = nullptr;
}
if (this->implicit_sub_swapchain) {
this->implicit_sub_swapchain->Release();
this->implicit_sub_swapchain = nullptr;
}
for (auto &sc : this->sub_swapchain) {
if (sc) {
sc->Release();
sc = nullptr;
}
}
for (auto &sc : this->fake_sub_swapchain) {
if (sc) {
sc->Release();
sc = nullptr;
}
}
d3d9_readback::release_device_resources(this->pReal);
if (overlay::ENABLED) {
const std::lock_guard<std::mutex> lock(overlay::OVERLAY_MUTEX);
// release overlay if we are the backing device which will release its owned references
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(this->pReal)) {
overlay::OVERLAY.reset();
}
}
}
// get reference count of underlying interface
ULONG refs = this->pReal != nullptr ? this->pReal->Release() : 0;
// the reference counts should be equivalent at this point, so log if they are not
if (local_refs == 0 && refs != 0) {
log_warning("graphics::d3d9",
"reference count for `IDirect3DDevice9` object {} wrapping {} "
"is inconsistent: {}, but expected 0",
fmt::ptr(this),
fmt::ptr(this->pReal),
refs);
refs = 0;
}
if (refs == 0) {
delete this;
}
return refs;
}
/*
* IDirect3DDevice9
*/
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::TestCooperativeLevel() {
WRAP_VERBOSE;
CHECK_RESULT(pReal->TestCooperativeLevel());
}
UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetAvailableTextureMem() {
WRAP_DEBUG;
return pReal->GetAvailableTextureMem();
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EvictManagedResources() {
WRAP_VERBOSE;
CHECK_RESULT(pReal->EvictManagedResources());
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDirect3D(
IDirect3D9 **ppD3D9)
{
WRAP_VERBOSE;
if (is_gfdm_two_head_exclusive() && gfdm_parent_d3d != nullptr) {
if (ppD3D9 == nullptr) {
return D3DERR_INVALIDCALL;
}
gfdm_parent_d3d->AddRef();
*ppD3D9 = gfdm_parent_d3d;
return D3D_OK;
}
CHECK_RESULT(pReal->GetDirect3D(ppD3D9));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDeviceCaps(
D3DCAPS9 *pCaps)
{
WRAP_VERBOSE;
HRESULT result = pReal->GetDeviceCaps(pCaps);
if (SUCCEEDED(result) && is_gfdm_two_head_exclusive() && pCaps != nullptr) {
pCaps->NumberOfAdaptersInGroup = GFDM_LOGICAL_HEAD_COUNT;
pCaps->MasterAdapterOrdinal = 0;
pCaps->AdapterOrdinalInGroup = 0;
}
CHECK_RESULT(result);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayMode(
UINT iSwapChain,
D3DDISPLAYMODE *pMode)
{
WRAP_VERBOSE;
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetDisplayMode(pMode);
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
CHECK_RESULT(pReal->GetDisplayMode(GFDM_NATIVE_SMALL_SWAPCHAIN, pMode));
}
CHECK_RESULT(pReal->GetDisplayMode(iSwapChain, pMode));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetCreationParameters(
D3DDEVICE_CREATION_PARAMETERS *pParameters)
{
WRAP_VERBOSE;
CHECK_RESULT(pReal->GetCreationParameters(pParameters));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetCursorProperties(
UINT XHotSpot,
UINT YHotSpot,
IDirect3DSurface9 *pCursorBitmap)
{
WRAP_VERBOSE;
CHECK_RESULT(pReal->SetCursorProperties(XHotSpot, YHotSpot, pCursorBitmap));
}
void STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetCursorPosition(
int X,
int Y,
DWORD Flags)
{
WRAP_VERBOSE;
return pReal->SetCursorPosition(X, Y, Flags);
}
BOOL STDMETHODCALLTYPE WrappedIDirect3DDevice9::ShowCursor(
BOOL bShow)
{
WRAP_VERBOSE;
return pReal->ShowCursor(bShow);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DSwapChain9 **ppSwapChain)
{
WRAP_VERBOSE;
if (pPresentationParameters == nullptr || ppSwapChain == nullptr) {
return D3DERR_INVALIDCALL;
}
if (is_gfdm_two_head_exclusive()) {
if (pPresentationParameters->BackBufferWidth == 800) {
return GetSwapChain(gfdm_logical_small_swapchain, ppSwapChain);
}
if (pPresentationParameters->BackBufferWidth != 1080) {
log_warning(
"graphics::d3d9",
"two-head exclusive: rejecting unexpected additional swap chain {}x{}",
pPresentationParameters->BackBufferWidth,
pPresentationParameters->BackBufferHeight);
return D3DERR_INVALIDCALL;
}
size_t index = 3;
for (UINT logical_swapchain = 1;
logical_swapchain < GFDM_LOGICAL_HEAD_COUNT;
logical_swapchain++)
{
if (!is_gfdm_logical_side_swapchain(logical_swapchain)) {
continue;
}
const size_t candidate =
gfdm_hidden_side_swapchain_slot(logical_swapchain);
if (fake_sub_swapchain[candidate] == nullptr) {
index = candidate;
break;
}
}
if (index == 3) {
return D3DERR_OUTOFVIDEOMEMORY;
}
fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(
this,
pPresentationParameters,
is_d3d9ex,
true);
fake_sub_swapchain[index]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[index]);
return D3D_OK;
}
int index = 0;
bool create_swap_chain = false;
bool create_fake_swap_chain = false;
bool arena_slot = false;
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
create_swap_chain = true;
} else if (games::gitadora::is_arena_model() &&
(GRAPHICS_SCREENSHOT_SUBSCREENS ||
GRAPHICS_PREVENT_SECONDARY_WINDOWS ||
GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS)) {
if (pPresentationParameters->BackBufferWidth == 800) {
// SMALL (subscreen)
create_swap_chain = true;
arena_slot = true;
index = 0;
} else if (pPresentationParameters->BackBufferWidth == 1080) {
// LEFT/RIGHT
create_swap_chain = true;
arena_slot = true;
index = 1;
if (sub_swapchain[index] || fake_sub_swapchain[index]) {
index = 2;
}
create_fake_swap_chain = GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS &&
!GRAPHICS_PREVENT_SECONDARY_WINDOWS;
} else {
log_warning("graphics::d3d9", "unknown swap chain detected in CreateAdditionalSwapChain");
}
}
// the api lists screens from this registry, so arena heads need their logical numbers in it
if (arena_slot) {
graphics_screens_register(GFDM_ARENA_SLOT_SCREENS[index]);
}
if (create_fake_swap_chain) {
if (!fake_sub_swapchain[index]) {
log_info(
"graphics::d3d9",
"CreateAdditionalSwapChain called for hidden GITADORA side swap chain {}, "
"using fake swap chain",
index);
fake_sub_swapchain[index] =
new FakeIDirect3DSwapChain9(this, pPresentationParameters, false);
}
fake_sub_swapchain[index]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[index]);
return D3D_OK;
}
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
if (create_swap_chain) {
log_misc(
"graphics::d3d9",
"CreateAdditionalSwapChain called for swap chain {}, creating swap chain",
index);
if (SUCCEEDED(hr) && !sub_swapchain[index]) {
sub_swapchain[index] = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
sub_swapchain[index]->should_run_hooks = false;
} else if (FAILED(hr) && !fake_sub_swapchain[index]) {
log_warning("graphics::d3d9",
"failed to create sub swap chain, hr={}, using fake swap chain",
FMT_HRESULT(hr));
fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(this, pPresentationParameters, false);
fake_sub_swapchain[index]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[index]);
return D3D_OK;
}
}
CHECK_RESULT(hr);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
UINT iSwapChain,
IDirect3DSwapChain9 **ppSwapChain)
{
WRAP_VERBOSE_FMT("GetSwapChain({})", iSwapChain);
if (ppSwapChain == nullptr) {
return D3DERR_INVALIDCALL;
}
if (iSwapChain == 0) {
if (!main_swapchain) {
HRESULT ret = pReal->GetSwapChain(iSwapChain, ppSwapChain);
if (FAILED(ret)) {
log_warning("graphics::d3d9", "failed to get swapchain {}, hr={}",
iSwapChain, FMT_HRESULT(ret));
return ret;
}
main_swapchain = new WrappedIDirect3DSwapChain9(
this,
*ppSwapChain,
is_gfdm_two_head_exclusive()
? WrappedIDirect3DSwapChain9::NativeGroupHead::Main
: WrappedIDirect3DSwapChain9::NativeGroupHead::Unknown);
}
main_swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(main_swapchain);
graphics_screens_register(iSwapChain);
return D3D_OK;
}
if (is_gfdm_two_head_exclusive()) {
if (is_gfdm_logical_small_swapchain(iSwapChain)) {
if (!sub_swapchain[0]) {
IDirect3DSwapChain9 *real_swapchain = nullptr;
HRESULT ret = pReal->GetSwapChain(
GFDM_NATIVE_SMALL_SWAPCHAIN,
&real_swapchain);
if (FAILED(ret)) {
return ret;
}
sub_swapchain[0] = new WrappedIDirect3DSwapChain9(
this,
real_swapchain,
WrappedIDirect3DSwapChain9::NativeGroupHead::Small);
sub_swapchain[0]->should_run_hooks = false;
}
sub_swapchain[0]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain[0]);
graphics_screens_register(iSwapChain);
return D3D_OK;
}
if (is_gfdm_logical_side_swapchain(iSwapChain)) {
FakeIDirect3DSwapChain9 *swapchain =
ensure_gfdm_hidden_side_swapchain(iSwapChain);
swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(swapchain);
graphics_screens_register(iSwapChain);
return D3D_OK;
}
return D3DERR_INVALIDCALL;
}
bool swap = false;
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
swap = true;
}
if (games::gitadora::is_arena_model() && iSwapChain == 2) {
swap = true;
}
if (swap) {
if (sub_swapchain[0]) {
sub_swapchain[0]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain[0]);
graphics_screens_register(iSwapChain);
return D3D_OK;
} else if (fake_sub_swapchain[0]) {
fake_sub_swapchain[0]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[0]);
graphics_screens_register(iSwapChain);
return D3D_OK;
} else if (SUBSCREEN_FORCE_REDRAW) {
// store implicit sub swap chain
if (!implicit_sub_swapchain) {
IDirect3DSwapChain9 *real_swapchain = nullptr;
HRESULT ret = pReal->GetSwapChain(iSwapChain, &real_swapchain);
if (FAILED(ret)) {
return ret;
}
implicit_sub_swapchain = new WrappedIDirect3DSwapChain9(this, real_swapchain);
implicit_sub_swapchain->should_run_hooks = false;
}
implicit_sub_swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(implicit_sub_swapchain);
graphics_screens_register(iSwapChain);
return D3D_OK;
}
}
HRESULT ret = pReal->GetSwapChain(iSwapChain, ppSwapChain);
if (GRAPHICS_LOG_HRESULT && FAILED(ret)) {
log_warning("graphics::d3d9", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(ret));
} else {
graphics_screens_register(iSwapChain);
}
return ret;
}
UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
WRAP_VERBOSE;
if (is_gfdm_two_head_exclusive()) {
return GFDM_LOGICAL_HEAD_COUNT;
}
UINT n = pReal->GetNumberOfSwapChains();
if (sub_swapchain[0] && avs::game::is_model({"LDJ", "KFC", "M39"})) {
n += 1;
}
return n;
}
void WrappedIDirect3DDevice9::get_screenshot_screens(std::vector<int> &screens) const {
if (games::gitadora::is_arena_model()) {
screens.push_back(0);
// every head the game renders into, whether or not it reaches a display
for (int slot = 0; slot < 3; slot++) {
if (sub_swapchain[slot] != nullptr || fake_sub_swapchain[slot] != nullptr) {
screens.push_back(GFDM_ARENA_SLOT_SCREENS[slot]);
}
}
return;
}
graphics_screens_get(screens);
// the sub screen is only registered once the game asks for it by index
if (sub_swapchain[0] != nullptr &&
avs::game::is_model({"LDJ", "KFC", "M39"}) &&
std::find(screens.begin(), screens.end(), 1) == screens.end())
{
screens.push_back(1);
}
}
HRESULT WrappedIDirect3DDevice9::get_screenshot_swap_chain(
UINT iSwapChain,
IDirect3DSwapChain9 **ppSwapChain)
{
if (ppSwapChain == nullptr) {
return D3DERR_INVALIDCALL;
}
// the game numbers the two-head SMALL head itself; keep screen 1 meaning SMALL
if (games::gitadora::is_arena_model() && is_gfdm_two_head_exclusive() && iSwapChain == 1) {
return GetSwapChain(gfdm_logical_small_swapchain, ppSwapChain);
}
if (games::gitadora::is_arena_model()) {
for (int slot = 0; slot < 3; slot++) {
if (GFDM_ARENA_SLOT_SCREENS[slot] != (int) iSwapChain) {
continue;
}
if (sub_swapchain[slot] != nullptr) {
sub_swapchain[slot]->AddRef();
*ppSwapChain = sub_swapchain[slot];
return D3D_OK;
}
if (fake_sub_swapchain[slot] != nullptr) {
fake_sub_swapchain[slot]->AddRef();
*ppSwapChain = fake_sub_swapchain[slot];
return D3D_OK;
}
}
}
return GetSwapChain(iSwapChain, ppSwapChain);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
D3DPRESENT_PARAMETERS *pPresentationParameters)
{
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::Reset");
if (pPresentationParameters) {
if (GRAPHICS_WINDOWED) {
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
} else if (GRAPHICS_FORCE_REFRESH > 0) {
pPresentationParameters->FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH;
}
}
// reset overlay
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
overlay::OVERLAY->reset_invalidate();
}
HRESULT res = pReal->Reset(pPresentationParameters);
// recreate overlay
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal) && SUCCEEDED(res)) {
overlay::OVERLAY->reset_recreate();
}
CHECK_RESULT(res);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Present(
const RECT *pSourceRect,
const RECT *pDestRect,
HWND hDestWindowOverride,
const RGNDATA *pDirtyRegion)
{
WRAP_DEBUG;
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::Present");
});
graphics_d3d9_on_present(hFocusWindow, pReal, this);
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer(
UINT iSwapChain,
UINT iBackBuffer,
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface9 **ppBackBuffer)
{
WRAP_VERBOSE;
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetBackBuffer(
iBackBuffer,
Type,
ppBackBuffer);
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
CHECK_RESULT(pReal->GetBackBuffer(
GFDM_NATIVE_SMALL_SWAPCHAIN,
iBackBuffer,
Type,
ppBackBuffer));
}
CHECK_RESULT(pReal->GetBackBuffer(iSwapChain, iBackBuffer, Type, ppBackBuffer));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetRasterStatus(
UINT iSwapChain,
D3DRASTER_STATUS *pRasterStatus)
{
WRAP_DEBUG;
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetRasterStatus(
pRasterStatus);
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
CHECK_RESULT(pReal->GetRasterStatus(
GFDM_NATIVE_SMALL_SWAPCHAIN,
pRasterStatus));
}
CHECK_RESULT(pReal->GetRasterStatus(iSwapChain, pRasterStatus));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetDialogBoxMode(
BOOL bEnableDialogs)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetDialogBoxMode(bEnableDialogs));
}
void STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetGammaRamp(
UINT iSwapChain,
DWORD Flags,
const D3DGAMMARAMP *pRamp)
{
WRAP_DEBUG;
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return;
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
pReal->SetGammaRamp(GFDM_NATIVE_SMALL_SWAPCHAIN, Flags, pRamp);
return;
}
pReal->SetGammaRamp(iSwapChain, Flags, pRamp);
}
void STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetGammaRamp(
UINT iSwapChain,
D3DGAMMARAMP *pRamp)
{
WRAP_DEBUG;
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
if (pRamp != nullptr) {
*pRamp = {};
}
return;
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
pReal->GetGammaRamp(GFDM_NATIVE_SMALL_SWAPCHAIN, pRamp);
return;
}
pReal->GetGammaRamp(iSwapChain, pRamp);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateTexture(
UINT Width,
UINT Height,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DTexture9 **ppTexture,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateTexture({}, {}, {}, {}, {}, {})",
Width, Height, Levels, usage2s(Usage), Format, pool2s(Pool));
// fix nvidia proprietary depthstencil texture format
if (avs::game::is_model("I36") && (Usage & D3DUSAGE_DEPTHSTENCIL) && Format == D3DFMT_DF24) {
HRESULT ret = pReal->CreateTexture(Width, Height, Levels,
Usage, Format, Pool, ppTexture, pSharedHandle);
if (FAILED(ret)) {
Format = D3DFMT_D24S8;
} else {
return ret;
}
}
#ifndef SPICE64
// texture fix for iidx (iix18 resort anthem, iidx19 lincle)
if (avs::game::is_model({ "JDZ", "KDZ" })) {
// patch texture format type from 15-bit+alpha to 32-bit.
if (Width == 256 && Height == 256 && Levels == 1 && Usage == D3DUSAGE_RENDERTARGET
&& Format == D3DFMT_A1R5G5B5 && Pool == D3DPOOL_DEFAULT && !pSharedHandle) {
Format = D3DFMT_A8R8G8B8;
}
}
#endif
HRESULT res = pReal->CreateTexture(Width, Height, Levels,
Usage, Format, Pool, ppTexture, pSharedHandle);
if (CUSTOM_RESET && ppTexture && Pool == D3DPOOL_MANAGED && SUCCEEDED(res)) {
*ppTexture = new WrappedIDirect3DTexture9(this, *ppTexture);
}
CHECK_RESULT_FMT(res, "({}, {}, {}, {}, {}, {})",
Width, Height, Levels, usage2s(Usage), Format, pool2s(Pool));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateVolumeTexture(
UINT Width,
UINT Height,
UINT Depth,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DVolumeTexture9 **ppVolumeTexture,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateVolumeTexture({}, {}, {}, {}, {}, {}, {})",
Width, Height, Depth, Levels, usage2s(Usage), Format, pool2s(Pool));
CHECK_RESULT(pReal->CreateVolumeTexture(Width, Height, Depth, Levels,
Usage, Format, Pool, ppVolumeTexture, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateCubeTexture(
UINT EdgeLength,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DCubeTexture9 **ppCubeTexture,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateCubeTexture({}, {}, {}, {}, {})",
EdgeLength, Levels, usage2s(Usage), Format, pool2s(Pool));
CHECK_RESULT(pReal->CreateCubeTexture(
EdgeLength, Levels, Usage, Format, Pool, ppCubeTexture, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirect3DVertexBuffer9 **ppVertexBuffer,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateVertexBuffer({}, {}, {}, {})", Length, usage2s(Usage), FVF, pool2s(Pool));
CHECK_RESULT(pReal->CreateVertexBuffer(Length, Usage, FVF, Pool, ppVertexBuffer, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateIndexBuffer(
UINT Length,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DIndexBuffer9 **ppIndexBuffer,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateIndexBuffer({}, {}, {}, {})", Length, usage2s(Usage), Format, pool2s(Pool));
CHECK_RESULT(pReal->CreateIndexBuffer(Length, Usage, Format, Pool, ppIndexBuffer, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateRenderTarget(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
DWORD MultisampleQuality,
BOOL Lockable,
IDirect3DSurface9 **ppSurface,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateRenderTarget({}, {}, {}, {}, {}, {})",
Width, Height, Format, MultiSample, MultisampleQuality, Lockable);
CHECK_RESULT(pReal->CreateRenderTarget(Width, Height, Format,
MultiSample, MultisampleQuality, Lockable, ppSurface, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateDepthStencilSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
DWORD MultisampleQuality,
BOOL Discard,
IDirect3DSurface9 **ppSurface,
HANDLE *pSharedHandle)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->CreateDepthStencilSurface(Width, Height, Format,
MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::UpdateSurface(
IDirect3DSurface9 *pSourceSurface,
const RECT *pSourceRect,
IDirect3DSurface9 *pDestinationSurface,
const POINT *pDestPoint)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->UpdateSurface(pSourceSurface, pSourceRect, pDestinationSurface, pDestPoint));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::UpdateTexture(
IDirect3DBaseTexture9 *pSourceTexture,
IDirect3DBaseTexture9 *pDestinationTexture)
{
WRAP_DEBUG;
if (CUSTOM_RESET) {
#if defined(__GNUC__) && !defined(__clang__)
// Do a pointer compare rather than `QueryInterface` because it is a lot cheaper than incrementing
// the reference count
if (pSourceTexture && (&pSourceTexture->QueryInterface) == WrappedIDirect3DTexture9::QueryInterface) {
pSourceTexture = static_cast<WrappedIDirect3DTexture9 *>(pSourceTexture)->pReal;
}
if (pDestinationTexture &&
(&pDestinationTexture->QueryInterface) == WrappedIDirect3DTexture9::QueryInterface)
{
pDestinationTexture = static_cast<WrappedIDirect3DTexture9 *>(pDestinationTexture)->pReal;
}
#else
void *dummy = nullptr;
if (pSourceTexture && SUCCEEDED(pSourceTexture->QueryInterface(
IID_WrappedIDirect3DTexture9, &dummy)))
{
pSourceTexture = static_cast<WrappedIDirect3DTexture9 *>(pSourceTexture)->pReal;
}
if (pDestinationTexture && SUCCEEDED(pDestinationTexture->QueryInterface(
IID_WrappedIDirect3DTexture9, &dummy)))
{
pDestinationTexture = static_cast<WrappedIDirect3DTexture9 *>(pDestinationTexture)->pReal;
}
#endif
}
CHECK_RESULT(pReal->UpdateTexture(pSourceTexture, pDestinationTexture));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetRenderTargetData(
IDirect3DSurface9 *pRenderTarget,
IDirect3DSurface9 *pDestSurface)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetRenderTargetData(pRenderTarget, pDestSurface));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetFrontBufferData(
UINT iSwapChain,
IDirect3DSurface9 *pDestSurface)
{
WRAP_DEBUG;
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return D3DERR_INVALIDCALL;
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
CHECK_RESULT(pReal->GetFrontBufferData(
GFDM_NATIVE_SMALL_SWAPCHAIN,
pDestSurface));
}
CHECK_RESULT(pReal->GetFrontBufferData(iSwapChain, pDestSurface));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::StretchRect(
IDirect3DSurface9 *pSourceSurface,
const RECT *pSourceRect,
IDirect3DSurface9 *pDestSurface,
const RECT *pDestRect,
D3DTEXTUREFILTERTYPE Filter)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->StretchRect(pSourceSurface, pSourceRect, pDestSurface, pDestRect, Filter));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ColorFill(
IDirect3DSurface9 *pSurface,
const RECT *pRect,
D3DCOLOR color)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->ColorFill(pSurface, pRect, color));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateOffscreenPlainSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DSurface9 **ppSurface,
HANDLE *pSharedHandle)
{
WRAP_VERBOSE_FMT("CreateOffscreenPlainSurface({}, {}, {}, {})",
Width, Height, Format, pool2s(Pool));
CHECK_RESULT(pReal->CreateOffscreenPlainSurface(
Width, Height, Format, Pool, ppSurface, pSharedHandle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetRenderTarget(
DWORD RenderTargetIndex,
IDirect3DSurface9 *pRenderTarget)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetRenderTarget(RenderTargetIndex, pRenderTarget));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetRenderTarget(
DWORD RenderTargetIndex,
IDirect3DSurface9 **ppRenderTarget)
{
WRAP_DEBUG;
return pReal->GetRenderTarget(RenderTargetIndex, ppRenderTarget);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetDepthStencilSurface(
IDirect3DSurface9 *pNewZStencil)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetDepthStencilSurface(pNewZStencil));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDepthStencilSurface(
IDirect3DSurface9 **ppZStencilSurface)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetDepthStencilSurface(ppZStencilSurface));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::BeginScene() {
WRAP_DEBUG;
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::BeginScene");
});
return pReal->BeginScene();
}
static IDirect3DSurface9 *topSurface = nullptr;
static IDirect3DSurface9 *backbuffer = nullptr;
static IDirect3DTexture9 *tex = nullptr;
static UINT topSurface_width = 0;
static UINT topSurface_height = 0;
static float topSurface_aspect_ratio = 1.f;
static UINT backbuffer_width = 0;
static UINT backbuffer_height = 0;
void SurfaceHook(IDirect3DDevice9 *pReal) {
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
// but larger in size. this is needed to ensure that when the user zooms out, we have
// sufficient buffer space (black border) around the original image
//
// (only done once)
if (!topSurface) {
// GetSwapChain/GetBackBuffer AddRef their out parameters; the swap chain is only
// needed here (during one-time init), so release it after use to avoid a per-frame
// ref leak. The backbuffer ref is intentionally retained for the lifetime of the
// cached resources.
LPDIRECT3DSWAPCHAIN9 swapChain = nullptr;
D3DPRESENT_PARAMETERS param {};
pReal->GetSwapChain(0, &swapChain);
swapChain->GetPresentParameters(&param);
backbuffer_width = param.BackBufferWidth;
backbuffer_height = param.BackBufferHeight;
topSurface_width = backbuffer_width * 3;
topSurface_height = backbuffer_height * 3;
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &tex, NULL) != D3D_OK) {
log_warning("graphics::d3d9", "SurfaceHook - create texture failed");
}
log_misc(
"graphics::d3d9", "SurfaceHook - Backbuffer: {} {} {}",
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
tex->GetSurfaceLevel(0, &topSurface);
if (swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
}
swapChain->Release();
}
// pre-calculate dimensions used for phase 1
const int rectLeft = backbuffer_width;
const int rectTop = backbuffer_height;
const int w = backbuffer_width;
const int h = backbuffer_height;
// this code used to clear the surface using ColorFill on every call, but
// this turned out to be very expensive, leading to major frame drops in
// SDVX Live2D scenario
if (cfg::SCREENRESIZE->need_surface_clean) {
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
cfg::SCREENRESIZE->need_surface_clean = false;
}
HRESULT hr;
// phase 1 - copy the original back buffer onto the new surface.
// we draw it 1:1 in the center of the surface.
// if orientation is swapped, fix up the rect and draw it in the center.
//
// for this phase we always render with the same rect so that it's always overwritten
// by a new frame on the same spot.
RECT originRect {
rectLeft,
rectTop,
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
if (GRAPHICS_FS_ORIENTATION_SWAP) {
originRect.left = (topSurface_width / 2) - (GRAPHICS_FS_ORIGINAL_WIDTH / 2);
originRect.top = (topSurface_height / 2) - (GRAPHICS_FS_ORIGINAL_HEIGHT / 2);
originRect.right = originRect.left + GRAPHICS_FS_ORIGINAL_WIDTH;
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
}
bool duplicate = false;
RECT originRect2 = originRect;
if (cfg::SCREENRESIZE->enable_screen_resize) {
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
if (scene.duplicate == cfg::ScreenDuplicateMode::CopyLeft) {
duplicate = true;
originRect2.right = originRect.left;
originRect2.left = (LONG)(originRect2.left - (originRect.right - originRect.left));
} else if (scene.duplicate == cfg::ScreenDuplicateMode::CopyRight) {
duplicate = true;
originRect2.left = originRect.right;
originRect2.right = (LONG)(originRect2.left + (originRect.right - originRect.left));
}
}
// log_misc(
// "graphics::d3d9", "originRect: {} {} {} {}",
// originRect.left, originRect.top, originRect.right, originRect.bottom);
hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &originRect,
D3DTEXF_LINEAR);
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect backbuffer failed for main image, rect: {} {} {} {}",
originRect.left, originRect.top, originRect.right, originRect.bottom);
}
if (duplicate) {
hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &originRect2,
D3DTEXF_LINEAR);
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect backbuffer failed for the duplicate, rect: {} {} {} {}",
originRect2.left, originRect2.top, originRect2.right, originRect2.bottom);
}
}
// phase 2 - viewport calculation - do the actual zoom / offset math
// figure out what region of the surface to copy back to the back buffer.
RECT targetRect {
rectLeft,
rectTop,
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
if (cfg::SCREENRESIZE->enable_screen_resize) {
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
auto w_new = w / scene.scale_x;
auto h_new = h / scene.scale_y;
// make sure the scaling is within bounds
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
if (w_new > topSurface_width || h_new > topSurface_height) {
w_new = topSurface_width;
h_new = topSurface_height;
}
} else {
w_new = MIN(w_new, topSurface_width);
h_new = MIN(h_new, topSurface_height);
}
targetRect.left = (topSurface_width / 2) - (w_new / 2) - scene.offset_x;
targetRect.top = (topSurface_height / 2) - (h_new / 2) - scene.offset_y;
targetRect.right = targetRect.left + w_new;
targetRect.bottom = targetRect.top + h_new;
// boundary check to prevent StretchRect failures
if (targetRect.left < 0) {
targetRect.left = 0;
targetRect.right = w_new;
} else if (targetRect.right > (LONG)topSurface_width) {
targetRect.left = topSurface_width - w_new;
targetRect.right = topSurface_width;
}
if (targetRect.top < 0) {
targetRect.top = 0;
targetRect.bottom = h_new;
} else if (targetRect.bottom > (LONG)topSurface_height) {
targetRect.top = topSurface_height - h_new;
targetRect.bottom = topSurface_height;
}
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
// increase the viewport to fit the image on screen (effectively, zoom out a little)
if (GRAPHICS_FS_ORIGINAL_WIDTH < GRAPHICS_FS_ORIGINAL_HEIGHT) {
// portrait game on landscape display
// height should match the original image
targetRect.top = originRect.top;
targetRect.bottom = originRect.bottom;
// width of viewport should be wider, effectively shrinking the image on x-axis when rendered
const auto w_new = GRAPHICS_FS_ORIGINAL_HEIGHT * topSurface_aspect_ratio;
targetRect.left = (topSurface_width / 2) - (w_new / 2);
targetRect.right = targetRect.left + w_new;
} else {
// landscape game on portrait display
targetRect.left = originRect.left;
targetRect.right = originRect.right;
// height of viewport should be taller, effectively shrinking the image on y-axis when rendered
const auto h_new = GRAPHICS_FS_ORIGINAL_WIDTH / topSurface_aspect_ratio;
targetRect.top = (topSurface_height / 2) - (h_new / 2);
targetRect.bottom = targetRect.top + h_new;
}
}
// log_misc("graphics::d3d9", "targetRect: {} {} {} {}",
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
// phase 3 - draw the surface to back buffer
bool use_linear_filter = true;
if (cfg::SCREENRESIZE->enable_screen_resize) {
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
}
hr = pReal->StretchRect(
topSurface, &targetRect,
backbuffer, nullptr,
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect targetRect failed, you must reset image scaling to default values! rect: {} {} {} {}",
targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
}
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
WRAP_DEBUG;
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::EndScene");
});
return pReal->EndScene();
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Clear(
DWORD Count,
const D3DRECT *pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->Clear(Count, pRects, Flags, Color, Z, Stencil));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetTransform(
D3DTRANSFORMSTATETYPE State,
const D3DMATRIX *pMatrix)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetTransform(State, pMatrix));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetTransform(
D3DTRANSFORMSTATETYPE State,
D3DMATRIX *pMatrix)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetTransform(State, pMatrix));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::MultiplyTransform(
D3DTRANSFORMSTATETYPE State,
const D3DMATRIX *pMatrix)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->MultiplyTransform(State, pMatrix));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetViewport(
const D3DVIEWPORT9 *pViewport)
{
WRAP_DEBUG;
#ifndef SPICE64
if (avs::game::is_model({ "JDZ", "KDZ", "K39", "L39", "LBR" })) {
// Set vertex shader constant to half pixel viewport offset
const float ConstantHalfTexelFixupOffset[2] = {-1.0F / (float)pViewport->Width, 1.0F / (float)pViewport->Height};
HRESULT ret = pReal->SetVertexShaderConstantF(63, ConstantHalfTexelFixupOffset, sizeof(ConstantHalfTexelFixupOffset) / sizeof(ConstantHalfTexelFixupOffset[0]));
CHECK_RESULT(ret);
}
#endif
CHECK_RESULT(pReal->SetViewport(pViewport));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetViewport(
D3DVIEWPORT9 *pViewport)
{
WRAP_DEBUG;
const auto result = pReal->GetViewport(pViewport);
// SDVX landscape mode
// without this, the game calculates the camera angle incorrectly and
// ends up with zoomed out view of the lanes
const auto landscape_width = GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first : GRAPHICS_FS_ORIGINAL_HEIGHT;
const auto landscape_height = GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second : GRAPHICS_FS_ORIGINAL_WIDTH;
if (SUCCEEDED(result) &&
GRAPHICS_FS_ORIENTATION_SWAP &&
avs::game::is_model("KFC") &&
pViewport != nullptr &&
pViewport->Width == landscape_width &&
pViewport->Height == landscape_height) {
static std::once_flag log_once;
std::call_once(log_once, [pViewport]() {
log_info(
"graphics::d3d9",
"SDVX landscape viewport fix: {}x{} => {}x{}",
pViewport->Width,
pViewport->Height,
pViewport->Height,
pViewport->Width);
});
std::swap(pViewport->Width, pViewport->Height);
}
return result;
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetMaterial(
const D3DMATERIAL9 *pMaterial)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetMaterial(pMaterial));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetMaterial(
D3DMATERIAL9 *pMaterial)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetMaterial(pMaterial));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetLight(
DWORD Index,
const D3DLIGHT9 *pLight)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetLight(Index, pLight));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetLight(
DWORD Index,
D3DLIGHT9 *pLight)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetLight(Index, pLight));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::LightEnable(
DWORD Index,
BOOL Enable)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->LightEnable(Index, Enable));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetLightEnable(
DWORD Index,
BOOL *pEnable)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetLightEnable(Index, pEnable));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetClipPlane(
DWORD Index,
const float *pPlane)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetClipPlane(Index, pPlane));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetClipPlane(
DWORD Index,
float *pPlane)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetClipPlane(Index, pPlane));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetRenderState(
D3DRENDERSTATETYPE State,
DWORD Value)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetRenderState(State, Value));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetRenderState(
D3DRENDERSTATETYPE State,
DWORD *pValue)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetRenderState(State, pValue));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateStateBlock(
D3DSTATEBLOCKTYPE Type,
IDirect3DStateBlock9 **ppSB)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->CreateStateBlock(Type, ppSB));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::BeginStateBlock() {
WRAP_DEBUG;
return pReal->BeginStateBlock();
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndStateBlock(
IDirect3DStateBlock9 **ppSB)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->EndStateBlock(ppSB));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetClipStatus(
const D3DCLIPSTATUS9 *pClipStatus)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetClipStatus(pClipStatus));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetClipStatus(
D3DCLIPSTATUS9 *pClipStatus)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetClipStatus(pClipStatus));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetTexture(
DWORD Stage,
IDirect3DBaseTexture9 **ppTexture)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetTexture(Stage, ppTexture));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetTexture(
DWORD Stage,
IDirect3DBaseTexture9 *pTexture)
{
WRAP_DEBUG_FMT("SetTexture({}, {})", Stage, fmt::ptr(pTexture));
if (CUSTOM_RESET) {
void *dummy = nullptr;
if (pTexture && SUCCEEDED(pTexture->QueryInterface(
IID_WrappedIDirect3DTexture9, &dummy)))
{
pTexture = static_cast<WrappedIDirect3DTexture9 *>(pTexture)->pReal;
}
}
CHECK_RESULT(pReal->SetTexture(Stage, pTexture));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD *pValue)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetTextureStageState(Stage, Type, pValue));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD Value)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetTextureStageState(Stage, Type, Value));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSamplerState(
DWORD Sampler,
D3DSAMPLERSTATETYPE Type,
DWORD *pValue)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetSamplerState(Sampler, Type, pValue));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetSamplerState(
DWORD Sampler,
D3DSAMPLERSTATETYPE Type,
DWORD Value)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetSamplerState(Sampler, Type, Value));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ValidateDevice(
DWORD *pNumPasses)
{
WRAP_VERBOSE;
CHECK_RESULT(pReal->ValidateDevice(pNumPasses));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetPaletteEntries(
UINT PaletteNumber,
const PALETTEENTRY *pEntries)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetPaletteEntries(PaletteNumber, pEntries));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetPaletteEntries(
UINT PaletteNumber,
PALETTEENTRY *pEntries)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetPaletteEntries(PaletteNumber, pEntries));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetCurrentTexturePalette(
UINT PaletteNumber)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetCurrentTexturePalette(PaletteNumber));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetCurrentTexturePalette(
UINT *PaletteNumber)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetCurrentTexturePalette(PaletteNumber));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetScissorRect(
const RECT *pRect)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetScissorRect(pRect));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetScissorRect(
RECT *pRect)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetScissorRect(pRect));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetSoftwareVertexProcessing(
BOOL bSoftware)
{
WRAP_VERBOSE;
CHECK_RESULT(pReal->SetSoftwareVertexProcessing(bSoftware));
}
BOOL STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSoftwareVertexProcessing() {
WRAP_VERBOSE;
return pReal->GetSoftwareVertexProcessing();
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetNPatchMode(float nSegments) {
WRAP_DEBUG;
CHECK_RESULT(pReal->SetNPatchMode(nSegments));
}
float STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNPatchMode() {
WRAP_DEBUG;
return pReal->GetNPatchMode();
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount)
{
WRAP_DEBUG;
if (d3d9_live2d::should_skip_draw()) [[unlikely]] {
return D3D_OK;
}
CHECK_RESULT(pReal->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DrawIndexedPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount)
{
WRAP_DEBUG;
if (d3d9_live2d::should_skip_draw()) [[unlikely]] {
return D3D_OK;
}
CHECK_RESULT(pReal->DrawIndexedPrimitive(
PrimitiveType, BaseVertexIndex,
MinVertexIndex, NumVertices,
StartIndex, PrimitiveCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DrawPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT PrimitiveCount,
const void *pVertexStreamZeroData,
UINT VertexStreamZeroStride)
{
WRAP_DEBUG_FMT("DrawPrimitiveUP({}, {}, {}, {})",
PrimitiveType, PrimitiveCount,
fmt::ptr(pVertexStreamZeroData), VertexStreamZeroStride);
if (d3d9_live2d::should_skip_draw()) [[unlikely]] {
return D3D_OK;
}
CHECK_RESULT(pReal->DrawPrimitiveUP(
PrimitiveType, PrimitiveCount,
pVertexStreamZeroData, VertexStreamZeroStride));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DrawIndexedPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertices,
UINT PrimitiveCount,
const void *pIndexData,
D3DFORMAT IndexDataFormat,
const void *pVertexStreamZeroData,
UINT VertexStreamZeroStride)
{
WRAP_DEBUG;
if (d3d9_live2d::should_skip_draw()) [[unlikely]] {
return D3D_OK;
}
CHECK_RESULT(pReal->DrawIndexedPrimitiveUP(
PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData,
IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ProcessVertices(
UINT SrcStartIndex,
UINT DestIndex,
UINT VertexCount,
IDirect3DVertexBuffer9 *pDestBuffer,
IDirect3DVertexDeclaration9 *pVertexDecl,
DWORD Flags)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->ProcessVertices(SrcStartIndex, DestIndex,
VertexCount, pDestBuffer, pVertexDecl, Flags));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateVertexDeclaration(
const D3DVERTEXELEMENT9 *pVertexElements,
IDirect3DVertexDeclaration9 **ppDecl)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->CreateVertexDeclaration(pVertexElements, ppDecl));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetVertexDeclaration(
IDirect3DVertexDeclaration9 *pDecl)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetVertexDeclaration(pDecl));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetVertexDeclaration(
IDirect3DVertexDeclaration9 **ppDecl)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetVertexDeclaration(ppDecl));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetFVF(
DWORD FVF)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetFVF(FVF));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetFVF(
DWORD *pFVF)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetFVF(pFVF));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateVertexShader(
const DWORD *pFunction,
IDirect3DVertexShader9 **ppShader)
{
WRAP_VERBOSE_FMT("CreateVertexShader({})", fmt::ptr(pFunction));
HRESULT ret = pReal->CreateVertexShader(pFunction, ppShader);
if (SUCCEEDED(ret) && ppShader != nullptr) {
d3d9_live2d::on_create_vertex_shader(*ppShader, pFunction);
}
if (GRAPHICS_LOG_HRESULT && FAILED(ret)) [[unlikely]] {
log_warning("graphics::d3d9", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(ret));
}
return ret;
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetVertexShader(
IDirect3DVertexShader9 *pShader)
{
WRAP_DEBUG_FMT("SetVertexShader({})", fmt::ptr(pShader));
d3d9_live2d::on_set_vertex_shader(pShader);
#ifndef SPICE64
// diagonal line fix
if (avs::game::is_model({ "JDZ", "KDZ", "K39", "L39", "LBR" })) {
if (!vertex_shader) {
log_info("graphics::d3d9", "initializing and setting up vertex shaders");
// create and set up the vertex shader shim
HRESULT ret = pReal->CreateVertexShader((const DWORD *) g_vs11_vs_main, &vertex_shader);
if (FAILED(ret)) {
log_warning("graphics::d3d9", "CreateVertexShader failed, hr={}", FMT_HRESULT(ret));
}
}
if (pShader != nullptr) {
pShader = vertex_shader;
}
}
#endif
CHECK_RESULT(pReal->SetVertexShader(pShader));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetVertexShader(
IDirect3DVertexShader9 **ppShader)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetVertexShader(ppShader));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetVertexShaderConstantF(
UINT StartRegister,
const float *pConstantData,
UINT Vector4fCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetVertexShaderConstantF(
StartRegister, pConstantData, Vector4fCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetVertexShaderConstantF(
UINT StartRegister,
float *pConstantData,
UINT Vector4fCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetVertexShaderConstantF(
StartRegister, pConstantData, Vector4fCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetVertexShaderConstantI(
UINT StartRegister,
const int *pConstantData,
UINT Vector4iCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetVertexShaderConstantI(
StartRegister, pConstantData, Vector4iCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetVertexShaderConstantI(
UINT StartRegister,
int *pConstantData,
UINT Vector4iCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetVertexShaderConstantI(
StartRegister, pConstantData, Vector4iCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetVertexShaderConstantB(
UINT StartRegister,
const BOOL *pConstantData,
UINT BoolCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetVertexShaderConstantB(
StartRegister, pConstantData, BoolCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetVertexShaderConstantB(
UINT StartRegister,
BOOL *pConstantData,
UINT BoolCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetVertexShaderConstantB(
StartRegister, pConstantData, BoolCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer9 *pStreamData,
UINT OffsetInBytes,
UINT Stride)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetStreamSource(
StreamNumber, pStreamData, OffsetInBytes, Stride));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer9 **ppStreamData,
UINT *OffsetInBytes,
UINT *pStride)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetStreamSource(
StreamNumber, ppStreamData, OffsetInBytes, pStride));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetStreamSourceFreq(
UINT StreamNumber,
UINT Divider)
{
WRAP_DEBUG;
return pReal->SetStreamSourceFreq(StreamNumber, Divider);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetStreamSourceFreq(
UINT StreamNumber,
UINT *Divider)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetStreamSourceFreq(StreamNumber, Divider));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetIndices(
IDirect3DIndexBuffer9 *pIndexData)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetIndices(pIndexData));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetIndices(
IDirect3DIndexBuffer9 **ppIndexData)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetIndices(ppIndexData));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreatePixelShader(
const DWORD *pFunction,
IDirect3DPixelShader9 **ppShader)
{
WRAP_VERBOSE_FMT("CreatePixelShader({})", fmt::ptr(pFunction));
HRESULT ret = pReal->CreatePixelShader(pFunction, ppShader);
if (SUCCEEDED(ret) && ppShader != nullptr) {
d3d9_live2d::on_create_pixel_shader(*ppShader, pFunction);
}
if (GRAPHICS_LOG_HRESULT && FAILED(ret)) [[unlikely]] {
log_warning("graphics::d3d9", "{} failed, hr={}", __FUNCTION__, FMT_HRESULT(ret));
}
return ret;
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetPixelShader(
IDirect3DPixelShader9 *pShader)
{
WRAP_DEBUG_FMT("SetPixelShader({})", fmt::ptr(pShader));
d3d9_live2d::on_set_pixel_shader(pShader);
CHECK_RESULT(pReal->SetPixelShader(pShader));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetPixelShader(
IDirect3DPixelShader9 **ppShader)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetPixelShader(ppShader));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetPixelShaderConstantF(
UINT StartRegister,
const float *pConstantData,
UINT Vector4fCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetPixelShaderConstantF(
StartRegister, pConstantData, Vector4fCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetPixelShaderConstantF(
UINT StartRegister,
float *pConstantData,
UINT Vector4fCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetPixelShaderConstantF(
StartRegister, pConstantData, Vector4fCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetPixelShaderConstantI(
UINT StartRegister,
const int *pConstantData,
UINT Vector4iCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetPixelShaderConstantI(
StartRegister, pConstantData, Vector4iCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetPixelShaderConstantI(
UINT StartRegister,
int *pConstantData,
UINT Vector4iCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetPixelShaderConstantI(
StartRegister, pConstantData, Vector4iCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetPixelShaderConstantB(
UINT StartRegister,
const BOOL *pConstantData,
UINT BoolCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->SetPixelShaderConstantB(
StartRegister, pConstantData, BoolCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetPixelShaderConstantB(
UINT StartRegister,
BOOL *pConstantData,
UINT BoolCount)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->GetPixelShaderConstantB(
StartRegister, pConstantData, BoolCount));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DrawRectPatch(
UINT Handle,
const float *pNumSegs,
const D3DRECTPATCH_INFO *pRectPatchInfo)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->DrawRectPatch(Handle, pNumSegs, pRectPatchInfo));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DrawTriPatch(
UINT Handle,
const float *pNumSegs,
const D3DTRIPATCH_INFO *pTriPatchInfo)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->DrawTriPatch(Handle, pNumSegs, pTriPatchInfo));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::DeletePatch(UINT Handle) {
WRAP_VERBOSE;
CHECK_RESULT(pReal->DeletePatch(Handle));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateQuery(
D3DQUERYTYPE Type,
IDirect3DQuery9 **ppQuery)
{
WRAP_DEBUG;
CHECK_RESULT(pReal->CreateQuery(Type, ppQuery));
}
/*
* IDirect3DDevice9Ex
*/
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetConvolutionMonoKernel(
UINT width,
UINT height,
float *rows,
float *columns)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->SetConvolutionMonoKernel(
width, height, rows, columns));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ComposeRects(
IDirect3DSurface9 *pSrc,
IDirect3DSurface9 *pDst,
IDirect3DVertexBuffer9 *pSrcRectDescs,
UINT NumRects,
IDirect3DVertexBuffer9 *pDstRectDescs,
D3DCOMPOSERECTSOP Operation,
int Xoffset,
int Yoffset)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->ComposeRects(
pSrc, pDst, pSrcRectDescs, NumRects, pDstRectDescs,
Operation, Xoffset, Yoffset));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::PresentEx(
const RECT *pSourceRect,
const RECT *pDestRect,
HWND hDestWindowOverride,
const RGNDATA *pDirtyRegion,
DWORD dwFlags)
{
WRAP_DEBUG;
assert(is_d3d9ex);
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::PresentEx");
});
graphics_d3d9_on_present(hFocusWindow, pReal, this);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->PresentEx(
pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetGPUThreadPriority(
INT *pPriority)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->GetGPUThreadPriority(pPriority));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetGPUThreadPriority(
INT Priority)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->SetGPUThreadPriority(Priority));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::WaitForVBlank(
UINT iSwapChain)
{
WRAP_DEBUG;
assert(is_d3d9ex);
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return D3D_OK;
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->WaitForVBlank(
GFDM_NATIVE_SMALL_SWAPCHAIN));
}
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->WaitForVBlank(iSwapChain));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CheckResourceResidency(
IDirect3DResource9 **pResourceArray,
UINT32 NumResources)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->CheckResourceResidency(
pResourceArray, NumResources));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetMaximumFrameLatency(
UINT MaxLatency)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->SetMaximumFrameLatency(MaxLatency));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetMaximumFrameLatency(
UINT *pMaxLatency)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->GetMaximumFrameLatency(pMaxLatency));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CheckDeviceState(HWND hDestinationWindow) {
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->CheckDeviceState(hDestinationWindow));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateRenderTargetEx(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
DWORD MultisampleQuality,
BOOL Lockable,
IDirect3DSurface9 **ppSurface,
HANDLE *pSharedHandle,
DWORD Usage)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->CreateRenderTargetEx(
Width, Height, Format, MultiSample,
MultisampleQuality, Lockable, ppSurface, pSharedHandle, Usage));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateOffscreenPlainSurfaceEx(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DSurface9 **ppSurface,
HANDLE *pSharedHandle,
DWORD Usage)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->CreateOffscreenPlainSurfaceEx(
Width, Height, Format, Pool, ppSurface, pSharedHandle, Usage));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateDepthStencilSurfaceEx(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
DWORD MultisampleQuality,
BOOL Discard,
IDirect3DSurface9 **ppSurface,
HANDLE *pSharedHandle,
DWORD Usage)
{
WRAP_DEBUG;
assert(is_d3d9ex);
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->CreateDepthStencilSurfaceEx(
Width, Height, Format, MultiSample,
MultisampleQuality, Discard, ppSurface, pSharedHandle, Usage));
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
D3DPRESENT_PARAMETERS *pPresentationParameters,
D3DDISPLAYMODEEX *pFullscreenDisplayMode)
{
WRAP_DEBUG;
assert(is_d3d9ex);
log_misc("graphics::d3d9", "WrappedIDirect3DDevice9::ResetEx");
if (is_gfdm_two_head_exclusive()) {
gfdm_disarm_present_mode_recovery();
if (pPresentationParameters == nullptr) {
return D3DERR_INVALIDCALL;
}
release_gfdm_hidden_side_swapchains();
}
if (GRAPHICS_WINDOWED) {
if (pPresentationParameters) {
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
}
pFullscreenDisplayMode = nullptr;
} else if (GRAPHICS_FORCE_REFRESH > 0) {
if (pPresentationParameters) {
pPresentationParameters->FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH;
}
if (pFullscreenDisplayMode) {
pFullscreenDisplayMode->RefreshRate = GRAPHICS_FORCE_REFRESH;
}
}
GfdmTwoHeadDeviceState gfdm_parameters(
pPresentationParameters,
pFullscreenDisplayMode,
gfdm_logical_small_swapchain);
if (is_gfdm_two_head_exclusive()) {
HRESULT result = graphics_d3d9_gfdm_select_two_head_group_parameters(
pPresentationParameters,
pFullscreenDisplayMode,
gfdm_parameters.native_presentation_parameters.data(),
pFullscreenDisplayMode != nullptr
? gfdm_parameters.native_fullscreen_display_modes.data()
: nullptr,
&gfdm_parameters.logical_small_swapchain,
"ResetEx");
if (FAILED(result)) {
return result;
}
set_gfdm_logical_group_parameters(pPresentationParameters);
gfdm_parameters.use_native_parameters();
if (gfdm_parameters.native_presentation_parameters[0].Windowed
|| gfdm_parameters.native_presentation_parameters[1].Windowed)
{
return D3DERR_INVALIDCALL;
}
if (GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
gfdm_parameters.native_presentation_parameters[1]
.FullScreen_RefreshRateInHz =
GRAPHICS_FORCE_REFRESH_SUB.value();
if (gfdm_parameters.fullscreen_display_modes != nullptr) {
gfdm_parameters.fullscreen_display_modes[1].RefreshRate =
GRAPHICS_FORCE_REFRESH_SUB.value();
}
}
result = graphics_d3d9_gfdm_remap_two_head_group_parameters(
gfdm_parameters.native_presentation_parameters.data(),
gfdm_parameters.fullscreen_display_modes,
"ResetEx");
if (FAILED(result)) {
return result;
}
if (!graphics_gitadora_prepare_two_head_device_window(
gfdm_parameters.native_presentation_parameters[1].hDeviceWindow,
nullptr,
gfdm_parameters.native_presentation_parameters[1].BackBufferWidth,
gfdm_parameters.native_presentation_parameters[1].BackBufferHeight))
{
return D3DERR_INVALIDCALL;
}
if (gfdm_parameters.fullscreen_display_modes != nullptr) {
IDirect3D9 *raw_d3d = nullptr;
IDirect3D9Ex *raw_d3d_ex = nullptr;
D3DDEVICE_CREATION_PARAMETERS creation {};
HRESULT raw_result = pReal->GetDirect3D(&raw_d3d);
if (SUCCEEDED(raw_result) && raw_d3d != nullptr) {
raw_result = raw_d3d->QueryInterface(IID_PPV_ARGS(&raw_d3d_ex));
}
if (SUCCEEDED(raw_result)) {
raw_result = pReal->GetCreationParameters(&creation);
}
if (SUCCEEDED(raw_result) && raw_d3d_ex != nullptr) {
graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
raw_d3d_ex,
creation.AdapterOrdinal,
gfdm_parameters.native_presentation_parameters.data(),
gfdm_parameters.fullscreen_display_modes,
"ResetEx");
}
if (raw_d3d_ex != nullptr) {
raw_d3d_ex->Release();
}
if (raw_d3d != nullptr) {
raw_d3d->Release();
}
gfdm_parameters.recovery_parameters =
gfdm_parameters.native_presentation_parameters;
gfdm_parameters.recovery_modes =
gfdm_parameters.native_fullscreen_display_modes;
gfdm_parameters.recovery_candidate = true;
}
}
// reset overlay
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
overlay::OVERLAY->reset_invalidate();
}
HRESULT res = static_cast<IDirect3DDevice9Ex *>(pReal)->ResetEx(
gfdm_parameters.presentation_parameters,
gfdm_parameters.fullscreen_display_modes);
if (is_gfdm_two_head_exclusive()
&& SUCCEEDED(res)
&& gfdm_parameters.recovery_candidate)
{
gfdm_arm_present_mode_recovery(
gfdm_parameters.recovery_parameters.data(),
gfdm_parameters.recovery_modes.data());
}
if (is_gfdm_two_head_exclusive() && SUCCEEDED(res)) {
gfdm_logical_small_swapchain = gfdm_parameters.logical_small_swapchain;
pPresentationParameters[0] = gfdm_parameters.presentation_parameters[0];
pPresentationParameters[gfdm_parameters.logical_small_swapchain] =
gfdm_parameters.presentation_parameters[1];
if (pFullscreenDisplayMode != nullptr) {
pFullscreenDisplayMode[0] = gfdm_parameters.fullscreen_display_modes[0];
pFullscreenDisplayMode[gfdm_parameters.logical_small_swapchain] =
gfdm_parameters.fullscreen_display_modes[1];
}
}
// recreate overlay
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal) && SUCCEEDED(res)) {
overlay::OVERLAY->reset_recreate();
}
CHECK_RESULT(res);
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayModeEx(
UINT iSwapChain,
D3DDISPLAYMODEEX *pMode,
D3DDISPLAYROTATION *pRotation)
{
WRAP_DEBUG;
assert(is_d3d9ex);
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_side_swapchain(iSwapChain))
{
return ensure_gfdm_hidden_side_swapchain(iSwapChain)->GetDisplayModeEx(
pMode,
pRotation);
}
if (is_gfdm_two_head_exclusive()
&& is_gfdm_logical_small_swapchain(iSwapChain))
{
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->GetDisplayModeEx(
GFDM_NATIVE_SMALL_SWAPCHAIN,
pMode,
pRotation));
}
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->GetDisplayModeEx(
iSwapChain, pMode, pRotation));
}