Compare commits

...

2 Commits

Author SHA1 Message Date
bicarus ce2f93d234 graphics: refactor DX9 screenshot (part 2) (#861) 2026-08-11 01:11:42 -07:00
bicarus 13a171f199 sdvx: fix api touch in windowed mode (#862)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #858 

## Description of change
Fix orientation being wrong in windowed mode when API touch is used

Fix mouse no longer working when API client is in use.

This PR only affects windowed mode SDVX.

## Testing
2026-08-10 18:58:48 -07:00
3 changed files with 189 additions and 125 deletions
+7 -5
View File
@@ -55,10 +55,12 @@ 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) {
// landscape API coordinates already match the primary screen orientation; // windowed and landscape API coordinates already match the primary screen orientation;
// portrait coordinates are rotated by apply_touch_errata // fullscreen portrait coordinates are rotated by apply_touch_errata
native_canvas_w = GRAPHICS_FS_ORIENTATION_SWAP ? 1920 : 1080; const bool landscape_coordinates =
native_canvas_h = GRAPHICS_FS_ORIENTATION_SWAP ? 1080 : 1920; GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
native_canvas_w = landscape_coordinates ? 1920 : 1080;
native_canvas_h = landscape_coordinates ? 1080 : 1920;
} else if (avs::game::is_model("LDJ")) { } else if (avs::game::is_model("LDJ")) {
// TDJ subscreen; FHD models are upscaled to 1080p by apply_touch_errata // TDJ subscreen; FHD models are upscaled to 1080p by apply_touch_errata
native_canvas_w = is_tdj_fhd ? 1920 : 1280; native_canvas_w = is_tdj_fhd ? 1920 : 1280;
@@ -219,7 +221,7 @@ namespace api::modules {
// 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_raw * 1920 / 1280;
y = y_raw * 1080 / 720; y = y_raw * 1080 / 720;
} else if (is_sdvx && !GRAPHICS_FS_ORIENTATION_SWAP) { } else if (is_sdvx && !GRAPHICS_WINDOWED && !GRAPHICS_FS_ORIENTATION_SWAP) {
// rotate API coordinates into SDVX's portrait touch space // rotate API coordinates into SDVX's portrait touch space
x = 1080 - y_raw; x = 1080 - y_raw;
y = x_raw; y = x_raw;
@@ -2,7 +2,9 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <utility>
#include <external/robin_hood.h> #include <external/robin_hood.h>
@@ -38,6 +40,33 @@ typedef HRESULT (WINAPI *D3DXSaveSurfaceToFileA_t)(
static bool ATTEMPTED_D3DX9_LOAD_LIBRARY = false; static bool ATTEMPTED_D3DX9_LOAD_LIBRARY = false;
namespace {
enum class ImageRequestKind {
Screenshot,
Capture,
};
struct ImageRequest {
ImageRequestKind kind;
int screen;
};
struct SurfaceReleaser {
void operator()(IDirect3DSurface9 *surface) const {
surface->Release();
}
};
using SurfacePtr = std::unique_ptr<IDirect3DSurface9, SurfaceReleaser>;
struct BackbufferCopy {
D3DSURFACE_DESC desc {};
SurfacePtr surface;
};
} // namespace
static void save_capture( static void save_capture(
int screen, int screen,
D3DFORMAT format, D3DFORMAT format,
@@ -226,34 +255,24 @@ void graphics_d3d9_poll_screenshot_hotkey() {
} }
} }
void graphics_d3d9_process_screenshot_and_capture( static std::optional<BackbufferCopy> acquire_backbuffer_copy(
IDirect3DDevice9 *device, IDirect3DDevice9 *device, IDirect3DSwapChain9 *sub_swap_chain, int screen) {
IDirect3DSwapChain9 *sub_swap_chain) {
// process pending screenshot
bool screenshot = false;
bool capture = false;
int capture_screen = 0;
if ((screenshot = graphics_screenshot_consume())
|| ((capture = graphics_capture_consume(&capture_screen)))) {
HRESULT hr = S_OK; HRESULT hr = S_OK;
// TODO: verify capture_screen is a valid swapchain // TODO: verify screen is a valid swapchain
// get back buffer
IDirect3DSurface9 *buffer = nullptr; IDirect3DSurface9 *buffer = nullptr;
if (sub_swap_chain != nullptr && capture_screen & 1) { if (sub_swap_chain != nullptr && screen & 1) {
hr = sub_swap_chain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &buffer); hr = sub_swap_chain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &buffer);
} else { } else {
hr = device->GetBackBuffer(capture_screen, 0, D3DBACKBUFFER_TYPE_MONO, &buffer); 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, hr={}",
FMT_HRESULT(hr)); FMT_HRESULT(hr));
if (capture) { return std::nullopt;
graphics_capture_skip(capture_screen);
}
return;
} }
D3DSURFACE_DESC desc {}; D3DSURFACE_DESC desc {};
@@ -263,10 +282,7 @@ void graphics_d3d9_process_screenshot_and_capture(
"failed to acquire back buffer descriptor, hr={}", "failed to acquire back buffer descriptor, hr={}",
FMT_HRESULT(hr)); FMT_HRESULT(hr));
buffer->Release(); buffer->Release();
if (capture) { return std::nullopt;
graphics_capture_skip(capture_screen);
}
return;
} }
// TODO: cache render targets // TODO: cache render targets
@@ -279,10 +295,7 @@ void graphics_d3d9_process_screenshot_and_capture(
"failed to acquire temporary surface, hr={}", "failed to acquire temporary surface, hr={}",
FMT_HRESULT(hr)); FMT_HRESULT(hr));
buffer->Release(); buffer->Release();
if (capture) { return std::nullopt;
graphics_capture_skip(capture_screen);
}
return;
} }
hr = device->StretchRect(buffer, nullptr, temp_surface, nullptr, D3DTEXF_NONE); hr = device->StretchRect(buffer, nullptr, temp_surface, nullptr, D3DTEXF_NONE);
@@ -292,42 +305,45 @@ void graphics_d3d9_process_screenshot_and_capture(
FMT_HRESULT(hr)); FMT_HRESULT(hr));
temp_surface->Release(); temp_surface->Release();
buffer->Release(); buffer->Release();
if (capture) { return std::nullopt;
graphics_capture_skip(capture_screen);
}
return;
} }
// release original back buffer reference // release original back buffer reference
buffer->Release(); buffer->Release();
// function for storing the surface return BackbufferCopy {
auto surface_process = [=]() { .desc = desc,
.surface = SurfacePtr(temp_surface),
};
}
// capture static void dispatch_surface_save(
if (capture) { const ImageRequest &request,
save_capture(capture_screen, desc.Format, desc.Width, desc.Height, temp_surface); BackbufferCopy copy) {
} auto surface_process = [request, copy = std::move(copy)]() {
switch (request.kind) {
case ImageRequestKind::Capture:
save_capture(
request.screen,
copy.desc.Format,
copy.desc.Width,
copy.desc.Height,
copy.surface.get());
break;
// screenshot case ImageRequestKind::Screenshot: {
if (screenshot) {
// check where we can save it
auto file_path = graphics_screenshot_genpath(); auto file_path = graphics_screenshot_genpath();
if (!file_path.empty()) { if (!file_path.empty()) {
// write to file
save_screenshot( save_screenshot(
file_path, file_path,
desc.Format, copy.desc.Format,
desc.Width, copy.desc.Width,
desc.Height, copy.desc.Height,
temp_surface); copy.surface.get());
}
break;
} }
} }
// release surface
temp_surface->Release();
}; };
// list of games that crash when running the screenshot processor on another thread // list of games that crash when running the screenshot processor on another thread
@@ -347,7 +363,47 @@ void graphics_d3d9_process_screenshot_and_capture(
surface_process(); surface_process();
} else { } else {
static auto pool = ThreadPool(2); static auto pool = ThreadPool(2);
pool.add(surface_process); pool.add(std::move(surface_process));
}
} }
} }
static std::optional<ImageRequest> consume_image_request() {
if (graphics_screenshot_consume()) {
return ImageRequest {
.kind = ImageRequestKind::Screenshot,
.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(
IDirect3DDevice9 *device,
IDirect3DSwapChain9 *sub_swap_chain) {
const auto request = consume_image_request();
if (!request.has_value()) {
return;
}
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));
}
+6
View File
@@ -147,6 +147,12 @@ namespace nativetouch::transform {
return screen_to_game(window, position); return screen_to_game(window, position);
} }
// exception: sdvx windowed subscreen does not use the subscreen overlay transform
if (GRAPHICS_WINDOWED && window == SDVX_SUBSCREEN_WINDOW) {
POINT client_position = *position;
return screen_to_game_client(window, &client_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
// but the window is hidden or not under the cursor, reject mouse-as-touch // but the window is hidden or not under the cursor, reject mouse-as-touch
// (e.g., iidx/sdvx are rejected here, but nostalgia is allowed) // (e.g., iidx/sdvx are rejected here, but nostalgia is allowed)