gitadora: add native dual fullscreen for Arena (#846)
## Link to GitHub Issue or related Pull Request, if one exists Fixed https://github.com/spice2x/spice2x.github.io/issues/740 ## Description of change - Enable native D3D9 adapter-group dual fullscreen for the GITADORA Arena model when the two-screen MAIN + SMALL configuration is selected without windowed mode. - Present MAIN and SMALL on their respective monitor heads while keeping the invisible LEFT and RIGHT targets offscreen. - Scope the new fake-swap-chain query behavior to the hidden GITADORA two-head targets, preserving existing behavior for other games and configurations. The existing borderless-windowed two-screen path could not consistently keep input and game timing synchronized and also reduced rendering performance. Native dual fullscreen avoids that windowed composition path. ## Testing - Manually tested GITADORA Arena with separate MAIN and SMALL monitors in fullscreen mode. Windowed mode still works if the user chooses to use that. - Confirmed both displays render correctly, touch input works on the SMALL screen, gameplay input stays synchronized, and gameplay holds a steady 60 FPS. - Built `spicetools_spice64` at commit `e4a98e9` with the repository Docker toolchain. - Confirmed no new compiler warnings. - Confirmed the forbidden static DLL import check passes. - Confirmed the Windows 7 DLL compatibility check reports `All DLLs OK!`. --------- Co-authored-by: vgod <428979+vgod@users.noreply.github.com>
This commit is contained in:
co-authored by
vgod
parent
c590b87cff
commit
3012139028
@@ -113,7 +113,10 @@ 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 void graphics_d3d9_ldj_init_sub_screen(
|
||||
IDirect3DDevice9Ex *device,
|
||||
D3DPRESENT_PARAMETERS *present_params,
|
||||
UINT gfdm_small_swapchain);
|
||||
|
||||
static std::string behavior2s(DWORD behavior_flags) {
|
||||
FLAGS_START(behavior_flags);
|
||||
@@ -409,7 +412,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::QueryInterface(
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIDirect3D9 ||
|
||||
if ((riid == IID_IUnknown && gfdm_two_head_exclusive()) ||
|
||||
riid == IID_WrappedIDirect3D9 ||
|
||||
riid == IID_IDirect3D9 ||
|
||||
riid == IID_IDirect3D9Ex)
|
||||
{
|
||||
@@ -467,6 +471,18 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
|
||||
UINT result = pReal->GetAdapterCount();
|
||||
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
if (result == 2) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
return GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires two native adapters; found {}",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!FAKE_SUBSCREEN_ADAPTER) {
|
||||
if (games::popn::is_pikapika_model() && result == 1) {
|
||||
FAKE_SUBSCREEN_ADAPTER = true;
|
||||
@@ -493,7 +509,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
D3DADAPTER_IDENTIFIER9 *pIdentifier)
|
||||
{
|
||||
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0 && pIdentifier) {
|
||||
if (is_fake_subscreen_adapter(Adapter) && pIdentifier) {
|
||||
*pIdentifier = {};
|
||||
const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", Adapter);
|
||||
strcpy(pIdentifier->DeviceName, adapter_name.c_str());
|
||||
@@ -509,7 +525,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||
}
|
||||
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
if (is_fake_subscreen_adapter(Adapter)) {
|
||||
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter {}", Adapter);
|
||||
return 1;
|
||||
}
|
||||
@@ -523,13 +539,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
UINT Mode,
|
||||
D3DDISPLAYMODE *pMode)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
if (is_fake_subscreen_adapter(Adapter)) {
|
||||
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter {}", Adapter);
|
||||
if (Mode == 0 && pMode) {
|
||||
pMode->Width = 1280;
|
||||
pMode->Height = 800;
|
||||
pMode->RefreshRate = 60;
|
||||
pMode->Format = D3DFMT_X8R8G8B8;
|
||||
get_fake_subscreen_display_mode(Adapter, pMode);
|
||||
return S_OK;
|
||||
} else {
|
||||
return D3DERR_INVALIDCALL;
|
||||
@@ -611,6 +624,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE *pMode) {
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
get_fake_subscreen_display_mode(Adapter, pMode);
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->GetAdapterDisplayMode(Adapter, pMode));
|
||||
}
|
||||
|
||||
@@ -621,6 +642,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceType(
|
||||
D3DFORMAT BackBufferFormat,
|
||||
BOOL bWindowed)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(iAdapter)) {
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceType(iAdapter, DevType, DisplayFormat, BackBufferFormat, bWindowed));
|
||||
}
|
||||
|
||||
@@ -632,7 +657,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
|
||||
D3DRESOURCETYPE RType,
|
||||
D3DFORMAT CheckFormat)
|
||||
{
|
||||
if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) {
|
||||
if (is_fake_subscreen_adapter(Adapter)) {
|
||||
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter {}", Adapter);
|
||||
return D3D_OK;
|
||||
}
|
||||
@@ -648,6 +673,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceMultiSampleType(
|
||||
D3DMULTISAMPLE_TYPE MultiSampleType,
|
||||
DWORD *pQualityLevels)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pQualityLevels != nullptr) {
|
||||
*pQualityLevels = 1;
|
||||
}
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceMultiSampleType(
|
||||
Adapter,
|
||||
DeviceType,
|
||||
@@ -664,6 +696,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDepthStencilMatch(
|
||||
D3DFORMAT RenderTargetFormat,
|
||||
D3DFORMAT DepthStencilFormat)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDepthStencilMatch(
|
||||
Adapter,
|
||||
DeviceType,
|
||||
@@ -678,6 +714,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormatConversion(
|
||||
D3DFORMAT SourceFormat,
|
||||
D3DFORMAT TargetFormat)
|
||||
{
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat));
|
||||
}
|
||||
|
||||
@@ -689,6 +729,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (gfdm_two_head_exclusive() && Adapter < GFDM_LOGICAL_HEAD_COUNT) {
|
||||
HRESULT ret = this->pReal->GetDeviceCaps(
|
||||
Adapter >= 2 ? 0 : Adapter,
|
||||
DeviceType,
|
||||
pCaps);
|
||||
if (SUCCEEDED(ret)) {
|
||||
pCaps->NumberOfAdaptersInGroup =
|
||||
Adapter == 0 ? GFDM_LOGICAL_HEAD_COUNT : 0;
|
||||
pCaps->MasterAdapterOrdinal = 0;
|
||||
pCaps->AdapterOrdinalInGroup = Adapter;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -741,6 +795,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
||||
}
|
||||
|
||||
HMONITOR STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterMonitor(UINT Adapter) {
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return pReal->GetAdapterMonitor(0);
|
||||
}
|
||||
|
||||
return pReal->GetAdapterMonitor(Adapter);
|
||||
}
|
||||
|
||||
@@ -861,7 +919,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2
|
||||
&& GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
@@ -902,7 +961,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
|
||||
graphics_hook_window(hFocusWindow, pPresentationParameters);
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(
|
||||
hFocusWindow,
|
||||
*ppReturnedDeviceInterface);
|
||||
}
|
||||
|
||||
// return result
|
||||
@@ -916,6 +977,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCountEx(UINT Adapter, const D3DDISPLAYMODEFILTER *pFilter) {
|
||||
assert(is_d3d9ex);
|
||||
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return static_cast<IDirect3D9Ex *>(pReal)->GetAdapterModeCountEx(Adapter, pFilter);
|
||||
}
|
||||
|
||||
@@ -926,6 +991,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModesEx(
|
||||
D3DDISPLAYMODEEX *pMode)
|
||||
{
|
||||
assert(is_d3d9ex);
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (Mode != 0 || pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
get_fake_subscreen_display_mode_ex(Adapter, pMode);
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->EnumAdapterModesEx(Adapter, pFilter, Mode, pMode));
|
||||
}
|
||||
|
||||
@@ -935,6 +1008,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterDisplayModeEx(
|
||||
D3DDISPLAYROTATION *pRotation)
|
||||
{
|
||||
assert(is_d3d9ex);
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
get_fake_subscreen_display_mode_ex(Adapter, pMode);
|
||||
if (pRotation != nullptr) {
|
||||
*pRotation = D3DDISPLAYROTATION_IDENTITY;
|
||||
}
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->GetAdapterDisplayModeEx(Adapter, pMode, pRotation));
|
||||
}
|
||||
|
||||
@@ -963,6 +1047,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (gfdm_two_head_exclusive() && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"-forceressub is unavailable; SMALL must remain {}x{}",
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
DWORD orig_behavior_flags = BehaviorFlags;
|
||||
size_t num_adapters = 1;
|
||||
@@ -989,6 +1081,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
if (SUCCEEDED(this->pReal->GetDeviceCaps(Adapter, DeviceType, &device_caps))) {
|
||||
num_adapters = device_caps.NumberOfAdaptersInGroup;
|
||||
}
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
num_adapters = GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
@@ -1105,7 +1200,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
if (!GRAPHICS_WINDOWED && !gfdm_two_head_exclusive()
|
||||
&& num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
@@ -1131,16 +1227,93 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
pPresentationParameters->BackBufferCount = GRAPHICS_FORCE_VSYNC_BUFFER.value();
|
||||
}
|
||||
|
||||
GfdmTwoHeadDeviceState gfdm_parameters(
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode);
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
if (!(BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"CreateDeviceEx did not request an adapter-group device");
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
HRESULT select = graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode,
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
gfdm_parameters.native_fullscreen_display_modes.data(),
|
||||
&gfdm_parameters.logical_small_swapchain,
|
||||
"CreateDeviceEx");
|
||||
if (FAILED(select)) {
|
||||
return select;
|
||||
}
|
||||
if (GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
gfdm_parameters.native_presentation_parameters[1]
|
||||
.FullScreen_RefreshRateInHz =
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
if (pFullscreenDisplayMode != nullptr) {
|
||||
gfdm_parameters.native_fullscreen_display_modes[1].RefreshRate =
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
}
|
||||
HRESULT remap = graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
pFullscreenDisplayMode != nullptr
|
||||
? gfdm_parameters.native_fullscreen_display_modes.data()
|
||||
: nullptr,
|
||||
"CreateDeviceEx");
|
||||
if (FAILED(remap)) {
|
||||
return remap;
|
||||
}
|
||||
if (pFullscreenDisplayMode != nullptr) {
|
||||
graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
static_cast<IDirect3D9Ex *>(this->pReal),
|
||||
Adapter,
|
||||
gfdm_parameters.native_presentation_parameters.data(),
|
||||
gfdm_parameters.native_fullscreen_display_modes.data(),
|
||||
"CreateDeviceEx");
|
||||
}
|
||||
if (!graphics_gitadora_prepare_two_head_device_window(
|
||||
gfdm_parameters.native_presentation_parameters[1].hDeviceWindow,
|
||||
this->pReal->GetAdapterMonitor(Adapter + 1),
|
||||
gfdm_parameters.native_presentation_parameters[1].BackBufferWidth,
|
||||
gfdm_parameters.native_presentation_parameters[1].BackBufferHeight))
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
HRESULT validation = validate_gfdm_two_head_exclusive(
|
||||
this->pReal,
|
||||
Adapter,
|
||||
DeviceType,
|
||||
BehaviorFlags,
|
||||
gfdm_parameters.native_presentation_parameters.data());
|
||||
if (FAILED(validation)) {
|
||||
return validation;
|
||||
}
|
||||
gfdm_parameters.use_native_parameters();
|
||||
}
|
||||
|
||||
// call original
|
||||
HRESULT result = static_cast<IDirect3D9Ex *>(this->pReal)->CreateDeviceEx(
|
||||
Adapter,
|
||||
DeviceType,
|
||||
hFocusWindow,
|
||||
BehaviorFlags,
|
||||
pPresentationParameters,
|
||||
pFullscreenDisplayMode,
|
||||
gfdm_parameters.presentation_parameters,
|
||||
gfdm_parameters.fullscreen_display_modes,
|
||||
ppReturnedDeviceInterface);
|
||||
|
||||
if (SUCCEEDED(result) && gfdm_two_head_exclusive()) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (result != D3D_OK) {
|
||||
|
||||
@@ -1151,17 +1324,28 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
|
||||
graphics_hook_window(hFocusWindow, pPresentationParameters);
|
||||
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
|
||||
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(
|
||||
hFocusWindow,
|
||||
*ppReturnedDeviceInterface,
|
||||
gfdm_parameters.logical_small_swapchain,
|
||||
gfdm_two_head_exclusive() ? static_cast<IDirect3D9 *>(this) : nullptr,
|
||||
gfdm_two_head_exclusive() ? pPresentationParameters : nullptr);
|
||||
|
||||
// initialize sub screen if the game requested a multi-head context
|
||||
if (avs::game::is_model({"LDJ", "KFC", "M39", "M32"}) &&
|
||||
(orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
|
||||
UINT i = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
i = 2;
|
||||
}
|
||||
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[i]);
|
||||
UINT i = games::gitadora::is_arena_model()
|
||||
? gfdm_parameters.logical_small_swapchain
|
||||
: 1;
|
||||
D3DPRESENT_PARAMETERS *subscreen_parameters =
|
||||
gfdm_two_head_exclusive()
|
||||
? &gfdm_parameters.presentation_parameters[1]
|
||||
: &pPresentationParameters[i];
|
||||
graphics_d3d9_ldj_init_sub_screen(
|
||||
*ppReturnedDeviceInterface,
|
||||
subscreen_parameters,
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,6 +1354,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterLUID(UINT Adapter, LUID *pLUID) {
|
||||
assert(is_d3d9ex);
|
||||
if (gfdm_two_head_exclusive() && is_fake_subscreen_adapter(Adapter)) {
|
||||
if (pLUID == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
pLUID->LowPart = static_cast<DWORD>(-static_cast<LONG>(Adapter));
|
||||
pLUID->HighPart = -static_cast<LONG>(Adapter);
|
||||
return D3D_OK;
|
||||
}
|
||||
CHECK_RESULT(static_cast<IDirect3D9Ex *>(pReal)->GetAdapterLUID(Adapter, pLUID));
|
||||
}
|
||||
|
||||
@@ -1178,7 +1370,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterLUID(UINT Adapter, LUID *
|
||||
// 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) {
|
||||
static void graphics_d3d9_ldj_init_sub_screen(
|
||||
IDirect3DDevice9Ex *device,
|
||||
D3DPRESENT_PARAMETERS *present_params,
|
||||
UINT gfdm_small_swapchain)
|
||||
{
|
||||
D3DCAPS9 caps {};
|
||||
HRESULT hr = device->GetDeviceCaps(&caps);
|
||||
if (FAILED(hr)) {
|
||||
@@ -1212,10 +1408,9 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
|
||||
}
|
||||
} else {
|
||||
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 2;
|
||||
}
|
||||
const int swapchain = games::gitadora::is_arena_model()
|
||||
? static_cast<int>(gfdm_small_swapchain)
|
||||
: 1;
|
||||
|
||||
hr = device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "d3d9_gfdm.h"
|
||||
|
||||
// {EEE9CCF6-53D6-4326-9AE5-60921B3DB394}
|
||||
static const GUID IID_WrappedIDirect3D9 = {
|
||||
0xeee9ccf6, 0x53d6, 0x4326, { 0x9a, 0xe5, 0x60, 0x92, 0x1b, 0x3d, 0xb3, 0x94 }
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
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);
|
||||
}
|
||||
@@ -85,7 +84,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::QueryInterface(
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIDirect3DDevice9 ||
|
||||
if ((riid == IID_IUnknown && is_gfdm_two_head_exclusive()) ||
|
||||
riid == IID_WrappedIDirect3DDevice9 ||
|
||||
riid == IID_IDirect3DDevice9 ||
|
||||
riid == IID_IDirect3DDevice9Ex)
|
||||
{
|
||||
@@ -202,6 +202,15 @@ 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));
|
||||
}
|
||||
|
||||
@@ -209,7 +218,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDeviceCaps(
|
||||
D3DCAPS9 *pCaps)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal->GetDeviceCaps(pCaps));
|
||||
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(
|
||||
@@ -217,6 +232,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayMode(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -258,6 +284,51 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
{
|
||||
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;
|
||||
@@ -271,6 +342,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
// SMALL (subscreen)
|
||||
create_swap_chain = true;
|
||||
index = 0;
|
||||
|
||||
} else if (pPresentationParameters->BackBufferWidth == 1080) {
|
||||
// LEFT/RIGHT
|
||||
create_swap_chain = true;
|
||||
@@ -335,6 +407,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
{
|
||||
WRAP_VERBOSE_FMT("GetSwapChain({})", iSwapChain);
|
||||
|
||||
if (ppSwapChain == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (iSwapChain == 0) {
|
||||
if (!main_swapchain) {
|
||||
HRESULT ret = pReal->GetSwapChain(iSwapChain, ppSwapChain);
|
||||
@@ -344,7 +420,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
return ret;
|
||||
}
|
||||
|
||||
main_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
main_swapchain = new WrappedIDirect3DSwapChain9(
|
||||
this,
|
||||
*ppSwapChain,
|
||||
is_gfdm_two_head_exclusive()
|
||||
? WrappedIDirect3DSwapChain9::NativeGroupHead::Main
|
||||
: WrappedIDirect3DSwapChain9::NativeGroupHead::Unknown);
|
||||
}
|
||||
|
||||
main_swapchain->AddRef();
|
||||
@@ -354,6 +435,39 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
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;
|
||||
@@ -390,6 +504,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
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"})) {
|
||||
@@ -453,6 +571,24 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -461,6 +597,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetRasterStatus(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -477,6 +627,18 @@ void STDMETHODCALLTYPE WrappedIDirect3DDevice9::SetGammaRamp(
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -485,6 +647,21 @@ void STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetGammaRamp(
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -685,6 +862,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetFrontBufferData(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1779,6 +1969,18 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::WaitForVBlank(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1882,6 +2084,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
||||
|
||||
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;
|
||||
@@ -1898,13 +2108,115 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
pPresentationParameters, pFullscreenDisplayMode);
|
||||
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)) {
|
||||
@@ -1922,6 +2234,22 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetDisplayModeEx(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <d3d9.h>
|
||||
@@ -40,23 +42,54 @@ static const GUID IID_WrappedIDirect3DDevice9 = {
|
||||
void SurfaceHook(IDirect3DDevice9 *pReal);
|
||||
|
||||
struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9 *orig)
|
||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(false) {
|
||||
explicit WrappedIDirect3DDevice9(
|
||||
HWND hFocusWindow,
|
||||
IDirect3DDevice9 *orig,
|
||||
UINT gfdm_logical_small_swapchain = 2,
|
||||
IDirect3D9 *gfdm_parent_d3d = nullptr,
|
||||
const D3DPRESENT_PARAMETERS *gfdm_logical_group_parameters = nullptr)
|
||||
: hFocusWindow(hFocusWindow),
|
||||
pReal(orig),
|
||||
is_d3d9ex(false),
|
||||
gfdm_logical_small_swapchain(gfdm_logical_small_swapchain),
|
||||
gfdm_parent_d3d(gfdm_parent_d3d) {
|
||||
if (this->gfdm_parent_d3d != nullptr) {
|
||||
this->gfdm_parent_d3d->AddRef();
|
||||
}
|
||||
IDirect3DDevice9Ex *device = nullptr;
|
||||
|
||||
// attempt to upgrade handle
|
||||
if (SUCCEEDED(this->QueryInterface(IID_PPV_ARGS(&device))) && device != nullptr) {
|
||||
device->Release();
|
||||
}
|
||||
set_gfdm_logical_group_parameters(gfdm_logical_group_parameters);
|
||||
}
|
||||
|
||||
explicit WrappedIDirect3DDevice9(HWND hFocusWindow, IDirect3DDevice9Ex *orig)
|
||||
: hFocusWindow(hFocusWindow), pReal(orig), is_d3d9ex(true) {}
|
||||
explicit WrappedIDirect3DDevice9(
|
||||
HWND hFocusWindow,
|
||||
IDirect3DDevice9Ex *orig,
|
||||
UINT gfdm_logical_small_swapchain = 2,
|
||||
IDirect3D9 *gfdm_parent_d3d = nullptr,
|
||||
const D3DPRESENT_PARAMETERS *gfdm_logical_group_parameters = nullptr)
|
||||
: hFocusWindow(hFocusWindow),
|
||||
pReal(orig),
|
||||
is_d3d9ex(true),
|
||||
gfdm_logical_small_swapchain(gfdm_logical_small_swapchain),
|
||||
gfdm_parent_d3d(gfdm_parent_d3d) {
|
||||
if (this->gfdm_parent_d3d != nullptr) {
|
||||
this->gfdm_parent_d3d->AddRef();
|
||||
}
|
||||
set_gfdm_logical_group_parameters(gfdm_logical_group_parameters);
|
||||
}
|
||||
|
||||
WrappedIDirect3DDevice9(const WrappedIDirect3DDevice9 &) = delete;
|
||||
WrappedIDirect3DDevice9 &operator=(const WrappedIDirect3DDevice9 &) = delete;
|
||||
|
||||
virtual ~WrappedIDirect3DDevice9() = default;
|
||||
virtual ~WrappedIDirect3DDevice9() {
|
||||
if (gfdm_parent_d3d != nullptr) {
|
||||
gfdm_parent_d3d->Release();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
@@ -201,6 +234,27 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||
#pragma endregion
|
||||
|
||||
bool is_gfdm_two_head_exclusive() const;
|
||||
bool is_gfdm_logical_small_swapchain(UINT swapchain) const;
|
||||
bool is_gfdm_logical_side_swapchain(UINT swapchain) const;
|
||||
size_t gfdm_hidden_side_swapchain_slot(UINT swapchain) const;
|
||||
void set_gfdm_logical_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters);
|
||||
FakeIDirect3DSwapChain9 *ensure_gfdm_hidden_side_swapchain(UINT iSwapChain);
|
||||
void release_gfdm_hidden_side_swapchains();
|
||||
|
||||
enum class GfdmPresentModeRecoveryResult {
|
||||
NotAttempted,
|
||||
ReadyToRetry,
|
||||
Failed,
|
||||
};
|
||||
void gfdm_disarm_present_mode_recovery();
|
||||
void gfdm_arm_present_mode_recovery(
|
||||
const D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *native_fullscreen_display_modes);
|
||||
GfdmPresentModeRecoveryResult gfdm_recover_present_mode_change(
|
||||
HRESULT *failure_result);
|
||||
|
||||
HWND const hFocusWindow;
|
||||
IDirect3DDevice9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
@@ -211,5 +265,17 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
||||
WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr };
|
||||
FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr };
|
||||
|
||||
UINT gfdm_logical_small_swapchain = 2;
|
||||
std::array<D3DPRESENT_PARAMETERS, 4> gfdm_logical_group_parameters {};
|
||||
bool gfdm_logical_group_parameters_valid = false;
|
||||
IDirect3D9 *gfdm_parent_d3d = nullptr;
|
||||
|
||||
std::mutex gfdm_recovery_mutex;
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> gfdm_recovery_parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> gfdm_recovery_modes {};
|
||||
bool gfdm_recovery_armed = false;
|
||||
std::atomic_bool gfdm_recovery_consumed {false};
|
||||
const DWORD gfdm_device_creation_thread_id = GetCurrentThreadId();
|
||||
|
||||
IDirect3DVertexShader9 *vertex_shader = nullptr;
|
||||
};
|
||||
|
||||
@@ -59,7 +59,12 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::Present(const RECT *pSourceRe
|
||||
log_misc("graphics::d3d9", "FakeIDirect3DSwapChain9::Present");
|
||||
});
|
||||
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
present_count.fetch_add(1, std::memory_order_relaxed);
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||
WRAP_VERBOSE;
|
||||
@@ -86,11 +91,32 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffe
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) {
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pRasterStatus == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pRasterStatus = {};
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *pMode) {
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
pMode->Width = present_params.BackBufferWidth;
|
||||
pMode->Height = present_params.BackBufferHeight;
|
||||
pMode->RefreshRate = present_params.FullScreen_RefreshRateInHz;
|
||||
pMode->Format = present_params.BackBufferFormat;
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDevice(IDirect3DDevice9 **ppDevice) {
|
||||
WRAP_VERBOSE;
|
||||
@@ -108,17 +134,46 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetPresentParameters(
|
||||
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
||||
{
|
||||
WRAP_VERBOSE;
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pPresentationParameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pPresentationParameters = present_params;
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
// IDirect3DSwapChain9Ex
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetLastPresentCount(UINT *pLastPresentCount) {
|
||||
assert(is_d3d9ex);
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pLastPresentCount == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pLastPresentCount = present_count.load(std::memory_order_relaxed);
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) {
|
||||
assert(is_d3d9ex);
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pPresentationStatistics == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
*pPresentationStatistics = {};
|
||||
pPresentationStatistics->PresentCount =
|
||||
present_count.load(std::memory_order_relaxed);
|
||||
return D3D_OK;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMODEEX *pMode,
|
||||
D3DDISPLAYROTATION *pRotation)
|
||||
@@ -126,5 +181,22 @@ HRESULT STDMETHODCALLTYPE FakeIDirect3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMO
|
||||
WRAP_VERBOSE;
|
||||
|
||||
assert(is_d3d9ex);
|
||||
return D3DERR_INVALIDCALL;
|
||||
if (!is_hidden) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (pMode == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
pMode->Size = sizeof(*pMode);
|
||||
pMode->Width = present_params.BackBufferWidth;
|
||||
pMode->Height = present_params.BackBufferHeight;
|
||||
pMode->RefreshRate = present_params.FullScreen_RefreshRateInHz;
|
||||
pMode->Format = present_params.BackBufferFormat;
|
||||
pMode->ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
|
||||
if (pRotation != nullptr) {
|
||||
*pRotation = D3DDISPLAYROTATION_IDENTITY;
|
||||
}
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,19 @@
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
struct FakeIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
FakeIDirect3DSwapChain9(IDirect3DDevice9 *pDev, D3DPRESENT_PARAMETERS *present_params, bool is_d3d9ex) :
|
||||
pDev(pDev), is_d3d9ex(is_d3d9ex)
|
||||
FakeIDirect3DSwapChain9(
|
||||
IDirect3DDevice9 *pDev,
|
||||
D3DPRESENT_PARAMETERS *present_params,
|
||||
bool is_d3d9ex,
|
||||
bool is_hidden = false) :
|
||||
pDev(pDev), is_d3d9ex(is_d3d9ex), is_hidden(is_hidden)
|
||||
{
|
||||
// copy presentation parameters
|
||||
memcpy(&this->present_params, present_params, sizeof(this->present_params));
|
||||
@@ -78,8 +83,10 @@ struct FakeIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
|
||||
IDirect3DDevice9 *const pDev;
|
||||
bool is_d3d9ex;
|
||||
bool is_hidden;
|
||||
|
||||
std::atomic<ULONG> ref_cnt = 1;
|
||||
std::atomic<UINT> present_count = 0;
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
std::vector<IDirect3DSurface9 *> render_targets;
|
||||
|
||||
@@ -0,0 +1,715 @@
|
||||
#include "d3d9_gfdm.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include "d3d9_device.h"
|
||||
|
||||
bool gfdm_two_head_exclusive() {
|
||||
return games::gitadora::is_arena_model()
|
||||
&& games::gitadora::ARENA_TWO_HEAD_EXCLUSIVE
|
||||
&& !GRAPHICS_WINDOWED;
|
||||
}
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||
D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
D3DDISPLAYMODEEX *native_fullscreen_display_modes,
|
||||
UINT *logical_small_swapchain,
|
||||
const char *operation)
|
||||
{
|
||||
if (!gfdm_two_head_exclusive()
|
||||
|| logical_presentation_parameters == nullptr
|
||||
|| native_presentation_parameters == nullptr
|
||||
|| logical_small_swapchain == nullptr)
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
UINT small_slot = GFDM_LOGICAL_HEAD_COUNT;
|
||||
for (UINT i = 1; i < GFDM_LOGICAL_HEAD_COUNT; i++) {
|
||||
const auto ¶ms = logical_presentation_parameters[i];
|
||||
if (params.BackBufferWidth != GFDM_SMALL_WIDTH
|
||||
|| params.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (small_slot != GFDM_LOGICAL_HEAD_COUNT) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} found multiple SMALL slots: {} and {}",
|
||||
operation,
|
||||
small_slot,
|
||||
i);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
small_slot = i;
|
||||
}
|
||||
|
||||
if (small_slot == GFDM_LOGICAL_HEAD_COUNT) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} could not find an 800x1280 logical SMALL descriptor",
|
||||
operation);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (logical_fullscreen_display_modes != nullptr
|
||||
&& (logical_fullscreen_display_modes[small_slot].Width != GFDM_SMALL_WIDTH
|
||||
|| logical_fullscreen_display_modes[small_slot].Height != GFDM_SMALL_HEIGHT))
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} SMALL slot {} mode is {}x{}, expected {}x{}",
|
||||
operation,
|
||||
small_slot,
|
||||
logical_fullscreen_display_modes[small_slot].Width,
|
||||
logical_fullscreen_display_modes[small_slot].Height,
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
native_presentation_parameters[0] = logical_presentation_parameters[0];
|
||||
native_presentation_parameters[1] = logical_presentation_parameters[small_slot];
|
||||
if (logical_fullscreen_display_modes != nullptr
|
||||
&& native_fullscreen_display_modes != nullptr)
|
||||
{
|
||||
native_fullscreen_display_modes[0] = logical_fullscreen_display_modes[0];
|
||||
native_fullscreen_display_modes[1] = logical_fullscreen_display_modes[small_slot];
|
||||
}
|
||||
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} selected logical SMALL slot {} -> native group head 1{}",
|
||||
operation,
|
||||
small_slot,
|
||||
logical_presentation_parameters[small_slot].hDeviceWindow == GFDM_SUBSCREEN_WINDOW
|
||||
? " (named SMALL host)"
|
||||
: "");
|
||||
*logical_small_swapchain = small_slot;
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
bool is_fake_subscreen_adapter(UINT adapter) {
|
||||
// MAIN and SMALL remain native D3D heads; LEFT/RIGHT are virtual.
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
return adapter >= 2 && adapter < GFDM_LOGICAL_HEAD_COUNT;
|
||||
}
|
||||
|
||||
return FAKE_SUBSCREEN_ADAPTER && adapter > 0;
|
||||
}
|
||||
|
||||
void get_fake_subscreen_display_mode(
|
||||
UINT adapter,
|
||||
D3DDISPLAYMODE *mode)
|
||||
{
|
||||
(void) adapter;
|
||||
if (gfdm_two_head_exclusive()) {
|
||||
mode->Width = GFDM_SIDE_WIDTH;
|
||||
mode->Height = GFDM_SIDE_HEIGHT;
|
||||
} else {
|
||||
mode->Width = 1280;
|
||||
mode->Height = 800;
|
||||
}
|
||||
mode->RefreshRate = 60;
|
||||
mode->Format = D3DFMT_X8R8G8B8;
|
||||
}
|
||||
|
||||
void get_fake_subscreen_display_mode_ex(
|
||||
UINT adapter,
|
||||
D3DDISPLAYMODEEX *mode)
|
||||
{
|
||||
D3DDISPLAYMODE legacy_mode {};
|
||||
get_fake_subscreen_display_mode(adapter, &legacy_mode);
|
||||
|
||||
mode->Size = sizeof(*mode);
|
||||
mode->Width = legacy_mode.Width;
|
||||
mode->Height = legacy_mode.Height;
|
||||
mode->RefreshRate = legacy_mode.RefreshRate;
|
||||
mode->Format = legacy_mode.Format;
|
||||
mode->ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
|
||||
}
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation)
|
||||
{
|
||||
if (!gfdm_two_head_exclusive()) {
|
||||
return D3D_OK;
|
||||
}
|
||||
if (presentation_parameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
auto &secondary = presentation_parameters[1];
|
||||
if (secondary.BackBufferWidth != GFDM_SMALL_WIDTH
|
||||
|| secondary.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} expected physical SMALL {}x{} at group slot 1, got {}x{}",
|
||||
operation,
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT,
|
||||
secondary.BackBufferWidth,
|
||||
secondary.BackBufferHeight);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
if (GFDM_SUBSCREEN_WINDOW == nullptr || !IsWindow(GFDM_SUBSCREEN_WINDOW)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} cannot use the named SMALL window as physical group head 1",
|
||||
operation);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
secondary.hDeviceWindow = GFDM_SUBSCREEN_WINDOW;
|
||||
|
||||
if (fullscreen_display_modes != nullptr
|
||||
&& (fullscreen_display_modes[1].Width != GFDM_SMALL_WIDTH
|
||||
|| fullscreen_display_modes[1].Height != GFDM_SMALL_HEIGHT))
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} SMALL head mode is {}x{}, expected 800x1280",
|
||||
operation,
|
||||
fullscreen_display_modes[1].Width,
|
||||
fullscreen_display_modes[1].Height);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
void graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
IDirect3D9Ex *d3d,
|
||||
UINT master_adapter,
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation)
|
||||
{
|
||||
if (d3d == nullptr
|
||||
|| presentation_parameters == nullptr
|
||||
|| fullscreen_display_modes == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (UINT head = 0; head < 2; head++) {
|
||||
const bool explicitly_forced =
|
||||
(head == 0 && GRAPHICS_FORCE_REFRESH > 0)
|
||||
|| (head == 1 && GRAPHICS_FORCE_REFRESH_SUB.has_value());
|
||||
if (explicitly_forced) {
|
||||
continue;
|
||||
}
|
||||
|
||||
D3DDISPLAYMODEEX desktop_mode {};
|
||||
desktop_mode.Size = sizeof(desktop_mode);
|
||||
D3DDISPLAYROTATION rotation = D3DDISPLAYROTATION_IDENTITY;
|
||||
const HRESULT result = d3d->GetAdapterDisplayModeEx(
|
||||
master_adapter + head,
|
||||
&desktop_mode,
|
||||
&rotation);
|
||||
if (FAILED(result)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} could not query native head {} desktop refresh, hr={}",
|
||||
operation,
|
||||
head,
|
||||
FMT_HRESULT(result));
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto ¶meters = presentation_parameters[head];
|
||||
if (desktop_mode.Width != parameters.BackBufferWidth
|
||||
|| desktop_mode.Height != parameters.BackBufferHeight
|
||||
|| desktop_mode.Format != parameters.BackBufferFormat
|
||||
|| desktop_mode.RefreshRate == 0)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} head {} mode differs from the desktop",
|
||||
operation,
|
||||
head);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameters.FullScreen_RefreshRateInHz != desktop_mode.RefreshRate
|
||||
|| fullscreen_display_modes[head].RefreshRate != desktop_mode.RefreshRate)
|
||||
{
|
||||
log_info(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive: {} head {} refresh {} / {} -> {} (rotation {})",
|
||||
operation,
|
||||
head,
|
||||
parameters.FullScreen_RefreshRateInHz,
|
||||
fullscreen_display_modes[head].RefreshRate,
|
||||
desktop_mode.RefreshRate,
|
||||
static_cast<UINT>(rotation));
|
||||
presentation_parameters[head].FullScreen_RefreshRateInHz =
|
||||
desktop_mode.RefreshRate;
|
||||
fullscreen_display_modes[head].RefreshRate =
|
||||
desktop_mode.RefreshRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT validate_gfdm_two_head_exclusive(
|
||||
IDirect3D9 *d3d,
|
||||
UINT adapter,
|
||||
D3DDEVTYPE device_type,
|
||||
DWORD behavior_flags,
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters)
|
||||
{
|
||||
if (!(behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires D3DCREATE_ADAPTERGROUP_DEVICE");
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
if (adapter != 0) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive mode currently requires master adapter 0, game requested {}",
|
||||
adapter);
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
if (presentation_parameters == nullptr) {
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
const UINT native_adapter_count = d3d->GetAdapterCount();
|
||||
if (native_adapter_count != 2) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires two native D3D9 adapters; found {}",
|
||||
native_adapter_count);
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
D3DCAPS9 caps {};
|
||||
const HRESULT caps_result = d3d->GetDeviceCaps(adapter, device_type, &caps);
|
||||
if (FAILED(caps_result)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive mode could not query native adapter caps, hr={}",
|
||||
FMT_HRESULT(caps_result));
|
||||
return caps_result;
|
||||
}
|
||||
if (caps.MasterAdapterOrdinal != adapter
|
||||
|| caps.AdapterOrdinalInGroup != 0
|
||||
|| caps.NumberOfAdaptersInGroup != 2)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"invalid adapter group: master={}, index={}, size={}",
|
||||
caps.MasterAdapterOrdinal,
|
||||
caps.AdapterOrdinalInGroup,
|
||||
caps.NumberOfAdaptersInGroup);
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
const auto &main = presentation_parameters[0];
|
||||
const auto &small_params = presentation_parameters[1];
|
||||
if (main.Windowed || small_params.Windowed) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head exclusive mode requires both group heads to be fullscreen");
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (small_params.BackBufferWidth != GFDM_SMALL_WIDTH
|
||||
|| small_params.BackBufferHeight != GFDM_SMALL_HEIGHT)
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SMALL head must be {}x{}; got {}x{}",
|
||||
GFDM_SMALL_WIDTH,
|
||||
GFDM_SMALL_HEIGHT,
|
||||
small_params.BackBufferWidth,
|
||||
small_params.BackBufferHeight);
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (main.EnableAutoDepthStencil != small_params.EnableAutoDepthStencil) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode requires matching automatic depth-stencil settings");
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
if (main.EnableAutoDepthStencil
|
||||
&& (main.AutoDepthStencilFormat != small_params.AutoDepthStencilFormat
|
||||
|| main.BackBufferWidth != small_params.BackBufferWidth
|
||||
|| main.BackBufferHeight != small_params.BackBufferHeight
|
||||
|| main.BackBufferFormat != small_params.BackBufferFormat))
|
||||
{
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"two-head mode cannot use unequal sizes with automatic depth-stencil");
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
static bool gfdm_find_alternate_small_mode(
|
||||
IDirect3D9Ex *d3d,
|
||||
UINT adapter,
|
||||
const D3DDISPLAYMODEEX &desired,
|
||||
D3DDISPLAYMODEEX *alternate)
|
||||
{
|
||||
if (d3d == nullptr || alternate == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
D3DDISPLAYMODEFILTER filter {};
|
||||
filter.Size = sizeof(filter);
|
||||
filter.Format = desired.Format;
|
||||
filter.ScanLineOrdering = D3DSCANLINEORDERING_UNKNOWN;
|
||||
const UINT count = d3d->GetAdapterModeCountEx(adapter, &filter);
|
||||
const bool portrait = desired.Width < desired.Height;
|
||||
|
||||
for (UINT index = 0; index < count; index++) {
|
||||
D3DDISPLAYMODEEX mode {};
|
||||
mode.Size = sizeof(mode);
|
||||
if (FAILED(d3d->EnumAdapterModesEx(adapter, &filter, index, &mode))
|
||||
|| mode.Width != desired.Width
|
||||
|| mode.Height != desired.Height
|
||||
|| mode.Format != desired.Format
|
||||
|| (mode.RefreshRate == desired.RefreshRate
|
||||
&& mode.ScanLineOrdering == desired.ScanLineOrdering))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
*alternate = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int preserve_orientation = 1; preserve_orientation >= 0; preserve_orientation--) {
|
||||
for (int preserve_refresh = 1; preserve_refresh >= 0; preserve_refresh--) {
|
||||
for (UINT index = 0; index < count; index++) {
|
||||
D3DDISPLAYMODEEX mode {};
|
||||
mode.Size = sizeof(mode);
|
||||
if (FAILED(d3d->EnumAdapterModesEx(adapter, &filter, index, &mode))
|
||||
|| (mode.Width == desired.Width && mode.Height == desired.Height)
|
||||
|| mode.Format != desired.Format)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (preserve_orientation && (mode.Width < mode.Height) != portrait) {
|
||||
continue;
|
||||
}
|
||||
if (preserve_refresh && mode.RefreshRate != desired.RefreshRate) {
|
||||
continue;
|
||||
}
|
||||
*alternate = mode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool gfdm_wait_for_small_mode(
|
||||
HWND window,
|
||||
const D3DDISPLAYMODEEX &expected)
|
||||
{
|
||||
const HMONITOR monitor =
|
||||
window != nullptr ? MonitorFromWindow(window, MONITOR_DEFAULTTONULL) : nullptr;
|
||||
MONITORINFOEXA monitor_info {};
|
||||
monitor_info.cbSize = sizeof(monitor_info);
|
||||
if (monitor == nullptr || !GetMonitorInfoA(monitor, &monitor_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (UINT poll = 0; poll < 50; poll++) {
|
||||
DEVMODEA current {};
|
||||
current.dmSize = sizeof(current);
|
||||
if (EnumDisplaySettingsExA(
|
||||
monitor_info.szDevice,
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
¤t,
|
||||
0)
|
||||
&& current.dmPelsWidth == expected.Width
|
||||
&& current.dmPelsHeight == expected.Height
|
||||
&& (expected.RefreshRate == 0
|
||||
|| current.dmDisplayFrequency == expected.RefreshRate))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Sleep(10);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_recover_two_head_present_mode(
|
||||
IDirect3D9Ex *d3d,
|
||||
IDirect3DDevice9Ex *device,
|
||||
UINT master_adapter,
|
||||
const D3DPRESENT_PARAMETERS *desired_parameters,
|
||||
const D3DDISPLAYMODEEX *desired_modes)
|
||||
{
|
||||
if (d3d == nullptr
|
||||
|| device == nullptr
|
||||
|| desired_parameters == nullptr
|
||||
|| desired_modes == nullptr)
|
||||
{
|
||||
return D3DERR_INVALIDCALL;
|
||||
}
|
||||
|
||||
D3DDISPLAYMODEEX alternate_small {};
|
||||
if (!gfdm_find_alternate_small_mode(
|
||||
d3d,
|
||||
master_adapter + 1,
|
||||
desired_modes[1],
|
||||
&alternate_small))
|
||||
{
|
||||
log_warning("graphics::d3d9", "no alternate SMALL mode is available for recovery");
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
D3DPRESENT_PARAMETERS temporary_parameters[2] {
|
||||
desired_parameters[0],
|
||||
desired_parameters[1]};
|
||||
D3DDISPLAYMODEEX temporary_modes[2] {
|
||||
desired_modes[0],
|
||||
desired_modes[1]};
|
||||
temporary_parameters[1].BackBufferWidth = alternate_small.Width;
|
||||
temporary_parameters[1].BackBufferHeight = alternate_small.Height;
|
||||
temporary_parameters[1].BackBufferFormat = alternate_small.Format;
|
||||
temporary_parameters[1].FullScreen_RefreshRateInHz = alternate_small.RefreshRate;
|
||||
temporary_modes[1] = alternate_small;
|
||||
|
||||
HRESULT temporary_result = device->ResetEx(temporary_parameters, temporary_modes);
|
||||
const bool temporary_settled =
|
||||
temporary_result == D3D_OK
|
||||
&& gfdm_wait_for_small_mode(desired_parameters[1].hDeviceWindow, alternate_small);
|
||||
|
||||
D3DPRESENT_PARAMETERS restore_parameters[2] {
|
||||
desired_parameters[0],
|
||||
desired_parameters[1]};
|
||||
D3DDISPLAYMODEEX restore_modes[2] {
|
||||
desired_modes[0],
|
||||
desired_modes[1]};
|
||||
HRESULT restore_result = device->ResetEx(restore_parameters, restore_modes);
|
||||
const bool restore_settled =
|
||||
restore_result == D3D_OK
|
||||
&& gfdm_wait_for_small_mode(desired_parameters[1].hDeviceWindow, desired_modes[1]);
|
||||
|
||||
if (temporary_result != D3D_OK) {
|
||||
return temporary_result;
|
||||
}
|
||||
if (restore_result != D3D_OK) {
|
||||
return restore_result;
|
||||
}
|
||||
if (!temporary_settled || !restore_settled) {
|
||||
return D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
return device->CheckDeviceState(desired_parameters[0].hDeviceWindow) == D3D_OK
|
||||
? D3D_OK
|
||||
: D3DERR_NOTAVAILABLE;
|
||||
}
|
||||
|
||||
bool WrappedIDirect3DDevice9::is_gfdm_two_head_exclusive() const {
|
||||
return ::gfdm_two_head_exclusive();
|
||||
}
|
||||
|
||||
bool WrappedIDirect3DDevice9::is_gfdm_logical_small_swapchain(
|
||||
UINT swapchain) const
|
||||
{
|
||||
return swapchain == gfdm_logical_small_swapchain;
|
||||
}
|
||||
|
||||
bool WrappedIDirect3DDevice9::is_gfdm_logical_side_swapchain(
|
||||
UINT swapchain) const
|
||||
{
|
||||
return swapchain > 0
|
||||
&& swapchain < GFDM_LOGICAL_HEAD_COUNT
|
||||
&& swapchain != gfdm_logical_small_swapchain;
|
||||
}
|
||||
|
||||
size_t WrappedIDirect3DDevice9::gfdm_hidden_side_swapchain_slot(
|
||||
UINT swapchain) const
|
||||
{
|
||||
assert(is_gfdm_logical_side_swapchain(swapchain));
|
||||
return swapchain < gfdm_logical_small_swapchain
|
||||
? swapchain
|
||||
: swapchain - 1;
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::set_gfdm_logical_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters)
|
||||
{
|
||||
if (presentation_parameters == nullptr) {
|
||||
gfdm_logical_group_parameters_valid = false;
|
||||
return;
|
||||
}
|
||||
std::copy_n(
|
||||
presentation_parameters,
|
||||
gfdm_logical_group_parameters.size(),
|
||||
gfdm_logical_group_parameters.begin());
|
||||
gfdm_logical_group_parameters_valid = true;
|
||||
}
|
||||
|
||||
FakeIDirect3DSwapChain9 *
|
||||
WrappedIDirect3DDevice9::ensure_gfdm_hidden_side_swapchain(UINT iSwapChain) {
|
||||
assert(is_gfdm_logical_side_swapchain(iSwapChain));
|
||||
|
||||
const size_t index = gfdm_hidden_side_swapchain_slot(iSwapChain);
|
||||
if (fake_sub_swapchain[index] == nullptr) {
|
||||
D3DPRESENT_PARAMETERS params {};
|
||||
if (gfdm_logical_group_parameters_valid) {
|
||||
params = gfdm_logical_group_parameters[iSwapChain];
|
||||
} else {
|
||||
params.BackBufferWidth = 1080;
|
||||
params.BackBufferHeight = 1920;
|
||||
params.BackBufferFormat = D3DFMT_X8R8G8B8;
|
||||
params.BackBufferCount = 1;
|
||||
params.MultiSampleType = D3DMULTISAMPLE_NONE;
|
||||
params.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
params.Windowed = FALSE;
|
||||
params.FullScreen_RefreshRateInHz = 60;
|
||||
params.EnableAutoDepthStencil = FALSE;
|
||||
params.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||
}
|
||||
fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(
|
||||
this,
|
||||
¶ms,
|
||||
is_d3d9ex,
|
||||
true);
|
||||
}
|
||||
return fake_sub_swapchain[index];
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::release_gfdm_hidden_side_swapchains() {
|
||||
for (UINT i = 1; i < GFDM_LOGICAL_HEAD_COUNT; i++) {
|
||||
if (!is_gfdm_logical_side_swapchain(i)) {
|
||||
continue;
|
||||
}
|
||||
const size_t index = gfdm_hidden_side_swapchain_slot(i);
|
||||
if (fake_sub_swapchain[index] != nullptr) {
|
||||
fake_sub_swapchain[index]->Release();
|
||||
fake_sub_swapchain[index] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::gfdm_disarm_present_mode_recovery() {
|
||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||
gfdm_recovery_armed = false;
|
||||
gfdm_recovery_consumed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void WrappedIDirect3DDevice9::gfdm_arm_present_mode_recovery(
|
||||
const D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *native_fullscreen_display_modes)
|
||||
{
|
||||
if (native_presentation_parameters == nullptr
|
||||
|| native_fullscreen_display_modes == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||
gfdm_recovery_parameters[0] = native_presentation_parameters[0];
|
||||
gfdm_recovery_parameters[1] = native_presentation_parameters[1];
|
||||
gfdm_recovery_modes[0] = native_fullscreen_display_modes[0];
|
||||
gfdm_recovery_modes[1] = native_fullscreen_display_modes[1];
|
||||
if (gfdm_recovery_modes[0].Size == 0) {
|
||||
gfdm_recovery_modes[0].Size = sizeof(D3DDISPLAYMODEEX);
|
||||
}
|
||||
if (gfdm_recovery_modes[1].Size == 0) {
|
||||
gfdm_recovery_modes[1].Size = sizeof(D3DDISPLAYMODEEX);
|
||||
}
|
||||
gfdm_recovery_armed =
|
||||
!gfdm_recovery_parameters[0].Windowed
|
||||
&& !gfdm_recovery_parameters[1].Windowed
|
||||
&& IsWindow(gfdm_recovery_parameters[1].hDeviceWindow);
|
||||
gfdm_recovery_consumed.store(
|
||||
!gfdm_recovery_armed,
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
WrappedIDirect3DDevice9::GfdmPresentModeRecoveryResult
|
||||
WrappedIDirect3DDevice9::gfdm_recover_present_mode_change(
|
||||
HRESULT *failure_result)
|
||||
{
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = D3D_OK;
|
||||
}
|
||||
if (!is_gfdm_two_head_exclusive() || !is_d3d9ex
|
||||
|| gfdm_recovery_consumed.exchange(
|
||||
true,
|
||||
std::memory_order_acq_rel))
|
||||
{
|
||||
return GfdmPresentModeRecoveryResult::NotAttempted;
|
||||
}
|
||||
if (GetCurrentThreadId() != gfdm_device_creation_thread_id) {
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = D3DERR_INVALIDCALL;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::Failed;
|
||||
}
|
||||
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> modes {};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gfdm_recovery_mutex);
|
||||
if (!gfdm_recovery_armed) {
|
||||
return GfdmPresentModeRecoveryResult::NotAttempted;
|
||||
}
|
||||
parameters = gfdm_recovery_parameters;
|
||||
modes = gfdm_recovery_modes;
|
||||
}
|
||||
|
||||
IDirect3D9 *raw_d3d = nullptr;
|
||||
HRESULT result = pReal->GetDirect3D(&raw_d3d);
|
||||
IDirect3D9Ex *raw_d3d_ex = nullptr;
|
||||
if (SUCCEEDED(result)) {
|
||||
result = raw_d3d != nullptr
|
||||
? raw_d3d->QueryInterface(IID_PPV_ARGS(&raw_d3d_ex))
|
||||
: E_FAIL;
|
||||
}
|
||||
D3DDEVICE_CREATION_PARAMETERS creation {};
|
||||
if (SUCCEEDED(result)) {
|
||||
result = pReal->GetCreationParameters(&creation);
|
||||
}
|
||||
if (FAILED(result) || raw_d3d_ex == nullptr) {
|
||||
if (raw_d3d_ex != nullptr) {
|
||||
raw_d3d_ex->Release();
|
||||
}
|
||||
if (raw_d3d != nullptr) {
|
||||
raw_d3d->Release();
|
||||
}
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = result;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::Failed;
|
||||
}
|
||||
|
||||
result = graphics_d3d9_gfdm_recover_two_head_present_mode(
|
||||
raw_d3d_ex,
|
||||
static_cast<IDirect3DDevice9Ex *>(pReal),
|
||||
creation.AdapterOrdinal,
|
||||
parameters.data(),
|
||||
modes.data());
|
||||
raw_d3d_ex->Release();
|
||||
raw_d3d->Release();
|
||||
|
||||
if (result == D3DERR_NOTAVAILABLE) {
|
||||
return GfdmPresentModeRecoveryResult::NotAttempted;
|
||||
}
|
||||
if (FAILED(result)) {
|
||||
if (failure_result != nullptr) {
|
||||
*failure_result = result;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::Failed;
|
||||
}
|
||||
return GfdmPresentModeRecoveryResult::ReadyToRetry;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
inline constexpr UINT GFDM_SIDE_WIDTH = 1080;
|
||||
inline constexpr UINT GFDM_SIDE_HEIGHT = 1920;
|
||||
inline constexpr UINT GFDM_SMALL_WIDTH = 800;
|
||||
inline constexpr UINT GFDM_SMALL_HEIGHT = 1280;
|
||||
inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4;
|
||||
inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1;
|
||||
|
||||
struct GfdmTwoHeadDeviceState {
|
||||
explicit GfdmTwoHeadDeviceState(
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
UINT logical_small_swapchain = 2)
|
||||
: presentation_parameters(presentation_parameters),
|
||||
fullscreen_display_modes(fullscreen_display_modes),
|
||||
logical_small_swapchain(logical_small_swapchain) {}
|
||||
|
||||
void use_native_parameters() {
|
||||
presentation_parameters = native_presentation_parameters.data();
|
||||
fullscreen_display_modes = source_fullscreen_display_modes != nullptr
|
||||
? native_fullscreen_display_modes.data()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> native_presentation_parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> native_fullscreen_display_modes {};
|
||||
std::array<D3DPRESENT_PARAMETERS, 2> recovery_parameters {};
|
||||
std::array<D3DDISPLAYMODEEX, 2> recovery_modes {};
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters;
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes;
|
||||
D3DDISPLAYMODEEX *const source_fullscreen_display_modes = fullscreen_display_modes;
|
||||
UINT logical_small_swapchain;
|
||||
bool recovery_candidate = false;
|
||||
};
|
||||
|
||||
bool gfdm_two_head_exclusive();
|
||||
bool is_fake_subscreen_adapter(UINT adapter);
|
||||
void get_fake_subscreen_display_mode(UINT adapter, D3DDISPLAYMODE *mode);
|
||||
void get_fake_subscreen_display_mode_ex(UINT adapter, D3DDISPLAYMODEEX *mode);
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters(
|
||||
const D3DPRESENT_PARAMETERS *logical_presentation_parameters,
|
||||
const D3DDISPLAYMODEEX *logical_fullscreen_display_modes,
|
||||
D3DPRESENT_PARAMETERS *native_presentation_parameters,
|
||||
D3DDISPLAYMODEEX *native_fullscreen_display_modes,
|
||||
UINT *logical_small_swapchain,
|
||||
const char *operation);
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_remap_two_head_group_parameters(
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation);
|
||||
|
||||
void graphics_d3d9_gfdm_align_two_head_refresh_to_desktop(
|
||||
IDirect3D9Ex *d3d,
|
||||
UINT master_adapter,
|
||||
D3DPRESENT_PARAMETERS *presentation_parameters,
|
||||
D3DDISPLAYMODEEX *fullscreen_display_modes,
|
||||
const char *operation);
|
||||
|
||||
HRESULT validate_gfdm_two_head_exclusive(
|
||||
IDirect3D9 *d3d,
|
||||
UINT adapter,
|
||||
D3DDEVTYPE device_type,
|
||||
DWORD behavior_flags,
|
||||
const D3DPRESENT_PARAMETERS *presentation_parameters);
|
||||
|
||||
HRESULT graphics_d3d9_gfdm_recover_two_head_present_mode(
|
||||
IDirect3D9Ex *d3d,
|
||||
IDirect3DDevice9Ex *device,
|
||||
UINT master_adapter,
|
||||
const D3DPRESENT_PARAMETERS *desired_parameters,
|
||||
const D3DDISPLAYMODEEX *desired_modes);
|
||||
@@ -26,7 +26,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::QueryInterface(REFIID riid
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (//riid == __uuidof(IUnknown) || Ignore IUnknown, it's often queried to test object equality between different interfaces
|
||||
if ((riid == IID_IUnknown && pDev->is_gfdm_two_head_exclusive()) ||
|
||||
riid == IID_IDirect3DSwapChain9 ||
|
||||
riid == IID_IDirect3DSwapChain9Ex)
|
||||
{
|
||||
@@ -89,7 +89,42 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc
|
||||
graphics_d3d9_on_present(pDev->hFocusWindow, pDev->pReal, pDev);
|
||||
}
|
||||
|
||||
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags));
|
||||
HRESULT result = pReal->Present(
|
||||
pSourceRect,
|
||||
pDestRect,
|
||||
hDestWindowOverride,
|
||||
pDirtyRegion,
|
||||
dwFlags);
|
||||
|
||||
// Some drivers report S_PRESENT_MODE_CHANGED after Windows moves the portrait SMALL
|
||||
// head into the requested exclusive mode, leaving both group heads black. Toggle SMALL
|
||||
// through another supported mode and restore it once, then retry the interrupted MAIN
|
||||
// present. Recovery is restricted to the creation thread and consumes its armed state,
|
||||
// so subsequent presents cannot repeat the mode switch.
|
||||
if (pDev->is_gfdm_two_head_exclusive()
|
||||
&& native_group_head == NativeGroupHead::Main
|
||||
&& result == S_PRESENT_MODE_CHANGED)
|
||||
{
|
||||
HRESULT recovery_failure = D3D_OK;
|
||||
const auto recovery = pDev->gfdm_recover_present_mode_change(
|
||||
&recovery_failure);
|
||||
if (recovery
|
||||
== WrappedIDirect3DDevice9::GfdmPresentModeRecoveryResult::ReadyToRetry)
|
||||
{
|
||||
result = pReal->Present(
|
||||
pSourceRect,
|
||||
pDestRect,
|
||||
hDestWindowOverride,
|
||||
pDirtyRegion,
|
||||
dwFlags);
|
||||
} else if (recovery
|
||||
== WrappedIDirect3DDevice9::GfdmPresentModeRecoveryResult::Failed
|
||||
&& FAILED(recovery_failure))
|
||||
{
|
||||
result = recovery_failure;
|
||||
}
|
||||
}
|
||||
CHECK_RESULT(result);
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||
CHECK_RESULT(pReal->GetFrontBufferData(pDestSurface));
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
interface WrappedIDirect3DDevice9;
|
||||
|
||||
struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
WrappedIDirect3DSwapChain9(WrappedIDirect3DDevice9 *dev, IDirect3DSwapChain9 *orig) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(false)
|
||||
enum class NativeGroupHead { Unknown, Main, Small };
|
||||
|
||||
WrappedIDirect3DSwapChain9(
|
||||
WrappedIDirect3DDevice9 *dev,
|
||||
IDirect3DSwapChain9 *orig,
|
||||
NativeGroupHead native_group_head = NativeGroupHead::Unknown) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(false), native_group_head(native_group_head)
|
||||
{
|
||||
IDirect3DSwapChain9Ex *swapchain = nullptr;
|
||||
|
||||
@@ -16,8 +21,11 @@ struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
}
|
||||
}
|
||||
|
||||
WrappedIDirect3DSwapChain9(WrappedIDirect3DDevice9 *dev, IDirect3DSwapChain9Ex *orig) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(true)
|
||||
WrappedIDirect3DSwapChain9(
|
||||
WrappedIDirect3DDevice9 *dev,
|
||||
IDirect3DSwapChain9Ex *orig,
|
||||
NativeGroupHead native_group_head = NativeGroupHead::Unknown) :
|
||||
pDev(dev), pReal(orig), is_d3d9ex(true), native_group_head(native_group_head)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -53,6 +61,7 @@ struct WrappedIDirect3DSwapChain9 : IDirect3DSwapChain9Ex {
|
||||
|
||||
IDirect3DSwapChain9 *pReal;
|
||||
bool is_d3d9ex = false;
|
||||
NativeGroupHead native_group_head = NativeGroupHead::Unknown;
|
||||
|
||||
bool should_run_hooks = true;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user