diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index cc99e15b8..08770a851 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -436,3 +436,8 @@ Controller::ButtonList AnalogController::StaticGetButtonNames() B(Square), B(L1), B(L2), B(R1), B(R2), B(L3), B(R3)}; #undef B } + +u32 AnalogController::StaticGetVibrationMotorCount() +{ + return NUM_MOTORS; +} diff --git a/src/core/analog_controller.h b/src/core/analog_controller.h index 1449a8779..74ba6fbbe 100644 --- a/src/core/analog_controller.h +++ b/src/core/analog_controller.h @@ -48,6 +48,7 @@ public: static std::optional StaticGetButtonCodeByName(std::string_view button_name); static AxisList StaticGetAxisNames(); static ButtonList StaticGetButtonNames(); + static u32 StaticGetVibrationMotorCount(); ControllerType GetType() const override; std::optional GetAxisCodeByName(std::string_view axis_name) const override; diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 9ce77e9d9..1ec67eab5 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -94,6 +94,22 @@ Controller::ButtonList Controller::GetButtonNames(ControllerType type) } } +u32 Controller::GetVibrationMotorCount(ControllerType type) +{ + switch (type) + { + case ControllerType::DigitalController: + return DigitalController::StaticGetVibrationMotorCount(); + + case ControllerType::AnalogController: + return AnalogController::StaticGetVibrationMotorCount(); + + case ControllerType::None: + default: + return 0; + } +} + std::optional Controller::GetAxisCodeByName(ControllerType type, std::string_view axis_name) { switch (type) diff --git a/src/core/controller.h b/src/core/controller.h index 573e0a5fa..5f05deae0 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -61,4 +61,7 @@ public: /// Returns a list of buttons for the specified controller type. static ButtonList GetButtonNames(ControllerType type); + + /// Returns the number of vibration motors. + static u32 GetVibrationMotorCount(ControllerType type); }; diff --git a/src/core/digital_controller.cpp b/src/core/digital_controller.cpp index 821b5c0d0..5add08426 100644 --- a/src/core/digital_controller.cpp +++ b/src/core/digital_controller.cpp @@ -147,3 +147,8 @@ Controller::ButtonList DigitalController::StaticGetButtonNames() B(Cross), B(Circle), B(Square), B(L1), B(L2), B(R1), B(R2)}; #undef B } + +u32 DigitalController::StaticGetVibrationMotorCount() +{ + return 0; +} diff --git a/src/core/digital_controller.h b/src/core/digital_controller.h index 58dbb465c..e970c64f4 100644 --- a/src/core/digital_controller.h +++ b/src/core/digital_controller.h @@ -36,6 +36,7 @@ public: static std::optional StaticGetButtonCodeByName(std::string_view button_name); static AxisList StaticGetAxisNames(); static ButtonList StaticGetButtonNames(); + static u32 StaticGetVibrationMotorCount(); ControllerType GetType() const override; std::optional GetAxisCodeByName(std::string_view axis_name) const override; diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index a70bc192a..8b29fabe1 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -163,6 +163,7 @@ void GPU_HW_OpenGL::SetCapabilities(HostDisplay* host_display) Log_WarningPrintf("GL_EXT_copy_image missing, this may affect performance."); m_supports_texture_buffer = (GLAD_GL_VERSION_3_1 || GLAD_GL_ES_VERSION_3_2); + m_supports_texture_buffer = false; if (m_supports_texture_buffer) { glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, reinterpret_cast(&m_max_texture_buffer_size)); @@ -766,7 +767,7 @@ void GPU_HW_OpenGL::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 wid src_y = m_vram_texture.GetHeight() - src_y - height; dst_y = m_vram_texture.GetHeight() - dst_y - height; - if (GLAD_GL_VERSION_4_3) + /*if (GLAD_GL_VERSION_4_3) { glCopyImageSubData(m_vram_texture.GetGLId(), GL_TEXTURE_2D, 0, src_x, src_y, 0, m_vram_texture.GetGLId(), GL_TEXTURE_2D, 0, dst_x, dst_y, 0, width, height, 1); @@ -776,7 +777,7 @@ void GPU_HW_OpenGL::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 wid glCopyImageSubDataEXT(m_vram_texture.GetGLId(), GL_TEXTURE_2D, 0, src_x, src_y, 0, m_vram_texture.GetGLId(), GL_TEXTURE_2D, 0, dst_x, dst_y, 0, width, height, 1); } - else + else*/ { glDisable(GL_SCISSOR_TEST); m_vram_texture.BindFramebuffer(GL_READ_FRAMEBUFFER); @@ -794,7 +795,7 @@ void GPU_HW_OpenGL::UpdateVRAMReadTexture() const u32 x = scaled_rect.left; const u32 y = m_vram_texture.GetHeight() - scaled_rect.top - height; - if (GLAD_GL_VERSION_4_3) + /*if (GLAD_GL_VERSION_4_3) { glCopyImageSubData(m_vram_texture.GetGLId(), GL_TEXTURE_2D, 0, x, y, 0, m_vram_read_texture.GetGLId(), GL_TEXTURE_2D, 0, x, y, 0, width, height, 1); @@ -804,7 +805,7 @@ void GPU_HW_OpenGL::UpdateVRAMReadTexture() glCopyImageSubDataEXT(m_vram_texture.GetGLId(), GL_TEXTURE_2D, 0, x, y, 0, m_vram_read_texture.GetGLId(), GL_TEXTURE_2D, 0, x, y, 0, width, height, 1); } - else + else*/ { m_vram_read_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER); m_vram_texture.BindFramebuffer(GL_READ_FRAMEBUFFER); diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index 0b65932ba..8bbf023fa 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -547,7 +547,7 @@ float4 SampleFromVRAM(int4 texpage, int2 icoord) texcol.rgb /= float3(ialpha, ialpha, ialpha); semitransparent = (texcol.a != 0.0); #else - float4 texcol = SampleFromVRAM(v_texpage, int2(v_tex0)); + float4 texcol = SampleFromVRAM(v_texpage, int2(floor(v_tex0))); if (VECTOR_EQ(texcol, TRANSPARENT_PIXEL_COLOR)) discard; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index ae4662ed5..8281388a7 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -792,6 +792,7 @@ void QtHostInterface::threadEntryPoint() } m_system->RunFrame(); + UpdateControllerRumble(); renderDisplay(); diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 52a08c69b..1175a8433 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -1301,10 +1301,11 @@ void SDLHostInterface::Run() if (m_system && !m_paused) { m_system->RunFrame(); + UpdateControllerRumble(); if (m_frame_step_request) { m_frame_step_request = false; - m_paused = true; + PauseSystem(true); } } diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 7a06ed096..9b6716860 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -335,8 +335,20 @@ void CommonHostInterface::OnSystemPaused(bool paused) { HostInterface::OnSystemPaused(paused); - if (paused && IsFullscreen()) - SetFullscreen(false); + if (paused) + { + if (IsFullscreen()) + SetFullscreen(false); + + StopControllerRumble(); + } +} + +void CommonHostInterface::OnSystemDestroyed() +{ + HostInterface::OnSystemDestroyed(); + + StopControllerRumble(); } void CommonHostInterface::OnControllerTypeChanged(u32 slot) @@ -405,6 +417,55 @@ void CommonHostInterface::UpdateInputMap(SettingsInterface& si) UpdateHotkeyInputMap(si); } +void CommonHostInterface::AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback) +{ + ControllerRumbleState rumble; + rumble.controller_index = 0; + rumble.num_motors = std::min(num_motors, ControllerRumbleState::MAX_MOTORS); + rumble.last_strength.fill(0.0f); + rumble.update_callback = std::move(callback); + m_controller_vibration_motors.push_back(std::move(rumble)); +} + +void CommonHostInterface::UpdateControllerRumble() +{ + DebugAssert(m_system); + + for (ControllerRumbleState& rumble : m_controller_vibration_motors) + { + Controller* controller = m_system->GetController(rumble.controller_index); + if (!controller) + continue; + + bool changed = false; + for (u32 i = 0; i < rumble.num_motors; i++) + { + const float strength = controller->GetVibrationMotorStrength(i); + changed |= (strength != rumble.last_strength[i]); + rumble.last_strength[i] = strength; + } + + if (changed) + rumble.update_callback(rumble.last_strength.data(), rumble.num_motors); + } +} + +void CommonHostInterface::StopControllerRumble() +{ + for (ControllerRumbleState& rumble : m_controller_vibration_motors) + { + bool changed = true; + for (u32 i = 0; i < rumble.num_motors; i++) + { + changed |= (rumble.last_strength[i] != 0.0f); + rumble.last_strength[i] = 0.0f; + } + + if (changed) + rumble.update_callback(rumble.last_strength.data(), rumble.num_motors); + } +} + static bool SplitBinding(const std::string& binding, std::string_view* device, std::string_view* sub_binding) { const std::string::size_type slash_pos = binding.find('/'); @@ -421,6 +482,9 @@ static bool SplitBinding(const std::string& binding, std::string_view* device, s void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) { + StopControllerRumble(); + m_controller_vibration_motors.clear(); + for (u32 controller_index = 0; controller_index < 2; controller_index++) { const ControllerType ctype = m_settings.controller_types[controller_index]; @@ -477,6 +541,14 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) }); } } + + const u32 num_motors = Controller::GetVibrationMotorCount(ctype); + if (num_motors > 0) + { + const std::vector bindings = si.GetStringList(category, TinyString::FromFormat("Rumble")); + for (const std::string& binding : bindings) + AddRumbleToInputMap(binding, controller_index, num_motors); + } } } @@ -600,6 +672,34 @@ bool CommonHostInterface::AddAxisToInputMap(const std::string& binding, const st return false; } +bool CommonHostInterface::AddRumbleToInputMap(const std::string& binding, u32 controller_index, u32 num_motors) +{ + if (StringUtil::StartsWith(binding, "Controller")) + { + if (!m_controller_interface) + { + Log_ErrorPrintf("No controller interface set, cannot bind '%s'", binding.c_str()); + return false; + } + + const std::optional host_controller_index = StringUtil::FromChars(binding.substr(10)); + if (!host_controller_index || *host_controller_index < 0) + { + Log_WarningPrintf("Invalid controller index in rumble binding '%s'", binding.c_str()); + return false; + } + + AddControllerRumble(controller_index, num_motors, + std::bind(&ControllerInterface::SetControllerRumbleStrength, m_controller_interface.get(), + host_controller_index.value(), std::placeholders::_1, std::placeholders::_2)); + + return true; + } + + Log_WarningPrintf("Unknown input device in rumble binding '%s'", binding.c_str()); + return false; +} + void CommonHostInterface::RegisterGeneralHotkeys() { RegisterHotkey(StaticString("General"), StaticString("FastForward"), StaticString("Toggle Fast Forward"), diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 4b4ea51bc..fd931c1db 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -22,6 +22,7 @@ public: using InputButtonHandler = std::function; using InputAxisHandler = std::function; + using ControllerRumbleCallback = std::function; struct HotkeyInfo { @@ -69,6 +70,7 @@ protected: virtual void OnSystemCreated() override; virtual void OnSystemPaused(bool paused) override; + virtual void OnSystemDestroyed() override; virtual void OnControllerTypeChanged(u32 slot) override; virtual void SetDefaultSettings(SettingsInterface& si) override; @@ -79,6 +81,7 @@ protected: const std::string_view& button, InputButtonHandler handler); virtual bool AddAxisToInputMap(const std::string& binding, const std::string_view& device, const std::string_view& axis, InputAxisHandler handler); + virtual bool AddRumbleToInputMap(const std::string& binding, u32 controller_index, u32 num_motors); /// Reloads the input map from config. Callable from controller interface. virtual void UpdateInputMap() = 0; @@ -87,6 +90,10 @@ protected: bool HandleHostKeyEvent(HostKeyCode code, bool pressed); void UpdateInputMap(SettingsInterface& si); + void AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback); + void UpdateControllerRumble(); + void StopControllerRumble(); + std::unique_ptr m_controller_interface; private: @@ -101,6 +108,21 @@ private: // input key maps std::map m_keyboard_input_handlers; + // controller vibration motors/rumble + struct ControllerRumbleState + { + enum : u32 + { + MAX_MOTORS = 2 + }; + + u32 controller_index; + u32 num_motors; + std::array last_strength; + ControllerRumbleCallback update_callback; + }; + std::vector m_controller_vibration_motors; + // running in batch mode? i.e. exit after stopping emulation bool m_batch_mode = false; }; diff --git a/src/frontend-common/controller_interface.cpp b/src/frontend-common/controller_interface.cpp index 87da03282..43f4a170f 100644 --- a/src/frontend-common/controller_interface.cpp +++ b/src/frontend-common/controller_interface.cpp @@ -90,4 +90,3 @@ bool ControllerInterface::BindControllerAxisToButton(int controller_index, int a return false; } -void ControllerInterface::UpdateControllerRumble() {} diff --git a/src/frontend-common/controller_interface.h b/src/frontend-common/controller_interface.h index e95863abf..ed35e6108 100644 --- a/src/frontend-common/controller_interface.h +++ b/src/frontend-common/controller_interface.h @@ -36,9 +36,12 @@ public: virtual bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) = 0; virtual bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) = 0; - + virtual void PollEvents() = 0; - virtual void UpdateControllerRumble() = 0; + + // Changing rumble strength. + virtual u32 GetControllerRumbleMotorCount(int controller_index) = 0; + virtual void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) = 0; // Input monitoring for external access. struct Hook diff --git a/src/frontend-common/sdl_controller_interface.cpp b/src/frontend-common/sdl_controller_interface.cpp index 05e5701c9..53af1b297 100644 --- a/src/frontend-common/sdl_controller_interface.cpp +++ b/src/frontend-common/sdl_controller_interface.cpp @@ -161,12 +161,35 @@ bool SDLControllerInterface::OpenGameController(int index) cd.controller = gcontroller; cd.player_id = player_id; cd.joystick_id = joystick_id; + cd.haptic_left_right_effect = -1; SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick); - if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) == 0) - cd.haptic = haptic; - else if (haptic) - SDL_HapticClose(haptic); + if (haptic) + { + SDL_HapticEffect ef = {}; + ef.leftright.type = SDL_HAPTIC_LEFTRIGHT; + ef.leftright.length = 1000; + + int ef_id = SDL_HapticNewEffect(haptic, &ef); + if (ef_id >= 0) + { + cd.haptic = haptic; + cd.haptic_left_right_effect = ef_id; + } + else + { + Log_ErrorPrintf("Failed to create haptic left/right effect: %s", SDL_GetError()); + if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0) + { + cd.haptic = haptic; + } + else + { + Log_ErrorPrintf("No haptic rumble supported: %s", SDL_GetError()); + SDL_HapticClose(haptic); + } + } + } if (cd.haptic) Log_InfoPrintf("Rumble is supported on '%s'", SDL_GameControllerName(gcontroller)); @@ -314,31 +337,52 @@ bool SDLControllerInterface::HandleControllerButtonEvent(const SDL_Event* ev) return true; } -void SDLControllerInterface::UpdateControllerRumble() +u32 SDLControllerInterface::GetControllerRumbleMotorCount(int controller_index) { - for (auto& cd : m_controllers) + auto it = GetControllerDataForPlayerId(controller_index); + if (it == m_controllers.end()) + return 0; + + return (it->haptic_left_right_effect >= 0) ? 2 : (it->haptic ? 1 : 0); +} + +void SDLControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) +{ + auto it = GetControllerDataForPlayerId(controller_index); + if (it == m_controllers.end()) + return; + + // we'll update before this duration is elapsed + static constexpr float MIN_STRENGTH = 0.01f; + static constexpr u32 DURATION = 100000; + + SDL_Haptic* haptic = static_cast(it->haptic); + if (it->haptic_left_right_effect >= 0 && num_motors > 1) { - // TODO: FIXME proper binding - if (!cd.haptic || cd.player_id < 0 || cd.player_id >= 2) - continue; - - float new_strength = 0.0f; - Controller* controller = GetController(cd.player_id); - if (controller) + if (strengths[0] >= MIN_STRENGTH || strengths[1] >= MIN_STRENGTH) { - const u32 motor_count = controller->GetVibrationMotorCount(); - for (u32 i = 0; i < motor_count; i++) - new_strength = std::max(new_strength, controller->GetVibrationMotorStrength(i)); + SDL_HapticEffect ef; + ef.type = SDL_HAPTIC_LEFTRIGHT; + ef.leftright.large_magnitude = static_cast(strengths[0] * 65535.0f); + ef.leftright.small_magnitude = static_cast(strengths[1] * 65535.0f); + ef.leftright.length = DURATION; + SDL_HapticUpdateEffect(haptic, it->haptic_left_right_effect, &ef); + SDL_HapticRunEffect(haptic, it->haptic_left_right_effect, SDL_HAPTIC_INFINITY); } - - if (cd.last_rumble_strength == new_strength) - continue; - - if (new_strength > 0.01f) - SDL_HapticRumblePlay(static_cast(cd.haptic), new_strength, 100000); else - SDL_HapticRumbleStop(static_cast(cd.haptic)); + { + SDL_HapticStopEffect(haptic, it->haptic_left_right_effect); + } + } + else + { + float max_strength = 0.0f; + for (u32 i = 0; i < num_motors; i++) + max_strength = std::max(max_strength, strengths[i]); - cd.last_rumble_strength = new_strength; + if (max_strength >= MIN_STRENGTH) + SDL_HapticRumblePlay(haptic, max_strength, DURATION); + else + SDL_HapticRumbleStop(haptic); } } diff --git a/src/frontend-common/sdl_controller_interface.h b/src/frontend-common/sdl_controller_interface.h index 08a0493b8..6293b7268 100644 --- a/src/frontend-common/sdl_controller_interface.h +++ b/src/frontend-common/sdl_controller_interface.h @@ -25,20 +25,22 @@ public: bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) override; + // Changing rumble strength. + u32 GetControllerRumbleMotorCount(int controller_index) override; + void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override; + void PollEvents() override; bool ProcessSDLEvent(const SDL_Event* event); - void UpdateControllerRumble() override; - private: struct ControllerData { void* controller; void* haptic; + int haptic_left_right_effect; int joystick_id; int player_id; - float last_rumble_strength; std::array axis_mapping; std::array button_mapping;