gitadora: allow a landscape monitor to display subscreen (#899)
## Link to GitHub Issue or related Pull Request, if one exists #0 ## Description of change Arena Model's SMALL touch subscreen is a portrait (800x1280) panel - however, it's common for people to set up a touch screen in landscape orientation since IIDX/SDVX/popn all use landscape. Add a new option, `-gdsublandscape`, which forces the subscreen to launch at 1080p landscape. The image is rendered with black bars on the sides. Touches will be unaligned initially; user needs to go into test mode and recalibrate. In reality this option is just a short hand for `-forceressub 1920,1080`. Wire up `-forceressub` which previously didn't work for gitadora, so that a resolution other than 1080p can be used in either orientation. In other games `-forceressub` just forces the rendering resolution and IIDX/SDVX/popn's game engine automatically stretches to fill... however for gitadora it works differently since we want to keep the aspect ratio of the original screen; it instead hijacks the back buffer and makes the game draw to an intermediate buffer. ## Testing
This commit is contained in:
@@ -39,10 +39,38 @@ namespace games::gitadora {
|
||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
||||
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
||||
bool ARENA_SUBSCREEN_LANDSCAPE = false;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
bool ALLOW_REALTEK_AUDIO = false;
|
||||
bool NATIVE_TOUCH = false;
|
||||
|
||||
std::pair<UINT, UINT> 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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
@@ -21,6 +22,7 @@ namespace games::gitadora {
|
||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
||||
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
||||
extern bool ARENA_SUBSCREEN_LANDSCAPE;
|
||||
extern std::optional<std::string> 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<UINT, UINT> 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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<D3DPRESENT_PARAMETERS, 4> 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<D3DPRESENT_PARAMETERS, 2> gfdm_recovery_parameters {};
|
||||
|
||||
@@ -16,6 +16,54 @@ bool gfdm_two_head_exclusive() {
|
||||
&& !GRAPHICS_WINDOWED;
|
||||
}
|
||||
|
||||
static std::pair<UINT, UINT> 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<LONG>(desc.Width),
|
||||
static_cast<LONG>(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<std::mutex> lock(gfdm_recovery_mutex);
|
||||
gfdm_recovery_armed = false;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3425,6 +3425,19 @@ static const std::vector<OptionDefinition> 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<std::string> &launcher::get_categories(Options::OptionsCategory category) {
|
||||
|
||||
@@ -323,7 +323,8 @@ namespace launcher {
|
||||
OBSWebSocketPassword,
|
||||
OBSWebSocketDebug,
|
||||
ScreenshotIncludeOverlay,
|
||||
ScreenshotSubscreens
|
||||
ScreenshotSubscreens,
|
||||
GitaDoraSubscreenLandscape
|
||||
};
|
||||
|
||||
enum class OptionsCategory {
|
||||
|
||||
Reference in New Issue
Block a user