diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index 998457e..460b1b6 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -39,10 +39,38 @@ namespace games::gitadora { std::optional PICK_ALGO = socd::SocdAlgorithm::PreferRecent; std::optional ARENA_WINDOW_COUNT = std::nullopt; bool ARENA_TWO_HEAD_EXCLUSIVE = false; + bool ARENA_SUBSCREEN_LANDSCAPE = false; std::optional ASIO_DRIVER = std::nullopt; bool ALLOW_REALTEK_AUDIO = false; bool NATIVE_TOUCH = false; + std::pair arena_subscreen_host_size() { + if (GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) { + return GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value(); + } + if (ARENA_SUBSCREEN_LANDSCAPE) { + return { ARENA_SUBSCREEN_LANDSCAPE_WIDTH, ARENA_SUBSCREEN_LANDSCAPE_HEIGHT }; + } + return { ARENA_SUBSCREEN_WIDTH, ARENA_SUBSCREEN_HEIGHT }; + } + + RECT arena_subscreen_content_rect(LONG host_width, LONG host_height) { + if (host_width <= 0 || host_height <= 0) { + return RECT {}; + } + + LONG width = MulDiv(host_height, ARENA_SUBSCREEN_WIDTH, ARENA_SUBSCREEN_HEIGHT); + LONG height = host_height; + if (width > host_width) { + width = host_width; + height = MulDiv(host_width, ARENA_SUBSCREEN_HEIGHT, ARENA_SUBSCREEN_WIDTH); + } + + const LONG left = (host_width - width) / 2; + const LONG top = (host_height - height) / 2; + return RECT { left, top, left + width, top + height }; + } + /* * Prevent GitaDora from creating folders on F drive */ @@ -343,6 +371,23 @@ namespace games::gitadora { "gitadora", "arena model: unsupported window count: {}", count); } + + if (ARENA_SUBSCREEN_LANDSCAPE && !ARENA_TWO_HEAD_EXCLUSIVE) { + log_warning( + "gitadora", + "arena model: landscape subscreen needs full screen two-window mode, ignoring"); + ARENA_SUBSCREEN_LANDSCAPE = false; + } + if (ARENA_TWO_HEAD_EXCLUSIVE) { + const auto [host_width, host_height] = arena_subscreen_host_size(); + if (host_width != ARENA_SUBSCREEN_WIDTH + || host_height != ARENA_SUBSCREEN_HEIGHT) { + log_info( + "gitadora", + "arena model: SMALL head runs at {}x{}, subscreen is scaled to fit", + host_width, host_height); + } + } } #endif diff --git a/src/spice2x/games/gitadora/gitadora.h b/src/spice2x/games/gitadora/gitadora.h index 32aa8e5..7e6edaf 100644 --- a/src/spice2x/games/gitadora/gitadora.h +++ b/src/spice2x/games/gitadora/gitadora.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -21,6 +22,7 @@ namespace games::gitadora { extern std::optional PICK_ALGO; extern std::optional ARENA_WINDOW_COUNT; extern bool ARENA_TWO_HEAD_EXCLUSIVE; + extern bool ARENA_SUBSCREEN_LANDSCAPE; extern std::optional ASIO_DRIVER; extern bool ALLOW_REALTEK_AUDIO; extern bool NATIVE_TOUCH; @@ -29,6 +31,18 @@ namespace games::gitadora { static constexpr int ARENA_SUBSCREEN_WIDTH = 800; static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280; + // used when a landscape monitor drives the SMALL head and -forceressub is unset + static constexpr int ARENA_SUBSCREEN_LANDSCAPE_WIDTH = 1920; + static constexpr int ARENA_SUBSCREEN_LANDSCAPE_HEIGHT = 1080; + + // resolution the SMALL head actually runs at; the portrait panel size unless + // -forceressub or the landscape option overrides it + std::pair arena_subscreen_host_size(); + + // area of the host the portrait subscreen occupies, centered and aspect-preserved; + // whatever is left over on either side stays black + RECT arena_subscreen_content_rect(LONG host_width, LONG host_height); + class GitaDoraGame : public games::Game { public: GitaDoraGame(); diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index 966f943..719f561 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -1033,14 +1033,6 @@ 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; @@ -1096,7 +1088,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( params->BackBufferHeight, params->BackBufferWidth); std::swap(params->BackBufferWidth, params->BackBufferHeight); } - } else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) { + } else if (i == 1 && !gfdm_two_head_exclusive() + && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) { log_misc( "graphics::d3d9", "use custom sub resolution {}x{} => {}x{}", @@ -1140,7 +1133,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( } else if (GRAPHICS_FS_ORIENTATION_SWAP) { std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height); } - } else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) { + } else if (i == 1 && !gfdm_two_head_exclusive() + && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) { fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first; fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second; } @@ -1290,14 +1284,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( 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]; - } + gfdm_publish_two_head_parameters( + pPresentationParameters, + pFullscreenDisplayMode, + gfdm_parameters); } // check for error diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp index 34a8863..e7bba5f 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp @@ -157,6 +157,9 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() { } } + // holds a reference on the device, so it has to go before the counts are compared + this->gfdm_small_head.release(); + d3d9_readback::release_device_resources(this->pReal); if (overlay::ENABLED) { @@ -634,6 +637,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset( overlay::OVERLAY->reset_invalidate(); } + gfdm_small_head.release(); + // Reset refuses to run while any default pool resource is outstanding d3d9_readback::discard_snapshot_targets(pReal); @@ -662,6 +667,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Present( graphics_d3d9_on_present(hFocusWindow, pReal, this); + // an adapter group device presents every head at once, so the SMALL head has to be + // composed here as well as in its own swap chain + if (gfdm_small_head.scaled()) { + gfdm_small_head.compose(pReal); + } + CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion)); } @@ -683,6 +694,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer( if (is_gfdm_two_head_exclusive() && is_gfdm_logical_small_swapchain(iSwapChain)) { + if (gfdm_small_head.scaled() + && iBackBuffer == 0 + && Type == D3DBACKBUFFER_TYPE_MONO) + { + CHECK_RESULT(gfdm_small_head.backbuffer(pReal, ppBackBuffer)); + } CHECK_RESULT(pReal->GetBackBuffer( GFDM_NATIVE_SMALL_SWAPCHAIN, iBackBuffer, @@ -2324,6 +2341,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx( overlay::OVERLAY->reset_invalidate(); } + gfdm_small_head.release(); + // ResetEx refuses to run while any default pool resource is outstanding d3d9_readback::discard_snapshot_targets(pReal); @@ -2341,14 +2360,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx( } 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]; - } + gfdm_publish_two_head_parameters( + pPresentationParameters, + pFullscreenDisplayMode, + gfdm_parameters); } // recreate overlay diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h index 586f3c4..dffb25e 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h @@ -11,6 +11,7 @@ #include "util/logging.h" #include "d3d9_fake_swapchain.h" +#include "d3d9_gfdm.h" #include "d3d9_swapchain.h" /* @@ -279,6 +280,7 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex { std::array gfdm_logical_group_parameters {}; bool gfdm_logical_group_parameters_valid = false; IDirect3D9 *gfdm_parent_d3d = nullptr; + GfdmSmallHead gfdm_small_head; std::mutex gfdm_recovery_mutex; std::array gfdm_recovery_parameters {}; diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.cpp index 2f696b7..280c4c7 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.cpp @@ -16,6 +16,54 @@ bool gfdm_two_head_exclusive() { && !GRAPHICS_WINDOWED; } +static std::pair gfdm_small_head_size() { + if (!gfdm_two_head_exclusive()) { + return { GFDM_SMALL_WIDTH, GFDM_SMALL_HEIGHT }; + } + return games::gitadora::arena_subscreen_host_size(); +} + +// scaling is only needed when the head is not the panel size the game draws into +static bool gfdm_small_head_scaled() { + const auto [width, height] = gfdm_small_head_size(); + return width != GFDM_SMALL_WIDTH || height != GFDM_SMALL_HEIGHT; +} + +// Hand the resolved MAIN/SMALL heads back to the game's own array. The SMALL entry keeps +// the portrait size the game asked for: that is what it renders, and the next reset finds +// the SMALL head by matching it. +void gfdm_publish_two_head_parameters( + D3DPRESENT_PARAMETERS *logical_presentation_parameters, + D3DDISPLAYMODEEX *logical_fullscreen_display_modes, + const GfdmTwoHeadDeviceState &state) +{ + if (logical_presentation_parameters == nullptr) { + return; + } + + const UINT small = state.logical_small_swapchain; + const bool publish_modes = logical_fullscreen_display_modes != nullptr + && state.fullscreen_display_modes != nullptr; + + logical_presentation_parameters[0] = state.presentation_parameters[0]; + logical_presentation_parameters[small] = state.presentation_parameters[1]; + if (publish_modes) { + logical_fullscreen_display_modes[0] = state.fullscreen_display_modes[0]; + logical_fullscreen_display_modes[small] = state.fullscreen_display_modes[1]; + } + + if (!gfdm_small_head_scaled()) { + return; + } + + logical_presentation_parameters[small].BackBufferWidth = GFDM_SMALL_WIDTH; + logical_presentation_parameters[small].BackBufferHeight = GFDM_SMALL_HEIGHT; + if (publish_modes) { + logical_fullscreen_display_modes[small].Width = GFDM_SMALL_WIDTH; + logical_fullscreen_display_modes[small].Height = GFDM_SMALL_HEIGHT; + } +} + HRESULT graphics_d3d9_gfdm_select_two_head_group_parameters( const D3DPRESENT_PARAMETERS *logical_presentation_parameters, const D3DDISPLAYMODEEX *logical_fullscreen_display_modes, @@ -186,6 +234,26 @@ HRESULT graphics_d3d9_gfdm_remap_two_head_group_parameters( return D3DERR_INVALIDCALL; } + // the game keeps rendering the portrait subscreen; only the head it is scanned out + // on changes size, and the scaling happens when the head is presented + if (gfdm_small_head_scaled()) { + const auto [host_width, host_height] = gfdm_small_head_size(); + log_info( + "graphics::d3d9", + "two-head exclusive: {} SMALL head {}x{} -> {}x{}", + operation, + secondary.BackBufferWidth, + secondary.BackBufferHeight, + host_width, + host_height); + secondary.BackBufferWidth = host_width; + secondary.BackBufferHeight = host_height; + if (fullscreen_display_modes != nullptr) { + fullscreen_display_modes[1].Width = host_width; + fullscreen_display_modes[1].Height = host_height; + } + } + return D3D_OK; } @@ -318,20 +386,21 @@ HRESULT validate_gfdm_two_head_exclusive( const auto &main = presentation_parameters[0]; const auto &small_params = presentation_parameters[1]; + const auto [expected_small_width, expected_small_height] = gfdm_small_head_size(); 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) + if (small_params.BackBufferWidth != expected_small_width + || small_params.BackBufferHeight != expected_small_height) { log_warning( "graphics::d3d9", "SMALL head must be {}x{}; got {}x{}", - GFDM_SMALL_WIDTH, - GFDM_SMALL_HEIGHT, + expected_small_width, + expected_small_height, small_params.BackBufferWidth, small_params.BackBufferHeight); return D3DERR_INVALIDCALL; @@ -547,6 +616,7 @@ void WrappedIDirect3DDevice9::set_gfdm_logical_group_parameters( { if (presentation_parameters == nullptr) { gfdm_logical_group_parameters_valid = false; + gfdm_small_head.resolve(nullptr); return; } std::copy_n( @@ -554,6 +624,8 @@ void WrappedIDirect3DDevice9::set_gfdm_logical_group_parameters( gfdm_logical_group_parameters.size(), gfdm_logical_group_parameters.begin()); gfdm_logical_group_parameters_valid = true; + gfdm_small_head.resolve( + &gfdm_logical_group_parameters[gfdm_logical_small_swapchain]); } FakeIDirect3DSwapChain9 * @@ -599,6 +671,110 @@ void WrappedIDirect3DDevice9::release_gfdm_hidden_side_swapchains() { } } +void GfdmSmallHead::resolve(const D3DPRESENT_PARAMETERS *logical_small_parameters) { + release(); + + active = gfdm_small_head_scaled(); + format = D3DFMT_X8R8G8B8; + multisample = D3DMULTISAMPLE_NONE; + multisample_quality = 0; + if (!active || logical_small_parameters == nullptr) { + return; + } + + if (logical_small_parameters->BackBufferFormat != D3DFMT_UNKNOWN) { + format = logical_small_parameters->BackBufferFormat; + } + multisample = logical_small_parameters->MultiSampleType; + multisample_quality = logical_small_parameters->MultiSampleQuality; +} + +void GfdmSmallHead::apply_logical_size(UINT *width, UINT *height) const { + if (!active) { + return; + } + if (width != nullptr) { + *width = GFDM_SMALL_WIDTH; + } + if (height != nullptr) { + *height = GFDM_SMALL_HEIGHT; + } +} + +HRESULT GfdmSmallHead::backbuffer(IDirect3DDevice9 *device, IDirect3DSurface9 **out) { + if (device == nullptr || out == nullptr || !active) { + return D3DERR_INVALIDCALL; + } + + if (surface == nullptr) { + const HRESULT result = device->CreateRenderTarget( + GFDM_SMALL_WIDTH, + GFDM_SMALL_HEIGHT, + format, + multisample, + multisample_quality, + FALSE, + &surface, + nullptr); + if (FAILED(result)) { + log_warning( + "graphics::d3d9", + "two-head exclusive: could not create the portrait SMALL surface, hr={}", + FMT_HRESULT(result)); + surface = nullptr; + return result; + } + device->ColorFill(surface, nullptr, D3DCOLOR_XRGB(0, 0, 0)); + } + + surface->AddRef(); + *out = surface; + return D3D_OK; +} + +void GfdmSmallHead::release() { + if (surface != nullptr) { + surface->Release(); + surface = nullptr; + } +} + +HRESULT GfdmSmallHead::compose(IDirect3DDevice9 *device) { + IDirect3DSurface9 *proxy = nullptr; + HRESULT result = backbuffer(device, &proxy); + if (FAILED(result)) { + return result; + } + + IDirect3DSurface9 *head = nullptr; + result = device->GetBackBuffer( + GFDM_NATIVE_SMALL_SWAPCHAIN, + 0, + D3DBACKBUFFER_TYPE_MONO, + &head); + if (FAILED(result)) { + proxy->Release(); + return result; + } + + D3DSURFACE_DESC desc {}; + result = head->GetDesc(&desc); + if (SUCCEEDED(result)) { + const RECT content = games::gitadora::arena_subscreen_content_rect( + static_cast(desc.Width), + static_cast(desc.Height)); + + // discard swap effect leaves the whole head undefined every frame, so the bars + // have to be repainted along with the image + device->ColorFill(head, nullptr, D3DCOLOR_XRGB(0, 0, 0)); + result = device->StretchRect(proxy, nullptr, head, &content, D3DTEXF_LINEAR); + } + + head->Release(); + proxy->Release(); + return result; +} + void WrappedIDirect3DDevice9::gfdm_disarm_present_mode_recovery() { std::lock_guard lock(gfdm_recovery_mutex); gfdm_recovery_armed = false; diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h index 1c4fd3e..87a61fe 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_gfdm.h @@ -13,6 +13,34 @@ inline constexpr UINT GFDM_SMALL_HEIGHT = games::gitadora::ARENA_SUBSCREEN_HEIGH inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4; inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1; +// Owns the portrait surface the game draws into while the SMALL head is scanned out at a +// different resolution, and composes it onto the real head. Allocates nothing and reports +// nothing until resolve() finds a head that is not the panel size. +struct GfdmSmallHead { + GfdmSmallHead() = default; + ~GfdmSmallHead() { release(); } + GfdmSmallHead(const GfdmSmallHead &) = delete; + GfdmSmallHead &operator=(const GfdmSmallHead &) = delete; + + // settled with the device, so it cannot change under the game mid-session + void resolve(const D3DPRESENT_PARAMETERS *logical_small_parameters); + bool scaled() const { return active; } + + HRESULT backbuffer(IDirect3DDevice9 *device, IDirect3DSurface9 **out); + HRESULT compose(IDirect3DDevice9 *device); + void release(); + + // the game only ever sees the portrait size it asked for + void apply_logical_size(UINT *width, UINT *height) const; + +private: + bool active = false; + D3DFORMAT format = D3DFMT_X8R8G8B8; + D3DMULTISAMPLE_TYPE multisample = D3DMULTISAMPLE_NONE; + DWORD multisample_quality = 0; + IDirect3DSurface9 *surface = nullptr; +}; + struct GfdmTwoHeadDeviceState { explicit GfdmTwoHeadDeviceState( D3DPRESENT_PARAMETERS *presentation_parameters, @@ -41,6 +69,10 @@ struct GfdmTwoHeadDeviceState { }; bool gfdm_two_head_exclusive(); +void gfdm_publish_two_head_parameters( + D3DPRESENT_PARAMETERS *logical_presentation_parameters, + D3DDISPLAYMODEEX *logical_fullscreen_display_modes, + const GfdmTwoHeadDeviceState &state); 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); diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_swapchain.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_swapchain.cpp index 3bb4b47..e035972 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_swapchain.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_swapchain.cpp @@ -21,6 +21,12 @@ } \ return ret +// inert unless this is the SMALL head and it is being scaled onto a differently sized monitor +static bool scales_small_head(const WrappedIDirect3DSwapChain9 *chain) { + return chain->native_group_head == WrappedIDirect3DSwapChain9::NativeGroupHead::Small + && chain->pDev->gfdm_small_head.scaled(); +} + HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) { if (ppvObj == nullptr) { return E_POINTER; @@ -89,6 +95,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc graphics_d3d9_on_present(pDev->hFocusWindow, pDev->pReal, pDev); } + if (scales_small_head(this)) { + pDev->gfdm_small_head.compose(pDev->pReal); + } + HRESULT result = pReal->Present( pSourceRect, pDestRect, @@ -138,6 +148,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) { + if (scales_small_head(this) && iBackBuffer == 0 && Type == D3DBACKBUFFER_TYPE_MONO) { + CHECK_RESULT(pDev->gfdm_small_head.backbuffer(pDev->pReal, ppBackBuffer)); + } + CHECK_RESULT(pReal->GetBackBuffer(iBackBuffer, Type, ppBackBuffer)); } HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) { @@ -159,7 +173,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetDevice(IDirect3DDevice9 HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetPresentParameters( D3DPRESENT_PARAMETERS *pPresentationParameters) { - CHECK_RESULT(pReal->GetPresentParameters(pPresentationParameters)); + HRESULT result = pReal->GetPresentParameters(pPresentationParameters); + if (SUCCEEDED(result) && pPresentationParameters != nullptr && scales_small_head(this)) { + pDev->gfdm_small_head.apply_logical_size( + &pPresentationParameters->BackBufferWidth, + &pPresentationParameters->BackBufferHeight); + } + + CHECK_RESULT(result); } /* diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 47339ac..f130fc5 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -693,6 +693,9 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::GitaDoraArenaRealtekAccess].value_bool()) { games::gitadora::ALLOW_REALTEK_AUDIO = true; } + if (options[launcher::Options::GitaDoraSubscreenLandscape].value_bool()) { + games::gitadora::ARENA_SUBSCREEN_LANDSCAPE = true; + } if (options[launcher::Options::LoadNostalgiaModule].value_bool()) { attach_nostalgia = true; } diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 22daf22..b10715c 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -3425,6 +3425,19 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Bool, .category = "General Overlay", }, + { + // GitaDoraSubscreenLandscape + .title = "GitaDora Arena Landscape Subscreen (EXPERIMENTAL)", + .name = "gdsublandscape", + .desc = "For Arena Model full screen two-window mode: drive the SMALL touch subscreen " + "with a landscape monitor instead of a portrait one.\n\n" + "The portrait image is centered and black bars fill the space on either side.\n\n" + "Launches at 1920x1080; use -forceressub if you need a different resolution.", + .type = OptionType::Bool, + .game_name = "GitaDora", + .category = "Game Options", + .quick_setting_category = "Game", + }, }; const std::vector &launcher::get_categories(Options::OptionsCategory category) { diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 24ff56a..6a7627d 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -323,7 +323,8 @@ namespace launcher { OBSWebSocketPassword, OBSWebSocketDebug, ScreenshotIncludeOverlay, - ScreenshotSubscreens + ScreenshotSubscreens, + GitaDoraSubscreenLandscape }; enum class OptionsCategory {