mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-10-07 02:28:13 -07:00
Compare commits
5 Commits
82e0c053d0
...
26-08-16
| Author | SHA1 | Date | |
|---|---|---|---|
| adf4cccd4a | |||
| bf8e194685 | |||
| a2e508208c | |||
| f5888609a8 | |||
| c6cd72c528 |
@@ -12,7 +12,9 @@
|
|||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
#include "touch/native/nativetouchhook.h"
|
#include "touch/native/nativetouchhook.h"
|
||||||
|
#include "touch/native/transform.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "games/iidx/iidx.h"
|
#include "games/iidx/iidx.h"
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
@@ -40,6 +42,35 @@ namespace api::modules {
|
|||||||
return nativetouch::inject::inject_synthetic_touch(position, true);
|
return nativetouch::inject::inject_synthetic_touch(position, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// map API coordinates onto the touch space SDVX reads, which depends on how it is displayed
|
||||||
|
static void sdvx_touch_errata(
|
||||||
|
int &x, int &y, bool use_native, int canvas_w, int canvas_h) {
|
||||||
|
|
||||||
|
// windowed coordinates already match the sub screen window they land on
|
||||||
|
if (GRAPHICS_WINDOWED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// landscape mode: native injection hands the game these coordinates
|
||||||
|
// unchanged, so apply the rotation the touchscreen gets, while wintouchemu instead
|
||||||
|
// rotates them later through the subscreen overlay
|
||||||
|
if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||||
|
if (use_native) {
|
||||||
|
POINT position { x, y };
|
||||||
|
if (nativetouch::transform::sdvx_landscape_rotate(&position, canvas_w, canvas_h)) {
|
||||||
|
x = position.x;
|
||||||
|
y = position.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate into the portrait touch space
|
||||||
|
const int x_raw = x;
|
||||||
|
x = canvas_w - y;
|
||||||
|
y = x_raw;
|
||||||
|
}
|
||||||
|
|
||||||
Touch::Touch() : Module("touch") {
|
Touch::Touch() : Module("touch") {
|
||||||
is_sdvx = avs::game::is_model("KFC");
|
is_sdvx = avs::game::is_model("KFC");
|
||||||
|
|
||||||
@@ -55,8 +86,8 @@ namespace api::modules {
|
|||||||
native_canvas_w = 0;
|
native_canvas_w = 0;
|
||||||
native_canvas_h = 0;
|
native_canvas_h = 0;
|
||||||
if (is_sdvx) {
|
if (is_sdvx) {
|
||||||
// windowed and landscape API coordinates already match the primary screen orientation;
|
// windowed API coordinates land on the sub screen window as-is; fullscreen
|
||||||
// fullscreen portrait coordinates are rotated by apply_touch_errata
|
// coordinates are rotated into the game's touch space by apply_touch_errata
|
||||||
const bool landscape_coordinates =
|
const bool landscape_coordinates =
|
||||||
GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
|
GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
|
||||||
native_canvas_w = landscape_coordinates ? 1920 : 1080;
|
native_canvas_w = landscape_coordinates ? 1920 : 1080;
|
||||||
@@ -69,6 +100,10 @@ namespace api::modules {
|
|||||||
// pop'n music API touch surface
|
// pop'n music API touch surface
|
||||||
native_canvas_w = 1280;
|
native_canvas_w = 1280;
|
||||||
native_canvas_h = 800;
|
native_canvas_h = 800;
|
||||||
|
} else if (games::gitadora::is_arena_model()) {
|
||||||
|
// GITADORA arena SMALL subscreen, either in its own window or in the overlay
|
||||||
|
native_canvas_w = games::gitadora::ARENA_SUBSCREEN_WIDTH;
|
||||||
|
native_canvas_h = games::gitadora::ARENA_SUBSCREEN_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
||||||
@@ -212,19 +247,14 @@ namespace api::modules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Touch::apply_touch_errata(int &x, int &y) {
|
void Touch::apply_touch_errata(int &x, int &y) {
|
||||||
int x_raw = x;
|
|
||||||
int y_raw = y;
|
|
||||||
|
|
||||||
if (is_tdj_fhd) {
|
if (is_tdj_fhd) {
|
||||||
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
|
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
|
||||||
// we don't know what screen is being shown on the companion and the API doesn't specify
|
// we don't know what screen is being shown on the companion and the API doesn't specify
|
||||||
// the target of the touch events so just assume it's the sub screen
|
// the target of the touch events so just assume it's the sub screen
|
||||||
x = x_raw * 1920 / 1280;
|
x = x * 1920 / 1280;
|
||||||
y = y_raw * 1080 / 720;
|
y = y * 1080 / 720;
|
||||||
} else if (is_sdvx && !GRAPHICS_WINDOWED && !GRAPHICS_FS_ORIENTATION_SWAP) {
|
} else if (is_sdvx) {
|
||||||
// rotate API coordinates into SDVX's portrait touch space
|
sdvx_touch_errata(x, y, use_native, native_canvas_w, native_canvas_h);
|
||||||
x = 1080 - y_raw;
|
|
||||||
y = x_raw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace games::gitadora {
|
|||||||
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
bool ARENA_TWO_HEAD_EXCLUSIVE = false;
|
||||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||||
bool ALLOW_REALTEK_AUDIO = false;
|
bool ALLOW_REALTEK_AUDIO = false;
|
||||||
|
bool NATIVE_TOUCH = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prevent GitaDora from creating folders on F drive
|
* Prevent GitaDora from creating folders on F drive
|
||||||
@@ -809,18 +810,17 @@ namespace games::gitadora {
|
|||||||
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
|
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single-window mode needs touch injection for its overlay. Two-head fullscreen
|
// touch injection drives mouse-as-touch and API touch for the subscreen,
|
||||||
// mode uses the real SMALL output, so it only needs the display-topology shim.
|
// no matter whether it is drawn by the overlay (single-window mode) or by
|
||||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
// the dedicated SMALL window
|
||||||
// enable touch hook for subscreen overlay
|
NATIVE_TOUCH = !wintouchemu::FORCE &&
|
||||||
const auto native_touch_ready = !wintouchemu::FORCE &&
|
|
||||||
nativetouch::hook(avs::game::DLL_INSTANCE);
|
nativetouch::hook(avs::game::DLL_INSTANCE);
|
||||||
if (!native_touch_ready) {
|
if (!NATIVE_TOUCH && GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
|
// the legacy fallback can only feed the subscreen overlay
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#if !SPICE_XP
|
#if !SPICE_XP
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ namespace games::gitadora {
|
|||||||
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
extern bool ARENA_TWO_HEAD_EXCLUSIVE;
|
||||||
extern std::optional<std::string> ASIO_DRIVER;
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
extern bool ALLOW_REALTEK_AUDIO;
|
extern bool ALLOW_REALTEK_AUDIO;
|
||||||
|
extern bool NATIVE_TOUCH;
|
||||||
|
|
||||||
|
// arena SMALL subscreen (touch panel) resolution
|
||||||
|
static constexpr int ARENA_SUBSCREEN_WIDTH = 800;
|
||||||
|
static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280;
|
||||||
|
|
||||||
class GitaDoraGame : public games::Game {
|
class GitaDoraGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -105,8 +105,62 @@ Present1_t Present1_orig = nullptr;
|
|||||||
bool g_swapchain_hooked = false;
|
bool g_swapchain_hooked = false;
|
||||||
bool g_swapchain1_hooked = false;
|
bool g_swapchain1_hooked = false;
|
||||||
|
|
||||||
|
// sub-screens / IME helpers are usually child or zero-sized windows.
|
||||||
|
// visibility isn't checked - the game may present before showing the window.
|
||||||
|
bool looks_like_game_window(HWND hwnd) {
|
||||||
|
RECT client {};
|
||||||
|
return GetAncestor(hwnd, GA_ROOT) == hwnd
|
||||||
|
&& GetClientRect(hwnd, &client)
|
||||||
|
&& client.right > client.left
|
||||||
|
&& client.bottom > client.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only the main game window; ignore sub-screens / IME helpers.
|
||||||
|
bool is_main_game_swapchain(IDXGISwapChain *swapchain) {
|
||||||
|
DXGI_SWAP_CHAIN_DESC desc {};
|
||||||
|
if (!swapchain || FAILED(swapchain->GetDesc(&desc)) || !desc.OutputWindow) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND main = d3d11_hooks::main_hwnd();
|
||||||
|
if (!main) {
|
||||||
|
// no creation hook recorded a window, so fall back to the presenting one;
|
||||||
|
// the choice is permanent, so require a plausible game window
|
||||||
|
if (!looks_like_game_window(desc.OutputWindow)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc(
|
||||||
|
"graphics::d3d11",
|
||||||
|
"try to notemain hwnd from swapchain present: 0x{:x}",
|
||||||
|
(uintptr_t)desc.OutputWindow);
|
||||||
|
|
||||||
|
d3d11_hooks::note_main_hwnd(desc.OutputWindow);
|
||||||
|
|
||||||
|
// it may have been ignored, or another thread may have won the slot
|
||||||
|
main = d3d11_hooks::main_hwnd();
|
||||||
|
}
|
||||||
|
return desc.OutputWindow == main;
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks are ordered cheapest first, since this runs on every present
|
||||||
void try_create_overlay(IDXGISwapChain *swapchain) {
|
void try_create_overlay(IDXGISwapChain *swapchain) {
|
||||||
if (!swapchain || overlay::OVERLAY) {
|
if (!swapchain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// overlay is disabled by user
|
||||||
|
if (!overlay::ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// overlay is already enabled and attached
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore sub windows
|
||||||
|
if (!is_main_game_swapchain(swapchain)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,12 +169,6 @@ void try_create_overlay(IDXGISwapChain *swapchain) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only attach to the main game window; ignore sub-screens / IME helpers.
|
|
||||||
HWND main = d3d11_hooks::main_hwnd();
|
|
||||||
if (main && desc.OutputWindow != main) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// theme the native title bar; first present is the only reliable point for
|
// theme the native title bar; first present is the only reliable point for
|
||||||
// windows whose swapchain bypasses our factory hooks (e.g. UnityPlayer.dll)
|
// windows whose swapchain bypasses our factory hooks (e.g. UnityPlayer.dll)
|
||||||
set_window_dark_titlebar(desc.OutputWindow);
|
set_window_dark_titlebar(desc.OutputWindow);
|
||||||
@@ -146,13 +194,23 @@ void try_create_overlay(IDXGISwapChain *swapchain) {
|
|||||||
device->Release();
|
device->Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pump_overlay(IDXGISwapChain *swapchain) {
|
// screenshots have to keep working with the overlay disabled, so they are not gated on it
|
||||||
if (!overlay::OVERLAY || !overlay::OVERLAY->uses_swapchain(swapchain)) {
|
void pump_frame(IDXGISwapChain *swapchain) {
|
||||||
|
const bool has_overlay =
|
||||||
|
overlay::OVERLAY && overlay::OVERLAY->uses_swapchain(swapchain);
|
||||||
|
if (!has_overlay && !is_main_game_swapchain(swapchain)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics_poll_screenshot_hotkey();
|
graphics_poll_screenshot_hotkey();
|
||||||
|
|
||||||
|
// before the overlay render so the screenshot excludes it
|
||||||
|
if (!GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY) {
|
||||||
|
d3d11_hooks::try_screenshot(swapchain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_overlay) {
|
||||||
|
|
||||||
// size imgui to the backbuffer (not window client). dxgi may upscale
|
// size imgui to the backbuffer (not window client). dxgi may upscale
|
||||||
// a small backbuffer into a larger client rect; without this override
|
// a small backbuffer into a larger client rect; without this override
|
||||||
// imgui would draw past the RTV and the mouse mapping would be off.
|
// imgui would draw past the RTV and the mouse mapping would be off.
|
||||||
@@ -166,9 +224,12 @@ void pump_overlay(IDXGISwapChain *swapchain) {
|
|||||||
overlay::OVERLAY->update();
|
overlay::OVERLAY->update();
|
||||||
overlay::OVERLAY->new_frame();
|
overlay::OVERLAY->new_frame();
|
||||||
overlay::OVERLAY->render();
|
overlay::OVERLAY->render();
|
||||||
|
}
|
||||||
|
|
||||||
// after overlay render so toasts/menus end up in the saved image.
|
// after the overlay render so the screenshot includes toasts / menus
|
||||||
|
if (GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY) {
|
||||||
d3d11_hooks::try_screenshot(swapchain);
|
d3d11_hooks::try_screenshot(swapchain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
@@ -177,8 +238,11 @@ void pump_overlay(IDXGISwapChain *swapchain) {
|
|||||||
HRESULT STDMETHODCALLTYPE Present_hook(
|
HRESULT STDMETHODCALLTYPE Present_hook(
|
||||||
IDXGISwapChain *swapchain, UINT SyncInterval, UINT Flags)
|
IDXGISwapChain *swapchain, UINT SyncInterval, UINT Flags)
|
||||||
{
|
{
|
||||||
|
// a test present doesn't display anything; don't pick a window or take a screenshot off it
|
||||||
|
if (!(Flags & DXGI_PRESENT_TEST)) {
|
||||||
try_create_overlay(swapchain);
|
try_create_overlay(swapchain);
|
||||||
pump_overlay(swapchain);
|
pump_frame(swapchain);
|
||||||
|
}
|
||||||
return Present_orig(swapchain, SyncInterval, Flags);
|
return Present_orig(swapchain, SyncInterval, Flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +250,10 @@ HRESULT STDMETHODCALLTYPE Present1_hook(
|
|||||||
IDXGISwapChain1 *swapchain, UINT SyncInterval, UINT Flags,
|
IDXGISwapChain1 *swapchain, UINT SyncInterval, UINT Flags,
|
||||||
const DXGI_PRESENT_PARAMETERS *pParams)
|
const DXGI_PRESENT_PARAMETERS *pParams)
|
||||||
{
|
{
|
||||||
|
if (!(Flags & DXGI_PRESENT_TEST)) {
|
||||||
try_create_overlay(swapchain);
|
try_create_overlay(swapchain);
|
||||||
pump_overlay(swapchain);
|
pump_frame(swapchain);
|
||||||
|
}
|
||||||
return Present1_orig(swapchain, SyncInterval, Flags, pParams);
|
return Present1_orig(swapchain, SyncInterval, Flags, pParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1478,7 +1478,7 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
|||||||
void graphics_d3d9_on_present(
|
void graphics_d3d9_on_present(
|
||||||
HWND hFocusWindow,
|
HWND hFocusWindow,
|
||||||
IDirect3DDevice9 *device,
|
IDirect3DDevice9 *device,
|
||||||
IDirect3DDevice9 *wrapped_device) {
|
WrappedIDirect3DDevice9 *wrapped_device) {
|
||||||
|
|
||||||
// image resize / orientation swap. run here (the present path) rather than from `EndScene`,
|
// image resize / orientation swap. run here (the present path) rather than from `EndScene`,
|
||||||
// which may fire several times per frame on multi-pass / render-to-texture games. this is the
|
// which may fire several times per frame on multi-pass / render-to-texture games. this is the
|
||||||
@@ -1489,6 +1489,13 @@ void graphics_d3d9_on_present(
|
|||||||
SurfaceHook(device);
|
SurfaceHook(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graphics_poll_screenshot_hotkey();
|
||||||
|
|
||||||
|
// before the overlay render so the screenshot excludes it
|
||||||
|
if (!GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY) {
|
||||||
|
graphics_d3d9_process_screenshot(device, wrapped_device);
|
||||||
|
}
|
||||||
|
|
||||||
// Do overlay init as many d3d9 hooks create a dummy instance to get vtable offsets and never
|
// Do overlay init as many d3d9 hooks create a dummy instance to get vtable offsets and never
|
||||||
// call `Present`. This avoids race conditions on `IDirect3D9::CreateDevice` like with
|
// call `Present`. This avoids race conditions on `IDirect3D9::CreateDevice` like with
|
||||||
// `dx9osd.dll` for pfreepanic.
|
// `dx9osd.dll` for pfreepanic.
|
||||||
@@ -1508,6 +1515,15 @@ void graphics_d3d9_on_present(
|
|||||||
device->EndScene();
|
device->EndScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// after the overlay render so the screenshot includes toasts / menus
|
||||||
|
if (GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY) {
|
||||||
|
graphics_d3d9_process_screenshot(device, wrapped_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API capture always includes the overlay; it must run before the subscreen present
|
||||||
|
// below, which leaves the arena SMALL back buffer black
|
||||||
|
graphics_d3d9_process_capture(device, wrapped_device);
|
||||||
|
|
||||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||||
const bool is_vm = games::sdvx::is_valkyrie_model();
|
const bool is_vm = games::sdvx::is_valkyrie_model();
|
||||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||||
@@ -1521,9 +1537,6 @@ void graphics_d3d9_on_present(
|
|||||||
if (is_mfc) {
|
if (is_mfc) {
|
||||||
wintouchemu::update();
|
wintouchemu::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics_poll_screenshot_hotkey();
|
|
||||||
graphics_d3d9_process_screenshot_and_capture(device, SUB_SWAP_CHAIN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "d3d9_gfdm.h"
|
#include "d3d9_gfdm.h"
|
||||||
|
|
||||||
|
struct WrappedIDirect3DDevice9;
|
||||||
|
|
||||||
// {EEE9CCF6-53D6-4326-9AE5-60921B3DB394}
|
// {EEE9CCF6-53D6-4326-9AE5-60921B3DB394}
|
||||||
static const GUID IID_WrappedIDirect3D9 = {
|
static const GUID IID_WrappedIDirect3D9 = {
|
||||||
0xeee9ccf6, 0x53d6, 0x4326, { 0x9a, 0xe5, 0x60, 0x92, 0x1b, 0x3d, 0xb3, 0x94 }
|
0xeee9ccf6, 0x53d6, 0x4326, { 0x9a, 0xe5, 0x60, 0x92, 0x1b, 0x3d, 0xb3, 0x94 }
|
||||||
@@ -13,7 +15,7 @@ void graphics_d3d9_init();
|
|||||||
void graphics_d3d9_on_present(
|
void graphics_d3d9_on_present(
|
||||||
HWND hFocusWindow,
|
HWND hFocusWindow,
|
||||||
IDirect3DDevice9 *device,
|
IDirect3DDevice9 *device,
|
||||||
IDirect3DDevice9 *wrapped_device);
|
WrappedIDirect3DDevice9 *wrapped_device);
|
||||||
|
|
||||||
void graphics_d3d9_notify_subscreen_present();
|
void graphics_d3d9_notify_subscreen_present();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@
|
|||||||
#include "shaders/vertex_shader.h"
|
#include "shaders/vertex_shader.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// maps arena's cached additional swap chains (SMALL, LEFT, RIGHT) to screen numbers.
|
||||||
|
// MAIN is the implicit swap chain, is not in those slots, and is always screen 0.
|
||||||
|
// screen 1 is the subscreen for every other game, so SMALL takes that number here too.
|
||||||
|
static constexpr int GFDM_ARENA_SLOT_SCREENS[] { 1, 2, 3 };
|
||||||
|
|
||||||
#define CHECK_RESULT_FMT(x, fmt, ...) \
|
#define CHECK_RESULT_FMT(x, fmt, ...) \
|
||||||
HRESULT __ret = (x); \
|
HRESULT __ret = (x); \
|
||||||
if (GRAPHICS_LOG_HRESULT && FAILED(__ret)) [[unlikely]] { \
|
if (GRAPHICS_LOG_HRESULT && FAILED(__ret)) [[unlikely]] { \
|
||||||
@@ -336,20 +341,25 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
bool create_swap_chain = false;
|
bool create_swap_chain = false;
|
||||||
bool create_fake_swap_chain = false;
|
bool create_fake_swap_chain = false;
|
||||||
|
bool arena_slot = false;
|
||||||
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
|
||||||
create_swap_chain = true;
|
create_swap_chain = true;
|
||||||
|
|
||||||
} else if (games::gitadora::is_arena_model() &&
|
} else if (games::gitadora::is_arena_model() &&
|
||||||
(GRAPHICS_PREVENT_SECONDARY_WINDOWS || GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS)) {
|
(GRAPHICS_SCREENSHOT_SUBSCREENS ||
|
||||||
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS ||
|
||||||
|
GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS)) {
|
||||||
|
|
||||||
if (pPresentationParameters->BackBufferWidth == 800) {
|
if (pPresentationParameters->BackBufferWidth == 800) {
|
||||||
// SMALL (subscreen)
|
// SMALL (subscreen)
|
||||||
create_swap_chain = true;
|
create_swap_chain = true;
|
||||||
|
arena_slot = true;
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|
||||||
} else if (pPresentationParameters->BackBufferWidth == 1080) {
|
} else if (pPresentationParameters->BackBufferWidth == 1080) {
|
||||||
// LEFT/RIGHT
|
// LEFT/RIGHT
|
||||||
create_swap_chain = true;
|
create_swap_chain = true;
|
||||||
|
arena_slot = true;
|
||||||
index = 1;
|
index = 1;
|
||||||
if (sub_swapchain[index] || fake_sub_swapchain[index]) {
|
if (sub_swapchain[index] || fake_sub_swapchain[index]) {
|
||||||
index = 2;
|
index = 2;
|
||||||
@@ -361,6 +371,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the api lists screens from this registry, so arena heads need their logical numbers in it
|
||||||
|
if (arena_slot) {
|
||||||
|
graphics_screens_register(GFDM_ARENA_SLOT_SCREENS[index]);
|
||||||
|
}
|
||||||
|
|
||||||
if (create_fake_swap_chain) {
|
if (create_fake_swap_chain) {
|
||||||
if (!fake_sub_swapchain[index]) {
|
if (!fake_sub_swapchain[index]) {
|
||||||
log_info(
|
log_info(
|
||||||
@@ -539,6 +554,64 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WrappedIDirect3DDevice9::get_screenshot_screens(std::vector<int> &screens) const {
|
||||||
|
if (games::gitadora::is_arena_model()) {
|
||||||
|
screens.push_back(0);
|
||||||
|
|
||||||
|
// every head the game renders into, whether or not it reaches a display
|
||||||
|
for (int slot = 0; slot < 3; slot++) {
|
||||||
|
if (sub_swapchain[slot] != nullptr || fake_sub_swapchain[slot] != nullptr) {
|
||||||
|
screens.push_back(GFDM_ARENA_SLOT_SCREENS[slot]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics_screens_get(screens);
|
||||||
|
|
||||||
|
// the sub screen is only registered once the game asks for it by index
|
||||||
|
if (sub_swapchain[0] != nullptr &&
|
||||||
|
avs::game::is_model({"LDJ", "KFC", "M39"}) &&
|
||||||
|
std::find(screens.begin(), screens.end(), 1) == screens.end())
|
||||||
|
{
|
||||||
|
screens.push_back(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT WrappedIDirect3DDevice9::get_screenshot_swap_chain(
|
||||||
|
UINT iSwapChain,
|
||||||
|
IDirect3DSwapChain9 **ppSwapChain)
|
||||||
|
{
|
||||||
|
if (ppSwapChain == nullptr) {
|
||||||
|
return D3DERR_INVALIDCALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the game numbers the two-head SMALL head itself; keep screen 1 meaning SMALL
|
||||||
|
if (games::gitadora::is_arena_model() && is_gfdm_two_head_exclusive() && iSwapChain == 1) {
|
||||||
|
return GetSwapChain(gfdm_logical_small_swapchain, ppSwapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (games::gitadora::is_arena_model()) {
|
||||||
|
for (int slot = 0; slot < 3; slot++) {
|
||||||
|
if (GFDM_ARENA_SLOT_SCREENS[slot] != (int) iSwapChain) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sub_swapchain[slot] != nullptr) {
|
||||||
|
sub_swapchain[slot]->AddRef();
|
||||||
|
*ppSwapChain = sub_swapchain[slot];
|
||||||
|
return D3D_OK;
|
||||||
|
}
|
||||||
|
if (fake_sub_swapchain[slot] != nullptr) {
|
||||||
|
fake_sub_swapchain[slot]->AddRef();
|
||||||
|
*ppSwapChain = fake_sub_swapchain[slot];
|
||||||
|
return D3D_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetSwapChain(iSwapChain, ppSwapChain);
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
|
||||||
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
D3DPRESENT_PARAMETERS *pPresentationParameters)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <initguid.h>
|
#include <initguid.h>
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
@@ -234,6 +235,10 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
|
|||||||
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
virtual HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
// logical screens the game draws, and the swap chain each one lives on
|
||||||
|
void get_screenshot_screens(std::vector<int> &screens) const;
|
||||||
|
HRESULT get_screenshot_swap_chain(UINT iSwapChain, IDirect3DSwapChain9 **ppSwapChain);
|
||||||
|
|
||||||
bool is_gfdm_two_head_exclusive() const;
|
bool is_gfdm_two_head_exclusive() const;
|
||||||
bool is_gfdm_logical_small_swapchain(UINT swapchain) const;
|
bool is_gfdm_logical_small_swapchain(UINT swapchain) const;
|
||||||
bool is_gfdm_logical_side_swapchain(UINT swapchain) const;
|
bool is_gfdm_logical_side_swapchain(UINT swapchain) const;
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
|
|
||||||
|
#include "games/gitadora/gitadora.h"
|
||||||
|
|
||||||
inline constexpr UINT GFDM_SIDE_WIDTH = 1080;
|
inline constexpr UINT GFDM_SIDE_WIDTH = 1080;
|
||||||
inline constexpr UINT GFDM_SIDE_HEIGHT = 1920;
|
inline constexpr UINT GFDM_SIDE_HEIGHT = 1920;
|
||||||
inline constexpr UINT GFDM_SMALL_WIDTH = 800;
|
inline constexpr UINT GFDM_SMALL_WIDTH = games::gitadora::ARENA_SUBSCREEN_WIDTH;
|
||||||
inline constexpr UINT GFDM_SMALL_HEIGHT = 1280;
|
inline constexpr UINT GFDM_SMALL_HEIGHT = games::gitadora::ARENA_SUBSCREEN_HEIGHT;
|
||||||
inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4;
|
inline constexpr UINT GFDM_LOGICAL_HEAD_COUNT = 4;
|
||||||
inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1;
|
inline constexpr UINT GFDM_NATIVE_SMALL_SWAPCHAIN = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#include "d3d9_screenshot.h"
|
#include "d3d9_screenshot.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <external/robin_hood.h>
|
#include <external/robin_hood.h>
|
||||||
|
|
||||||
@@ -21,6 +24,8 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/threadpool.h"
|
#include "util/threadpool.h"
|
||||||
|
|
||||||
|
#include "d3d9_device.h"
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
typedef decltype(D3DXSaveSurfaceToFileA) *D3DXSaveSurfaceToFileA_t;
|
typedef decltype(D3DXSaveSurfaceToFileA) *D3DXSaveSurfaceToFileA_t;
|
||||||
#else
|
#else
|
||||||
@@ -36,6 +41,9 @@ typedef HRESULT (WINAPI *D3DXSaveSurfaceToFileA_t)(
|
|||||||
|
|
||||||
static bool ATTEMPTED_D3DX9_LOAD_LIBRARY = false;
|
static bool ATTEMPTED_D3DX9_LOAD_LIBRARY = false;
|
||||||
|
|
||||||
|
// genpath picks free filenames by probing the disk, so only one save may run at a time
|
||||||
|
static std::mutex SCREENSHOT_SAVE_M;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum class ImageRequestKind {
|
enum class ImageRequestKind {
|
||||||
@@ -57,6 +65,7 @@ struct SurfaceReleaser {
|
|||||||
using SurfacePtr = std::unique_ptr<IDirect3DSurface9, SurfaceReleaser>;
|
using SurfacePtr = std::unique_ptr<IDirect3DSurface9, SurfaceReleaser>;
|
||||||
|
|
||||||
struct BackbufferCopy {
|
struct BackbufferCopy {
|
||||||
|
int screen {};
|
||||||
D3DSURFACE_DESC desc {};
|
D3DSURFACE_DESC desc {};
|
||||||
SurfacePtr surface;
|
SurfacePtr surface;
|
||||||
};
|
};
|
||||||
@@ -142,7 +151,7 @@ static void save_capture(
|
|||||||
graphics_capture_enqueue(screen, pixels.release(), width, height);
|
graphics_capture_enqueue(screen, pixels.release(), width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void save_screenshot(
|
static bool save_screenshot(
|
||||||
const std::string &file_path,
|
const std::string &file_path,
|
||||||
D3DFORMAT format,
|
D3DFORMAT format,
|
||||||
UINT width,
|
UINT width,
|
||||||
@@ -156,7 +165,7 @@ static void save_screenshot(
|
|||||||
HRESULT hr = surface->LockRect(&finished_copy, nullptr, 0);
|
HRESULT hr = surface->LockRect(&finished_copy, nullptr, 0);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
log_warning("graphics::d3d9", "failed to lock screenshot surface, hr={}", FMT_HRESULT(hr));
|
log_warning("graphics::d3d9", "failed to lock screenshot surface, hr={}", FMT_HRESULT(hr));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t pitch = finished_copy.Pitch;
|
const size_t pitch = finished_copy.Pitch;
|
||||||
@@ -170,7 +179,7 @@ static void save_screenshot(
|
|||||||
hr = surface->UnlockRect();
|
hr = surface->UnlockRect();
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
log_warning("graphics::d3d9", "failed to unlock screenshot surface, hr={}", FMT_HRESULT(hr));
|
log_warning("graphics::d3d9", "failed to unlock screenshot surface, hr={}", FMT_HRESULT(hr));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +219,10 @@ static void save_screenshot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (D3DXSaveSurfaceToFileA_ptr != nullptr) {
|
if (D3DXSaveSurfaceToFileA_ptr == nullptr) {
|
||||||
|
log_warning("graphics::d3d9", "Direct3D save helper function not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// save to file
|
// save to file
|
||||||
log_info("graphics::d3d9", "saving screenshot to {}", file_path);
|
log_info("graphics::d3d9", "saving screenshot to {}", file_path);
|
||||||
@@ -219,39 +231,33 @@ static void save_screenshot(
|
|||||||
|
|
||||||
if (FAILED(save_result)) {
|
if (FAILED(save_result)) {
|
||||||
log_warning("graphics::d3d9", "Failed to save screenshot");
|
log_warning("graphics::d3d9", "Failed to save screenshot");
|
||||||
overlay::notifications::add(
|
return false;
|
||||||
overlay::notifications::Severity::Error,
|
|
||||||
"Screenshot failed to save");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// save to clipboard
|
return true;
|
||||||
clipboard::copy_image(file_path);
|
}
|
||||||
|
|
||||||
overlay::notifications::add(
|
// screen 0 keeps the plain name so existing tooling and the clipboard copy are unaffected
|
||||||
overlay::notifications::Severity::Success,
|
static std::string screenshot_path_for_screen(const std::string &primary_path, int screen) {
|
||||||
fmt::format("Screenshot saved: {}", fileutils::basename(file_path)));
|
if (screen == 0) {
|
||||||
} else {
|
return primary_path;
|
||||||
log_warning("graphics::d3d9", "Direct3D save helper function not available");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path path(primary_path);
|
||||||
|
return (path.parent_path() /
|
||||||
|
fmt::format("{}_{}{}", path.stem().string(), screen, path.extension().string()))
|
||||||
|
.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<BackbufferCopy> acquire_backbuffer_copy(
|
static std::optional<BackbufferCopy> acquire_backbuffer_copy(
|
||||||
IDirect3DDevice9 *device, IDirect3DSwapChain9 *sub_swap_chain, int screen) {
|
IDirect3DDevice9 *device, IDirect3DSwapChain9 *swap_chain, int screen) {
|
||||||
|
|
||||||
HRESULT hr = S_OK;
|
|
||||||
|
|
||||||
// TODO: verify screen is a valid swapchain
|
|
||||||
|
|
||||||
IDirect3DSurface9 *buffer = nullptr;
|
IDirect3DSurface9 *buffer = nullptr;
|
||||||
if (sub_swap_chain != nullptr && screen & 1) {
|
HRESULT hr = swap_chain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &buffer);
|
||||||
hr = sub_swap_chain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &buffer);
|
|
||||||
} else {
|
|
||||||
hr = device->GetBackBuffer(screen, 0, D3DBACKBUFFER_TYPE_MONO, &buffer);
|
|
||||||
}
|
|
||||||
if (FAILED(hr) || buffer == nullptr) {
|
if (FAILED(hr) || buffer == nullptr) {
|
||||||
log_warning("graphics::d3d9",
|
log_warning("graphics::d3d9",
|
||||||
"failed to get back buffer, hr={}",
|
"failed to get back buffer for screen {}, hr={}",
|
||||||
|
screen,
|
||||||
FMT_HRESULT(hr));
|
FMT_HRESULT(hr));
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -293,6 +299,7 @@ static std::optional<BackbufferCopy> acquire_backbuffer_copy(
|
|||||||
buffer->Release();
|
buffer->Release();
|
||||||
|
|
||||||
return BackbufferCopy {
|
return BackbufferCopy {
|
||||||
|
.screen = screen,
|
||||||
.desc = desc,
|
.desc = desc,
|
||||||
.surface = SurfacePtr(temp_surface),
|
.surface = SurfacePtr(temp_surface),
|
||||||
};
|
};
|
||||||
@@ -300,10 +307,12 @@ static std::optional<BackbufferCopy> acquire_backbuffer_copy(
|
|||||||
|
|
||||||
static void dispatch_surface_save(
|
static void dispatch_surface_save(
|
||||||
const ImageRequest &request,
|
const ImageRequest &request,
|
||||||
BackbufferCopy copy) {
|
std::vector<BackbufferCopy> copies,
|
||||||
auto surface_process = [request, copy = std::move(copy)]() {
|
size_t screen_count) {
|
||||||
|
auto surface_process = [request, screen_count, copies = std::move(copies)]() {
|
||||||
switch (request.kind) {
|
switch (request.kind) {
|
||||||
case ImageRequestKind::Capture:
|
case ImageRequestKind::Capture: {
|
||||||
|
const auto © = copies.front();
|
||||||
save_capture(
|
save_capture(
|
||||||
request.screen,
|
request.screen,
|
||||||
copy.desc.Format,
|
copy.desc.Format,
|
||||||
@@ -311,16 +320,61 @@ static void dispatch_surface_save(
|
|||||||
copy.desc.Height,
|
copy.desc.Height,
|
||||||
copy.surface.get());
|
copy.surface.get());
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ImageRequestKind::Screenshot: {
|
case ImageRequestKind::Screenshot: {
|
||||||
auto file_path = graphics_screenshot_genpath();
|
std::lock_guard<std::mutex> lock(SCREENSHOT_SAVE_M);
|
||||||
if (!file_path.empty()) {
|
|
||||||
save_screenshot(
|
std::vector<int> screens;
|
||||||
file_path,
|
screens.reserve(copies.size());
|
||||||
|
for (const auto © : copies) {
|
||||||
|
screens.push_back(copy.screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto base_path = graphics_screenshot_genpath(screens);
|
||||||
|
if (base_path.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// screens missing from copies already failed to be acquired
|
||||||
|
size_t failed = screen_count - copies.size();
|
||||||
|
std::string primary_path;
|
||||||
|
std::string notify_path;
|
||||||
|
for (const auto © : copies) {
|
||||||
|
const auto path = screenshot_path_for_screen(base_path, copy.screen);
|
||||||
|
if (!save_screenshot(
|
||||||
|
path,
|
||||||
copy.desc.Format,
|
copy.desc.Format,
|
||||||
copy.desc.Width,
|
copy.desc.Width,
|
||||||
copy.desc.Height,
|
copy.desc.Height,
|
||||||
copy.surface.get());
|
copy.surface.get())) {
|
||||||
|
failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (notify_path.empty()) {
|
||||||
|
notify_path = path;
|
||||||
|
}
|
||||||
|
if (copy.screen == 0) {
|
||||||
|
primary_path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only the primary screen goes to the clipboard, but any saved file is a success
|
||||||
|
if (!primary_path.empty()) {
|
||||||
|
clipboard::copy_image(primary_path);
|
||||||
|
}
|
||||||
|
if (!notify_path.empty()) {
|
||||||
|
overlay::notifications::add(
|
||||||
|
overlay::notifications::Severity::Success,
|
||||||
|
fmt::format("Screenshot saved: {}", fileutils::basename(notify_path)));
|
||||||
|
} else {
|
||||||
|
overlay::notifications::add(
|
||||||
|
overlay::notifications::Severity::Error,
|
||||||
|
"Screenshot failed to save");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
log_warning("graphics::d3d9", "{} screenshot screen(s) missing", failed);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -348,43 +402,69 @@ static void dispatch_surface_save(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ImageRequest> consume_image_request() {
|
static void process_image_request(
|
||||||
|
IDirect3DDevice9 *device,
|
||||||
|
WrappedIDirect3DDevice9 *wrapped_device,
|
||||||
|
const ImageRequest &request) {
|
||||||
|
const bool screenshot = request.kind == ImageRequestKind::Screenshot;
|
||||||
|
|
||||||
|
std::vector<int> screens { request.screen };
|
||||||
|
if (screenshot && GRAPHICS_SCREENSHOT_SUBSCREENS) {
|
||||||
|
screens.clear();
|
||||||
|
wrapped_device->get_screenshot_screens(screens);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BackbufferCopy> copies;
|
||||||
|
copies.reserve(screens.size());
|
||||||
|
for (const int screen : screens) {
|
||||||
|
std::optional<BackbufferCopy> copy;
|
||||||
|
|
||||||
|
IDirect3DSwapChain9 *swap_chain = nullptr;
|
||||||
|
HRESULT hr = wrapped_device->get_screenshot_swap_chain(screen, &swap_chain);
|
||||||
|
if (FAILED(hr) || swap_chain == nullptr) {
|
||||||
|
log_warning("graphics::d3d9",
|
||||||
|
"failed to get swap chain for screen {}, hr={}",
|
||||||
|
screen,
|
||||||
|
FMT_HRESULT(hr));
|
||||||
|
} else {
|
||||||
|
copy = acquire_backbuffer_copy(device, swap_chain, screen);
|
||||||
|
swap_chain->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy.has_value()) {
|
||||||
|
copies.emplace_back(std::move(*copy));
|
||||||
|
} else if (!screenshot) {
|
||||||
|
graphics_capture_skip(request.screen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copies.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_surface_save(request, std::move(copies), screens.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphics_d3d9_process_screenshot(
|
||||||
|
IDirect3DDevice9 *device,
|
||||||
|
WrappedIDirect3DDevice9 *wrapped_device) {
|
||||||
if (graphics_screenshot_consume()) {
|
if (graphics_screenshot_consume()) {
|
||||||
return ImageRequest {
|
process_image_request(device, wrapped_device, ImageRequest {
|
||||||
.kind = ImageRequestKind::Screenshot,
|
.kind = ImageRequestKind::Screenshot,
|
||||||
.screen = 0,
|
.screen = 0,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int capture_screen = 0;
|
|
||||||
if (graphics_capture_consume(&capture_screen)) {
|
|
||||||
return ImageRequest {
|
|
||||||
.kind = ImageRequestKind::Capture,
|
|
||||||
.screen = capture_screen,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphics_d3d9_process_screenshot_and_capture(
|
void graphics_d3d9_process_capture(
|
||||||
IDirect3DDevice9 *device,
|
IDirect3DDevice9 *device,
|
||||||
IDirect3DSwapChain9 *sub_swap_chain) {
|
WrappedIDirect3DDevice9 *wrapped_device) {
|
||||||
const auto request = consume_image_request();
|
int screen = 0;
|
||||||
if (!request.has_value()) {
|
if (graphics_capture_consume(&screen)) {
|
||||||
return;
|
process_image_request(device, wrapped_device, ImageRequest {
|
||||||
|
.kind = ImageRequestKind::Capture,
|
||||||
|
.screen = screen,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto copy = acquire_backbuffer_copy(
|
|
||||||
device,
|
|
||||||
sub_swap_chain,
|
|
||||||
request->screen);
|
|
||||||
if (!copy.has_value()) {
|
|
||||||
if (request->kind == ImageRequestKind::Capture) {
|
|
||||||
graphics_capture_skip(request->screen);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch_surface_save(*request, std::move(*copy));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
|
|
||||||
void graphics_d3d9_process_screenshot_and_capture(
|
struct WrappedIDirect3DDevice9;
|
||||||
|
|
||||||
|
void graphics_d3d9_process_screenshot(
|
||||||
IDirect3DDevice9 *device,
|
IDirect3DDevice9 *device,
|
||||||
IDirect3DSwapChain9 *sub_swap_chain);
|
WrappedIDirect3DDevice9 *wrapped_device);
|
||||||
|
|
||||||
|
void graphics_d3d9_process_capture(
|
||||||
|
IDirect3DDevice9 *device,
|
||||||
|
WrappedIDirect3DDevice9 *wrapped_device);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "touch/native/inject.h"
|
#include "touch/native/inject.h"
|
||||||
|
#include "touch/native/nativetouchhook.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
|
||||||
@@ -118,6 +119,8 @@ uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
|
|||||||
// settings
|
// settings
|
||||||
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
|
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
|
||||||
std::string GRAPHICS_SCREENSHOT_DIR = ".\\screenshots";
|
std::string GRAPHICS_SCREENSHOT_DIR = ".\\screenshots";
|
||||||
|
bool GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY = false;
|
||||||
|
bool GRAPHICS_SCREENSHOT_SUBSCREENS = false;
|
||||||
|
|
||||||
static decltype(ChangeDisplaySettingsA) *ChangeDisplaySettingsA_orig = nullptr;
|
static decltype(ChangeDisplaySettingsA) *ChangeDisplaySettingsA_orig = nullptr;
|
||||||
static decltype(ChangeDisplaySettingsExA) *ChangeDisplaySettingsExA_orig = nullptr;
|
static decltype(ChangeDisplaySettingsExA) *ChangeDisplaySettingsExA_orig = nullptr;
|
||||||
@@ -214,6 +217,18 @@ static void gitadora_remember_window(HWND hWnd, const std::string &window_name)
|
|||||||
} else if (window_name == "SMALL") {
|
} else if (window_name == "SMALL") {
|
||||||
GFDM_SUBSCREEN_WINDOW = hWnd;
|
GFDM_SUBSCREEN_WINDOW = hWnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// touch belongs to the SMALL panel when it exists, otherwise to the main window
|
||||||
|
// that draws the subscreen overlay
|
||||||
|
const bool hosts_touch = window_name == "SMALL" ||
|
||||||
|
(window_name == "GITADORA" && !graphics_gitadora_has_dedicated_subscreen());
|
||||||
|
if (nativetouch::is_hooked() && hWnd != nullptr && hosts_touch) {
|
||||||
|
nativetouch::inject::set_preferred_injection_window(hWnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool graphics_gitadora_has_dedicated_subscreen() {
|
||||||
|
return GFDM_SUBSCREEN_WINDOW != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool graphics_gitadora_prepare_two_head_device_window(
|
bool graphics_gitadora_prepare_two_head_device_window(
|
||||||
@@ -614,8 +629,10 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool is_sdvx = avs::game::is_model("KFC");
|
||||||
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
|
||||||
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
|
bool is_sdvx_sub_window = is_sdvx && window_name.ends_with(" Sub Screen");
|
||||||
|
bool is_sdvx_main_window = is_sdvx && window_name.ends_with(" Main Screen");
|
||||||
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
||||||
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
||||||
? gitadora_canonical_window_name(effective_window_name)
|
? gitadora_canonical_window_name(effective_window_name)
|
||||||
@@ -718,6 +735,20 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDVX registers touch on both windows, so name the one synthetic touches must land on
|
||||||
|
// instead of letting window creation order decide: the sub screen window when windowed,
|
||||||
|
// the main window in fullscreen since the game reads it in primary-display coordinates
|
||||||
|
if (nativetouch::is_hooked() &&
|
||||||
|
result != nullptr &&
|
||||||
|
(GRAPHICS_WINDOWED ? is_sdvx_sub_window : is_sdvx_main_window)) {
|
||||||
|
log_misc(
|
||||||
|
"graphics",
|
||||||
|
"SDVX touch surface is {}, {}",
|
||||||
|
fmt::ptr(result),
|
||||||
|
window_name);
|
||||||
|
nativetouch::inject::set_preferred_injection_window(result);
|
||||||
|
}
|
||||||
|
|
||||||
// only hook touch window if multiple windows are allowed
|
// only hook touch window if multiple windows are allowed
|
||||||
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
||||||
gitadora_remember_window(result, gfdm_window_name);
|
gitadora_remember_window(result, gfdm_window_name);
|
||||||
@@ -729,6 +760,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the dedicated SMALL window is the touch panel; mouse and API touch target it
|
||||||
|
if (nativetouch::is_hooked() && result != nullptr) {
|
||||||
|
nativetouch::inject::register_and_attach_window(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
gitadora_force_window_style(result);
|
gitadora_force_window_style(result);
|
||||||
@@ -1102,6 +1138,15 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fullscreen SDVX keeps two adapters so the subscreen overlay can draw, so the game still
|
||||||
|
// creates the sub window even when the user asked for it to be gone
|
||||||
|
if (avs::game::is_model("KFC") &&
|
||||||
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS &&
|
||||||
|
hWnd == SDVX_SUBSCREEN_WINDOW) {
|
||||||
|
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// call original
|
// call original
|
||||||
return ShowWindow_orig(hWnd, nCmdShow);
|
return ShowWindow_orig(hWnd, nCmdShow);
|
||||||
}
|
}
|
||||||
@@ -1318,7 +1363,9 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
|
|||||||
const bool native_touch_overlay =
|
const bool native_touch_overlay =
|
||||||
(games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
(games::iidx::NATIVE_TOUCH && games::iidx::TDJ_MODE && !GRAPHICS_IIDX_WSUB) ||
|
||||||
(games::popn::NATIVE_TOUCH &&
|
(games::popn::NATIVE_TOUCH &&
|
||||||
games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
games::popn::is_pikapika_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS) ||
|
||||||
|
(games::gitadora::NATIVE_TOUCH &&
|
||||||
|
games::gitadora::is_arena_model() && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
||||||
if (native_touch_overlay) {
|
if (native_touch_overlay) {
|
||||||
nativetouch::inject::register_and_attach_window(hWnd);
|
nativetouch::inject::register_and_attach_window(hWnd);
|
||||||
}
|
}
|
||||||
@@ -1530,7 +1577,7 @@ bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string graphics_screenshot_genpath() {
|
std::string graphics_screenshot_genpath(const std::vector<int> &screens) {
|
||||||
|
|
||||||
// verify dir path
|
// verify dir path
|
||||||
if (GRAPHICS_SCREENSHOT_DIR.empty()) {
|
if (GRAPHICS_SCREENSHOT_DIR.empty()) {
|
||||||
@@ -1555,11 +1602,21 @@ std::string graphics_screenshot_genpath() {
|
|||||||
auto tm_now = *std::gmtime(&t_now);
|
auto tm_now = *std::gmtime(&t_now);
|
||||||
auto prefix = to_string(std::put_time(&tm_now, "%Y%m%d"));
|
auto prefix = to_string(std::put_time(&tm_now, "%Y%m%d"));
|
||||||
|
|
||||||
// find next filename
|
// find next filename; the whole set has to be free so one shot stays numbered together
|
||||||
size_t id = 0;
|
size_t id = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
auto filepath = fmt::format("{}\\{}_{}.png", GRAPHICS_SCREENSHOT_DIR, prefix, id);
|
auto filepath = fmt::format("{}\\{}_{}.png", GRAPHICS_SCREENSHOT_DIR, prefix, id);
|
||||||
if (!fileutils::file_exists(filepath)) {
|
bool available = !fileutils::file_exists(filepath);
|
||||||
|
for (const auto screen : screens) {
|
||||||
|
if (!available) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (screen != 0) {
|
||||||
|
available = !fileutils::file_exists(fmt::format(
|
||||||
|
"{}\\{}_{}_{}.png", GRAPHICS_SCREENSHOT_DIR, prefix, id, screen));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (available) {
|
||||||
return filepath;
|
return filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ extern bool FAKE_SUBSCREEN_ADAPTER;
|
|||||||
// settings
|
// settings
|
||||||
extern std::string GRAPHICS_DEVICEID;
|
extern std::string GRAPHICS_DEVICEID;
|
||||||
extern std::string GRAPHICS_SCREENSHOT_DIR;
|
extern std::string GRAPHICS_SCREENSHOT_DIR;
|
||||||
|
extern bool GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY;
|
||||||
|
extern bool GRAPHICS_SCREENSHOT_SUBSCREENS;
|
||||||
|
|
||||||
// Direct3D 9 settings
|
// Direct3D 9 settings
|
||||||
extern std::optional<UINT> D3D9_ADAPTER;
|
extern std::optional<UINT> D3D9_ADAPTER;
|
||||||
@@ -119,6 +121,7 @@ extern bool D3D9_DEVICE_HOOK_DISABLE;
|
|||||||
|
|
||||||
void graphics_init();
|
void graphics_init();
|
||||||
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
|
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
|
||||||
|
bool graphics_gitadora_has_dedicated_subscreen();
|
||||||
// The native GITADORA two-head D3D9 group uses the game's named SMALL
|
// The native GITADORA two-head D3D9 group uses the game's named SMALL
|
||||||
// device window for the native physical SMALL head. The game requests
|
// device window for the native physical SMALL head. The game requests
|
||||||
// D3DCREATE_NOWINDOWCHANGES, so this host must be made borderless and sized
|
// D3DCREATE_NOWINDOWCHANGES, so this host must be made borderless and sized
|
||||||
@@ -145,7 +148,8 @@ bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
|
|||||||
bool rgb = true, int quality = 80, bool downsample = true, int divide = 0,
|
bool rgb = true, int quality = 80, bool downsample = true, int divide = 0,
|
||||||
uint64_t *timestamp = nullptr,
|
uint64_t *timestamp = nullptr,
|
||||||
int *width = nullptr, int *height = nullptr);
|
int *width = nullptr, int *height = nullptr);
|
||||||
std::string graphics_screenshot_genpath();
|
// the returned path is for screen 0; any extra screens only reserve their suffixed names
|
||||||
|
std::string graphics_screenshot_genpath(const std::vector<int> &screens = {});
|
||||||
|
|
||||||
// graphics_windowed.cpp
|
// graphics_windowed.cpp
|
||||||
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|||||||
@@ -1103,6 +1103,12 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::ScreenshotFolder].is_active()) {
|
if (options[launcher::Options::ScreenshotFolder].is_active()) {
|
||||||
GRAPHICS_SCREENSHOT_DIR = options[launcher::Options::ScreenshotFolder].value_text();
|
GRAPHICS_SCREENSHOT_DIR = options[launcher::Options::ScreenshotFolder].value_text();
|
||||||
}
|
}
|
||||||
|
if (options[launcher::Options::ScreenshotIncludeOverlay].value_bool()) {
|
||||||
|
GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY = true;
|
||||||
|
}
|
||||||
|
if (options[launcher::Options::ScreenshotSubscreens].value_bool()) {
|
||||||
|
GRAPHICS_SCREENSHOT_SUBSCREENS = true;
|
||||||
|
}
|
||||||
if (options[launcher::Options::DisableColoredOutput].value_bool()) {
|
if (options[launcher::Options::DisableColoredOutput].value_bool()) {
|
||||||
logger::COLOR = false;
|
logger::COLOR = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3397,6 +3397,22 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Bool,
|
.type = OptionType::Bool,
|
||||||
.category = "OBS Control",
|
.category = "OBS Control",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// ScreenshotIncludeOverlay
|
||||||
|
.title = "Include Overlay in Screenshots",
|
||||||
|
.name = "screenshotoverlay",
|
||||||
|
.desc = "Includes Spice overlay in screenshots.",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.category = "General Overlay",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ScreenshotSubscreens
|
||||||
|
.title = "Include Subscreens in Screenshots",
|
||||||
|
.name = "screenshotsub",
|
||||||
|
.desc = "Saves each subscreen as a separate PNG alongside the primary screenshot.",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.category = "General Overlay",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string> &launcher::get_categories(Options::OptionsCategory category) {
|
const std::vector<std::string> &launcher::get_categories(Options::OptionsCategory category) {
|
||||||
|
|||||||
@@ -320,7 +320,9 @@ namespace launcher {
|
|||||||
OBSWebSocketHost,
|
OBSWebSocketHost,
|
||||||
OBSWebSocketPort,
|
OBSWebSocketPort,
|
||||||
OBSWebSocketPassword,
|
OBSWebSocketPassword,
|
||||||
OBSWebSocketDebug
|
OBSWebSocketDebug,
|
||||||
|
ScreenshotIncludeOverlay,
|
||||||
|
ScreenshotSubscreens
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class OptionsCategory {
|
enum class OptionsCategory {
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ namespace overlay::windows {
|
|||||||
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
|
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
|
||||||
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
|
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
|
||||||
|
|
||||||
|
// a subscreen is an already-composited backbuffer, so its alpha bits are not
|
||||||
|
// meaningful for display. Use AddImage's diffuse alpha (opaque by default)
|
||||||
|
// instead of the texture alpha while preserving ImGui's normal blend state.
|
||||||
|
static void ignore_texture_alpha(const ImDrawList *, const ImDrawCmd *command) {
|
||||||
|
auto device = static_cast<IDirect3DDevice9 *>(command->UserCallbackData);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||||
|
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2);
|
||||||
|
}
|
||||||
|
|
||||||
GenericSubScreen::GenericSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
|
GenericSubScreen::GenericSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
|
||||||
this->remove_window_padding = true;
|
this->remove_window_padding = true;
|
||||||
// ImGuiWindowFlags_NoBackground is needed as the background is drawn on top of the subscreen image
|
// ImGuiWindowFlags_NoBackground is needed as the background is drawn on top of the subscreen image
|
||||||
@@ -239,10 +248,13 @@ namespace overlay::windows {
|
|||||||
auto draw_list = this->draws_window
|
auto draw_list = this->draws_window
|
||||||
? ImGui::GetWindowDrawList()
|
? ImGui::GetWindowDrawList()
|
||||||
: ImGui::GetBackgroundDrawList();
|
: ImGui::GetBackgroundDrawList();
|
||||||
|
draw_list->AddCallback(ignore_texture_alpha, this->device);
|
||||||
draw_list->AddImage(
|
draw_list->AddImage(
|
||||||
reinterpret_cast<ImTextureID>(this->texture),
|
reinterpret_cast<ImTextureID>(this->texture),
|
||||||
overlay_content_top_left,
|
overlay_content_top_left,
|
||||||
bottom_right);
|
bottom_right);
|
||||||
|
// following ImGui draws should resume normal texture-alpha modulation
|
||||||
|
draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_ResetRenderState);
|
||||||
|
|
||||||
if (this->draws_window) {
|
if (this->draws_window) {
|
||||||
// draw an invisible button so that it swallows mouse input
|
// draw an invisible button so that it swallows mouse input
|
||||||
|
|||||||
@@ -123,6 +123,30 @@ namespace nativetouch::inject {
|
|||||||
// asks the touch window thread to send an UPDATE when the game loop runs elsewhere
|
// asks the touch window thread to send an UPDATE when the game loop runs elsewhere
|
||||||
static UINT contact_refresh_message;
|
static UINT contact_refresh_message;
|
||||||
|
|
||||||
|
// the window that shows the touch surface; contacts can be registered from any thread,
|
||||||
|
// so this is published by the graphics window hooks rather than derived from the globals
|
||||||
|
static std::atomic<HWND> preferred_injection_window { nullptr };
|
||||||
|
|
||||||
|
void set_preferred_injection_window(HWND window) {
|
||||||
|
preferred_injection_window.store(window, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a game that publishes its touch surface needs injection only on that window
|
||||||
|
static bool is_published_touch_window(HWND window) {
|
||||||
|
const auto preferred = preferred_injection_window.load(std::memory_order_acquire);
|
||||||
|
return preferred == nullptr || window == preferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
// games can register several windows for touch; keep synthetic contacts on the one
|
||||||
|
// that actually shows the touch surface
|
||||||
|
static bool is_preferred_injection_window(HWND window) {
|
||||||
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
|
return window == TDJ_SUBSCREEN_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_published_touch_window(window);
|
||||||
|
}
|
||||||
|
|
||||||
// submit one synthetic contact frame to Windows touch injection
|
// submit one synthetic contact frame to Windows touch injection
|
||||||
static bool inject_touch_frame(
|
static bool inject_touch_frame(
|
||||||
POINT position, POINTER_FLAGS pointer_flags, bool retry_if_not_ready = false) {
|
POINT position, POINTER_FLAGS pointer_flags, bool retry_if_not_ready = false) {
|
||||||
@@ -325,11 +349,22 @@ namespace nativetouch::inject {
|
|||||||
if (contact_state.input_window == window) {
|
if (contact_state.input_window == window) {
|
||||||
release_active_contact();
|
release_active_contact();
|
||||||
}
|
}
|
||||||
|
|
||||||
HWND expected_window = window;
|
HWND expected_window = window;
|
||||||
injection_window.compare_exchange_strong(
|
injection_window.compare_exchange_strong(
|
||||||
expected_window, nullptr, std::memory_order_acq_rel);
|
expected_window, nullptr, std::memory_order_acq_rel);
|
||||||
|
|
||||||
|
HWND expected_preferred_window = window;
|
||||||
|
preferred_injection_window.compare_exchange_strong(
|
||||||
|
expected_preferred_window, nullptr, std::memory_order_acq_rel);
|
||||||
|
|
||||||
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
|
HWND expected_delivery_window = window;
|
||||||
|
touch_delivery_window.compare_exchange_strong(
|
||||||
|
expected_delivery_window, nullptr, std::memory_order_acq_rel);
|
||||||
|
}
|
||||||
|
|
||||||
contact_refresh_pending.store(false, std::memory_order_release);
|
contact_refresh_pending.store(false, std::memory_order_release);
|
||||||
touch_delivery_window.store(nullptr, std::memory_order_release);
|
|
||||||
RemoveWindowSubclass(window, touch_window_subclass_proc, subclass_id);
|
RemoveWindowSubclass(window, touch_window_subclass_proc, subclass_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +405,7 @@ namespace nativetouch::inject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// publish the UI-thread target for synthetic touch requests
|
// publish the UI-thread target for synthetic touch requests
|
||||||
if (!GRAPHICS_IIDX_WSUB || window == TDJ_SUBSCREEN_WINDOW) {
|
if (is_preferred_injection_window(window)) {
|
||||||
injection_window.store(window, std::memory_order_release);
|
injection_window.store(window, std::memory_order_release);
|
||||||
}
|
}
|
||||||
log_misc(
|
log_misc(
|
||||||
@@ -403,7 +438,7 @@ namespace nativetouch::inject {
|
|||||||
// call original
|
// call original
|
||||||
const auto result = RegisterTouchWindow_orig(window, flags);
|
const auto result = RegisterTouchWindow_orig(window, flags);
|
||||||
|
|
||||||
if (result) {
|
if (result && is_published_touch_window(window)) {
|
||||||
// attach but don't register for touch messages
|
// attach but don't register for touch messages
|
||||||
// (we're already in the middle of RegisterTouchWindow as a result
|
// (we're already in the middle of RegisterTouchWindow as a result
|
||||||
// of the game calling it)
|
// of the game calling it)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ struct tagTOUCHINPUT;
|
|||||||
namespace nativetouch::inject {
|
namespace nativetouch::inject {
|
||||||
void attach_window(HWND window);
|
void attach_window(HWND window);
|
||||||
void register_and_attach_window(HWND window);
|
void register_and_attach_window(HWND window);
|
||||||
|
void set_preferred_injection_window(HWND window);
|
||||||
bool hook_available(HMODULE module);
|
bool hook_available(HMODULE module);
|
||||||
bool hook(HMODULE module);
|
bool hook(HMODULE module);
|
||||||
bool inject_synthetic_touch(POINT position, bool down);
|
bool inject_synthetic_touch(POINT position, bool down);
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ namespace nativetouch::transform {
|
|||||||
window == TDJ_SUBSCREEN_WINDOW;
|
window == TDJ_SUBSCREEN_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mouse-as-touch only applies while the cursor is over the target window
|
||||||
|
static bool is_cursor_over_window(HWND window, POINT position) {
|
||||||
|
return screen_to_game_client(window, &position);
|
||||||
|
}
|
||||||
|
|
||||||
// convert game touch coordinates to Windows desktop coordinates
|
// convert game touch coordinates to Windows desktop coordinates
|
||||||
bool game_to_screen(HWND window, POINT *position) {
|
bool game_to_screen(HWND window, POINT *position) {
|
||||||
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
|
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
|
||||||
@@ -66,7 +71,13 @@ namespace nativetouch::transform {
|
|||||||
return ClientToScreen(window, position) != FALSE;
|
return ClientToScreen(window, position) != FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool has_active_overlay_transform() {
|
static bool overlay_owns_touch_input() {
|
||||||
|
// the arena SMALL window is the touch surface whenever it exists, so the
|
||||||
|
// subscreen overlay must not claim touch input in those window modes
|
||||||
|
if (graphics_gitadora_has_dedicated_subscreen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return overlay::OVERLAY != nullptr &&
|
return overlay::OVERLAY != nullptr &&
|
||||||
overlay::OVERLAY->get_active() &&
|
overlay::OVERLAY->get_active() &&
|
||||||
overlay::OVERLAY->has_subscreen_touch_transform();
|
overlay::OVERLAY->has_subscreen_touch_transform();
|
||||||
@@ -83,23 +94,28 @@ namespace nativetouch::transform {
|
|||||||
return overlay::OVERLAY->transform_touch_point(&position->x, &position->y);
|
return overlay::OVERLAY->transform_touch_point(&position->x, &position->y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the digitizer is mapped to the zero-based primary display, while SDVX
|
// SDVX still expects portrait coordinates when its image is rendered in landscape:
|
||||||
// still expects portrait coordinates when its image is rendered in landscape:
|
|
||||||
// (x, y) -> (width * (1 - y / height), height * x / width).
|
// (x, y) -> (width * (1 - y / height), height * x / width).
|
||||||
|
bool sdvx_landscape_rotate(POINT *position, LONG width, LONG height) {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto input_x = position->x;
|
||||||
|
position->x = width - MulDiv(position->y, width, height);
|
||||||
|
position->y = MulDiv(input_x, height, width);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the digitizer is mapped to the zero-based primary display, so the contact is already
|
||||||
|
// in the effective landscape resolution the rotation is based on
|
||||||
static bool transform_sdvx_landscape_touch_position(POINT *position) {
|
static bool transform_sdvx_landscape_touch_position(POINT *position) {
|
||||||
const auto landscape_width = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
const auto landscape_width = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
||||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first : GRAPHICS_FS_ORIGINAL_HEIGHT);
|
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first : GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||||
const auto landscape_height = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
const auto landscape_height = static_cast<LONG>(GRAPHICS_FS_CUSTOM_RESOLUTION.has_value() ?
|
||||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second : GRAPHICS_FS_ORIGINAL_WIDTH);
|
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second : GRAPHICS_FS_ORIGINAL_WIDTH);
|
||||||
if (landscape_width <= 0 || landscape_height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto input_x = position->x;
|
return sdvx_landscape_rotate(position, landscape_width, landscape_height);
|
||||||
position->x = landscape_width -
|
|
||||||
MulDiv(position->y, landscape_width, landscape_height);
|
|
||||||
position->y = MulDiv(input_x, landscape_height, landscape_width);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert physical screen coordinates to game touch coordinates for a known target
|
// convert physical screen coordinates to game touch coordinates for a known target
|
||||||
@@ -132,7 +148,7 @@ namespace nativetouch::transform {
|
|||||||
|
|
||||||
// check if subscreen overlay is active and can transform the touch point;
|
// check if subscreen overlay is active and can transform the touch point;
|
||||||
// if not, the touch point is valid as-is
|
// if not, the touch point is valid as-is
|
||||||
if (!has_active_overlay_transform()) {
|
if (!overlay_owns_touch_input()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +165,14 @@ namespace nativetouch::transform {
|
|||||||
|
|
||||||
// exception: sdvx windowed subscreen does not use the subscreen overlay transform
|
// exception: sdvx windowed subscreen does not use the subscreen overlay transform
|
||||||
if (GRAPHICS_WINDOWED && window == SDVX_SUBSCREEN_WINDOW) {
|
if (GRAPHICS_WINDOWED && window == SDVX_SUBSCREEN_WINDOW) {
|
||||||
POINT client_position = *position;
|
return is_cursor_over_window(window, *position);
|
||||||
return screen_to_game_client(window, &client_position);
|
}
|
||||||
|
|
||||||
|
// exception: the arena SMALL window is the touch panel, so accept the mouse there
|
||||||
|
// (and only there) with the coordinates a real contact on it would produce
|
||||||
|
if (graphics_gitadora_has_dedicated_subscreen()) {
|
||||||
|
return window == GFDM_SUBSCREEN_WINDOW &&
|
||||||
|
is_cursor_over_window(window, *position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this game has a subscreen overlay that can transform touch input
|
// if this game has a subscreen overlay that can transform touch input
|
||||||
@@ -168,7 +190,7 @@ namespace nativetouch::transform {
|
|||||||
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
// route hardware screen coordinates through dedicated or overlay mapping and report the result
|
||||||
Result hardware_to_game(POINT *position) {
|
Result hardware_to_game(POINT *position) {
|
||||||
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
|
||||||
const auto active_overlay = has_active_overlay_transform();
|
const auto active_overlay = overlay_owns_touch_input();
|
||||||
|
|
||||||
// special case for SDVX landscape mode
|
// special case for SDVX landscape mode
|
||||||
if (!dedicated_subscreen && !active_overlay &&
|
if (!dedicated_subscreen && !active_overlay &&
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace nativetouch::transform {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool is_tdj_dedicated_subscreen(HWND window);
|
bool is_tdj_dedicated_subscreen(HWND window);
|
||||||
|
bool sdvx_landscape_rotate(POINT *position, LONG width, LONG height);
|
||||||
bool game_to_screen(HWND window, POINT *position);
|
bool game_to_screen(HWND window, POINT *position);
|
||||||
bool screen_to_game(HWND window, POINT *position);
|
bool screen_to_game(HWND window, POINT *position);
|
||||||
bool mouse_to_game(HWND window, POINT *position);
|
bool mouse_to_game(HWND window, POINT *position);
|
||||||
|
|||||||
Reference in New Issue
Block a user