From caf7927445cd16cf2af14a07f55d77a654787f04 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Mon, 15 Nov 2021 19:09:04 +0100 Subject: [PATCH] Added lightgun laser emulation --- src/common/input/Button.h | 2 +- src/common/input/InputDevice.cpp | 2 +- src/common/input/InputDevice.h | 3 +- src/common/input/InputManager.cpp | 85 +++++++++++++++++++---- src/common/input/InputManager.h | 12 ++-- src/common/input/layout_xbox_device.h | 2 + src/core/common/imgui/ui.cpp | 19 ++++- src/core/common/imgui/ui.hpp | 5 +- src/core/common/video/RenderBase.cpp | 4 +- src/core/common/video/RenderBase.hpp | 2 +- src/core/hle/D3D8/Direct3D9/Direct3D9.cpp | 9 ++- src/core/hle/XAPI/Xapi.cpp | 5 +- src/core/kernel/init/CxbxKrnl.h | 1 + src/devices/video/EmuNV2A_PGRAPH.cpp | 7 ++ src/gui/input/DlgLightgunConfig.cpp | 7 +- src/gui/resource/Cxbx.rc | 7 +- src/gui/resource/ResCxbx.h | 2 + 17 files changed, 140 insertions(+), 34 deletions(-) diff --git a/src/common/input/Button.h b/src/common/input/Button.h index 4f98fb140..06dd13c85 100644 --- a/src/common/input/Button.h +++ b/src/common/input/Button.h @@ -31,7 +31,7 @@ #include #include -#define LIGHTGUN_NUM_BUTTONS 16 +#define LIGHTGUN_NUM_BUTTONS 17 #define XBOX_CTRL_NUM_BUTTONS 25 #define SBC_NUM_BUTTONS 56 #define HIGHEST_NUM_BUTTONS SBC_NUM_BUTTONS diff --git a/src/common/input/InputDevice.cpp b/src/common/input/InputDevice.cpp index 3a0c25597..c945fc474 100644 --- a/src/common/input/InputDevice.cpp +++ b/src/common/input/InputDevice.cpp @@ -178,7 +178,7 @@ const auto InputDevice::FindPort(std::string_view Port) const }); } -void InputDevice::SetPort(std::string_view Port, bool Connect) +void InputDevice::SetPort2(std::string_view Port, bool Connect) { if (Connect) { m_XboxPort.emplace_back(Port); diff --git a/src/common/input/InputDevice.h b/src/common/input/InputDevice.h index d7f52a38b..4b6dfc43b 100644 --- a/src/common/input/InputDevice.h +++ b/src/common/input/InputDevice.h @@ -122,7 +122,8 @@ public: // retrieves the port this device is attached to bool GetPort(std::string_view Port) const; // sets the port this device is attached to - void SetPort(std::string_view Port, bool Connect); + // NOTE: using SetPort2 to avoid a collision with the SetPort macro provided by Windows headers + void SetPort2(std::string_view Port, bool Connect); // retuns true if it is a libusb device, false otherwise virtual bool IsLibusb() const { return false; }; diff --git a/src/common/input/InputManager.cpp b/src/common/input/InputManager.cpp index 84931408b..36ecc71ff 100644 --- a/src/common/input/InputManager.cpp +++ b/src/common/input/InputManager.cpp @@ -37,6 +37,7 @@ #include // For PKINTERRUPT, etc. +#include "D3dx9math.h" // For the matrix math functions #include "SdlJoystick.h" #include "XInputPad.h" #include "RawDevice.h" @@ -273,7 +274,7 @@ void InputDeviceManager::UpdateDevices(std::string_view port, bool ack) else { auto host_dev = g_InputDeviceManager.FindDevice(port); if (host_dev != nullptr) { - host_dev->SetPort(port, false); + host_dev->SetPort2(port, false); } if (type != to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID)) { if (type != to_underlying(dev->type)) { @@ -310,7 +311,7 @@ void InputDeviceManager::DisconnectDevice(DeviceState *dev, std::string_view por } auto host_dev = g_InputDeviceManager.FindDevice(port); if (host_dev != nullptr) { - host_dev->SetPort(port, false); + host_dev->SetPort2(port, false); } } @@ -329,7 +330,7 @@ void InputDeviceManager::BindHostDevice(int type, std::string_view port) g_EmuShared->GetInputDevNameSettings(dev_name, port_num); auto dev = FindDevice(std::string(dev_name)); if (dev != nullptr) { - dev->SetPort(port, true); + dev->SetPort2(port, true); } return; } @@ -356,7 +357,7 @@ void InputDeviceManager::BindHostDevice(int type, std::string_view port) }); dev->SetBindings(index, (it != controls.end()) ? *it : nullptr, port_str); } - dev->SetPort(port, true); + dev->SetPort2(port, true); } } @@ -491,24 +492,36 @@ bool InputDeviceManager::UpdateInputLightgun(std::shared_ptr &Devic // We change the toggle buttons only when a press -> release input transaction is completed // 0 -> Turbo left // 1 -> Turbo right + // 2 -> Laser XpadInput *in_buf = reinterpret_cast(static_cast(Buffer) + XID_PACKET_HEADER); - uint8_t last_turbo = g_devs[Port_num].info.ligthgun.turbo; - for (int i = 14, j = 0; i < 16; i++, j++) { + g_devs[Port_num].info.ligthgun.last_turbo = g_devs[Port_num].info.ligthgun.turbo; + for (int i = 14, j = 0; i < 17; i++, j++) { ControlState state = (bindings[i] != nullptr) ? dynamic_cast(bindings[i])->GetState() : 0.0; uint8_t curr_state = static_cast(!!state); - if ((~curr_state) & ((g_devs[Port_num].info.ligthgun.last_turbo_state >> j) & 1)) { - if (j == 0) { + if ((~curr_state) & ((g_devs[Port_num].info.ligthgun.last_in_state >> j) & 1)) { + switch (j) + { + case 0: if (g_devs[Port_num].info.ligthgun.turbo != 2) { g_devs[Port_num].info.ligthgun.turbo += 1; } - } - else { + break; + + case 1: if (g_devs[Port_num].info.ligthgun.turbo != 0) { g_devs[Port_num].info.ligthgun.turbo -= 1; } + break; + + case 2: + g_devs[Port_num].info.ligthgun.laser ^= 1; + if (g_devs[Port_num].info.ligthgun.laser) { + + } + break; } } - (g_devs[Port_num].info.ligthgun.last_turbo_state &= ~(1 << j)) |= (curr_state << j); + (g_devs[Port_num].info.ligthgun.last_in_state &= ~(1 << j)) |= (curr_state << j); } in_buf->wButtons = XINPUT_LIGHTGUN_ONSCREEN; @@ -532,7 +545,7 @@ bool InputDeviceManager::UpdateInputLightgun(std::shared_ptr &Devic // Turbo mode 2 start_idx = 8; ++g_devs[Port_num].info.ligthgun.turbo_delay; - if (last_turbo != g_devs[Port_num].info.ligthgun.turbo) { + if (g_devs[Port_num].info.ligthgun.last_turbo != g_devs[Port_num].info.ligthgun.turbo) { g_devs[Port_num].info.ligthgun.turbo_delay = 0; } if (g_devs[Port_num].info.ligthgun.turbo_delay == LIGHTGUN_GRIP_DELAY) { @@ -887,3 +900,51 @@ void InputDeviceManager::HotplugHandler(bool is_sdl) } } } + +ImVec2 InputDeviceManager::CalcLaserPos(int port) +{ + static ImVec2 laser_coord[XBOX_NUM_PORTS] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + + // If somebody else is currently holding the lock, we won't wait and instead we report the last known laser position + if (m_Mtx.try_lock()) { + static D3DXVECTOR4 coeff_vec; + + // If the rendering window was not resized, we can skip calculating the conversion matrix + if (g_bRenderWindowResized) { + g_bRenderWindowResized = false; + + // We convert the laser input coordinates given by xinput (in the sThumbLXY members of XpadInput) with the procedure described in the link below + // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/jj635757(v=vs.85)?redirectedfrom=MSDN + // NOTE: the d3d math functions work even when d3d was not intialized, which happens when running with LLE GPU turned on + /* + xinput -> screen + v = M^-1 * u + width 0 0 1 0 a + height = 0 0 0 1 * b + 0 -32768 32767 1 0 c + 0 -32767 -32768 0 1 d + */ + RECT rect; + GetClientRect(m_hwnd, &rect); + const auto width = std::max(rect.right - rect.left, 1l); + const auto height = std::max(rect.bottom - rect.top, 1l); + D3DXMATRIX inverted_mtx, transposed_mtx; + D3DXMATRIX src_mtx(0, 0, 1, 0, 0, 0, 0, 1, -32768, 32767, 1, 0, -32767, -32768, 0, 1); + D3DXVECTOR4 screen_vec(width, height, 0, 0); + D3DXMatrixInverse(&inverted_mtx, nullptr, &src_mtx); + D3DXMatrixTranspose(&transposed_mtx, &inverted_mtx); + D3DXVec4Transform(&coeff_vec, &screen_vec, &transposed_mtx); + } + + // x' = ax + by + c + // y' = bx - ay + d + int16_t laser_x = g_devs[port].info.buff.ctrl.InBuffer.sThumbLX; + int16_t laser_y = g_devs[port].info.buff.ctrl.InBuffer.sThumbLY; + laser_coord[port].x = coeff_vec.x * laser_x + coeff_vec.y * laser_y + coeff_vec.z; + laser_coord[port].y = coeff_vec.y * laser_x - coeff_vec.x * laser_y + coeff_vec.w; + + m_Mtx.unlock(); + } + + return laser_coord[port]; +} diff --git a/src/common/input/InputManager.h b/src/common/input/InputManager.h index 3ddfde46e..8f20f3b52 100644 --- a/src/common/input/InputManager.h +++ b/src/common/input/InputManager.h @@ -31,11 +31,7 @@ #include #include "InputDevice.h" #include "EmuDevice.h" - -// Prevent a collision with the SetPort provided by Windows -#ifdef WIN32 -#undef SetPort -#endif +#include #define PORT_INVALID -1 #define PORT_1 0 @@ -162,9 +158,11 @@ struct XidSBCOutput { struct LightGunData { xbox::short_xt offset_x; xbox::short_xt offset_y; - uint8_t last_turbo_state; + uint8_t last_in_state; + uint8_t last_turbo; uint8_t turbo_delay; uint8_t turbo; + uint8_t laser; }; struct SbcData { @@ -234,6 +232,8 @@ public: void UpdateOpt(bool is_gui); // device hotplug event handler void HotplugHandler(bool is_sdl); + // converts xinput -> screen coordinates to display the lightgun laser on the rendering window + ImVec2 CalcLaserPos(int port); private: diff --git a/src/common/input/layout_xbox_device.h b/src/common/input/layout_xbox_device.h index e8b1865d8..bf9aa694f 100644 --- a/src/common/input/layout_xbox_device.h +++ b/src/common/input/layout_xbox_device.h @@ -137,6 +137,7 @@ inline int button_lightgun_id[LIGHTGUN_NUM_BUTTONS] = { IDC_LG_AIM_NEGY, IDC_TURBO_LEFT, IDC_TURBO_RIGHT, + IDC_LASER, }; #endif @@ -244,6 +245,7 @@ inline constexpr const char *button_lightgun_names[LIGHTGUN_NUM_BUTTONS] = { "Aim Y-", "Turbo Left", "Turbo Right", + "Laser", }; constexpr bool check_button_name_size(unsigned max_num_buttons) diff --git a/src/core/common/imgui/ui.cpp b/src/core/common/imgui/ui.cpp index 680a2bf93..38792c7b8 100644 --- a/src/core/common/imgui/ui.cpp +++ b/src/core/common/imgui/ui.cpp @@ -15,7 +15,14 @@ #include "core/kernel/init/CxbxKrnl.h" -bool ImGuiUI::Initialize() +const ImColor ImGuiUI::m_laser_col[4] = { + ImColor(ImVec4(1.0f, 0.0f, 0.0f, 1.0f)), // ply1: red + ImColor(ImVec4(0.0f, 1.0f, 0.0f, 1.0f)), // ply2: green + ImColor(ImVec4(0.0f, 0.0f, 1.0f, 1.0f)), // ply3: blue + ImColor(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)) // ply4: yellow +}; + +bool ImGuiUI::Initialize(int backbuffer_scale) { IMGUI_CHECKVERSION(); m_imgui_context = ImGui::CreateContext(); @@ -48,6 +55,7 @@ bool ImGuiUI::Initialize() // Internal initialize (when necessary, move into its own function.) fps_counter = 30.0f; + m_backbuffer_scale = backbuffer_scale; // Miscs m_audio.Initialize(); @@ -190,3 +198,12 @@ void ImGuiUI::DrawWidgets() m_audio.DrawWidgets(m_is_focus, input_handler); } + +void ImGuiUI::DrawLightgunLaser(int port) +{ + ImGui::Begin("Laser", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoDecoration); + + ImGui::GetForegroundDrawList()->AddCircleFilled(g_InputDeviceManager.CalcLaserPos(port), 10 * m_backbuffer_scale, m_laser_col[port], 32); + + ImGui::End(); +} diff --git a/src/core/common/imgui/ui.hpp b/src/core/common/imgui/ui.hpp index 19abf1498..480c4d815 100644 --- a/src/core/common/imgui/ui.hpp +++ b/src/core/common/imgui/ui.hpp @@ -32,10 +32,11 @@ public: void DrawMenu(); void DrawWidgets(); + void DrawLightgunLaser(int port); protected: - bool Initialize(); + bool Initialize(int backbuffer_scale); void Shutdown(); template @@ -61,6 +62,8 @@ protected: overlay_settings m_settings; unsigned int m_lle_flags; float fps_counter; + int m_backbuffer_scale; + static const ImColor m_laser_col[4]; // Make them as settings storage. /*bool m_show_fps; bool m_show_LLE_stats; diff --git a/src/core/common/video/RenderBase.cpp b/src/core/common/video/RenderBase.cpp index 0c86344e0..c0b356644 100644 --- a/src/core/common/video/RenderBase.cpp +++ b/src/core/common/video/RenderBase.cpp @@ -16,9 +16,9 @@ std::unique_ptr g_renderbase; -bool RenderBase::Initialize() +bool RenderBase::Initialize(int backbuffer_scale) { - if (!ImGuiUI::Initialize()) { + if (!ImGuiUI::Initialize(backbuffer_scale)) { return false; } diff --git a/src/core/common/video/RenderBase.hpp b/src/core/common/video/RenderBase.hpp index 1a2485a14..bc36ee425 100644 --- a/src/core/common/video/RenderBase.hpp +++ b/src/core/common/video/RenderBase.hpp @@ -16,7 +16,7 @@ public: RenderBase() = default; virtual ~RenderBase() = default; - virtual bool Initialize(); + virtual bool Initialize(int backbuffer_scale); virtual void Shutdown(); template diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 8cfeec77d..11e9f476f 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -87,6 +87,7 @@ using namespace std::literals::chrono_literals; // Global(s) HWND g_hEmuWindow = NULL; // rendering window +bool g_bRenderWindowResized = true; // indicates that the rendering window has had its size changed bool g_bClipCursor = false; // indicates that the mouse cursor should be confined inside the rendering window IDirect3DDevice9Ex *g_pD3DDevice = nullptr; // Direct3D Device @@ -194,6 +195,11 @@ static void CxbxImGui_RenderD3D9(ImGuiUI* m_imgui, IDirect3DSurface9* renderTarg m_imgui->DrawMenu(); m_imgui->DrawWidgets(); + for (int port = PORT_1; port < XBOX_NUM_PORTS; ++port) { + if (g_devs[port].type == XBOX_INPUT_DEVICE::LIGHTGUN && g_devs[port].info.ligthgun.laser) { + m_imgui->DrawLightgunLaser(port); + } + } ImGui::EndFrame(); @@ -663,7 +669,7 @@ void CxbxInitWindow(bool bFullInit) SetFocus(g_hEmuWindow); g_renderbase = std::unique_ptr(new RenderBase()); - g_renderbase->Initialize(); + g_renderbase->Initialize(g_RenderUpscaleFactor); ImGui_ImplWin32_Init(g_hEmuWindow); g_renderbase->SetWindowRelease([] { @@ -2032,6 +2038,7 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar case WM_SIZE: { + g_bRenderWindowResized = true; switch(wParam) { case SIZE_RESTORED: diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index b49f4c387..f6bd76b74 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -265,8 +265,9 @@ void ConstructHleInputDevice(DeviceState *dev, DeviceState *upstream, int type, dev->info.ucInputStateSize = sizeof(XpadInput); dev->info.ucFeedbackSize = sizeof(XpadOutput); dev->info.ligthgun.offset_x = dev->info.ligthgun.offset_x = 0; - dev->info.ligthgun.last_turbo_state = dev->info.ligthgun.turbo = 0; - dev->info.ligthgun.turbo_delay = 0; + dev->info.ligthgun.last_in_state = dev->info.ligthgun.turbo_delay = 0; + dev->info.ligthgun.turbo = dev->info.ligthgun.last_turbo = 0; + dev->info.ligthgun.laser = 1; // laser on by default break; case to_underlying(XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER): diff --git a/src/core/kernel/init/CxbxKrnl.h b/src/core/kernel/init/CxbxKrnl.h index f22a61fa5..c5d304b1e 100644 --- a/src/core/kernel/init/CxbxKrnl.h +++ b/src/core/kernel/init/CxbxKrnl.h @@ -187,6 +187,7 @@ bool CxbxIsElevated(); /*! kernel thunk table */ extern uint32_t CxbxKrnl_KernelThunkTable[379]; +extern bool g_bRenderWindowResized; extern bool g_bClipCursor; extern bool g_CxbxPrintUEM; extern ULONG g_CxbxFatalErrorCode; diff --git a/src/devices/video/EmuNV2A_PGRAPH.cpp b/src/devices/video/EmuNV2A_PGRAPH.cpp index 9e7f8acf1..c349ffdf0 100644 --- a/src/devices/video/EmuNV2A_PGRAPH.cpp +++ b/src/devices/video/EmuNV2A_PGRAPH.cpp @@ -33,6 +33,8 @@ // * // ****************************************************************** +#include "common\input\InputManager.h" + // FIXME #define qemu_mutex_lock_iothread() #define qemu_mutex_unlock_iothread() @@ -713,6 +715,11 @@ static void CxbxImGui_RenderOpenGL(ImGuiUI* m_imgui, std::nullptr_t unused) m_imgui->DrawMenu(); m_imgui->DrawWidgets(); + for (int port = PORT_1; port < XBOX_NUM_PORTS; ++port) { + if (g_devs[port].type == XBOX_INPUT_DEVICE::LIGHTGUN && g_devs[port].info.ligthgun.laser) { + m_imgui->DrawLightgunLaser(port); + } + } ImGui::Render(); diff --git a/src/gui/input/DlgLightgunConfig.cpp b/src/gui/input/DlgLightgunConfig.cpp index 24d07c071..0af0aff62 100644 --- a/src/gui/input/DlgLightgunConfig.cpp +++ b/src/gui/input/DlgLightgunConfig.cpp @@ -37,8 +37,8 @@ static constexpr std::array, 2> button_lightgun_default = { { { "Pad N", "Pad S", "Pad W", "Pad E", "Start", "Back", "Button A", "Button B", "Button X", "Button Y", "Left X+", "Left X-", - "Left Y+", "Left Y-", "Shoulder L", "Shoulder R" }, - { "UP", "DOWN", "LEFT", "RIGHT", "RETURN", "SPACE", "Click 0", "Click 1", "W", "E", "Cursor X+", "Cursor X-", "Cursor Y+", "Cursor Y-", "S", "D" } + "Left Y+", "Left Y-", "Shoulder L", "Shoulder R", "Thumb R" }, + { "UP", "DOWN", "LEFT", "RIGHT", "RETURN", "SPACE", "Click 0", "Click 1", "W", "E", "Cursor X+", "Cursor X-", "Cursor Y+", "Cursor Y-", "S", "D", "C"} } }; static LightgunInputWindow *g_InputWindow = nullptr; @@ -214,7 +214,8 @@ INT_PTR CALLBACK DlgLightgunConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, L case IDC_LG_AIM_POSY: case IDC_LG_AIM_NEGY: case IDC_TURBO_LEFT: - case IDC_TURBO_RIGHT: { + case IDC_TURBO_RIGHT: + case IDC_LASER: { if (HIWORD(wParam) == BN_CLICKED) { g_InputWindow->BindButton(LOWORD(wParam)); } diff --git a/src/gui/resource/Cxbx.rc b/src/gui/resource/Cxbx.rc index d77e95e0b..c6799be01 100644 --- a/src/gui/resource/Cxbx.rc +++ b/src/gui/resource/Cxbx.rc @@ -250,11 +250,14 @@ BEGIN LTEXT "Right",IDC_STATIC,285,129,20,14,SS_CENTERIMAGE PUSHBUTTON "Default Bindings",IDC_DEFAULT,362,200,69,14,BS_FLAT PUSHBUTTON "Clear",IDC_CLEAR,443,200,50,14,BS_FLAT - GROUPBOX "Turbo switch",IDC_TURBO,396,66,121,121,WS_GROUP - PUSHBUTTON "",IDC_TURBO_LEFT,443,76,57,14,BS_FLAT + GROUPBOX "Turbo switch",IDC_POWER_SWITCH,396,66,121,52,WS_GROUP + PUSHBUTTON "",IDC_TURBO_LEFT,443,132,57,14,BS_FLAT PUSHBUTTON "",IDC_TURBO_RIGHT,443,94,57,14,BS_FLAT LTEXT "Left",IDC_STATIC,412,76,20,14,SS_CENTERIMAGE LTEXT "Right",IDC_STATIC,412,94,20,14,SS_CENTERIMAGE + GROUPBOX "Power switch",IDC_TURBO,396,121,121,66,WS_GROUP + PUSHBUTTON "",IDC_LASER,443,76,57,14,BS_FLAT + LTEXT "Laser",IDC_STATIC,412,132,20,14,SS_CENTERIMAGE END IDD_LIBUSB_CFG DIALOGEX 0, 0, 250, 35 diff --git a/src/gui/resource/ResCxbx.h b/src/gui/resource/ResCxbx.h index f513ccbcc..1613e0115 100644 --- a/src/gui/resource/ResCxbx.h +++ b/src/gui/resource/ResCxbx.h @@ -293,6 +293,8 @@ #define IDC_TURBO_LEFT 1339 #define IDC_TURBO_RIGHT 1340 #define IDC_TURBO 1341 +#define IDC_POWER_SWITCH 1342 +#define IDC_LASER 1343 #define ID_FILE_EXIT 40005 #define ID_HELP_ABOUT 40008 #define ID_EMULATION_START 40009