mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
f119d7c988
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Subscreen overlay window used to draw above the main game surface, but below any other ImGui widgets. Fix this so that the subscreen image is drawn at the same z-level as the sub window itself. This has been a long-standing issue - at least since 2023 when spice2x first forked... ## Testing Tested TDJ in windowed mode.
263 lines
10 KiB
C++
263 lines
10 KiB
C++
#undef CINTERFACE
|
|
|
|
#include "generic_sub.h"
|
|
|
|
#include "avs/game.h"
|
|
#include "games/io.h"
|
|
#include "cfg/screen_resize.h"
|
|
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
|
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
|
|
#include "hooks/graphics/graphics.h"
|
|
#include "util/logging.h"
|
|
#include "util/utils.h"
|
|
#include "touch/touch.h"
|
|
|
|
int GENERIC_SUB_WINDOW_X = 0;
|
|
int GENERIC_SUB_WINDOW_Y = 0;
|
|
int GENERIC_SUB_WINDOW_WIDTH = 0;
|
|
int GENERIC_SUB_WINDOW_HEIGHT = 0;
|
|
bool GENERIC_SUB_WINDOW_FULLSIZE = false;
|
|
|
|
// #define OVERLAYDBG 1
|
|
|
|
namespace overlay::windows {
|
|
|
|
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
|
|
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
|
|
|
|
GenericSubScreen::GenericSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
|
|
this->remove_window_padding = true;
|
|
// ImGuiWindowFlags_NoBackground is needed as the background is drawn on top of the subscreen image
|
|
this->flags = ImGuiWindowFlags_NoScrollbar |
|
|
ImGuiWindowFlags_NoCollapse |
|
|
ImGuiWindowFlags_NoBackground |
|
|
ImGuiWindowFlags_NoDocking;
|
|
|
|
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
|
|
this->size_min = ImVec2(240, 135 + ImGui::GetFrameHeight());
|
|
this->init_size = size_min;
|
|
this->resize_callback = keep_16_by_9;
|
|
|
|
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
|
|
this->texture_width = 0;
|
|
this->texture_height = 0;
|
|
|
|
overlay->set_subscreen_mouse_handler([this](LONG *x, LONG *y) -> bool {
|
|
// convert to normalized form (relative window coordinates 0.f-1.f)
|
|
ImVec2 xy;
|
|
|
|
// log_misc("sub::overlay", "mouse handler {} {}", to_string(*x), to_string(*y));
|
|
// log_misc("sub::overlay", "spicetouch coords {} {} {} {}", to_string(SPICETOUCH_TOUCH_X), to_string(SPICETOUCH_TOUCH_Y), to_string(SPICETOUCH_TOUCH_WIDTH), to_string(SPICETOUCH_TOUCH_HEIGHT));
|
|
|
|
float ratio_x, ratio_y;
|
|
|
|
if (GRAPHICS_WINDOWED) {
|
|
// input coords are relative to spicetouch wnd
|
|
ratio_x = (float)*x / SPICETOUCH_TOUCH_WIDTH;
|
|
ratio_y = (float)*y / SPICETOUCH_TOUCH_HEIGHT;
|
|
} else {
|
|
// inputs coords are relative to (0,0) for non-windowed mode
|
|
ratio_x = (float)*x / ImGui::GetIO().DisplaySize.x;
|
|
ratio_y = (float)*y / ImGui::GetIO().DisplaySize.y;
|
|
}
|
|
// log_misc("sub::overlay", "game coords {} {}", to_string(ratio_x), to_string(ratio_y));
|
|
|
|
// transform to subscreen overlay coords
|
|
if (!GENERIC_SUB_WINDOW_FULLSIZE) {
|
|
ratio_x = (ratio_x * ImGui::GetIO().DisplaySize.x - GENERIC_SUB_WINDOW_X) / GENERIC_SUB_WINDOW_WIDTH;
|
|
ratio_y = (ratio_y * ImGui::GetIO().DisplaySize.y - GENERIC_SUB_WINDOW_Y) / GENERIC_SUB_WINDOW_HEIGHT;
|
|
// log_misc("sub::overlay", "overlay coords {} {} {} {}", to_string(GENERIC_SUB_WINDOW_X), to_string(GENERIC_SUB_WINDOW_Y), to_string(GENERIC_SUB_WINDOW_WIDTH), to_string(GENERIC_SUB_WINDOW_HEIGHT));
|
|
}
|
|
|
|
xy.x = ratio_x;
|
|
xy.y = ratio_y;
|
|
// log_misc("sub::overlay", "ratio {} {}", to_string(xy.x), to_string(xy.y));
|
|
|
|
// x/y can be outside of window
|
|
if (xy.x < 0.f || 1.f < xy.x || xy.y < 0.f || 1.f < xy.y) {
|
|
return false;
|
|
}
|
|
|
|
// call into child
|
|
this->touch_transform(xy, x, y);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
|
|
void GenericSubScreen::check_for_errors() {}
|
|
|
|
void GenericSubScreen::build_content() {
|
|
this->flags |= ImGuiWindowFlags_NoBackground;
|
|
|
|
check_for_errors();
|
|
if (this->disabled_message.has_value()) {
|
|
this->draws_window = true;
|
|
this->flags &= ~ImGuiWindowFlags_NoBackground;
|
|
ImGui::TextColored(YELLOW, "%s", this->disabled_message.value().c_str());
|
|
ImGui::TextUnformatted("");
|
|
if (ImGui::Button("Close")) {
|
|
this->set_active(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
this->draw_texture();
|
|
|
|
if (!this->texture) {
|
|
this->draws_window = true;
|
|
this->flags &= ~ImGuiWindowFlags_NoBackground;
|
|
ImGui::TextColored(YELLOW, "Failed to acquire surface texture for subscreen.");
|
|
if (avs::game::is_model("LDJ")) {
|
|
ImGui::TextColored(
|
|
YELLOW,
|
|
"Ensure that you are using the correct DLL type, \n"
|
|
"or patch the DLL to properly enable TDJ mode.");
|
|
} else if (avs::game::is_model("KFC")) {
|
|
ImGui::TextColored(
|
|
YELLOW,
|
|
"Ensure that you did not accidentally enable a patch \n"
|
|
"that turns off subscreen rendering.");
|
|
}
|
|
ImGui::TextUnformatted("");
|
|
if (ImGui::Button("Close")) {
|
|
this->set_active(false);
|
|
}
|
|
}
|
|
|
|
#if OVERLAYDBG
|
|
if (this->status_message.has_value()) {
|
|
log_warning("sub::overlay", "{}", this->status_message.value().c_str());
|
|
}
|
|
if (this->status_message.has_value()) {
|
|
ImGui::TextColored(YELLOW, "%s", this->status_message.value().c_str());
|
|
} else if (this->texture) {
|
|
ImGui::TextColored(WHITE, "Successfully acquired surface texture");
|
|
} else {
|
|
ImGui::TextColored(YELLOW, "Failed to acquire surface texture");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool GenericSubScreen::build_texture(IDirect3DSurface9 *surface, UINT width, UINT height) {
|
|
HRESULT hr;
|
|
|
|
D3DSURFACE_DESC desc {};
|
|
hr = surface->GetDesc(&desc);
|
|
if (FAILED(hr)) {
|
|
this->status_message = fmt::format("Failed to get surface descriptor, hr={}", FMT_HRESULT(hr));
|
|
return false;
|
|
}
|
|
|
|
hr = this->device->CreateTexture(width, height, 0, desc.Usage, desc.Format,
|
|
desc.Pool, &this->texture, nullptr);
|
|
if (FAILED(hr)) {
|
|
this->status_message = fmt::format("Failed to create render target, hr={}", FMT_HRESULT(hr));
|
|
return false;
|
|
}
|
|
|
|
this->texture_width = width;
|
|
this->texture_height = height;
|
|
return true;
|
|
}
|
|
|
|
void GenericSubScreen::draw_texture() {
|
|
HRESULT hr;
|
|
|
|
auto surface = graphics_d3d9_ldj_get_sub_screen();
|
|
if (surface == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (this->draws_window) {
|
|
// calculate the **content** location and size.
|
|
// GetContentRegionAvail returns the correct dimension, up to (and including) the resize handle
|
|
// don't be tempted to change it to some other ImGui routine that accomplishes similar things!
|
|
overlay_content_top_left = ImGui::GetCursorScreenPos();
|
|
overlay_content_size = ImGui::GetContentRegionAvail();
|
|
} else {
|
|
// no window, full screen
|
|
overlay_content_top_left = ImVec2(0, 0);
|
|
overlay_content_size = ImGui::GetIO().DisplaySize;
|
|
}
|
|
|
|
GENERIC_SUB_WINDOW_X = overlay_content_top_left.x;
|
|
GENERIC_SUB_WINDOW_Y = overlay_content_top_left.y;
|
|
GENERIC_SUB_WINDOW_WIDTH = overlay_content_size.x;
|
|
GENERIC_SUB_WINDOW_HEIGHT = overlay_content_size.y;
|
|
|
|
if (this->draws_window &&
|
|
this->texture &&
|
|
((UINT)overlay_content_size.x != this->texture_width)) {
|
|
|
|
#if OVERLAYDBG
|
|
log_info("sub::overlay", "resize {} != {} ", overlay_content_size.x, this->texture_width);
|
|
#endif
|
|
|
|
// hack needed for SDVX; resizing the texture results in darker image, so allocate texture
|
|
// again with the new size when the window is resized
|
|
this->texture->Release();
|
|
this->texture = nullptr;
|
|
this->texture_width = 0;
|
|
this->texture_height = 0;
|
|
}
|
|
|
|
if (this->texture == nullptr) {
|
|
if (!this->build_texture(surface, overlay_content_size.x, overlay_content_size.y)) {
|
|
this->texture = nullptr;
|
|
this->texture_width = 0;
|
|
this->texture_height = 0;
|
|
surface->Release();
|
|
return;
|
|
}
|
|
}
|
|
|
|
IDirect3DSurface9 *texture_surface = nullptr;
|
|
hr = this->texture->GetSurfaceLevel(0, &texture_surface);
|
|
if (FAILED(hr)) {
|
|
this->status_message = fmt::format("Failed to get texture surface, hr={}", FMT_HRESULT(hr));
|
|
surface->Release();
|
|
return;
|
|
}
|
|
|
|
hr = this->device->StretchRect(surface, nullptr, texture_surface, nullptr, D3DTEXF_LINEAR);
|
|
if (FAILED(hr)) {
|
|
this->status_message = fmt::format("Failed to copy back buffer contents, hr={}", FMT_HRESULT(hr));
|
|
surface->Release();
|
|
texture_surface->Release();
|
|
return;
|
|
}
|
|
|
|
surface->Release();
|
|
texture_surface->Release();
|
|
|
|
// draw the subscreen into the current window's draw list so it participates in
|
|
// normal ImGui z-ordering (i.e. other ImGui windows can appear above it)
|
|
auto bottom_right = overlay_content_size;
|
|
bottom_right.x += overlay_content_top_left.x;
|
|
bottom_right.y += overlay_content_top_left.y;
|
|
auto draw_list = this->draws_window
|
|
? ImGui::GetWindowDrawList()
|
|
: ImGui::GetBackgroundDrawList();
|
|
draw_list->AddImage(
|
|
reinterpret_cast<ImTextureID>(this->texture),
|
|
overlay_content_top_left,
|
|
bottom_right);
|
|
|
|
if (this->draws_window) {
|
|
// draw an invisible button so that it swallows mouse input
|
|
// this is needed to prevent the window from being dragged around
|
|
// (alternatively io.ConfigWindowsMoveFromTitleBarOnly can be set but that is global)
|
|
ImGui::InvisibleButton(
|
|
(this->title + "__InvisibleButton").c_str(),
|
|
overlay_content_size,
|
|
ImGuiButtonFlags_None);
|
|
}
|
|
|
|
#if OVERLAYDBG
|
|
log_info("sub::overlay", "{} / {} = {}", overlay_content_size.x, overlay_content_size.y, overlay_content_size.x / overlay_content_size.y);
|
|
#endif
|
|
|
|
}
|
|
}
|