From 3557c46ae65a32ac6fc49d81ec026d6a0787564d Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sat, 4 Jan 2020 02:51:18 +0100 Subject: [PATCH] Add support for buffered DirectInput data to reduce polling load when it is supported. Only attempt setting a DirectInput device's axis mode if the device reports having axes and its axis mode is not already set to absolute. If the axis mode still can't be set to absolute, blacklist the device so that any future attempts will abort early. This should help when dealing with broken device drivers that report being game controllers and not fully supporting game controller functionality. --- src/SFML/Window/Win32/JoystickImpl.cpp | 417 ++++++++++++++++++++----- src/SFML/Window/Win32/JoystickImpl.hpp | 14 +- 2 files changed, 349 insertions(+), 82 deletions(-) diff --git a/src/SFML/Window/Win32/JoystickImpl.cpp b/src/SFML/Window/Win32/JoystickImpl.cpp index 2e925a57..9f8339d5 100644 --- a/src/SFML/Window/Win32/JoystickImpl.cpp +++ b/src/SFML/Window/Win32/JoystickImpl.cpp @@ -80,6 +80,17 @@ namespace typedef std::vector JoystickList; JoystickList joystickList; + + struct JoystickBlacklistEntry + { + unsigned int vendorId; + unsigned int productId; + }; + + typedef std::vector JoystickBlacklist; + JoystickBlacklist joystickBlacklist; + + const DWORD directInputEventBufferSize = 32; } @@ -330,7 +341,16 @@ Joystick::Identification JoystickImpl::getIdentification() const JoystickState JoystickImpl::update() { if (directInput) - return updateDInput(); + { + if (m_buffered) + { + return updateDInputBuffered(); + } + else + { + return updateDInputPolled(); + } + } JoystickState state; @@ -391,7 +411,7 @@ void JoystickImpl::initializeDInput() // Try to acquire a DirectInput 8.x interface HRESULT result = directInput8Create(GetModuleHandleW(NULL), 0x0800, guids::IID_IDirectInput8W, reinterpret_cast(&directInput), NULL); - if (result) + if (FAILED(result)) { // De-initialize everything directInput = NULL; @@ -460,7 +480,7 @@ void JoystickImpl::updateConnectionsDInput() ++i; } - if (result) + if (FAILED(result)) { err() << "Failed to enumerate DirectInput devices: " << result << std::endl; @@ -499,6 +519,8 @@ bool JoystickImpl::openDInput(unsigned int index) std::memset(&m_deviceCaps, 0, sizeof(DIDEVCAPS)); m_deviceCaps.dwSize = sizeof(DIDEVCAPS); + m_state = JoystickState(); + m_buffered = false; // Search for a joystick with the given index in the connected list for (std::vector::iterator i = joystickList.begin(); i != joystickList.end(); ++i) @@ -508,13 +530,54 @@ bool JoystickImpl::openDInput(unsigned int index) // Create device HRESULT result = directInput->CreateDevice(i->guid, &m_device, NULL); - if (result) + if (FAILED(result)) { err() << "Failed to create DirectInput device: " << result << std::endl; return false; } + // Get vendor and product id of the device + DIPROPDWORD property; + std::memset(&property, 0, sizeof(property)); + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + + if (SUCCEEDED(m_device->GetProperty(DIPROP_VIDPID, &property.diph))) + { + m_identification.productId = HIWORD(property.dwData); + m_identification.vendorId = LOWORD(property.dwData); + + // Check if device is already blacklisted + if (m_identification.productId && m_identification.vendorId) + { + for (JoystickBlacklist::const_iterator iter = joystickBlacklist.begin(); iter != joystickBlacklist.end(); ++iter) + { + if ((m_identification.productId == iter->productId) && + (m_identification.vendorId == iter->vendorId)) + { + // Device is blacklisted + m_device->Release(); + m_device = NULL; + + return false; + } + } + } + } + + // Get friendly product name of the device + DIPROPSTRING stringProperty; + std::memset(&stringProperty, 0, sizeof(stringProperty)); + stringProperty.diph.dwSize = sizeof(stringProperty); + stringProperty.diph.dwHeaderSize = sizeof(stringProperty.diph); + stringProperty.diph.dwHow = DIPH_DEVICE; + stringProperty.diph.dwObj = 0; + + if (SUCCEEDED(m_device->GetProperty(DIPROP_PRODUCTNAME, &stringProperty.diph))) + m_identification.name = stringProperty.wsz; + static bool formatInitialized = false; static DIDATAFORMAT format; @@ -524,59 +587,80 @@ bool JoystickImpl::openDInput(unsigned int index) const DWORD povType = DIDFT_POV | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE; const DWORD buttonType = DIDFT_BUTTON | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE; - static DIOBJECTDATAFORMAT data[8 + 4 + sf::Joystick::ButtonCount]; + static DIOBJECTDATAFORMAT data[8 * 4 + 4 + sf::Joystick::ButtonCount]; - data[0].pguid = &guids::GUID_XAxis; - data[0].dwOfs = DIJOFS_X; + for (int i = 0; i < 4; ++i) + { + data[8 * i + 0].pguid = &guids::GUID_XAxis; + data[8 * i + 1].pguid = &guids::GUID_YAxis; + data[8 * i + 2].pguid = &guids::GUID_ZAxis; + data[8 * i + 3].pguid = &guids::GUID_RxAxis; + data[8 * i + 4].pguid = &guids::GUID_RyAxis; + data[8 * i + 5].pguid = &guids::GUID_RzAxis; + data[8 * i + 6].pguid = &guids::GUID_Slider; + data[8 * i + 7].pguid = &guids::GUID_Slider; + } - data[1].pguid = &guids::GUID_YAxis; - data[1].dwOfs = DIJOFS_Y; + data[ 0].dwOfs = DIJOFS_X; + data[ 1].dwOfs = DIJOFS_Y; + data[ 2].dwOfs = DIJOFS_Z; + data[ 3].dwOfs = DIJOFS_RX; + data[ 4].dwOfs = DIJOFS_RY; + data[ 5].dwOfs = DIJOFS_RZ; + data[ 6].dwOfs = DIJOFS_SLIDER(0); + data[ 7].dwOfs = DIJOFS_SLIDER(1); + data[ 8].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVX); + data[ 9].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVY); + data[10].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVZ); + data[11].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRx); + data[12].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRy); + data[13].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRz); + data[14].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglVSlider[0]); + data[15].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglVSlider[1]); + data[16].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAX); + data[17].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAY); + data[18].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAZ); + data[19].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARx); + data[20].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARy); + data[21].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARz); + data[22].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglASlider[0]); + data[23].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglASlider[1]); + data[24].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFX); + data[25].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFY); + data[26].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFZ); + data[27].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRx); + data[28].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRy); + data[29].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRz); + data[30].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglFSlider[0]); + data[31].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglFSlider[1]); - data[2].pguid = &guids::GUID_ZAxis; - data[2].dwOfs = DIJOFS_Z; - - data[3].pguid = &guids::GUID_RxAxis; - data[3].dwOfs = DIJOFS_RX; - - data[4].pguid = &guids::GUID_RyAxis; - data[4].dwOfs = DIJOFS_RY; - - data[5].pguid = &guids::GUID_RzAxis; - data[5].dwOfs = DIJOFS_RZ; - - data[6].pguid = &guids::GUID_Slider; - data[6].dwOfs = DIJOFS_SLIDER(0); - - data[7].pguid = &guids::GUID_Slider; - data[7].dwOfs = DIJOFS_SLIDER(1); - - for (int i = 0; i < 8; ++i) + for (int i = 0; i < 8 * 4; ++i) { data[i].dwType = axisType; - data[i].dwFlags = DIDOI_ASPECTPOSITION; + data[i].dwFlags = 0; } for (int i = 0; i < 4; ++i) { - data[8 + i].pguid = &guids::GUID_POV; - data[8 + i].dwOfs = static_cast(DIJOFS_POV(i)); - data[8 + i].dwType = povType; - data[8 + i].dwFlags = 0; + data[8 * 4 + i].pguid = &guids::GUID_POV; + data[8 * 4 + i].dwOfs = static_cast(DIJOFS_POV(i)); + data[8 * 4 + i].dwType = povType; + data[8 * 4 + i].dwFlags = 0; } for (int i = 0; i < sf::Joystick::ButtonCount; ++i) { - data[8 + 4 + i].pguid = NULL; - data[8 + 4 + i].dwOfs = static_cast(DIJOFS_BUTTON(i)); - data[8 + 4 + i].dwType = buttonType; - data[8 + 4 + i].dwFlags = 0; + data[8 * 4 + 4 + i].pguid = NULL; + data[8 * 4 + 4 + i].dwOfs = static_cast(DIJOFS_BUTTON(i)); + data[8 * 4 + 4 + i].dwType = buttonType; + data[8 * 4 + 4 + i].dwFlags = 0; } format.dwSize = sizeof(DIDATAFORMAT); format.dwObjSize = sizeof(DIOBJECTDATAFORMAT); format.dwFlags = DIDFT_ABSAXIS; - format.dwDataSize = sizeof(DIJOYSTATE); - format.dwNumObjs = 8 + 4 + sf::Joystick::ButtonCount; + format.dwDataSize = sizeof(DIJOYSTATE2); + format.dwNumObjs = 8 * 4 + 4 + sf::Joystick::ButtonCount; format.rgodf = data; formatInitialized = true; @@ -585,7 +669,7 @@ bool JoystickImpl::openDInput(unsigned int index) // Set device data format result = m_device->SetDataFormat(&format); - if (result) + if (FAILED(result)) { err() << "Failed to set DirectInput device data format: " << result << std::endl; @@ -598,7 +682,7 @@ bool JoystickImpl::openDInput(unsigned int index) // Get device capabilities result = m_device->GetCapabilities(&m_deviceCaps); - if (result) + if (FAILED(result)) { err() << "Failed to get DirectInput device capabilities: " << result << std::endl; @@ -608,30 +692,10 @@ bool JoystickImpl::openDInput(unsigned int index) return false; } - // Set axis mode to absolute - DIPROPDWORD property; - std::memset(&property, 0, sizeof(property)); - property.diph.dwSize = sizeof(property); - property.diph.dwHeaderSize = sizeof(property.diph); - property.diph.dwHow = DIPH_DEVICE; - property.dwData = DIPROPAXISMODE_ABS; - - result = m_device->SetProperty(DIPROP_AXISMODE, &property.diph); - - if (result) - { - err() << "Failed to set DirectInput device axis mode: " << result << std::endl; - - m_device->Release(); - m_device = NULL; - - return false; - } - // Enumerate device objects (axes/povs/buttons) result = m_device->EnumObjects(&JoystickImpl::deviceObjectEnumerationCallback, this, DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV); - if (result) + if (FAILED(result)) { err() << "Failed to enumerate DirectInput device objects: " << result << std::endl; @@ -641,29 +705,116 @@ bool JoystickImpl::openDInput(unsigned int index) return false; } - // Get friendly product name of the device - DIPROPSTRING stringProperty; - std::memset(&stringProperty, 0, sizeof(stringProperty)); - stringProperty.diph.dwSize = sizeof(stringProperty); - stringProperty.diph.dwHeaderSize = sizeof(stringProperty.diph); - stringProperty.diph.dwHow = DIPH_DEVICE; - stringProperty.diph.dwObj = 0; - - if (!m_device->GetProperty(DIPROP_PRODUCTNAME, &stringProperty.diph)) + // Set device's axis mode to absolute if the device reports having at least one axis + for (int i = 0; i < Joystick::AxisCount; ++i) { - m_identification.name = stringProperty.wsz; + if (m_axes[i] != -1) + { + std::memset(&property, 0, sizeof(property)); + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.diph.dwObj = 0; + + result = m_device->GetProperty(DIPROP_AXISMODE, &property.diph); + + if (FAILED(result)) + { + err() << "Failed to get DirectInput device axis mode for device \"" + << m_identification.name.toAnsiString() << "\": " << result << std::endl; + + m_device->Release(); + m_device = NULL; + + return false; + } + + // If the axis mode is already set to absolute we don't need to set it again ourselves + if (property.dwData == DIPROPAXISMODE_ABS) + break; + + std::memset(&property, 0, sizeof(property)); + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.dwData = DIPROPAXISMODE_ABS; + + m_device->SetProperty(DIPROP_AXISMODE, &property.diph); + + // Check if the axis mode has been set to absolute + std::memset(&property, 0, sizeof(property)); + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.diph.dwObj = 0; + + result = m_device->GetProperty(DIPROP_AXISMODE, &property.diph); + + if (FAILED(result)) + { + err() << "Failed to verify DirectInput device axis mode for device \"" + << m_identification.name.toAnsiString() << "\": " << result << std::endl; + + m_device->Release(); + m_device = NULL; + + return false; + } + + // If the axis mode hasn't been set to absolute fail here and blacklist the device + if (property.dwData != DIPROPAXISMODE_ABS) + { + if (m_identification.vendorId && m_identification.productId) + { + JoystickBlacklistEntry entry; + + entry.vendorId = m_identification.vendorId; + entry.productId = m_identification.productId; + + joystickBlacklist.push_back(entry); + + // Pre-C++11 shrink_to_fit() + JoystickBlacklist(joystickBlacklist.begin(), joystickBlacklist.end()).swap(joystickBlacklist); + } + + m_device->Release(); + m_device = NULL; + + return false; + } + + break; + } } - // Get vendor and produce id of the device + // Try to enable buffering by setting the buffer size std::memset(&property, 0, sizeof(property)); property.diph.dwSize = sizeof(property); property.diph.dwHeaderSize = sizeof(property.diph); property.diph.dwHow = DIPH_DEVICE; + property.dwData = directInputEventBufferSize; - if (!m_device->GetProperty(DIPROP_VIDPID, &property.diph)) + result = m_device->SetProperty(DIPROP_BUFFERSIZE, &property.diph); + + if (result == DI_OK) { - m_identification.productId = HIWORD(property.dwData); - m_identification.vendorId = LOWORD(property.dwData); + // Buffering supported + m_buffered = true; + } + else if (result == DI_POLLEDDEVICE) + { + // Only polling supported + m_buffered = false; + } + else + { + err() << "Failed to set DirectInput device buffer size for device \"" + << m_identification.name.toAnsiString() << "\": " << result << std::endl; + + m_device->Release(); + m_device = NULL; + + return false; } return true; @@ -709,7 +860,113 @@ JoystickCaps JoystickImpl::getCapabilitiesDInput() const //////////////////////////////////////////////////////////// -JoystickState JoystickImpl::updateDInput() +JoystickState JoystickImpl::updateDInputBuffered() +{ + // If we don't make it to the end of this function, mark the device as disconnected + m_state.connected = false; + + if (!m_device) + return m_state; + + DIDEVICEOBJECTDATA events[directInputEventBufferSize]; + DWORD eventCount = directInputEventBufferSize; + + // Try to get the device data + HRESULT result = m_device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), events, &eventCount, 0); + + // If we have not acquired or have lost the device, attempt to (re-)acquire it and get the device data again + if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST)) + { + m_device->Acquire(); + result = m_device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), events, &eventCount, 0); + } + + // If we still can't get the device data, assume it has been disconnected + if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST)) + { + m_device->Release(); + m_device = NULL; + + return m_state; + } + + if (FAILED(result)) + { + err() << "Failed to get DirectInput device data: " << result << std::endl; + + return m_state; + } + + // Iterate through all buffered events + for (DWORD i = 0; i < eventCount; ++i) + { + bool eventHandled = false; + + // Get the current state of each axis + for (int j = 0; j < Joystick::AxisCount; ++j) + { + if (m_axes[j] == events[i].dwOfs) + { + if (j == Joystick::PovX) + { + unsigned short value = LOWORD(events[i].dwData); + + if (value != 0xFFFF) + { + float angle = (static_cast(value)) * 3.141592654f / DI_DEGREES / 180.f; + + m_state.axes[j] = std::sin(angle) * 100.f; + } + else + { + m_state.axes[j] = 0; + } + } + else if (j == Joystick::PovY) + { + unsigned short value = LOWORD(events[i].dwData); + + if (value != 0xFFFF) + { + float angle = (static_cast(value)) * 3.141592654f / DI_DEGREES / 180.f; + + m_state.axes[j] = std::cos(angle) * 100.f; + } + else + { + m_state.axes[j] = 0.f; + } + } + else + { + m_state.axes[j] = (static_cast(static_cast(events[i].dwData)) + 0.5f) * 100.f / 32767.5f; + } + + eventHandled = true; + + break; + } + } + + if (eventHandled) + continue; + + // Get the current state of each button + for (int j = 0; j < Joystick::ButtonCount; ++j) + { + if (m_buttons[j] == events[i].dwOfs) + m_state.buttons[j] = (events[i].dwData != 0); + } + } + + m_state.connected = true; + + return m_state; +} + + +//////////////////////////////////////////////////////////// +JoystickState JoystickImpl::updateDInputPolled() { JoystickState state; @@ -718,7 +975,7 @@ JoystickState JoystickImpl::updateDInput() // Poll the device m_device->Poll(); - DIJOYSTATE joystate; + DIJOYSTATE2 joystate; // Try to get the device state HRESULT result = m_device->GetDeviceState(sizeof(joystate), &joystate); @@ -740,7 +997,7 @@ JoystickState JoystickImpl::updateDInput() return state; } - if (result) + if (FAILED(result)) { err() << "Failed to get DirectInput device state: " << result << std::endl; @@ -878,7 +1135,7 @@ BOOL CALLBACK JoystickImpl::deviceObjectEnumerationCallback(const DIDEVICEOBJECT HRESULT result = joystick.m_device->SetProperty(DIPROP_RANGE, &propertyRange.diph); - if (result) + if (result != DI_OK) err() << "Failed to set DirectInput device axis property range: " << result << std::endl; return DIENUM_CONTINUE; diff --git a/src/SFML/Window/Win32/JoystickImpl.hpp b/src/SFML/Window/Win32/JoystickImpl.hpp index 66175058..ffff8c33 100644 --- a/src/SFML/Window/Win32/JoystickImpl.hpp +++ b/src/SFML/Window/Win32/JoystickImpl.hpp @@ -186,12 +186,20 @@ public: JoystickCaps getCapabilitiesDInput() const; //////////////////////////////////////////////////////////// - /// \brief Update the joystick and get its new state (DInput) + /// \brief Update the joystick and get its new state (DInput, Buffered) /// /// \return Joystick state /// //////////////////////////////////////////////////////////// - JoystickState updateDInput(); + JoystickState updateDInputBuffered(); + + //////////////////////////////////////////////////////////// + /// \brief Update the joystick and get its new state (DInput, Polled) + /// + /// \return Joystick state + /// + //////////////////////////////////////////////////////////// + JoystickState updateDInputPolled(); private: @@ -227,6 +235,8 @@ private: int m_axes[Joystick::AxisCount]; //!< Offsets to the bytes containing the axes states, -1 if not available int m_buttons[Joystick::ButtonCount]; //!< Offsets to the bytes containing the button states, -1 if not available Joystick::Identification m_identification; //!< Joystick identification + JoystickState m_state; //!< Buffered joystick state + bool m_buffered; //!< true if the device uses buffering, false if the device uses polling }; } // namespace priv