Implemented Controller Hotkeys (#111)
Implemented Controller Hotkeys Added controller hotkeys Added guide button support for XInput and winkey The hotkey configurations can be found in HID -> Display controller hotkeys If the Xbox Gamebar overlay is enabled then use the Back button instead of the Guide button. - Fixed hotkey thread destruction - Fixed XINPUT_STATE by padding 4 bytes - Added hotkey vibration for user feedback - Replaced MessageBoxA with ImGuiDialog::ShowMessageBox Co-authored-by: Margen67 <Margen67@users.noreply.github.com>
This commit is contained in:
parent
ea1003c6bf
commit
459497f0b6
|
@ -31,6 +31,7 @@
|
|||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/hid/input_system.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
|
@ -47,6 +48,14 @@
|
|||
|
||||
DECLARE_bool(debug);
|
||||
|
||||
DECLARE_string(hid);
|
||||
|
||||
DECLARE_bool(guide_button);
|
||||
|
||||
DECLARE_bool(d3d12_readback_resolve);
|
||||
|
||||
DECLARE_bool(d3d12_clear_memory_page_state);
|
||||
|
||||
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
|
||||
"Display");
|
||||
|
||||
|
@ -134,6 +143,9 @@ using xe::ui::KeyEvent;
|
|||
using xe::ui::MenuItem;
|
||||
using xe::ui::UIEvent;
|
||||
|
||||
using namespace xe::hid;
|
||||
using namespace xe::gpu;
|
||||
|
||||
const std::string kBaseTitle = "Xenia-canary";
|
||||
|
||||
EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
||||
|
@ -230,6 +242,15 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
|||
if (cvars::fullscreen) {
|
||||
SetFullscreen(true);
|
||||
}
|
||||
|
||||
if (IsUseNexusForGameBarEnabled()) {
|
||||
XELOGE("Xbox Gamebar Enabled, using BACK button instead of GUIDE!!!");
|
||||
}
|
||||
|
||||
// Create a thread to listen for controller hotkeys.
|
||||
Gamepad_HotKeys_Listener =
|
||||
threading::Thread::Create({}, [&] { GamepadHotKeys(); });
|
||||
Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener");
|
||||
}
|
||||
|
||||
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
|
||||
|
@ -608,6 +629,9 @@ bool EmulatorWindow::Initialize() {
|
|||
hid_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "&Toggle controller vibration", "",
|
||||
std::bind(&EmulatorWindow::ToggleControllerVibration, this)));
|
||||
hid_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "&Display controller hotkeys", "",
|
||||
std::bind(&EmulatorWindow::DisplayHotKeysConfig, this)));
|
||||
}
|
||||
main_menu->AddChild(std::move(hid_menu));
|
||||
|
||||
|
@ -888,10 +912,10 @@ void EmulatorWindow::InstallContent() {
|
|||
if (result != X_STATUS_SUCCESS) {
|
||||
XELOGE("Failed to install content! Error code: {:08X}", result);
|
||||
|
||||
MessageBoxA(nullptr,
|
||||
"Failed to install content!\n\nCheck xenia.log for technical "
|
||||
"details.",
|
||||
"Failed to install content!", MB_ICONERROR);
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
imgui_drawer_.get(), "Failed to install content!",
|
||||
"Failed to install content!\n\nCheck xenia.log for technical "
|
||||
"details.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1076,26 +1100,308 @@ void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
|
|||
UpdateTitle();
|
||||
}
|
||||
|
||||
void EmulatorWindow::RunPreviouslyPlayedTitle() {
|
||||
if (recently_launched_titles_.size() >= 1) {
|
||||
RunTitle(recently_launched_titles_[0].path_to_file);
|
||||
// Notes:
|
||||
// Assumes titles do not use the guide button.
|
||||
// For titles that do such as dashboards these titles could be excluded based on
|
||||
// their title ID.
|
||||
//
|
||||
// If the Xbox Gamebar overlay is enabled Windows will consume the guide
|
||||
// button's input, this can be seen using hid-demo.
|
||||
//
|
||||
// Workaround: Detect if the Xbox Gamebar overlay is enabled then use the BACK
|
||||
// button instead of the GUIDE button. Therefore BACK and GUIDE are reserved
|
||||
// buttons for hotkeys.
|
||||
//
|
||||
// This is not an issue with DualShock controllers because Windows will not
|
||||
// open the gamebar overlay using the PlayStation menu button.
|
||||
//
|
||||
// Steam:
|
||||
// If guide button focus is enabled steam will open.
|
||||
// Steam uses BACK + GUIDE to open an On-Screen keyboard, however this is not a
|
||||
// problem since both these buttons are reserved.
|
||||
const std::map<int, EmulatorWindow::ControllerHotKey> controller_hotkey_map = {
|
||||
// Must use the Guide Button for all pass through hotkeys
|
||||
{X_INPUT_GAMEPAD_A | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::ReadbackResolve,
|
||||
"A + Guide = Toggle Readback Resolve", true)},
|
||||
{X_INPUT_GAMEPAD_B | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::CloseWindow,
|
||||
"B + Guide = Close Window")},
|
||||
{X_INPUT_GAMEPAD_Y | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::ToggleFullscreen,
|
||||
"Y + Guide = Toggle Fullscreen", true)},
|
||||
{X_INPUT_GAMEPAD_X | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::ClearMemoryPageState,
|
||||
"X + Guide = Toggle Clear Memory Page State", true)},
|
||||
|
||||
{X_INPUT_GAMEPAD_RIGHT_SHOULDER | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::ToggleControllerVibration,
|
||||
"Right Shoulder + Guide = Toggle Controller Vibration", true)},
|
||||
|
||||
// CPU Time Scalar with no rumble feedback
|
||||
{X_INPUT_GAMEPAD_DPAD_DOWN | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::CpuTimeScalarSetHalf,
|
||||
"D-PAD Down + Guide = Half CPU Scalar")},
|
||||
{X_INPUT_GAMEPAD_DPAD_UP | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::CpuTimeScalarSetDouble,
|
||||
"D-PAD Up + Guide = Double CPU Scalar")},
|
||||
{X_INPUT_GAMEPAD_DPAD_RIGHT | X_INPUT_GAMEPAD_GUIDE,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::CpuTimeScalarReset,
|
||||
"D-PAD Right + Guide = Reset CPU Scalar")},
|
||||
|
||||
// non-pass through hotkeys
|
||||
{X_INPUT_GAMEPAD_Y, EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::ToggleFullscreen,
|
||||
"Y = Toggle Fullscreen", true, false)},
|
||||
{X_INPUT_GAMEPAD_START,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::RunPreviouslyPlayedTitle,
|
||||
"Start = Run previously played title", false, false)},
|
||||
{X_INPUT_GAMEPAD_BACK | X_INPUT_GAMEPAD_START,
|
||||
EmulatorWindow::ControllerHotKey(
|
||||
EmulatorWindow::ButtonFunctions::CloseWindow,
|
||||
"Back + Start = Close Window", false, false)}};
|
||||
|
||||
EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
|
||||
int buttons) {
|
||||
// Default return value
|
||||
EmulatorWindow::ControllerHotKey Unknown_hotkey = {};
|
||||
|
||||
if (buttons == 0) return Unknown_hotkey;
|
||||
|
||||
// Hotkey cool-down to prevent toggling too fast
|
||||
const std::chrono::milliseconds delay(75);
|
||||
|
||||
// If the Xbox Gamebar is enabled or the Guide button is disabled then
|
||||
// replace the Guide button with the Back button without redeclaring the key
|
||||
// mappings
|
||||
if (IsUseNexusForGameBarEnabled() || !cvars::guide_button) {
|
||||
if ((buttons & X_INPUT_GAMEPAD_BACK) == X_INPUT_GAMEPAD_BACK) {
|
||||
buttons &= ~X_INPUT_GAMEPAD_BACK;
|
||||
buttons |= X_INPUT_GAMEPAD_GUIDE;
|
||||
}
|
||||
}
|
||||
|
||||
auto it = controller_hotkey_map.find(buttons);
|
||||
if (it == controller_hotkey_map.end()) {
|
||||
return Unknown_hotkey;
|
||||
}
|
||||
|
||||
// Do not activate hotkeys that are not intended for activation during
|
||||
// gameplay
|
||||
if (emulator_->is_title_open() && !it->second.title_passthru) {
|
||||
return Unknown_hotkey;
|
||||
}
|
||||
|
||||
EmulatorWindow::ControllerHotKey button_combination = it->second;
|
||||
|
||||
switch (button_combination.function) {
|
||||
case ButtonFunctions::ToggleFullscreen:
|
||||
ToggleFullscreen();
|
||||
|
||||
// Extra Sleep
|
||||
xe::threading::Sleep(delay);
|
||||
break;
|
||||
case ButtonFunctions::RunPreviouslyPlayedTitle:
|
||||
RunPreviouslyPlayedTitle();
|
||||
break;
|
||||
case ButtonFunctions::ClearMemoryPageState:
|
||||
ToggleGPUSetting(gpu_cvar::ClearMemoryPageState);
|
||||
|
||||
// Assume the user wants ClearCaches as well
|
||||
if (cvars::d3d12_clear_memory_page_state) {
|
||||
GpuClearCaches();
|
||||
}
|
||||
|
||||
// Extra Sleep
|
||||
xe::threading::Sleep(delay);
|
||||
break;
|
||||
case ButtonFunctions::ReadbackResolve:
|
||||
ToggleGPUSetting(gpu_cvar::ReadbackResolve);
|
||||
|
||||
// Extra Sleep
|
||||
xe::threading::Sleep(delay);
|
||||
break;
|
||||
case ButtonFunctions::CpuTimeScalarSetHalf:
|
||||
CpuTimeScalarSetHalf();
|
||||
break;
|
||||
case ButtonFunctions::CpuTimeScalarSetDouble:
|
||||
CpuTimeScalarSetDouble();
|
||||
break;
|
||||
case ButtonFunctions::CpuTimeScalarReset:
|
||||
CpuTimeScalarReset();
|
||||
break;
|
||||
case ButtonFunctions::ToggleControllerVibration:
|
||||
ToggleControllerVibration();
|
||||
|
||||
// Extra Sleep
|
||||
xe::threading::Sleep(delay);
|
||||
break;
|
||||
case ButtonFunctions::CloseWindow:
|
||||
window_->RequestClose();
|
||||
break;
|
||||
case ButtonFunctions::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
xe::threading::Sleep(delay);
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void EmulatorWindow::VibrateController(xe::hid::InputSystem* input_sys,
|
||||
bool toggle_rumble) {
|
||||
const std::chrono::milliseconds rumble_duration(100);
|
||||
|
||||
// Hold lock while sleeping this thread for the duration of the rumble,
|
||||
// otherwise the rumble may fail.
|
||||
auto input_lock = input_sys->lock();
|
||||
|
||||
X_INPUT_VIBRATION vibration;
|
||||
|
||||
vibration.left_motor_speed = toggle_rumble ? UINT16_MAX : 0;
|
||||
vibration.right_motor_speed = toggle_rumble ? UINT16_MAX : 0;
|
||||
|
||||
input_sys->SetState(0, &vibration);
|
||||
|
||||
// Vibration duration
|
||||
if (toggle_rumble) {
|
||||
xe::threading::Sleep(rumble_duration);
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::GamepadHotKeys() {
|
||||
X_INPUT_STATE state;
|
||||
|
||||
const std::chrono::milliseconds thread_delay(75);
|
||||
|
||||
auto input_sys = emulator_->input_system();
|
||||
|
||||
if (input_sys) {
|
||||
// SDL and XInput both support the guide button
|
||||
while (true) {
|
||||
// Block scope surrounding input_lock used to release the lock
|
||||
{
|
||||
auto input_lock = input_sys->lock();
|
||||
|
||||
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
||||
input_sys->GetState(user_index, &state);
|
||||
}
|
||||
}
|
||||
|
||||
if (ProcessControllerHotkey(state.gamepad.buttons).rumble) {
|
||||
// Enable Vibration
|
||||
VibrateController(input_sys, true);
|
||||
|
||||
// Disable Vibration
|
||||
VibrateController(input_sys, false);
|
||||
}
|
||||
|
||||
xe::threading::Sleep(thread_delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::ToggleGPUSetting(gpu_cvar value) {
|
||||
switch (value) {
|
||||
case gpu_cvar::ClearMemoryPageState:
|
||||
D3D12SaveGPUSetting(D3D12GPUSetting::ClearMemoryPageState,
|
||||
!cvars::d3d12_clear_memory_page_state);
|
||||
break;
|
||||
case gpu_cvar::ReadbackResolve:
|
||||
D3D12SaveGPUSetting(D3D12GPUSetting::ReadbackResolve,
|
||||
!cvars::d3d12_readback_resolve);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if the Xbox Gamebar is enabled via the Windows registry
|
||||
bool EmulatorWindow::IsUseNexusForGameBarEnabled() {
|
||||
#ifdef _WIN32
|
||||
const LPWSTR reg_path = L"SOFTWARE\\Microsoft\\GameBar";
|
||||
const LPWSTR key = L"UseNexusForGameBarEnabled";
|
||||
|
||||
DWORD value = 0;
|
||||
DWORD dataSize = sizeof(value);
|
||||
|
||||
RegGetValue(HKEY_CURRENT_USER, reg_path, key, RRF_RT_DWORD, nullptr, &value,
|
||||
&dataSize);
|
||||
|
||||
return (bool)value;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EmulatorWindow::DisplayHotKeysConfig() {
|
||||
std::string msg = "";
|
||||
std::string msg_passthru = "";
|
||||
|
||||
bool guide_enabled = !IsUseNexusForGameBarEnabled() && cvars::guide_button;
|
||||
|
||||
for (auto const& [key, val] : controller_hotkey_map) {
|
||||
std::string pretty_text = val.pretty;
|
||||
|
||||
if (!guide_enabled) {
|
||||
pretty_text = std::regex_replace(
|
||||
pretty_text,
|
||||
std::regex("Guide", std::regex_constants::syntax_option_type::icase),
|
||||
"Back");
|
||||
}
|
||||
|
||||
if (emulator_->is_title_open() && !val.title_passthru) {
|
||||
pretty_text += " (Disabled)";
|
||||
}
|
||||
|
||||
if (val.title_passthru) {
|
||||
msg += pretty_text + "\n";
|
||||
} else {
|
||||
msg_passthru += pretty_text + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Add Title
|
||||
msg.insert(0, "Gameplay Hotkeys\n");
|
||||
|
||||
// Prepend non-passthru hotkeys
|
||||
msg_passthru += "\n";
|
||||
msg.insert(0, msg_passthru);
|
||||
|
||||
msg += "\n";
|
||||
msg += "Readback Resolve: " +
|
||||
std::string(cvars::d3d12_readback_resolve ? "true\n" : "false\n");
|
||||
msg +=
|
||||
"Clear Memory Page State: " +
|
||||
std::string(cvars::d3d12_clear_memory_page_state ? "true\n" : "false\n");
|
||||
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Controller Hotkeys",
|
||||
msg);
|
||||
}
|
||||
|
||||
xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
|
||||
bool titleExists = !std::filesystem::exists(path);
|
||||
|
||||
if (path.empty() || titleExists) {
|
||||
char* log_msg = path.empty() ? "Failed to launch title path is empty"
|
||||
: "Failed to launch title path is invalid";
|
||||
char* log_msg = path.empty() ? "Failed to launch title path is empty."
|
||||
: "Failed to launch title path is invalid.";
|
||||
|
||||
XELOGE(log_msg);
|
||||
|
||||
MessageBoxA(nullptr, log_msg, "Title Launch Failed!", MB_ICONERROR);
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(),
|
||||
"Title Launch Failed!", log_msg);
|
||||
|
||||
return X_STATUS_NO_SUCH_FILE;
|
||||
}
|
||||
|
||||
|
||||
if (emulator_->is_title_open()) {
|
||||
// Terminate the current title and start a new title.
|
||||
// if (emulator_->TerminateTitle() == X_STATUS_SUCCESS) {
|
||||
|
@ -1113,10 +1419,9 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
|
|||
if (result) {
|
||||
XELOGE("Failed to launch target: {:08X}", result);
|
||||
|
||||
MessageBoxA(
|
||||
nullptr,
|
||||
"Failed to launch title.\n\nCheck xenia.log for technical details.",
|
||||
"Title Launch Failed!", MB_ICONERROR);
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
imgui_drawer_.get(), "Title Launch Failed!",
|
||||
"Failed to launch title.\n\nCheck xenia.log for technical details.");
|
||||
} else {
|
||||
AddRecentlyLaunchedTitle(path, emulator_->title_name());
|
||||
}
|
||||
|
@ -1124,6 +1429,12 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void EmulatorWindow::RunPreviouslyPlayedTitle() {
|
||||
if (recently_launched_titles_.size() >= 1) {
|
||||
RunTitle(recently_launched_titles_[0].path_to_file);
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::FillRecentlyLaunchedTitlesMenu(
|
||||
xe::ui::MenuItem* recent_menu) {
|
||||
unsigned int index = 0;
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
#define MAX_USERS 4
|
||||
|
||||
namespace xe {
|
||||
namespace app {
|
||||
|
||||
|
@ -53,6 +55,8 @@ class EmulatorWindow {
|
|||
static std::unique_ptr<EmulatorWindow> Create(
|
||||
Emulator* emulator, ui::WindowedAppContext& app_context);
|
||||
|
||||
std::unique_ptr<xe::threading::Thread> Gamepad_HotKeys_Listener;
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||
ui::Window* window() const { return window_.get(); }
|
||||
|
@ -69,6 +73,47 @@ class EmulatorWindow {
|
|||
void ToggleFullscreen();
|
||||
void SetInitializingShaderStorage(bool initializing);
|
||||
|
||||
// Types of button functions for hotkeys.
|
||||
enum class ButtonFunctions {
|
||||
ToggleFullscreen,
|
||||
RunPreviouslyPlayedTitle,
|
||||
CpuTimeScalarSetHalf,
|
||||
CpuTimeScalarSetDouble,
|
||||
CpuTimeScalarReset,
|
||||
ToggleControllerVibration,
|
||||
ClearMemoryPageState,
|
||||
ReadbackResolve,
|
||||
CloseWindow,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class gpu_cvar {
|
||||
ClearMemoryPageState,
|
||||
ReadbackResolve,
|
||||
};
|
||||
|
||||
class ControllerHotKey {
|
||||
public:
|
||||
// If true the hotkey can be activated while a title is running, otherwise
|
||||
// false.
|
||||
bool title_passthru;
|
||||
|
||||
// If true vibrate the controller after activating the hotkey, otherwise
|
||||
// false.
|
||||
bool rumble;
|
||||
std::string pretty;
|
||||
ButtonFunctions function;
|
||||
|
||||
ControllerHotKey(ButtonFunctions fn = ButtonFunctions::Unknown,
|
||||
std::string pretty = "", bool rumble = false,
|
||||
bool active = true) {
|
||||
function = fn;
|
||||
this->pretty = pretty;
|
||||
title_passthru = active;
|
||||
this->rumble = rumble;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
class EmulatorWindowListener final : public ui::WindowListener,
|
||||
public ui::WindowInputListener {
|
||||
|
@ -152,6 +197,13 @@ class EmulatorWindow {
|
|||
void ShowFAQ();
|
||||
void ShowBuildCommit();
|
||||
|
||||
EmulatorWindow::ControllerHotKey ProcessControllerHotkey(int buttons);
|
||||
void VibrateController(xe::hid::InputSystem* input_sys, bool vibrate = true);
|
||||
void GamepadHotKeys();
|
||||
void ToggleGPUSetting(gpu_cvar index);
|
||||
bool IsUseNexusForGameBarEnabled();
|
||||
void DisplayHotKeysConfig();
|
||||
|
||||
xe::X_STATUS RunTitle(std::filesystem::path path);
|
||||
void RunPreviouslyPlayedTitle();
|
||||
void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu);
|
||||
|
|
|
@ -55,8 +55,21 @@ DEFINE_bool(d3d12_clear_memory_page_state, false,
|
|||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value) {
|
||||
switch (setting) {
|
||||
case D3D12GPUSetting::ClearMemoryPageState:
|
||||
OVERRIDE_bool(d3d12_clear_memory_page_state, (bool)value);
|
||||
break;
|
||||
case D3D12GPUSetting::ReadbackResolve:
|
||||
OVERRIDE_bool(d3d12_readback_resolve, (bool)value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
namespace d3d12 {
|
||||
|
||||
|
||||
// Generated with `xb buildshaders`.
|
||||
namespace shaders {
|
||||
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/apply_gamma_pwl_cs.h"
|
||||
|
|
|
@ -42,6 +42,14 @@
|
|||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
enum class D3D12GPUSetting {
|
||||
ReadbackResolve,
|
||||
ClearMemoryPageState,
|
||||
};
|
||||
|
||||
void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value);
|
||||
|
||||
namespace d3d12 {
|
||||
struct MemExportRange {
|
||||
uint32_t base_address_dwords;
|
||||
|
|
|
@ -345,6 +345,7 @@ void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
|||
bool clear_log) const {
|
||||
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty =
|
||||
{
|
||||
{ui::VirtualKey::kXInputPadGuide, "Guide"},
|
||||
{ui::VirtualKey::kXInputPadA, "A"},
|
||||
{ui::VirtualKey::kXInputPadB, "B"},
|
||||
{ui::VirtualKey::kXInputPadX, "X"},
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
|
||||
#include "xenia/hid/hid_flags.h"
|
||||
|
||||
DEFINE_bool(guide_button, false, "Forward guide button presses to guest.",
|
||||
DEFINE_bool(guide_button, true, "Forward guide button presses to guest.",
|
||||
"HID");
|
||||
|
|
|
@ -249,7 +249,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
|||
// The order of this list is also the order in which events are send if
|
||||
// multiple buttons change at once.
|
||||
static_assert(sizeof(X_INPUT_GAMEPAD::buttons) == 2);
|
||||
static constexpr std::array<ui::VirtualKey, 34> kVkLookup = {
|
||||
static constexpr std::array<ui::VirtualKey, 35> kVkLookup = {
|
||||
// 00 - True buttons from xinput button field
|
||||
ui::VirtualKey::kXInputPadDpadUp,
|
||||
ui::VirtualKey::kXInputPadDpadDown,
|
||||
|
@ -261,7 +261,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
|||
ui::VirtualKey::kXInputPadRThumbPress,
|
||||
ui::VirtualKey::kXInputPadLShoulder,
|
||||
ui::VirtualKey::kXInputPadRShoulder,
|
||||
ui::VirtualKey::kNone, /* Guide has no VK */
|
||||
// Guide has no VK (kNone), however using kXInputPadGuide.
|
||||
ui::VirtualKey::kXInputPadGuide,
|
||||
ui::VirtualKey::kNone, /* Unknown */
|
||||
ui::VirtualKey::kXInputPadA,
|
||||
ui::VirtualKey::kXInputPadB,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
// constructing various tables.
|
||||
|
||||
// clang-format off
|
||||
XE_HID_WINKEY_BINDING(Guide, "GUIDE" , keybind_guide , "0x08")
|
||||
XE_HID_WINKEY_BINDING(DpadLeft, "DPAD_LEFT" , keybind_dpad_left , "^A" )
|
||||
XE_HID_WINKEY_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" )
|
||||
XE_HID_WINKEY_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" )
|
||||
|
|
|
@ -145,46 +145,49 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
|
|||
IsKeyDown(b.input_key)) {
|
||||
switch (b.output_key) {
|
||||
case ui::VirtualKey::kXInputPadA:
|
||||
buttons |= 0x1000; // XINPUT_GAMEPAD_A
|
||||
buttons |= X_INPUT_GAMEPAD_A;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadY:
|
||||
buttons |= 0x8000; // XINPUT_GAMEPAD_Y
|
||||
buttons |= X_INPUT_GAMEPAD_Y;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadB:
|
||||
buttons |= 0x2000; // XINPUT_GAMEPAD_B
|
||||
buttons |= X_INPUT_GAMEPAD_B;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadX:
|
||||
buttons |= 0x4000; // XINPUT_GAMEPAD_X
|
||||
buttons |= X_INPUT_GAMEPAD_X;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadGuide:
|
||||
buttons |= X_INPUT_GAMEPAD_GUIDE;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadLeft:
|
||||
buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT
|
||||
buttons |= X_INPUT_GAMEPAD_DPAD_LEFT;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadRight:
|
||||
buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT
|
||||
buttons |= X_INPUT_GAMEPAD_DPAD_RIGHT;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadDown:
|
||||
buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN
|
||||
buttons |= X_INPUT_GAMEPAD_DPAD_DOWN;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadDpadUp:
|
||||
buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP
|
||||
buttons |= X_INPUT_GAMEPAD_DPAD_UP;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRThumbPress:
|
||||
buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB
|
||||
buttons |= X_INPUT_GAMEPAD_RIGHT_THUMB;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLThumbPress:
|
||||
buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB
|
||||
buttons |= X_INPUT_GAMEPAD_LEFT_THUMB;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadBack:
|
||||
buttons |= 0x0020; // XINPUT_GAMEPAD_BACK
|
||||
buttons |= X_INPUT_GAMEPAD_BACK;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadStart:
|
||||
buttons |= 0x0010; // XINPUT_GAMEPAD_START
|
||||
buttons |= X_INPUT_GAMEPAD_START;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLShoulder:
|
||||
buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER
|
||||
buttons |= X_INPUT_GAMEPAD_LEFT_SHOULDER;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadRShoulder:
|
||||
buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER
|
||||
buttons |= X_INPUT_GAMEPAD_RIGHT_SHOULDER;
|
||||
break;
|
||||
case ui::VirtualKey::kXInputPadLTrigger:
|
||||
left_trigger = 0xFF;
|
||||
|
|
|
@ -27,6 +27,7 @@ XInputInputDriver::XInputInputDriver(xe::ui::Window* window,
|
|||
module_(nullptr),
|
||||
XInputGetCapabilities_(nullptr),
|
||||
XInputGetState_(nullptr),
|
||||
XInputGetStateEx_(nullptr),
|
||||
XInputGetKeystroke_(nullptr),
|
||||
XInputSetState_(nullptr),
|
||||
XInputEnable_(nullptr) {}
|
||||
|
@ -37,6 +38,7 @@ XInputInputDriver::~XInputInputDriver() {
|
|||
module_ = nullptr;
|
||||
XInputGetCapabilities_ = nullptr;
|
||||
XInputGetState_ = nullptr;
|
||||
XInputGetStateEx_ = nullptr;
|
||||
XInputGetKeystroke_ = nullptr;
|
||||
XInputSetState_ = nullptr;
|
||||
XInputEnable_ = nullptr;
|
||||
|
@ -49,9 +51,13 @@ X_STATUS XInputInputDriver::Setup() {
|
|||
return X_STATUS_DLL_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Support guide button with XInput using XInputGetStateEx
|
||||
auto const XInputGetStateEx = (LPCSTR)100;
|
||||
|
||||
// Required.
|
||||
auto xigc = GetProcAddress(module, "XInputGetCapabilities");
|
||||
auto xigs = GetProcAddress(module, "XInputGetState");
|
||||
auto xigsEx = GetProcAddress(module, XInputGetStateEx);
|
||||
auto xigk = GetProcAddress(module, "XInputGetKeystroke");
|
||||
auto xiss = GetProcAddress(module, "XInputSetState");
|
||||
|
||||
|
@ -67,17 +73,14 @@ X_STATUS XInputInputDriver::Setup() {
|
|||
module_ = module;
|
||||
XInputGetCapabilities_ = xigc;
|
||||
XInputGetState_ = xigs;
|
||||
XInputGetStateEx_ = xigsEx;
|
||||
XInputGetKeystroke_ = xigk;
|
||||
XInputSetState_ = xiss;
|
||||
XInputEnable_ = xie;
|
||||
|
||||
if (cvars::guide_button) {
|
||||
// Theoretically there is XInputGetStateEx
|
||||
// but thats undocumented and milage varies.
|
||||
XELOGW("XInput: Guide button support is not implemented.");
|
||||
}
|
||||
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
constexpr uint64_t SKIP_INVALID_CONTROLLER_TIME = 1100;
|
||||
static uint64_t last_invalid_time[4];
|
||||
|
||||
|
@ -137,10 +140,18 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
|
|||
if (skipper) {
|
||||
return skipper;
|
||||
}
|
||||
XINPUT_STATE native_state;
|
||||
auto xigs = (decltype(&XInputGetState))XInputGetState_;
|
||||
|
||||
struct {
|
||||
XINPUT_STATE state;
|
||||
unsigned int xinput_state_ex_padding; // Add padding in case we are using XInputGetStateEx
|
||||
} native_state;
|
||||
|
||||
DWORD result = xigs(user_index, &native_state);
|
||||
// If the guide button is enabled use XInputGetStateEx, otherwise use the default XInputGetState.
|
||||
auto xigs = cvars::guide_button
|
||||
? (decltype(&XInputGetState))XInputGetStateEx_
|
||||
: (decltype(&XInputGetState))XInputGetState_;
|
||||
|
||||
DWORD result = xigs(user_index, &native_state.state);
|
||||
if (result) {
|
||||
if (result == ERROR_DEVICE_NOT_CONNECTED) {
|
||||
set_skip(user_index);
|
||||
|
@ -148,14 +159,14 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
|
|||
return result;
|
||||
}
|
||||
|
||||
out_state->packet_number = native_state.dwPacketNumber;
|
||||
out_state->gamepad.buttons = native_state.Gamepad.wButtons;
|
||||
out_state->gamepad.left_trigger = native_state.Gamepad.bLeftTrigger;
|
||||
out_state->gamepad.right_trigger = native_state.Gamepad.bRightTrigger;
|
||||
out_state->gamepad.thumb_lx = native_state.Gamepad.sThumbLX;
|
||||
out_state->gamepad.thumb_ly = native_state.Gamepad.sThumbLY;
|
||||
out_state->gamepad.thumb_rx = native_state.Gamepad.sThumbRX;
|
||||
out_state->gamepad.thumb_ry = native_state.Gamepad.sThumbRY;
|
||||
out_state->packet_number = native_state.state.dwPacketNumber;
|
||||
out_state->gamepad.buttons = native_state.state.Gamepad.wButtons;
|
||||
out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger;
|
||||
out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger;
|
||||
out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX;
|
||||
out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY;
|
||||
out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX;
|
||||
out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ class XInputInputDriver final : public InputDriver {
|
|||
void* module_;
|
||||
void* XInputGetCapabilities_;
|
||||
void* XInputGetState_;
|
||||
void* XInputGetStateEx_;
|
||||
void* XInputGetKeystroke_;
|
||||
void* XInputSetState_;
|
||||
void* XInputEnable_;
|
||||
|
|
|
@ -243,6 +243,7 @@ enum class VirtualKey : uint16_t {
|
|||
// because XInput is the API used for the Xbox 360 controller.
|
||||
// To avoid confusion between VK_GAMEPAD_* and VK_PAD_*, here they are
|
||||
// prefixed with kXboxOne and kXInput respectively.
|
||||
// https://learn.microsoft.com/en-us/uwp/api/windows.system.virtualkey
|
||||
kXboxOneGamepadA = 0xC3,
|
||||
kXboxOneGamepadB = 0xC4,
|
||||
kXboxOneGamepadX = 0xC5,
|
||||
|
@ -317,7 +318,7 @@ enum class VirtualKey : uint16_t {
|
|||
// VK_PAD_* from XInput.h for XInputGetKeystroke. kXInput prefix added to
|
||||
// distinguish from VK_GAMEPAD_*, added much later for the Xbox One
|
||||
// controller.
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_keystroke
|
||||
kXInputPadA = 0x5800,
|
||||
kXInputPadB = 0x5801,
|
||||
kXInputPadX = 0x5802,
|
||||
|
@ -354,6 +355,8 @@ enum class VirtualKey : uint16_t {
|
|||
kXInputPadRThumbUpRight = 0x5835,
|
||||
kXInputPadRThumbDownRight = 0x5836,
|
||||
kXInputPadRThumbDownLeft = 0x5837,
|
||||
// Undocumented therefore kNone however using 0x5838 for now.
|
||||
kXInputPadGuide = 0x5838,
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
|
Loading…
Reference in New Issue