Procházet zdrojové kódy

XR Controller Input hookup to AzFramework (#37)

* First pass at writing a new InputDevice for XR

- Extends AzFramework as a new input device type.
- Does not compile yet (needs implementation).

Signed-off-by: amzn-phist <[email protected]>

* Implemented ProcessRawControllerState

- Feeds raw input state to the input channels and sends updates
  through.

Signed-off-by: amzn-phist <[email protected]>

* Add implementation files for the OpenXR controller

- Put the impl file under Platform/Common/Default since it would be
  shared by all platforms that enable OpenXR.

Signed-off-by: amzn-phist <[email protected]>

* Fills in a bit more of the OculusTouch device

- Write the Implementation class, esp the Create() factory function
  which makes the code compile now.

Signed-off-by: amzn-phist <[email protected]>

* Misc fixes and getting rid of warnings

Signed-off-by: amzn-phist <[email protected]>

* Misc cleanup of XR Gems

- Override virtual destructors
- Formatting and whitespace removal

Signed-off-by: amzn-phist <[email protected]>

* Cleanup and include fixes

Signed-off-by: amzn-phist <[email protected]>

* Checkpoint commit

- Still unfinished, and still a mess, but it's compiling again.
- Most of the connections are hooked up, only a few funtions remain
  (some buttons and haptic feedback).
- Could still use another round of refactoring to simplify and condense
  the various classes.

Signed-off-by: amzn-phist <[email protected]>

* Futher the digital button masks

- Updating the raw state button bits
- Changes the api at OpenXRVkInput to return bool button states
- Cleaned up and removed commented out code

Signed-off-by: amzn-phist <[email protected]>

* Hook up vibration haptics

- Hooked up vibration function to trigger haptics in XR
- More cleanup and reduce code duplication
- Getting ready for testing and debugging

Signed-off-by: amzn-phist <[email protected]>

* Fixing issues with maps of InputChannelId

- Changed from pointers to value types for key.
- Tested on RPI sample in ASV.

Signed-off-by: amzn-phist <[email protected]>

* Minor update to get button state functions

- Reduce code duplication

Signed-off-by: amzn-phist <[email protected]>

* More cleanup of commented out code

Signed-off-by: amzn-phist <[email protected]>

* Address PR feedback

- Removes some commented out code, fix spelling.

Signed-off-by: amzn-phist <[email protected]>

* Address more PR feedback

- Added local macro for behavior constants
- Cleaned up
- Changed functions from returning string_view to string

Signed-off-by: amzn-phist <[email protected]>

* Addressing more PR feedback

- Changed some of the code's namespace to OpenXRVk (from AzFramework).
- Provide some of the default values for thumbsticks.

Signed-off-by: amzn-phist <[email protected]>

* Address more PR feedback

- Reverts some things that I had changed

Signed-off-by: amzn-phist <[email protected]>

* Minor update to a macro

- added parentheses around parameter

Signed-off-by: amzn-phist <[email protected]>

Signed-off-by: amzn-phist <[email protected]>
amzn-phist před 2 roky
rodič
revize
46e55a626c
30 změnil soubory, kde provedl 1536 přidání a 383 odebrání
  1. 444 0
      Gems/OpenXRVk/Code/Include/OpenXRVk/InputDeviceXRController.h
  2. 42 60
      Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkInput.h
  3. 4 5
      Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkInstance.h
  4. 2 4
      Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkSession.h
  5. 3 4
      Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkSpace.h
  6. 6 5
      Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkUtils.h
  7. 422 0
      Gems/OpenXRVk/Code/Source/InputDeviceXRController.cpp
  8. 295 215
      Gems/OpenXRVk/Code/Source/OpenXRVkInput.cpp
  9. 9 6
      Gems/OpenXRVk/Code/Source/OpenXRVkSession.cpp
  10. 5 5
      Gems/OpenXRVk/Code/Source/OpenXRVkSpace.cpp
  11. 11 11
      Gems/OpenXRVk/Code/Source/OpenXRVkSwapChain.cpp
  12. 2 0
      Gems/OpenXRVk/Code/Source/Platform/Android/platform_private_android_files.cmake
  13. 161 0
      Gems/OpenXRVk/Code/Source/Platform/Common/Default/OculusTouch_Default.cpp
  14. 70 0
      Gems/OpenXRVk/Code/Source/Platform/Common/Default/OculusTouch_Default.h
  15. 2 0
      Gems/OpenXRVk/Code/Source/Platform/Linux/platform_private_linux_files.cmake
  16. 2 0
      Gems/OpenXRVk/Code/Source/Platform/Windows/platform_private_windows_files.cmake
  17. 2 0
      Gems/OpenXRVk/Code/openxrvk_private_common_files.cmake
  18. 4 4
      Gems/XR/Code/Include/XR/XRBase.h
  19. 9 10
      Gems/XR/Code/Include/XR/XRDevice.h
  20. 2 7
      Gems/XR/Code/Include/XR/XRInput.h
  21. 5 6
      Gems/XR/Code/Include/XR/XRInstance.h
  22. 1 1
      Gems/XR/Code/Include/XR/XRObject.h
  23. 4 5
      Gems/XR/Code/Include/XR/XRSession.h
  24. 2 3
      Gems/XR/Code/Include/XR/XRSpace.h
  25. 11 12
      Gems/XR/Code/Include/XR/XRSwapChain.h
  26. 8 9
      Gems/XR/Code/Include/XR/XRSystem.h
  27. 1 1
      Gems/XR/Code/Include/XR/XRSystemComponent.h
  28. 4 3
      Gems/XR/Code/Include/XR/XRUtils.h
  29. 3 3
      Gems/XR/Code/Source/XRSession.cpp
  30. 0 4
      Gems/XR/Code/Source/XRSystem.cpp

+ 444 - 0
Gems/OpenXRVk/Code/Include/OpenXRVk/InputDeviceXRController.h

@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzFramework/Input/Buses/Requests/InputHapticFeedbackRequestBus.h>
+#include <AzFramework/Input/Channels/InputChannelAnalog.h>
+#include <AzFramework/Input/Channels/InputChannelAxis1D.h>
+#include <AzFramework/Input/Channels/InputChannelAxis2D.h>
+#include <AzFramework/Input/Channels/InputChannelAxis3D.h>
+#include <AzFramework/Input/Channels/InputChannelDigital.h>
+#include <AzFramework/Input/Channels/InputChannelQuaternion.h>
+#include <AzFramework/Input/Devices/InputDevice.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace AzFramework
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Defines a generic XR controller pair device, including the ids of all associated input
+    //! channels. Platform specifics are defined as private implementations so that creating an
+    //! instance of this generic class will work correctly on any platform supporting this type of
+    //! hand-held XR controllers.
+    class InputDeviceXRController
+        : public InputDevice
+        , public InputHapticFeedbackRequestBus::Handler
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! The name used to identify an XR Controller input device
+        static constexpr const char* Name{ "xr_controller" };
+        static constexpr InputDeviceId IdForIndex0{ Name, 0 };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Check whether an input device id identifies an XR controller (regardless of index)
+        //! @param inputDeviceId The input device id to check
+        //! @return True if the input device id identifies as an XR controller, False otherwise
+        static bool IsXRControllerDevice(const InputDeviceId& inputDeviceId);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller digital button inputs
+        struct Button
+        {
+            static constexpr InputChannelId A{ "xr_controller_button_a" }; //!< The right-hand A button
+            static constexpr InputChannelId B{ "xr_controller_button_b" }; //!< The right-hand B button
+            static constexpr InputChannelId X{ "xr_controller_button_x" }; //!< The left-hand X button
+            static constexpr InputChannelId Y{ "xr_controller_button_y" }; //!< The left-hand Y button
+            static constexpr InputChannelId Home{ "xr_controller_button_home" }; //!< The right-hand "Home" button
+            static constexpr InputChannelId Menu{ "xr_controller_button_menu" }; //!< The left-hand "Menu" button
+            static constexpr InputChannelId L3{ "xr_controller_button_l3" }; //!< The left-hand thumb-stick click button
+            static constexpr InputChannelId R3{ "xr_controller_button_r3" }; //!< The right-hand thumb-stick click button
+
+            static constexpr InputChannelId TA{ "xr_controller_touch_button_a" }; //!< The A button touch detection
+            static constexpr InputChannelId TB{ "xr_controller_touch_button_b" }; //!< The B button touch detection
+            static constexpr InputChannelId TX{ "xr_controller_touch_button_x" }; //!< The X button touch detection
+            static constexpr InputChannelId TY{ "xr_controller_touch_button_y" }; //!< The Y button touch detection
+            static constexpr InputChannelId TLStick{ "xr_controller_touch_thumbstick_l" }; //!< The left thumb-stick touch detection
+            static constexpr InputChannelId TRStick{ "xr_controller_touch_thumbstick_r" }; //!< The right thumb-stick touch detection
+            static constexpr InputChannelId TLRest{ "xr_controller_touch_thumbrest_l" }; //!< The left thumb-rest touch detection
+            static constexpr InputChannelId TRRest{ "xr_controller_touch_thumbrest_r" }; //!< The right thumb-rest touch detection
+            static constexpr InputChannelId TLTrig{ "xr_controller_touch_trigger_l" }; //!< The left trigger touch detection
+            static constexpr InputChannelId TRTrig{ "xr_controller_touch_trigger_r" }; //!< The right trigger touch detection
+
+            //! All digital XR controller button ids
+            static constexpr AZStd::array All
+            {
+                A,
+                B,
+                X,
+                Y,
+                Home,
+                Menu,
+                L3,
+                R3,
+                TA,
+                TB,
+                TX,
+                TY,
+                TLStick,
+                TRStick,
+                TLRest,
+                TRRest,
+                TLTrig,
+                TRTrig,
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller analog inputs
+        struct Trigger
+        {
+            static constexpr InputChannelId LTrigger{ "xr_controller_trigger_l" }; //!< The left-hand trigger
+            static constexpr InputChannelId RTrigger{ "xr_controller_trigger_r" }; //!< The right-hand trigger
+            static constexpr InputChannelId LGrip{ "xr_controller_grip_l" }; //!< The left-hand grip
+            static constexpr InputChannelId RGrip{ "xr_controller_grip_r" }; //!< The right-hand grip
+
+            //! All analog XR Controller input ids
+            static constexpr AZStd::array All
+            {
+                LTrigger,
+                RTrigger,
+                LGrip,
+                RGrip
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller 1D axis inputs
+        struct ThumbStickAxis1D
+        {
+            static constexpr InputChannelId LX{ "xr_controller_thumbstick_l_x" }; //!< X-axis of the left-hand thumb-stick
+            static constexpr InputChannelId LY{ "xr_controller_thumbstick_l_y" }; //!< Y-axis of the left-hand thumb-stick
+            static constexpr InputChannelId RX{ "xr_controller_thumbstick_r_x" }; //!< X-axis of the right-hand thumb-stick
+            static constexpr InputChannelId RY{ "xr_controller_thumbstick_r_y" }; //!< Y-axis of the right-hand thumb-stick
+
+            //! All 1D axis XR Controller input ids
+            static constexpr AZStd::array All
+            {
+                LX,
+                LY,
+                RX,
+                RY
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller 2D axis inputs
+        struct ThumbStickAxis2D
+        {
+            static constexpr InputChannelId L{ "xr_controller_thumbstick_l" }; //!< The left-hand thumb-stick
+            static constexpr InputChannelId R{ "xr_controller_thumbstick_r" }; //!< The right-hand thumb-stick
+
+            //! All 2D axis XR Controller input ids
+            static constexpr AZStd::array All
+            {
+                L,
+                R
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller thumb-stick directions
+        struct ThumbStickDirection
+        {
+            static constexpr InputChannelId LU{ "xr_controller_thumbstick_l_up" }; //!< Up on the left-hand thumb-stick
+            static constexpr InputChannelId LD{ "xr_controller_thumbstick_l_down" }; //!< Down on the left-hand thumb-stick
+            static constexpr InputChannelId LL{ "xr_controller_thumbstick_l_left" }; //!< Left on the left-hand thumb-stick
+            static constexpr InputChannelId LR{ "xr_controller_thumbstick_l_right" }; //!< Right on the left-hand thumb-stick
+            static constexpr InputChannelId RU{ "xr_controller_thumbstick_r_up" }; //!< Up on the right-hand thumb-stick
+            static constexpr InputChannelId RD{ "xr_controller_thumbstick_r_down" }; //!< Down on the right-hand thumb-stick
+            static constexpr InputChannelId RL{ "xr_controller_thumbstick_r_left" }; //!< Left on the right-hand thumb-stick
+            static constexpr InputChannelId RR{ "xr_controller_thumbstick_r_right" }; //!< Right on the right-hand thumb-stick
+
+            //! All thumb-stick directional XR Controller input ids
+            static constexpr AZStd::array All
+            {
+                LU,
+                LD,
+                LL,
+                LR,
+                RU,
+                RD,
+                RL,
+                RR
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller 3D axis inputs
+        struct ControllerPosePosition
+        {
+            static constexpr InputChannelId LPos{ "xr_controller_position_l" }; //!< The left-hand position
+            static constexpr InputChannelId RPos{ "xr_controller_position_r" }; //!< The right-hand position
+
+            //! All XR Controller position input ids
+            static constexpr AZStd::array All
+            {
+                LPos,
+                RPos,
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! All the input channel ids that identify XR Controller orientation inputs
+        struct ControllerPoseOrientation
+        {
+            static constexpr InputChannelId LOrient{ "xr_controller_orientation_l" }; //!< The left-hand orientation
+            static constexpr InputChannelId ROrient{ "xr_controller_orientation_r" }; //!< The right-hand orientation
+
+            //! All XR Controller orientation input ids
+            static constexpr AZStd::array All
+            {
+                LOrient,
+                ROrient
+            };
+        };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Allocator
+        AZ_CLASS_ALLOCATOR(InputDeviceXRController, AZ::SystemAllocator, 0);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Type Info
+        AZ_RTTI(InputDeviceXRController, "{31FC6155-5902-46E3-9CB7-C7E7673FE4CC}", InputDevice);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Reflection
+        static void Reflect(AZ::ReflectContext* context);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // The internal implementation class that is passed to the constructor
+        class Implementation
+        {
+        public:
+            AZ_CLASS_ALLOCATOR(Implementation, AZ::SystemAllocator, 0);
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Default factory create function
+            //! @param inputDevice Reference to the input device being implemented
+            static Implementation* Create(InputDeviceXRController& inputDevice);
+
+            Implementation(InputDeviceXRController& inputDevice);
+            AZ_DISABLE_COPY_MOVE(Implementation);
+            virtual ~Implementation() = default;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Query for a path representing an input channel
+            //! Used for initializing Xr inputs.
+            virtual AZStd::string GetInputChannelPath(const InputChannelId& channelId) const = 0;
+
+            virtual AZStd::string GetInputDeviceProfilePath() const = 0;
+            virtual AZStd::string GetLeftHandSubPath() const = 0;
+            virtual AZStd::string GetRightHandSubPath() const = 0;
+
+            using TickCallbackFn = AZStd::function<void()>;
+            virtual void RegisterTickCallback(TickCallbackFn callbackFn) = 0;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Query the connected state of the device
+            //! @return True if the input device is currently connected, False otherwise
+            virtual bool IsConnected() const = 0;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Set the current vibration speed of the motors
+            //! @param leftMotorSpeedNormalized Speed of the left motor
+            //! @param rightMotorSpeedNormalized Speed of the right motor
+            virtual void SetVibration(float leftMotorSpeedNormalized,
+                                      float rightMotorSpeedNormalized) = 0;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Tick/update the input device to broadcast all input events since the last frame
+            virtual void TickInputDevice() = 0;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Broadcast an event when the input device connects to the system
+            void BroadcastInputDeviceConnectedEvent() const;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Broadcast an event when the input device disconnects from the system
+            void BroadcastInputDeviceDisconnectedEvent() const;
+
+            using ButtonIdToBitMaskMap = AZStd::unordered_map<InputChannelId, AZ::u32>;
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Platform agnostic representation of raw XR Controller state
+            struct RawXRControllerState
+            {
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Constructor
+                //! @param digitalButtonMap A map of digital button ids by bitmask
+                explicit RawXRControllerState(ButtonIdToBitMaskMap digitalButtonMap);
+
+                AZ_DISABLE_COPY_MOVE(RawXRControllerState);
+                ~RawXRControllerState() = default;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Reset the raw xr controller data
+                void Reset();
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the left trigger value adjusted for the dead zone and normalized
+                //! @return The adjusted left trigger value
+                float GetLeftTriggerAdjustedForDeadZoneAndNormalized() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the right trigger value adjusted for the dead zone and normalized
+                //! @return The adjusted right trigger value
+                float GetRightTriggerAdjustedForDeadZoneAndNormalized() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the left grip value adjusted for the dead zone and normalized
+                //! @return The adjusted left grip value
+                float GetLeftGripAdjustedForDeadZoneAndNormalized() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the right grip value adjusted for the dead zone and normalized
+                //! @return The adjusted right grip value
+                float GetRightGripAdjustedForDeadZoneAndNormalized() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the left thumb-stick values adjusted for the dead zone and normalized
+                //! @return The adjusted left thumb-stick values
+                AZ::Vector2 GetLeftThumbStickAdjustedForDeadZoneAndNormalized() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the right thumb-stick values adjusted for the dead zone and normalized
+                //! @return The adjusted right thumb-stick values
+                AZ::Vector2 GetRightThumbStickAdjustedForDeadZoneAndNormalized() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the left thumb-stick values normalized with no dead zone applied
+                //! @return The normalized left thumb-stick values
+                AZ::Vector2 GetLeftThumbStickNormalizedValues() const;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Get the right thumb-stick values normalized with no dead zone applied
+                //! @return The normalized right thumb-stick values
+                AZ::Vector2 GetRightThumbStickNormalizedValues() const;
+
+                const ButtonIdToBitMaskMap m_buttonIdsToBitMasks;
+
+                ////////////////////////////////////////////////////////////////////////////////////
+                // Raw Data
+                AZ::u32 m_digitalButtonStates = 0;          //!< The state of all digital buttons
+                float m_leftTriggerState = 0.f;             //!< The left trigger value
+                float m_rightTriggerState = 0.f;            //!< The right trigger value
+                float m_leftGripState = 0.f;                //!< The left grip value
+                float m_rightGripState = 0.f;               //!< The right grip value
+                float m_leftThumbStickXState = 0.f;         //!< The left thumb-stick x-axis
+                float m_leftThumbStickYState = 0.f;         //!< The left thumb-stick y-axis
+                float m_rightThumbStickXState = 0.f;        //!< The right thumb-stick x-axis
+                float m_rightThumbStickYState = 0.f;        //!< The right thumb-stick y-axis
+
+                float m_triggerMaxValue = 0.f;              //!< The maximum value of the analog triggers
+                float m_triggerDeadZoneValue = 0.f;         //!< The dead zone value of the analog triggers
+                float m_gripMaxValue = 0.f;                 //!< The maximum value of the grip triggers
+                float m_gripDeadZoneValue = 0.f;            //!< The dead zone value of the grip triggers
+                float m_thumbStickMaxValue = 0.f;           //!< The maximum value of the thumb-sticks
+                float m_leftThumbStickDeadZoneValue = 0.f;  //!< The left thumb-stick dead zone value
+                float m_rightThumbStickDeadZoneValue = 0.f; //!< The right thumb-stick dead zone value
+
+                float m_leftMotorVibrationValue = 0.f;      //!< The vibration amount of the left motor
+                float m_rightMotorVibrationValue = 0.f;     //!< The vibration amount of the right motor
+
+                AZ::Vector3 m_leftPositionState = AZ::Vector3::CreateZero();                //!< The left controller position
+                AZ::Vector3 m_rightPositionState = AZ::Vector3::CreateZero();               //!< The right controller position
+                AZ::Quaternion m_leftOrientationState = AZ::Quaternion::CreateIdentity();   //!< The left controller orientation
+                AZ::Quaternion m_rightOrientationState = AZ::Quaternion::CreateIdentity();  //!< The right controller orientation
+            }; // struct RawXRControllerState
+
+            virtual RawXRControllerState& GetRawState() = 0;
+
+        protected:
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Process a controller state that has been obtained since the last call to this function.
+            //! @param rawControllerState The raw controller state
+            void ProcessRawControllerState(const RawXRControllerState& rawControllerState);
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Reset the state of all this input device's associated input channels
+            void ResetInputChannelStates();
+
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! @see AzFramework::InputDeviceId::GetIndex
+            AZ::u32 GetInputDeviceIndex() const;
+
+        private:
+            InputDeviceXRController& m_inputDevice;
+        }; // class Implementation
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Alias for the function type used to create a custom implementation for this input device
+        using ImplementationFactory = AZStd::function<Implementation*(InputDeviceXRController&)>;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        InputDeviceXRController();
+        explicit InputDeviceXRController(const InputDeviceId& inputDeviceId,
+                                         ImplementationFactory implFactoryFn = &Implementation::Create);
+        AZ_DISABLE_COPY_MOVE(InputDeviceXRController);
+        ~InputDeviceXRController() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // AzFramework::InputDevice interface
+        const InputChannelByIdMap& GetInputChannelsById() const override;
+        bool IsSupported() const override;
+        bool IsConnected() const override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // AzFramework::InputDeviceRequests interface
+        void TickInputDevice() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // AzFramework::InputHapticFeedbackRequests interface
+        void SetVibration(float leftMotorSpeedNormalized, float rightMotorSpeedNormalized) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Set the implementation of this input device
+        //! @param impl The Implementation to use
+        void SetImplementation(AZStd::unique_ptr<Implementation> impl);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Set the implementation of this input device
+        //! @param implFactoryFn The Implementation factory create function to use
+        void SetImplementation(const ImplementationFactory& implFactoryFn);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Get the non-owning pointer to the implementation of this input device
+        //! @return The raw implementation pointer
+        Implementation* GetImplementation() const;
+
+    protected:
+        static constexpr float s_thumbStickMaxValue{ 1.f };
+        static constexpr float s_thumbStickMinValue{ -1.f };
+        static constexpr float s_thumbStickCenterValue{ 0.f };
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        using ButtonChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelDigital*>;
+        using TriggerChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelAnalog*>;
+        using ThumbStickAxis1DChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelAxis1D*>;
+        using ThumbStickAxis2DChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelAxis2D*>;
+        using ThumbStickDirectionChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelAnalog*>;
+        using ControllerAxis3DChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelAxis3D*>;
+        using ControllerPoseChannelByIdMap = AZStd::unordered_map<InputChannelId, InputChannelQuaternion*>;
+
+        InputChannelByIdMap m_allChannelsById{}; //!< All XR Controller input channels by id
+        ButtonChannelByIdMap m_buttonChannelsById{}; //!< All digital button channels by id
+        TriggerChannelByIdMap m_triggerChannelsById{}; //!< All analog trigger channels by id
+        ThumbStickAxis1DChannelByIdMap m_thumbStick1DChannelsById{}; //!< All thumb-stick 1D axis channels by id
+        ThumbStickAxis2DChannelByIdMap m_thumbStick2DChannelsById{}; //!< All thumb-stick 2D axis channels by id
+        ThumbStickDirectionChannelByIdMap m_thumbStickDirectionChannelsById{}; //!< All thumb-stick direction channels by id
+        ControllerAxis3DChannelByIdMap m_controllerPositionChannelsById{}; //!< All controller position channels by id
+        ControllerPoseChannelByIdMap m_controllerOrientationChannelsById{}; //!< All controller orientation channels by id
+
+    private:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Private pointer to the platform implementation
+        AZStd::unique_ptr<Implementation> m_impl;
+    };
+
+} // namespace AzFramework

+ 42 - 60
Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkInput.h

@@ -9,8 +9,10 @@
 #pragma once
 
 #include <XR/XRInput.h>
+#include <OpenXRVk/InputDeviceXRController.h>
 #include <OpenXRVk/OpenXRVkSpace.h>
 #include <OpenXRVk_Platform.h>
+#include <Atom/RPI.Public/XR/XRRenderingInterface.h>
 
 namespace OpenXRVk
 {
@@ -23,10 +25,10 @@ namespace OpenXRVk
         AZ_RTTI(Input, "{97ADD1FE-27DF-4F36-9F61-683F881F9477}", XR::Input);
 
         static XR::Ptr<Input> Create();
-    
+
         //! Sync all the actions and update controller
-        //! as well as various tracked space poses 
-        void PollActions() override;
+        //! as well as various tracked space poses
+        void PollActions();
 
         //! Initialize various actions/actions sets and add support for Oculus touch bindings
         AZ::RHI::ResultCode InitInternal() override;
@@ -35,7 +37,7 @@ namespace OpenXRVk
         AZ::RHI::ResultCode InitializeActionSpace(XrSession xrSession);
 
         //! Attach action sets
-        AZ::RHI::ResultCode InitializeActionSets(XrSession xrSession);
+        AZ::RHI::ResultCode InitializeActionSets(XrSession xrSession) const;
 
         //! Update Controller space information
         void LocateControllerSpace(XrTime predictedDisplayTime, XrSpace baseSpace, AZ::u32 handIndex);
@@ -43,20 +45,20 @@ namespace OpenXRVk
         //! Update information for a specific tracked space type (i.e visualizedSpaceType)
         void LocateVisualizedSpace(XrTime predictedDisplayTime, XrSpace space, XrSpace baseSpace, OpenXRVk::SpaceType visualizedSpaceType);
 
-        //! Return Pose data for a controller attached to a view index
+        //! Return Pose data for a controller attached to a hand index
         AZ::RHI::ResultCode GetControllerPose(AZ::u32 handIndex, AZ::RPI::PoseData& outPoseData) const;
 
-        //! Return scale for a controller attached to a view index
-        float GetControllerScale(AZ::u32 viewIndex) const;
+        //! Return scale for a controller attached to a hand index
+        float GetControllerScale(AZ::u32 handIndex) const;
 
         //! Return Pose data for a tracked space type (i.e visualizedSpaceType)
         AZ::RHI::ResultCode GetVisualizedSpacePose(OpenXRVk::SpaceType visualizedSpaceType, AZ::RPI::PoseData& outPoseData) const;
 
-        //! Get the Pose action
-        XrAction GetSqueezeAction() const;
+        //! Get the Squeeze action
+        XrAction GetSqueezeAction(AZ::u32 handIndex) const;
 
         //! Get the Pose action
-        XrAction GetPoseAction() const;
+        XrAction GetPoseAction(AZ::u32 handIndex) const;
 
         //! Get the Vibration action
         XrAction GetVibrationAction() const;
@@ -64,17 +66,20 @@ namespace OpenXRVk
         //! Get the Quit action
         XrAction GetQuitAction() const;
 
+        //! Get any button state
+        bool GetButtonState(const AzFramework::InputChannelId& channelId) const;
+
         //! Get the X button state
-        float GetXButtonState() const;
+        bool GetXButtonState() const;
 
         //! Get the Y button state
-        float GetYButtonState() const;
+        bool GetYButtonState() const;
 
         //! Get the A button state
-        float GetAButtonState() const;
+        bool GetAButtonState() const;
 
         //! Get the B button state
-        float GetBButtonState() const;
+        bool GetBButtonState() const;
 
         //! Get the joystick state for x-axis
         float GetXJoyStickState(AZ::u32 handIndex) const;
@@ -89,58 +94,35 @@ namespace OpenXRVk
         float GetTriggerState(AZ::u32 handIndex) const;
 
     private:
-
-        struct SingleActionData
-        {
-            XrAction m_actionHandle{ XR_NULL_HANDLE };
-            float m_actionState = 0.0f;
-        };
-
-        struct DualActionData
-        {
-            XrAction m_actionHandle{ XR_NULL_HANDLE };
-            AZStd::array<float, AZ::RPI::XRMaxNumControllers> m_actionState = { { 0.0f, 0.0f } };
-        };
-
-        struct ControllerActionData
-        {
-            SingleActionData m_actionData;
-            uint16_t m_handIndex = 0;
-        };
-
-        //! Create a XrAction
+        //! Creates an XrAction
         void CreateAction(XrAction& action, XrActionType actionType,
                           const char* actionName, const char* localizedActionName,
-                          uint32_t countSubactionPathCount, const XrPath* subActionPaths);
+                          uint32_t countSubactionPathCount, const XrPath* subActionPaths) const;
+
+
+        void CreateActionSet(const XrInstance& xrInstance);
+        void CreateAllActions(const XrInstance& xrInstance);
+        XrAction GetAction(const AzFramework::InputChannelId& channelId) const;
 
         //! Destroy native objects
         void ShutdownInternal() override;
 
-        bool GetActionState(XrSession xrSession, XrAction xrAction, uint16_t handIndex, float& outputSate);
-        bool UpdateActionState(XrSession xrSession, SingleActionData& actionData, uint16_t handIndex);
-        bool UpdateActionState(XrSession xrSession, DualActionData& actionData, uint16_t handIndex);
-
         XrActionSet m_actionSet{ XR_NULL_HANDLE };
-        XrAction m_poseAction{ XR_NULL_HANDLE };
-        XrAction m_vibrateAction{ XR_NULL_HANDLE };
-        XrAction m_quitAction{ XR_NULL_HANDLE };
-        DualActionData m_squeezeAction;
-        DualActionData m_triggerAction;
-
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> m_handSubactionPath;
-        AZStd::array<XrSpace, AZ::RPI::XRMaxNumControllers> m_handSpace;
-        AZStd::array<float, AZ::RPI::XRMaxNumControllers> m_handScale = { { 1.0f, 1.0f } };
-        AZStd::array<XrBool32, AZ::RPI::XRMaxNumControllers> m_handActive;
-
-        AZStd::array<XrSpaceLocation, AZ::RPI::XRMaxNumControllers> m_handSpaceLocation;
-        AZStd::array<XrSpaceLocation, SpaceType::Count> m_xrVisualizedSpaceLocations;
-
-        //Todo: This is assuming Quest 2 controller. Needs better abstraction to cover other types of controllers
-        SingleActionData m_xButtonAction;
-        SingleActionData m_yButtonAction;
-        SingleActionData m_aButtonAction;
-        SingleActionData m_bButtonAction;
-        DualActionData m_joyStickXAction;
-        DualActionData m_joyStickYAction;
+
+        XrAction m_hapticAction{};
+        AZStd::vector<XrActionSuggestedBinding> m_xrActionPaths{};
+        AZStd::unordered_map<AzFramework::InputChannelId, AZStd::size_t> m_xrActionIndices{};
+
+        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> m_handSubactionPath{};
+        AZStd::array<XrSpace, AZ::RPI::XRMaxNumControllers> m_handSpace{};
+        AZStd::array<float, AZ::RPI::XRMaxNumControllers> m_handScale{ { 1.0f, 1.0f } };
+        AZStd::array<XrBool32, AZ::RPI::XRMaxNumControllers> m_handActive{};
+
+        AZStd::array<XrSpaceLocation, AZ::RPI::XRMaxNumControllers> m_handSpaceLocation{};
+        AZStd::array<XrSpaceLocation, SpaceType::Count> m_xrVisualizedSpaceLocations{};
+
+        AzFramework::InputDeviceXRController m_xrController{};
+        AzFramework::InputDeviceXRController::Implementation* m_xrControllerImpl{};
+        bool m_wasQuitPressedLastSync{ false };
     };
 }

+ 4 - 5
Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkInstance.h

@@ -17,7 +17,7 @@
 
 namespace OpenXRVk
 {
-    //! Vulkan specific XR instance back-end class that will help manage 
+    //! Vulkan specific XR instance back-end class that will help manage
     //! XR specific vulkan native objects
     class Instance final
     : public XR::Instance
@@ -35,10 +35,10 @@ namespace OpenXRVk
         AZ::u32 GetNumPhysicalDevices() const override;
         AZ::RHI::ResultCode GetXRPhysicalDevice(AZ::RHI::XRPhysicalDeviceDescriptor* physicalDeviceDescriptor, int32_t index) override;
         //////////////////////////////////////////////////////////////////////////
-		
+
         //! Enumerate supported extension names.
         XR::StringList GetInstanceExtensionNames(const char* layerName = nullptr) const;
-		
+
         //! Enumerate supported layer names.
         XR::StringList GetInstanceLayerNames() const;
 
@@ -73,8 +73,7 @@ namespace OpenXRVk
         VkPhysicalDevice GetActivePhysicalDevice() const;
 
     private:
-
-        //! Clean native objects. 
+        //! Clean native objects.
         void ShutdownInternal() override;
 
         XrInstance m_xrInstance = XR_NULL_HANDLE;

+ 2 - 4
Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkSession.h

@@ -8,7 +8,6 @@
 
 #pragma once
 
-#include <AzCore/std/smart_ptr/intrusive_ptr.h>
 #include <OpenXRVk_Platform.h>
 #include <OpenXRVk/OpenXRVkSpace.h>
 #include <OpenXRVk/OpenXRVkInput.h>
@@ -32,7 +31,7 @@ namespace OpenXRVk
         //! Process session state when it is updated
         void HandleSessionStateChangedEvent(const XrEventDataSessionStateChanged& stateChangedEvent);
 
-        //! Try and poll the next event 
+        //! Try and poll the next event
         const XrEventDataBaseHeader* TryReadNextEvent();
 
         //! Return the native session
@@ -66,7 +65,6 @@ namespace OpenXRVk
         //////////////////////////////////////////////////////////////////////////
 
     private:
-
         void ShutdownInternal() override;
         void LogActionSourceName(XrAction action, const AZStd::string_view actionName) const;
         Input* GetNativeInput() const;
@@ -79,6 +77,6 @@ namespace OpenXRVk
 
         bool m_sessionRunning = false;
         bool m_exitRenderLoop = false;
-        bool m_requestRestart = false;        
+        bool m_requestRestart = false;
     };
 }

+ 3 - 4
Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkSpace.h

@@ -34,11 +34,11 @@ namespace OpenXRVk
     public:
         AZ_CLASS_ALLOCATOR(Space, AZ::SystemAllocator, 0);
         AZ_RTTI(Space, "{E99557D0-9061-4691-9524-CE0ACC3A14FA}", XR::Space);
-        
-        static XR::Ptr<Space> Create();           
+
+        static XR::Ptr<Space> Create();
         AZ::RHI::ResultCode InitInternal() override;
         void ShutdownInternal() override;
-    
+
         //!Initialize XrSpace per SpaceType we want to track
         void CreateVisualizedSpaces(XrSession xrSession);
 
@@ -49,7 +49,6 @@ namespace OpenXRVk
         XrSpace GetXrSpace(SpaceType spaceType) const;
 
     private:
-
         //! XrPose specific matrix translation, Rotation functions
         XrPosef Identity();
         XrPosef Translation(const XrVector3f& translation);

+ 6 - 5
Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkUtils.h

@@ -33,25 +33,26 @@ namespace OpenXRVk
 {
 
 #define RETURN_XR_RESULT_IF_UNSUCCESSFUL(result) \
-    if (result != XR_SUCCESS) {\
-        return result;\
+    if ((result) != XR_SUCCESS) {\
+        return (result);\
     }
 
 #define RETURN_IF_UNSUCCESSFUL(result) \
-    if (result != XR_SUCCESS) {\
+    if ((result) != XR_SUCCESS) {\
         return;\
     }
 
 #define WARN_IF_UNSUCCESSFUL(result) \
-    if (result != XR_SUCCESS) {\
+    if ((result) != XR_SUCCESS) {\
         AZ_Warning("OpenXRVk", false, "Warning error code: %s", to_string(result));\
     }
 
 #define ASSERT_IF_UNSUCCESSFUL(result) \
-    if (result != XR_SUCCESS) {\
+    if ((result) != XR_SUCCESS) {\
         AZ_Assert(false, "Assert error code: %s", to_string(result));\
     }
 
+
     AZ::RHI::ResultCode ConvertResult(XrResult xrResult);
     bool IsSuccess(XrResult result);
     bool IsError(XrResult result);

+ 422 - 0
Gems/OpenXRVk/Code/Source/InputDeviceXRController.cpp

@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <OpenXRVk/InputDeviceXRController.h>
+
+#include <AzCore/RTTI/BehaviorContext.h>
+#include <AzFramework/Input/Utils/AdjustAnalogInputForDeadZone.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace AzFramework
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // static
+    bool InputDeviceXRController::IsXRControllerDevice(const InputDeviceId& inputDeviceId)
+    {
+        // Only need to check the name (crc) to check the device is an xr controller type.
+        return (inputDeviceId.GetNameCrc32() == IdForIndex0.GetNameCrc32());
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
+        {
+#define BEHAVIOR_XR_CONSTANT(channel)       Constant(channel.GetName(), BehaviorConstant(channel.GetName()))
+
+            behaviorContext->Class<InputDeviceXRController>()
+                ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::RuntimeOwn)
+                ->Constant("name", BehaviorConstant(IdForIndex0.GetName()))
+                // Standard digital buttons...
+                ->BEHAVIOR_XR_CONSTANT(Button::A)
+                ->BEHAVIOR_XR_CONSTANT(Button::B)
+                ->BEHAVIOR_XR_CONSTANT(Button::X)
+                ->BEHAVIOR_XR_CONSTANT(Button::Y)
+                ->BEHAVIOR_XR_CONSTANT(Button::Home)
+                ->BEHAVIOR_XR_CONSTANT(Button::Menu)
+                ->BEHAVIOR_XR_CONSTANT(Button::L3)
+                ->BEHAVIOR_XR_CONSTANT(Button::R3)
+                // Touch capacitive...
+                ->BEHAVIOR_XR_CONSTANT(Button::TA)
+                ->BEHAVIOR_XR_CONSTANT(Button::TB)
+                ->BEHAVIOR_XR_CONSTANT(Button::TX)
+                ->BEHAVIOR_XR_CONSTANT(Button::TY)
+                ->BEHAVIOR_XR_CONSTANT(Button::TLStick)
+                ->BEHAVIOR_XR_CONSTANT(Button::TRStick)
+                ->BEHAVIOR_XR_CONSTANT(Button::TLRest)
+                ->BEHAVIOR_XR_CONSTANT(Button::TRRest)
+                ->BEHAVIOR_XR_CONSTANT(Button::TLTrig)
+                ->BEHAVIOR_XR_CONSTANT(Button::TRTrig)
+                // Analog triggers...
+                ->BEHAVIOR_XR_CONSTANT(Trigger::LTrigger)
+                ->BEHAVIOR_XR_CONSTANT(Trigger::RTrigger)
+                ->BEHAVIOR_XR_CONSTANT(Trigger::LGrip)
+                ->BEHAVIOR_XR_CONSTANT(Trigger::RGrip)
+                // Thumbsticks (1D)...
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickAxis1D::LX)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickAxis1D::LY)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickAxis1D::RX)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickAxis1D::RY)
+                // Thumbsticks (2D)...
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickAxis2D::L)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickAxis2D::R)
+                // Thumbstick directions...
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::LU)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::LD)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::LL)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::LR)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::RU)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::RD)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::RL)
+                ->BEHAVIOR_XR_CONSTANT(ThumbStickDirection::RR)
+                // Position (3D)...
+                ->BEHAVIOR_XR_CONSTANT(ControllerPosePosition::LPos)
+                ->BEHAVIOR_XR_CONSTANT(ControllerPosePosition::RPos)
+                // Orientation (quaternion)...
+                ->BEHAVIOR_XR_CONSTANT(ControllerPoseOrientation::LOrient)
+                ->BEHAVIOR_XR_CONSTANT(ControllerPoseOrientation::ROrient)
+            ;
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Default constructor
+    //! Using the default constructor will not create an implementation.  It is then up to the user
+    //! to call InputDeviceXRController::SetImplementation and supply either a unique_ptr<Implementation>
+    //! or an ImplementationFactory function.
+    InputDeviceXRController::InputDeviceXRController()
+        : InputDeviceXRController(InputDeviceId(Name, 0), nullptr)
+    {
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::InputDeviceXRController(const InputDeviceId& inputDeviceId,
+                                                     ImplementationFactory implFactoryFn)
+        : InputDevice(inputDeviceId)
+    {
+        // Create all digital button input channels
+        for (const InputChannelId& channelId : Button::All)
+        {
+            auto channel = aznew InputChannelDigital(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_buttonChannelsById[channelId] = channel;
+        }
+
+        // Create all analog trigger input channels
+        for (const InputChannelId& channelId : Trigger::All)
+        {
+            auto channel = aznew InputChannelAnalog(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_triggerChannelsById[channelId] = channel;
+        }
+
+        // Create all 1D thumb-stick input channels
+        for (const InputChannelId& channelId : ThumbStickAxis1D::All)
+        {
+            auto channel = aznew InputChannelAxis1D(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_thumbStick1DChannelsById[channelId] = channel;
+        }
+
+        // Create all 2D thumb-stick input channels
+        for (const InputChannelId& channelId : ThumbStickAxis2D::All)
+        {
+            auto channel = aznew InputChannelAxis2D(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_thumbStick2DChannelsById[channelId] = channel;
+        }
+
+        // Create all analog thumb-stick direction input channels
+        for (const InputChannelId& channelId : ThumbStickDirection::All)
+        {
+            auto channel = aznew InputChannelAnalog(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_thumbStickDirectionChannelsById[channelId] = channel;
+        }
+
+        // Create all 3D controller position input channels
+        for (const InputChannelId& channelId : ControllerPosePosition::All)
+        {
+            auto channel = aznew InputChannelAxis3D(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_controllerPositionChannelsById[channelId] = channel;
+        }
+
+        // Create all Quat controller orientation input channels
+        for (const InputChannelId& channelId : ControllerPoseOrientation::All)
+        {
+            auto channel = aznew InputChannelQuaternion(channelId, *this);
+            m_allChannelsById[channelId] = channel;
+            m_controllerOrientationChannelsById[channelId] = channel;
+        }
+
+        // Create the custom implementation
+        SetImplementation(AZStd::move(implFactoryFn));
+
+        // Connect to haptic feedback request bus
+        InputHapticFeedbackRequestBus::Handler::BusConnect(GetInputDeviceId());
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::~InputDeviceXRController()
+    {
+        // Disconnect from haptic feedback request bus
+        InputHapticFeedbackRequestBus::Handler::BusDisconnect(GetInputDeviceId());
+
+        // Destroy the custom implementation
+        m_impl.reset();
+
+        // Destroy all input channels
+        for (const auto& channelById : m_allChannelsById)
+        {
+            delete channelById.second;
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    const InputDevice::InputChannelByIdMap& InputDeviceXRController::GetInputChannelsById() const
+    {
+        return m_allChannelsById;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceXRController::IsSupported() const
+    {
+        return m_impl != nullptr;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceXRController::IsConnected() const
+    {
+        return m_impl ? m_impl->IsConnected() : false;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::TickInputDevice()
+    {
+        if (m_impl)
+        {
+            m_impl->TickInputDevice();
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::SetVibration(float leftMotorSpeedNormalized, float rightMotorSpeedNormalized)
+    {
+        if (m_impl)
+        {
+            m_impl->SetVibration(leftMotorSpeedNormalized, rightMotorSpeedNormalized);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::SetImplementation(AZStd::unique_ptr<Implementation> impl)
+    {
+        m_impl = AZStd::move(impl);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::SetImplementation(const ImplementationFactory& implFactoryFn)
+    {
+        if (implFactoryFn)
+        {
+            m_impl.reset(implFactoryFn(*this));
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::Implementation* InputDeviceXRController::GetImplementation() const
+    {
+        return m_impl.get();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! InputDeviceXRController::Implementation
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::Implementation::Implementation(InputDeviceXRController& inputDevice)
+        : m_inputDevice(inputDevice)
+    {
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::Implementation::BroadcastInputDeviceConnectedEvent() const
+    {
+        m_inputDevice.BroadcastInputDeviceConnectedEvent();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::Implementation::BroadcastInputDeviceDisconnectedEvent() const
+    {
+        m_inputDevice.BroadcastInputDeviceDisconnectedEvent();
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! InputDeviceXRController::Implementation::RawXRControllerState
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::Implementation::RawXRControllerState::RawXRControllerState(ButtonIdToBitMaskMap digitalButtonMap)
+        : m_buttonIdsToBitMasks(AZStd::move(digitalButtonMap))
+        , m_triggerMaxValue(1.f)
+        , m_gripMaxValue(1.f)
+        , m_thumbStickMaxValue(1.f)
+    {
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::Implementation::RawXRControllerState::Reset()
+    {
+        m_digitalButtonStates = 0;
+        m_leftTriggerState = 0.f;
+        m_rightTriggerState = 0.f;
+        m_leftGripState = 0.f;
+        m_rightGripState = 0.f;
+        m_leftThumbStickXState = 0.f;
+        m_leftThumbStickYState = 0.f;
+        m_rightThumbStickXState = 0.f;
+        m_rightThumbStickYState = 0.f;
+        m_leftPositionState = AZ::Vector3::CreateZero();
+        m_rightPositionState = AZ::Vector3::CreateZero();
+        m_leftOrientationState = AZ::Quaternion::CreateIdentity();
+        m_rightOrientationState = AZ::Quaternion::CreateIdentity();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    float InputDeviceXRController::Implementation::RawXRControllerState::GetLeftTriggerAdjustedForDeadZoneAndNormalized() const
+    {
+        return AdjustForDeadZoneAndNormalizeAnalogInput(m_leftTriggerState, m_triggerDeadZoneValue, m_triggerMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    float InputDeviceXRController::Implementation::RawXRControllerState::GetRightTriggerAdjustedForDeadZoneAndNormalized() const
+    {
+        return AdjustForDeadZoneAndNormalizeAnalogInput(m_rightTriggerState, m_triggerDeadZoneValue, m_triggerMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    float InputDeviceXRController::Implementation::RawXRControllerState::GetLeftGripAdjustedForDeadZoneAndNormalized() const
+    {
+        return AdjustForDeadZoneAndNormalizeAnalogInput(m_leftGripState, m_gripDeadZoneValue, m_gripMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    float InputDeviceXRController::Implementation::RawXRControllerState::GetRightGripAdjustedForDeadZoneAndNormalized() const
+    {
+        return AdjustForDeadZoneAndNormalizeAnalogInput(m_rightGripState, m_gripDeadZoneValue, m_gripMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ::Vector2 InputDeviceXRController::Implementation::RawXRControllerState::GetLeftThumbStickAdjustedForDeadZoneAndNormalized() const
+    {
+        return AdjustForDeadZoneAndNormalizeThumbStickInput(m_leftThumbStickXState, m_leftThumbStickYState,
+                                                            m_leftThumbStickDeadZoneValue, m_thumbStickMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ::Vector2 InputDeviceXRController::Implementation::RawXRControllerState::GetRightThumbStickAdjustedForDeadZoneAndNormalized() const
+    {
+        return AdjustForDeadZoneAndNormalizeThumbStickInput(m_rightThumbStickXState, m_rightThumbStickYState,
+                                                            m_rightThumbStickDeadZoneValue, m_thumbStickMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ::Vector2 InputDeviceXRController::Implementation::RawXRControllerState::GetLeftThumbStickNormalizedValues() const
+    {
+        return AZ::Vector2(m_leftThumbStickXState / m_thumbStickMaxValue,
+                           m_leftThumbStickYState / m_thumbStickMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ::Vector2 InputDeviceXRController::Implementation::RawXRControllerState::GetRightThumbStickNormalizedValues() const
+    {
+        return AZ::Vector2(m_rightThumbStickXState / m_thumbStickMaxValue,
+                           m_rightThumbStickYState / m_thumbStickMaxValue);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::Implementation::ProcessRawControllerState([[maybe_unused]] const RawXRControllerState& rawControllerState)
+    {
+        // Update digital button channels...
+        for (const auto& [channelId, bitMask] : rawControllerState.m_buttonIdsToBitMasks)
+        {
+            const bool buttonState = (rawControllerState.m_digitalButtonStates & bitMask) != 0;
+            m_inputDevice.m_buttonChannelsById[channelId]->ProcessRawInputEvent(buttonState);
+        }
+
+        using xrc = InputDeviceXRController;
+
+        // Update the analog triggers...
+        const float triggerL = rawControllerState.GetLeftTriggerAdjustedForDeadZoneAndNormalized();
+        const float triggerR = rawControllerState.GetRightTriggerAdjustedForDeadZoneAndNormalized();
+        const float gripL = rawControllerState.GetLeftGripAdjustedForDeadZoneAndNormalized();
+        const float gripR = rawControllerState.GetRightGripAdjustedForDeadZoneAndNormalized();
+        m_inputDevice.m_triggerChannelsById[xrc::Trigger::LTrigger]->ProcessRawInputEvent(triggerL);
+        m_inputDevice.m_triggerChannelsById[xrc::Trigger::RTrigger]->ProcessRawInputEvent(triggerR);
+        m_inputDevice.m_triggerChannelsById[xrc::Trigger::LGrip]->ProcessRawInputEvent(gripL);
+        m_inputDevice.m_triggerChannelsById[xrc::Trigger::RGrip]->ProcessRawInputEvent(gripR);
+
+        // Update thumb-stick channels...
+        const AZ::Vector2 leftThumbStick = rawControllerState.GetLeftThumbStickAdjustedForDeadZoneAndNormalized();
+        const AZ::Vector2 leftThumbStickPreDeadZone = rawControllerState.GetLeftThumbStickNormalizedValues();
+        const float leftStickUp = AZ::GetClamp(leftThumbStick.GetY(), s_thumbStickCenterValue, s_thumbStickMaxValue);
+        const float leftStickDown = fabsf(AZ::GetClamp(leftThumbStick.GetY(), s_thumbStickMinValue, s_thumbStickCenterValue));
+        const float leftStickLeft = fabsf(AZ::GetClamp(leftThumbStick.GetX(), s_thumbStickMinValue, s_thumbStickCenterValue));
+        const float leftStickRight = AZ::GetClamp(leftThumbStick.GetX(), s_thumbStickCenterValue, s_thumbStickMaxValue);
+        const AZ::Vector2 rightThumbStick = rawControllerState.GetRightThumbStickAdjustedForDeadZoneAndNormalized();
+        const AZ::Vector2 rightThumbStickPreDeadZone = rawControllerState.GetRightThumbStickNormalizedValues();
+        const float rightStickUp = AZ::GetClamp(rightThumbStick.GetY(), s_thumbStickCenterValue, s_thumbStickMaxValue);
+        const float rightStickDown = fabsf(AZ::GetClamp(rightThumbStick.GetY(), s_thumbStickMinValue, s_thumbStickCenterValue));
+        const float rightStickLeft = fabsf(AZ::GetClamp(rightThumbStick.GetX(), s_thumbStickMinValue, s_thumbStickCenterValue));
+        const float rightStickRight = AZ::GetClamp(rightThumbStick.GetX(), s_thumbStickCenterValue, s_thumbStickMaxValue);
+
+        m_inputDevice.m_thumbStick2DChannelsById[xrc::ThumbStickAxis2D::L]->ProcessRawInputEvent(leftThumbStick, &leftThumbStickPreDeadZone);
+        m_inputDevice.m_thumbStick1DChannelsById[xrc::ThumbStickAxis1D::LX]->ProcessRawInputEvent(leftThumbStick.GetX());
+        m_inputDevice.m_thumbStick1DChannelsById[xrc::ThumbStickAxis1D::LY]->ProcessRawInputEvent(leftThumbStick.GetY());
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::LU]->ProcessRawInputEvent(leftStickUp);
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::LD]->ProcessRawInputEvent(leftStickDown);
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::LL]->ProcessRawInputEvent(leftStickLeft);
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::LR]->ProcessRawInputEvent(leftStickRight);
+        m_inputDevice.m_thumbStick2DChannelsById[xrc::ThumbStickAxis2D::R]->ProcessRawInputEvent(rightThumbStick, &rightThumbStickPreDeadZone);
+        m_inputDevice.m_thumbStick1DChannelsById[xrc::ThumbStickAxis1D::RX]->ProcessRawInputEvent(rightThumbStick.GetX());
+        m_inputDevice.m_thumbStick1DChannelsById[xrc::ThumbStickAxis1D::RY]->ProcessRawInputEvent(rightThumbStick.GetY());
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::RU]->ProcessRawInputEvent(rightStickUp);
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::RD]->ProcessRawInputEvent(rightStickDown);
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::RL]->ProcessRawInputEvent(rightStickLeft);
+        m_inputDevice.m_thumbStickDirectionChannelsById[xrc::ThumbStickDirection::RR]->ProcessRawInputEvent(rightStickRight);
+
+        // Position update...
+        m_inputDevice.m_controllerPositionChannelsById[xrc::ControllerPosePosition::LPos]
+            ->ProcessRawInputEvent(rawControllerState.m_leftPositionState);
+        m_inputDevice.m_controllerPositionChannelsById[xrc::ControllerPosePosition::RPos]
+            ->ProcessRawInputEvent(rawControllerState.m_rightPositionState);
+
+        // TBD: Process Velocity and Acceleration...
+
+        // Orientation update...
+        m_inputDevice.m_controllerOrientationChannelsById[xrc::ControllerPoseOrientation::LOrient]
+            ->ProcessRawInputEvent(rawControllerState.m_leftOrientationState);
+        m_inputDevice.m_controllerOrientationChannelsById[xrc::ControllerPoseOrientation::ROrient]
+            ->ProcessRawInputEvent(rawControllerState.m_rightOrientationState);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::Implementation::ResetInputChannelStates()
+    {
+        m_inputDevice.ResetInputChannelStates();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZ::u32 InputDeviceXRController::Implementation::GetInputDeviceIndex() const
+    {
+        return m_inputDevice.GetInputDeviceId().GetIndex();
+    }
+
+} // namespace AzFramework

+ 295 - 215
Gems/OpenXRVk/Code/Source/OpenXRVkInput.cpp

@@ -14,119 +14,43 @@
 #include <OpenXRVk/OpenXRVkUtils.h>
 #include <AzCore/Casting/numeric_cast.h>
 
+#include <../Common/Default/OculusTouch_Default.h>
+
 namespace OpenXRVk
 {
     XR::Ptr<Input> Input::Create()
     {
-        return aznew Input;
+        const auto newInput = aznew Input;
+        newInput->m_xrController.SetImplementation(&AzFramework::InputDeviceXRController::Implementation::Create);
+        newInput->m_xrControllerImpl = newInput->m_xrController.GetImplementation();
+        return newInput;
     }
 
     AZ::RHI::ResultCode Input::InitInternal()
     {
-        Instance* xrVkInstance = static_cast<Instance*>(GetDescriptor().m_instance.get());
-        XrInstance xrInstance = xrVkInstance->GetXRInstance();
+        const auto xrVkInstance = static_cast<Instance*>(GetDescriptor().m_instance.get());
+        const XrInstance xrInstance = xrVkInstance->GetXRInstance();
 
         // Create an action set.
-        XrActionSetCreateInfo actionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
-        azstrcpy(actionSetInfo.actionSetName, sizeof(actionSetInfo.actionSetName), "gameplay");
-        azstrcpy(actionSetInfo.localizedActionSetName, sizeof(actionSetInfo.localizedActionSetName), "Gameplay");
-        actionSetInfo.priority = 0;
-        XrResult result = xrCreateActionSet(xrInstance, &actionSetInfo, &m_actionSet);
-        WARN_IF_UNSUCCESSFUL(result);
+        CreateActionSet(xrInstance);
 
-        // Get the XrPath for the left and right hands - we will use them as subaction paths.
-        result = xrStringToPath(xrInstance, "/user/hand/left", &m_handSubactionPath[static_cast<uint32_t>(XR::Side::Left)]);
-        WARN_IF_UNSUCCESSFUL(result);
-        result = xrStringToPath(xrInstance, "/user/hand/right", &m_handSubactionPath[static_cast<uint32_t>(XR::Side::Right)]);
-        WARN_IF_UNSUCCESSFUL(result);
+        // Create all the XrActions
+        CreateAllActions(xrInstance);
 
-        // Create actions.   
-        // Create an input action for grabbing objects with the left and right hands.
-        CreateAction(m_squeezeAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "squeeze_object", "Squeeze Object",
-                     aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-
-        CreateAction(m_triggerAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "trigger_object", "Trigger Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-
-        CreateAction(m_poseAction, XR_ACTION_TYPE_POSE_INPUT, "hand_pose", "Hand Pose",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-
-        CreateAction(m_vibrateAction, XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_hand", "Vibrate Hand",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-
-        CreateAction(m_quitAction, XR_ACTION_TYPE_BOOLEAN_INPUT, "quit_session", "Quit Session", 0, nullptr);
-
-        CreateAction(m_xButtonAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "x_button", "X Button Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-        CreateAction(m_yButtonAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "y_button", "Y Button Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-        CreateAction(m_aButtonAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "a_button", "A Button Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-        CreateAction(m_bButtonAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "b_button", "B Button Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-        CreateAction(m_joyStickXAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "joystick_x", "JoyStick X Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-        CreateAction(m_joyStickYAction.m_actionHandle, XR_ACTION_TYPE_FLOAT_INPUT, "joystick_y", "JoyStick Y Object",
-            aznumeric_cast<uint32_t>(m_handSubactionPath.size()), m_handSubactionPath.data());
-
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> squeezeValuePath;
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> triggerValuePath;
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> posePath;
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> hapticPath;
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> menuClickPath;
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> joyStickXPath;
-        AZStd::array<XrPath, AZ::RPI::XRMaxNumControllers> joyStickYPath;
-        XrPath xButtonValuePath;
-        XrPath yButtonValuePath;
-        XrPath aButtonValuePath;
-        XrPath bButtonValuePath;
-
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/squeeze/value", &squeezeValuePath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/squeeze/value", &squeezeValuePath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/trigger/value", &triggerValuePath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/trigger/value", &triggerValuePath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/grip/pose", &posePath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/grip/pose", &posePath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/output/haptic", &hapticPath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/output/haptic", &hapticPath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/menu/click", &menuClickPath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/menu/click", &menuClickPath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/thumbstick/x", &joyStickXPath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/thumbstick/x", &joyStickXPath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/thumbstick/y", &joyStickYPath[static_cast<uint32_t>(XR::Side::Left)]);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/thumbstick/y", &joyStickYPath[static_cast<uint32_t>(XR::Side::Right)]);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/x/click", &xButtonValuePath);
-        result = xrStringToPath(xrInstance, "/user/hand/left/input/y/click", &yButtonValuePath);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/a/click", &aButtonValuePath);
-        result = xrStringToPath(xrInstance, "/user/hand/right/input/b/click", &bButtonValuePath);
-        
         // Bindings for the Oculus Touch.
         XrPath oculusTouchInteractionProfilePath;
-        result = xrStringToPath(xrInstance, "/interaction_profiles/oculus/touch_controller", &oculusTouchInteractionProfilePath);
-        AZStd::vector<XrActionSuggestedBinding> bindings{   { m_squeezeAction.m_actionHandle, squeezeValuePath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_squeezeAction.m_actionHandle, squeezeValuePath[static_cast<uint32_t>(XR::Side::Right)] },
-                                                            { m_triggerAction.m_actionHandle, triggerValuePath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_triggerAction.m_actionHandle, triggerValuePath[static_cast<uint32_t>(XR::Side::Right)] },
-                                                            { m_poseAction, posePath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_poseAction, posePath[static_cast<uint32_t>(XR::Side::Right)] },
-                                                            { m_quitAction, menuClickPath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_vibrateAction, hapticPath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_vibrateAction, hapticPath[static_cast<uint32_t>(XR::Side::Right)] },
-                                                            { m_joyStickXAction.m_actionHandle, joyStickXPath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_joyStickXAction.m_actionHandle, joyStickXPath[static_cast<uint32_t>(XR::Side::Right)] },
-                                                            { m_joyStickYAction.m_actionHandle, joyStickYPath[static_cast<uint32_t>(XR::Side::Left)] },
-                                                            { m_joyStickYAction.m_actionHandle, joyStickYPath[static_cast<uint32_t>(XR::Side::Right)] },
-                                                            { m_xButtonAction.m_actionHandle, xButtonValuePath },
-                                                            { m_yButtonAction.m_actionHandle, yButtonValuePath },
-                                                            { m_aButtonAction.m_actionHandle, aButtonValuePath },
-                                                            { m_bButtonAction.m_actionHandle, bButtonValuePath } };
-        XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
+        AZStd::string controllerProfilePath{ m_xrControllerImpl->GetInputDeviceProfilePath() };
+        [[maybe_unused]] XrResult result = xrStringToPath(xrInstance, controllerProfilePath.data(), &oculusTouchInteractionProfilePath);
+        WARN_IF_UNSUCCESSFUL(result);
+
+        XrInteractionProfileSuggestedBinding suggestedBindings{};
+        suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
         suggestedBindings.interactionProfile = oculusTouchInteractionProfilePath;
-        suggestedBindings.suggestedBindings = bindings.data();
-        suggestedBindings.countSuggestedBindings = aznumeric_cast<uint32_t>(bindings.size());
+        suggestedBindings.suggestedBindings = m_xrActionPaths.data();
+        suggestedBindings.countSuggestedBindings = aznumeric_cast<AZ::u32>(m_xrActionPaths.size());
         result = xrSuggestInteractionProfileBindings(xrInstance, &suggestedBindings);
         WARN_IF_UNSUCCESSFUL(result);
-        
+
         //Init the location data so we dont read bad data when the device is in a bad state at start
         for (int i = 0; i < AZ::RPI::XRMaxNumControllers; i++)
         {
@@ -153,41 +77,117 @@ namespace OpenXRVk
     }
 
     void Input::CreateAction(XrAction& action, XrActionType actionType,
-                                  const char* actionName, const char* localizedActionName,
-                                  uint32_t countSubactionPathCount, const XrPath* subActionPaths)
+                             const char* actionName, const char* localizedActionName,
+                             uint32_t countSubactionPathCount, const XrPath* subActionPaths) const
     {
-        XrActionCreateInfo actionInfo{ XR_TYPE_ACTION_CREATE_INFO };
+        XrActionCreateInfo actionInfo{};
+        actionInfo.type = XR_TYPE_ACTION_CREATE_INFO;
         actionInfo.actionType = actionType;
         azstrcpy(actionInfo.actionName, sizeof(actionInfo.actionName), actionName);
         azstrcpy(actionInfo.localizedActionName, sizeof(actionInfo.localizedActionName), localizedActionName);
         actionInfo.countSubactionPaths = countSubactionPathCount;
         actionInfo.subactionPaths = subActionPaths;
-        [[maybe_unused]] XrResult result = xrCreateAction(m_actionSet, &actionInfo, &action);
+
+        [[maybe_unused]] const XrResult result = xrCreateAction(m_actionSet, &actionInfo, &action);
+        WARN_IF_UNSUCCESSFUL(result);
+    }
+
+    void Input::CreateActionSet(const XrInstance& xrInstance)
+    {
+        // Create an action set.
+        XrActionSetCreateInfo actionSetInfo{};
+        actionSetInfo.type = XR_TYPE_ACTION_SET_CREATE_INFO;
+        azstrcpy(actionSetInfo.actionSetName, sizeof(actionSetInfo.actionSetName), "gameplay");
+        azstrcpy(actionSetInfo.localizedActionSetName, sizeof(actionSetInfo.localizedActionSetName), "Gameplay");
+        actionSetInfo.priority = 0;
+
+        [[maybe_unused]] const XrResult result = xrCreateActionSet(xrInstance, &actionSetInfo, &m_actionSet);
         WARN_IF_UNSUCCESSFUL(result);
     }
 
+    void Input::CreateAllActions(const XrInstance& xrInstance)
+    {
+        // Get the XrPath for the left and right hands - we will use them as subaction paths.
+        const AZStd::string leftHandPath{ m_xrControllerImpl->GetLeftHandSubPath() };
+        const AZStd::string rightHandPath{ m_xrControllerImpl->GetRightHandSubPath() };
+
+        XrResult result = xrStringToPath(xrInstance, leftHandPath.data(), &m_handSubactionPath[static_cast<uint32_t>(XR::Side::Left)]);
+        WARN_IF_UNSUCCESSFUL(result);
+        result = xrStringToPath(xrInstance, rightHandPath.data(), &m_handSubactionPath[static_cast<uint32_t>(XR::Side::Right)]);
+        WARN_IF_UNSUCCESSFUL(result);
+
+        // Lambda to create an action and path, store them in m_xrActionPaths
+        using namespace AzFramework;
+        auto createXrAction = [this, &xrInstance](const InputChannelId& channelId, const XrActionType actionType)
+        {
+            m_xrActionIndices[channelId] = m_xrActionPaths.size();
+            m_xrActionPaths.push_back({});
+
+            CreateAction(m_xrActionPaths.back().action, actionType, channelId.GetName(), channelId.GetName(),
+                aznumeric_cast<AZ::u32>(AZStd::size(m_handSubactionPath)), m_handSubactionPath.data());
+
+            const AZStd::string xrPathStr{ m_xrControllerImpl->GetInputChannelPath(channelId) };
+            [[maybe_unused]] const XrResult pathResult = xrStringToPath(xrInstance, xrPathStr.data(), &m_xrActionPaths.back().binding);
+            WARN_IF_UNSUCCESSFUL(pathResult);
+        };
+
+        for (const InputChannelId& channelId : InputDeviceXRController::Button::All)
+        {
+            createXrAction(channelId, XR_ACTION_TYPE_BOOLEAN_INPUT);
+        }
+
+        for (const InputChannelId& channelId : InputDeviceXRController::Trigger::All)
+        {
+            createXrAction(channelId, XR_ACTION_TYPE_FLOAT_INPUT);
+        }
+
+        for (const InputChannelId& channelId : InputDeviceXRController::ThumbStickAxis1D::All)
+        {
+            createXrAction(channelId, XR_ACTION_TYPE_FLOAT_INPUT);
+        }
+
+        for (const InputChannelId& channelId : InputDeviceXRController::ControllerPosePosition::All)
+        {
+            createXrAction(channelId, XR_ACTION_TYPE_POSE_INPUT);
+        }
+
+        for (const InputChannelId& channelId : InputDeviceXRController::ControllerPoseOrientation::All)
+        {
+            createXrAction(channelId, XR_ACTION_TYPE_POSE_INPUT); // is this correct?
+        }
+
+        m_xrControllerImpl->RegisterTickCallback([this](){ PollActions(); });
+    }
+
     AZ::RHI::ResultCode Input::InitializeActionSpace(XrSession xrSession)
     {
-        XrActionSpaceCreateInfo actionSpaceInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
-        actionSpaceInfo.action = m_poseAction;
+        XrActionSpaceCreateInfo actionSpaceInfo{};
+        actionSpaceInfo.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
+        actionSpaceInfo.action = GetAction(AzFramework::InputDeviceXRController::ControllerPosePosition::LPos);
         actionSpaceInfo.poseInActionSpace.orientation.w = 1.f;
         actionSpaceInfo.subactionPath = m_handSubactionPath[static_cast<uint32_t>(XR::Side::Left)];
+
         XrResult result = xrCreateActionSpace(xrSession, &actionSpaceInfo, &m_handSpace[static_cast<uint32_t>(XR::Side::Left)]);
         WARN_IF_UNSUCCESSFUL(result);
         RETURN_RESULTCODE_IF_UNSUCCESSFUL(ConvertResult(result));
+
+        actionSpaceInfo.action = GetAction(AzFramework::InputDeviceXRController::ControllerPosePosition::RPos);
         actionSpaceInfo.subactionPath = m_handSubactionPath[static_cast<uint32_t>(XR::Side::Right)];
+
         result = xrCreateActionSpace(xrSession, &actionSpaceInfo, &m_handSpace[static_cast<uint32_t>(XR::Side::Right)]);
         WARN_IF_UNSUCCESSFUL(result);
 
         return ConvertResult(result);
     }
 
-    AZ::RHI::ResultCode Input::InitializeActionSets(XrSession xrSession)
+    AZ::RHI::ResultCode Input::InitializeActionSets(XrSession xrSession) const
     {
-        XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
+        XrSessionActionSetsAttachInfo attachInfo{};
+        attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
         attachInfo.countActionSets = 1;
         attachInfo.actionSets = &m_actionSet;
-        XrResult result = xrAttachSessionActionSets(xrSession, &attachInfo);
+
+        const XrResult result = xrAttachSessionActionSets(xrSession, &attachInfo);
         WARN_IF_UNSUCCESSFUL(result);
 
         return ConvertResult(result);
@@ -195,130 +195,186 @@ namespace OpenXRVk
 
     void Input::ShutdownInternal()
     {
-        if (m_actionSet != XR_NULL_HANDLE) 
+        if (m_actionSet != XR_NULL_HANDLE)
         {
-            for (auto hand : { XR::Side::Left, XR::Side::Right }) 
+            for (const auto hand : { XR::Side::Left, XR::Side::Right })
             {
-                xrDestroySpace(m_handSpace[static_cast<uint32_t>(hand)]);
+                xrDestroySpace(m_handSpace[static_cast<AZ::u32>(hand)]);
             }
             xrDestroyActionSet(m_actionSet);
         }
+
+        // Turn off the tick callback and reset the (non-owning) impl pointer back to null
+        m_xrControllerImpl->RegisterTickCallback(nullptr);
+        m_xrControllerImpl = nullptr;
+    }
+
+    XrAction Input::GetAction(const AzFramework::InputChannelId& channelId) const
+    {
+        // this is a private function and only input channel ids that were used to
+        // initialize structures in this class should be passed.
+
+        // "at" will assert if the channelId is something unexpected for xr controller
+        const auto index = m_xrActionIndices.at(channelId);
+        return m_xrActionPaths[index].action;
     }
 
     void Input::PollActions()
     {
-        Session* session = static_cast<Session*>(GetDescriptor().m_session.get());
+        const auto session = static_cast<Session*>(GetDescriptor().m_session.get());
         XrSession xrSession = session->GetXrSession();
-        Device* device = static_cast<Device*>(GetDescriptor().m_device.get());
+        const auto device = static_cast<Device*>(GetDescriptor().m_device.get());
         m_handActive = { XR_FALSE, XR_FALSE };
 
+        auto& rawControllerData = m_xrControllerImpl->GetRawState();
+
+        // Might not need to reset if we're constantly refreshing all raw values.
+        // In the future we may want to store off a couple ticks of data in a history
+        // so that derivatives and edge detection can be computed.
+        rawControllerData.Reset();
+
         // Sync actions
         const XrActiveActionSet activeActionSet{ m_actionSet, XR_NULL_PATH };
-        XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO };
+        XrActionsSyncInfo syncInfo{};
+        syncInfo.type = XR_TYPE_ACTIONS_SYNC_INFO;
         syncInfo.countActiveActionSets = 1;
         syncInfo.activeActionSets = &activeActionSet;
+
         XrResult result = xrSyncActions(xrSession, &syncInfo);
+        WARN_IF_UNSUCCESSFUL(result);
+
+        using namespace AzFramework;
+        using xrc = InputDeviceXRController;
+
+        // Updating digital buttons is somewhat unique, because it compacts and combines them all to a u32 with bit masks...
+        for (const auto& [channelId, bitMask] : rawControllerData.m_buttonIdsToBitMasks)
+        {
+            XrActionStateGetInfo getButtonInfo{};
+            getButtonInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
+            getButtonInfo.next = nullptr;
+            getButtonInfo.action = GetAction(channelId);
+            getButtonInfo.subactionPath = XR_NULL_PATH;
 
-        // Get pose and grab action state and start haptic vibrate when hand is 90% squeezed for testing purposes
-        for (auto hand : { XR::Side::Left, XR::Side::Right })
+            XrActionStateBoolean buttonValue{};
+            buttonValue.type = XR_TYPE_ACTION_STATE_BOOLEAN;
+
+            result = xrGetActionStateBoolean(xrSession, &getButtonInfo, &buttonValue);
+            WARN_IF_UNSUCCESSFUL(result);
+
+            rawControllerData.m_digitalButtonStates |= (
+                (buttonValue.isActive == XR_TRUE && buttonValue.currentState == XR_TRUE)
+                ? bitMask
+                : 0
+            );
+        }
+
+        // lambda that obtains a float state from an action...
+        auto getActionStateFloat = [&xrSession, this](const InputChannelId& channelId) -> float
         {
-            bool isActive = UpdateActionState(xrSession, m_squeezeAction, static_cast<uint16_t>(hand));
-            if (isActive)
+            XrActionStateGetInfo getAnalogInfo{};
+            getAnalogInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
+            getAnalogInfo.next = nullptr;
+            getAnalogInfo.action = GetAction(channelId);
+            getAnalogInfo.subactionPath = XR_NULL_PATH;
+
+            XrActionStateFloat analogValue{};
+            analogValue.type = XR_TYPE_ACTION_STATE_FLOAT;
+
+            const XrResult result = xrGetActionStateFloat(xrSession, &getAnalogInfo, &analogValue);
+            WARN_IF_UNSUCCESSFUL(result);
+
+            if (analogValue.isActive == XR_TRUE)
+            {
+                return analogValue.currentState;
+            }
+            return 0.f;
+        };
+
+        // Update Analog values...
+        rawControllerData.m_leftTriggerState = getActionStateFloat(xrc::Trigger::LTrigger);
+        rawControllerData.m_rightTriggerState = getActionStateFloat(xrc::Trigger::RTrigger);
+        rawControllerData.m_leftGripState = getActionStateFloat(xrc::Trigger::LGrip);
+        rawControllerData.m_rightGripState = getActionStateFloat(xrc::Trigger::RGrip);
+        rawControllerData.m_leftThumbStickXState = getActionStateFloat(xrc::ThumbStickAxis1D::LX);
+        rawControllerData.m_leftThumbStickYState = getActionStateFloat(xrc::ThumbStickAxis1D::LY);
+        rawControllerData.m_rightThumbStickXState = getActionStateFloat(xrc::ThumbStickAxis1D::RX);
+        rawControllerData.m_rightThumbStickYState = getActionStateFloat(xrc::ThumbStickAxis1D::RY);
+
+        // Scale the rendered hand by 1.0f (open) to 0.5f (fully squeezed).
+        m_handScale[static_cast<AZ::u32>(XR::Side::Left)] = 1.f - 0.5f * rawControllerData.m_leftGripState;
+        m_handScale[static_cast<AZ::u32>(XR::Side::Right)] = 1.f - 0.5f * rawControllerData.m_rightGripState;
+
+        // lambda that outputs vibration amount to a particular side
+        auto setHapticVibration = [this, &xrSession](AZ::u32 side, float amount)
+        {
+            if (amount > 0.f)
             {
-                // Scale the rendered hand by 1.0f (open) to 0.5f (fully squeezed).
-                m_handScale[static_cast<uint32_t>(hand)] = 1.0f - 0.5f * m_squeezeAction.m_actionState[static_cast<uint32_t>(hand)];
-                if (m_squeezeAction.m_actionState[static_cast<uint32_t>(hand)] > 0.9f)
-                {
-                    //This vibration event is currently added here for testing purposes.
-                    //Remove this when this is moved to an event that is triggered externally 
-                    XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
-                    vibration.amplitude = 0.5;
-                    vibration.duration = XR_MIN_HAPTIC_DURATION;
-                    vibration.frequency = XR_FREQUENCY_UNSPECIFIED;
-
-                    XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
-                    hapticActionInfo.action = m_vibrateAction;
-                    hapticActionInfo.subactionPath = m_handSubactionPath[static_cast<uint32_t>(hand)];
-                    result = xrApplyHapticFeedback(xrSession, &hapticActionInfo, (XrHapticBaseHeader*)&vibration);
-                    WARN_IF_UNSUCCESSFUL(result);
-                }
+                XrHapticVibration hapticVibration{};
+                hapticVibration.type = XR_TYPE_HAPTIC_VIBRATION;
+                hapticVibration.amplitude = amount;
+                hapticVibration.duration = XR_MIN_HAPTIC_DURATION;
+                hapticVibration.frequency = XR_FREQUENCY_UNSPECIFIED;
+
+                XrHapticActionInfo hapticActionInfo{};
+                hapticActionInfo.type = XR_TYPE_HAPTIC_ACTION_INFO;
+                hapticActionInfo.action = m_hapticAction;
+                hapticActionInfo.subactionPath = m_handSubactionPath[side];
+
+                [[maybe_unused]] const XrResult result = xrApplyHapticFeedback(
+                    xrSession, &hapticActionInfo, reinterpret_cast<XrHapticBaseHeader*>(&hapticVibration));
+                WARN_IF_UNSUCCESSFUL(result);
             }
+        };
+
+        setHapticVibration(static_cast<AZ::u32>(XR::Side::Left), rawControllerData.m_leftMotorVibrationValue);
+        setHapticVibration(static_cast<AZ::u32>(XR::Side::Right), rawControllerData.m_rightMotorVibrationValue);
+        // after the vibration values have been used, reset them
+        rawControllerData.m_leftMotorVibrationValue = 0.f;
+        rawControllerData.m_rightMotorVibrationValue = 0.f;
+
+        // Update poses
+        for (const auto hand : { XR::Side::Left, XR::Side::Right })
+        {
+            XrActionStateGetInfo getInfo{};
+            getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
+            getInfo.action = GetPoseAction(static_cast<AZ::u32>(hand));
+
+            XrActionStatePose poseState{};
+            poseState.type = XR_TYPE_ACTION_STATE_POSE;
 
-            XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
-            getInfo.action = m_poseAction;
-            XrActionStatePose poseState{ XR_TYPE_ACTION_STATE_POSE };
             result = xrGetActionStatePose(xrSession, &getInfo, &poseState);
             WARN_IF_UNSUCCESSFUL(result);
             m_handActive[static_cast<uint32_t>(hand)] = poseState.isActive;
 
-            LocateControllerSpace(device->GetPredictedDisplayTime(), session->GetXrSpace(OpenXRVk::SpaceType::View), static_cast<uint32_t>(hand));
-
-            UpdateActionState(xrSession, m_triggerAction, static_cast<uint16_t>(hand));
-            UpdateActionState(xrSession, m_joyStickXAction, static_cast<uint16_t>(hand));
-            UpdateActionState(xrSession, m_joyStickYAction, static_cast<uint16_t>(hand));
-        }  
-
-        UpdateActionState(xrSession, m_xButtonAction, static_cast<uint32_t>(XR::Side::Left));
-        UpdateActionState(xrSession, m_yButtonAction, static_cast<uint32_t>(XR::Side::Left));
-        UpdateActionState(xrSession, m_aButtonAction, static_cast<uint32_t>(XR::Side::Right));
-        UpdateActionState(xrSession, m_bButtonAction, static_cast<uint32_t>(XR::Side::Right));
+            LocateControllerSpace(device->GetPredictedDisplayTime(), session->GetXrSpace(OpenXRVk::SpaceType::View), static_cast<AZ::u32>(hand));
+        }
 
-        //Cache 3d location information
-        for (uint32_t i = 0; i < static_cast<uint32_t>(SpaceType::Count); i++)
+        // Cache 3d location information
+        for (AZ::u32 i = 0; i < static_cast<AZ::u32>(SpaceType::Count); i++)
         {
-            SpaceType spaceType = static_cast<SpaceType>(i);
+            const auto spaceType = static_cast<SpaceType>(i);
             LocateVisualizedSpace(device->GetPredictedDisplayTime(), session->GetXrSpace(spaceType),
-                                    session->GetXrSpace(OpenXRVk::SpaceType::View), spaceType);
+                                  session->GetXrSpace(OpenXRVk::SpaceType::View), spaceType);
         }
 
-        // There were no subaction paths specified for the quit action, because we don't care which hand did it.
-        XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO, nullptr, m_quitAction, XR_NULL_PATH };
-        XrActionStateBoolean quitValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
-        result = xrGetActionStateBoolean(xrSession, &getInfo, &quitValue);
-        WARN_IF_UNSUCCESSFUL(result);
-        if ((quitValue.isActive == XR_TRUE) && (quitValue.changedSinceLastSync == XR_TRUE) && (quitValue.currentState == XR_TRUE))
+        const bool quitPressed = GetButtonState(InputDeviceXRController::Button::Home);
+        if (quitPressed && !m_wasQuitPressedLastSync)
         {
             result = xrRequestExitSession(xrSession);
             WARN_IF_UNSUCCESSFUL(result);
         }
+        m_wasQuitPressedLastSync = quitPressed;
     }
 
-    bool Input::GetActionState(XrSession xrSession, XrAction xrAction, uint16_t handIndex, float& outputSate)
+    void Input::LocateControllerSpace(XrTime predictedDisplayTime, XrSpace baseSpace, AZ::u32 handIndex)
     {
-        XrActionStateGetInfo buttonGetInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
-        buttonGetInfo.action = xrAction;
-        buttonGetInfo.subactionPath = m_handSubactionPath[handIndex];
-
-        XrActionStateFloat buttonValue{ XR_TYPE_ACTION_STATE_FLOAT };
-        XrResult result = xrGetActionStateFloat(xrSession, &buttonGetInfo, &buttonValue);
-        WARN_IF_UNSUCCESSFUL(result);
-        if (buttonValue.isActive == XR_TRUE)
-        {
-            outputSate = buttonValue.currentState;
-            return true;
-        }
-        return false;
-    }
-
-    bool Input::UpdateActionState(XrSession xrSession, SingleActionData& actionData, uint16_t handIndex)
-    {
-        return GetActionState(xrSession, actionData.m_actionHandle, handIndex, actionData.m_actionState);
-    }
-
-    bool Input::UpdateActionState(XrSession xrSession, DualActionData& actionData, uint16_t handIndex)
-    {
-        return GetActionState(xrSession, actionData.m_actionHandle, handIndex, actionData.m_actionState[handIndex]);
-    }
-
-
-    void Input::LocateControllerSpace(XrTime predictedDisplayTime, XrSpace baseSpace, uint32_t handIndex)
-    {
-        XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION };
-        XrResult result = xrLocateSpace(m_handSpace[handIndex], baseSpace, predictedDisplayTime, &spaceLocation);
-        if (result== XR_SUCCESS)
+        XrSpaceLocation spaceLocation{};
+        spaceLocation.type = XR_TYPE_SPACE_LOCATION;
+        if (const XrResult result = xrLocateSpace(m_handSpace[handIndex], baseSpace, predictedDisplayTime, &spaceLocation);
+            result == XR_SUCCESS)
         {
             if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
-                (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) 
+                (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0)
             {
                 m_handSpaceLocation[handIndex] = spaceLocation;
             }
@@ -327,9 +383,10 @@ namespace OpenXRVk
 
     void Input::LocateVisualizedSpace(XrTime predictedDisplayTime, XrSpace space, XrSpace baseSpace, OpenXRVk::SpaceType visualizedSpaceType)
     {
-        XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION };
-        XrResult result = xrLocateSpace(space, baseSpace, predictedDisplayTime, &spaceLocation);
-        if (result == XR_SUCCESS)
+        XrSpaceLocation spaceLocation{};
+        spaceLocation.type = XR_TYPE_SPACE_LOCATION;
+        if (const XrResult result = xrLocateSpace(space, baseSpace, predictedDisplayTime, &spaceLocation);
+            result == XR_SUCCESS)
         {
             if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
                 (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0)
@@ -341,7 +398,7 @@ namespace OpenXRVk
 
     AZ::RHI::ResultCode Input::GetControllerPose(AZ::u32 handIndex, AZ::RPI::PoseData& outPoseData) const
     {
-        if (handIndex < m_handSpaceLocation.size())
+        if (handIndex < AZStd::size(m_handSpaceLocation))
         {
             const XrQuaternionf& orientation = m_handSpaceLocation[handIndex].pose.orientation;
             const XrVector3f& position = m_handSpaceLocation[handIndex].pose.position;
@@ -354,8 +411,8 @@ namespace OpenXRVk
 
     AZ::RHI::ResultCode Input::GetVisualizedSpacePose(OpenXRVk::SpaceType visualizedSpaceType, AZ::RPI::PoseData& outPoseData) const
     {
-        uint32_t spaceIndex = static_cast<uint32_t>(visualizedSpaceType);
-        if (spaceIndex < m_xrVisualizedSpaceLocations.size())
+        const auto spaceIndex = static_cast<uint32_t>(visualizedSpaceType);
+        if (spaceIndex < AZStd::size(m_xrVisualizedSpaceLocations))
         {
             const XrQuaternionf& orientation = m_xrVisualizedSpaceLocations[spaceIndex].pose.orientation;
             const XrVector3f& position = m_xrVisualizedSpaceLocations[spaceIndex].pose.position;
@@ -366,68 +423,91 @@ namespace OpenXRVk
         return AZ::RHI::ResultCode::Fail;
     }
 
-    float Input::GetControllerScale(AZ::u32 viewIndex) const
+    float Input::GetControllerScale(AZ::u32 handIndex) const
     {
-        return m_handScale[viewIndex];
+        return m_handScale[handIndex];
     }
 
-    XrAction Input::GetSqueezeAction() const
+    XrAction Input::GetSqueezeAction(AZ::u32 handIndex) const
     {
-        return m_squeezeAction.m_actionHandle;
+        return (handIndex == static_cast<AZ::u32>(XR::Side::Left))
+            ? GetAction(AzFramework::InputDeviceXRController::Trigger::LGrip)
+            : GetAction(AzFramework::InputDeviceXRController::Trigger::RGrip);
     }
 
-    XrAction Input::GetPoseAction() const
+    XrAction Input::GetPoseAction(AZ::u32 handIndex) const
     {
-        return m_poseAction;
+        return (handIndex == static_cast<AZ::u32>(XR::Side::Left))
+            ? GetAction(AzFramework::InputDeviceXRController::ControllerPosePosition::LPos)
+            : GetAction(AzFramework::InputDeviceXRController::ControllerPosePosition::RPos);
     }
 
     XrAction Input::GetVibrationAction() const
     {
-        return m_vibrateAction;
+        return m_hapticAction;
     }
 
     XrAction Input::GetQuitAction() const
     {
-        return m_quitAction;
+        return GetAction(AzFramework::InputDeviceXRController::Button::Home);
+    }
+
+    bool Input::GetButtonState(const AzFramework::InputChannelId& channelId) const
+    {
+        const auto& state = m_xrControllerImpl->GetRawState();
+        const AZ::u32 mask = state.m_buttonIdsToBitMasks.at(channelId);
+        return (state.m_digitalButtonStates & mask) != 0;
     }
 
-    float Input::GetXButtonState() const
+    bool Input::GetXButtonState() const
     {
-        return m_xButtonAction.m_actionState;
+        return GetButtonState(AzFramework::InputDeviceXRController::Button::X);
     }
 
-    float Input::GetYButtonState() const
+    bool Input::GetYButtonState() const
     {
-        return m_yButtonAction.m_actionState;
+        return GetButtonState(AzFramework::InputDeviceXRController::Button::Y);
     }
 
-    float Input::GetAButtonState() const
+    bool Input::GetAButtonState() const
     {
-        return m_aButtonAction.m_actionState;
+        return GetButtonState(AzFramework::InputDeviceXRController::Button::A);
     }
 
-    float Input::GetBButtonState() const
+    bool Input::GetBButtonState() const
     {
-        return m_bButtonAction.m_actionState;
+        return GetButtonState(AzFramework::InputDeviceXRController::Button::B);
     }
 
     float Input::GetXJoyStickState(AZ::u32 handIndex) const
     {
-        return m_joyStickXAction.m_actionState[handIndex];
+        const auto& state = m_xrControllerImpl->GetRawState();
+        return (handIndex == static_cast<AZ::u32>(XR::Side::Left))
+            ? state.m_leftThumbStickXState
+            : state.m_rightThumbStickXState;
     }
 
     float Input::GetYJoyStickState(AZ::u32 handIndex) const
     {
-        return m_joyStickYAction.m_actionState[handIndex];
+        const auto& state = m_xrControllerImpl->GetRawState();
+        return (handIndex == static_cast<AZ::u32>(XR::Side::Left))
+            ? state.m_leftThumbStickYState
+            : state.m_rightThumbStickYState;
     }
 
     float Input::GetSqueezeState(AZ::u32 handIndex) const
     {
-        return m_squeezeAction.m_actionState[handIndex];
+        const auto& state = m_xrControllerImpl->GetRawState();
+        return (handIndex == static_cast<AZ::u32>(XR::Side::Left))
+            ? state.m_leftGripState
+            : state.m_rightGripState;
     }
 
     float Input::GetTriggerState(AZ::u32 handIndex) const
     {
-        return m_triggerAction.m_actionState[handIndex];
+        const auto& state = m_xrControllerImpl->GetRawState();
+        return (handIndex == static_cast<AZ::u32>(XR::Side::Left))
+            ? state.m_leftTriggerState
+            : state.m_rightTriggerState;
     }
 }

+ 9 - 6
Gems/OpenXRVk/Code/Source/OpenXRVkSession.cpp

@@ -14,6 +14,7 @@
 #include <OpenXRVk/OpenXRVkUtils.h>
 #include <AzCore/Debug/Trace.h>
 #include <AzCore/Casting/numeric_cast.h>
+#include <Atom/RHI.Reflect/Vulkan/XRVkDescriptors.h>
 #include <XR/XRBase.h>
 
 namespace OpenXRVk
@@ -198,9 +199,11 @@ namespace OpenXRVk
                     if (GetDescriptor().m_validationMode == AZ::RHI::ValidationMode::Enabled)
                     {
                         Input* xrVkInput = GetNativeInput();
-                        LogActionSourceName(xrVkInput->GetSqueezeAction(), "Squeeze");
+                        LogActionSourceName(xrVkInput->GetSqueezeAction(static_cast<AZ::u32>(XR::Side::Left)), "Squeeze Left");
+                        LogActionSourceName(xrVkInput->GetSqueezeAction(static_cast<AZ::u32>(XR::Side::Right)), "Squeeze Right");
                         LogActionSourceName(xrVkInput->GetQuitAction(), "Quit");
-                        LogActionSourceName(xrVkInput->GetPoseAction(), "Pose");
+                        LogActionSourceName(xrVkInput->GetPoseAction(static_cast<AZ::u32>(XR::Side::Left)), "Pose Left");
+                        LogActionSourceName(xrVkInput->GetPoseAction(static_cast<AZ::u32>(XR::Side::Right)), "Pose Right");
                         LogActionSourceName(xrVkInput->GetVibrationAction(), "Vibrate");
                     }
                     break;
@@ -310,22 +313,22 @@ namespace OpenXRVk
 
     float Session::GetXButtonState() const
     {
-        return GetNativeInput()->GetXButtonState();
+        return (GetNativeInput()->GetXButtonState() ? 1.f : 0.f);
     }
 
     float Session::GetYButtonState() const
     {
-        return GetNativeInput()->GetYButtonState();
+        return (GetNativeInput()->GetYButtonState() ? 1.f : 0.f);
     }
 
     float Session::GetAButtonState() const
     {
-        return GetNativeInput()->GetAButtonState();
+        return (GetNativeInput()->GetAButtonState() ? 1.f : 0.f);
     }
 
     float Session::GetBButtonState() const
     {
-        return GetNativeInput()->GetBButtonState();
+        return (GetNativeInput()->GetBButtonState() ? 1.f : 0.f);
     }
 
     float Session::GetXJoyStickState(AZ::u32 handIndex) const

+ 5 - 5
Gems/OpenXRVk/Code/Source/OpenXRVkSpace.cpp

@@ -25,7 +25,7 @@ namespace OpenXRVk
     void Space::CreateVisualizedSpaces(XrSession xrSession)
     {
         AZ_Assert(xrSession != XR_NULL_HANDLE, "XR session is null");
-  
+
         for (uint32_t i = 0; i < static_cast<uint32_t>(SpaceType::Count); i++)
         {
             XrReferenceSpaceCreateInfo referenceSpaceCreateInfo = GetXrReferenceSpaceCreateInfo(static_cast<SpaceType>(i));
@@ -50,7 +50,7 @@ namespace OpenXRVk
         {
             case SpaceType::View:
             {
-                //Track the view origin used to generate view transforms for the primary viewer (or centroid of 
+                //Track the view origin used to generate view transforms for the primary viewer (or centroid of
                 //view origins if stereo), with +Y up, +X to the right, and -Z forward.
                 referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
                 break;
@@ -64,7 +64,7 @@ namespace OpenXRVk
             }
             case SpaceType::Local:
             {
-                //Track center Local space which is world-locked origin, gravity-aligned to exclude 
+                //Track center Local space which is world-locked origin, gravity-aligned to exclude
                 //pitch and roll, with +Y up, +X to the right, and -Z forward.
                 referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
                 break;
@@ -77,14 +77,14 @@ namespace OpenXRVk
             }
             case SpaceType::StageLeft:
             {
-                //Track Left Stage space which is basically the center stage translated to the left and down by 5m. 
+                //Track Left Stage space which is basically the center stage translated to the left and down by 5m.
                 referenceSpaceCreateInfo.poseInReferenceSpace = RotateCCWAboutYAxis(0.f, { -5.f, 0.f, -5.f });
                 referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
                 break;
             }
             case SpaceType::StageRight:
             {
-                //Track Right Stage space which is basically the center stage translated to the right and down by 5m. 
+                //Track Right Stage space which is basically the center stage translated to the right and down by 5m.
                 referenceSpaceCreateInfo.poseInReferenceSpace = RotateCCWAboutYAxis(0.f, { 5.f, 0.f, -5.f });
                 referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
                 break;

+ 11 - 11
Gems/OpenXRVk/Code/Source/OpenXRVkSwapChain.cpp

@@ -43,7 +43,7 @@ namespace OpenXRVk
         m_height = height;
         return AZ::RHI::ResultCode::Success;
     }
-    
+
     AZ::u32 SwapChain::View::GetCurrentImageIndex() const
     {
         return m_activeImageIndex;
@@ -64,7 +64,7 @@ namespace OpenXRVk
     {
         return m_handle;
     }
-        
+
     AZ::u32 SwapChain::View::GetWidth() const
     {
         return m_width;
@@ -111,13 +111,13 @@ namespace OpenXRVk
         //Only supporting XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO for now
         XrViewConfigurationType viewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
 
-        result = xrEnumerateViewConfigurationViews(xrInstance, xrSystemId, 
-                                                            viewConfigType, 0, &m_numViews, nullptr);
+        result = xrEnumerateViewConfigurationViews(xrInstance, xrSystemId,
+                                                   viewConfigType, 0, &m_numViews, nullptr);
         WARN_IF_UNSUCCESSFUL(result);
 
         m_configViews.resize(m_numViews, { XR_TYPE_VIEW_CONFIGURATION_VIEW });
-        result = xrEnumerateViewConfigurationViews(xrInstance, xrSystemId, 
-                                                viewConfigType, m_numViews, &m_numViews, m_configViews.data());
+        result = xrEnumerateViewConfigurationViews(xrInstance, xrSystemId,
+                                                   viewConfigType, m_numViews, &m_numViews, m_configViews.data());
         WARN_IF_UNSUCCESSFUL(result);
 
         // Create and cache view buffer for xrLocateViews later.
@@ -131,7 +131,7 @@ namespace OpenXRVk
             result = xrEnumerateSwapchainFormats(xrSession, 0, &swapchainFormatCount, nullptr);
             AZStd::vector<int64_t> swapChainFormats(swapchainFormatCount);
             result = xrEnumerateSwapchainFormats(xrSession, aznumeric_cast<uint32_t>(swapChainFormats.size()),
-                                                &swapchainFormatCount, swapChainFormats.data());
+                                                 &swapchainFormatCount, swapChainFormats.data());
             WARN_IF_UNSUCCESSFUL(result);
             AZ_Assert(swapchainFormatCount == swapChainFormats.size(), "Size mismatch swapchainFormatCount %i swapChainFormats size %i", swapchainFormatCount, swapChainFormats.size());
 
@@ -191,14 +191,14 @@ namespace OpenXRVk
 
                     AZ::RHI::ResultCode resultCode = viewSwapChain->Init(handle, swapchainCreateInfo.width, swapchainCreateInfo.height);
                     if(resultCode == AZ::RHI::ResultCode::Success)
-                    { 
+                    {
                         m_viewSwapchains.push_back(viewSwapChain);
                     }
                 }
 
                 result = xrEnumerateSwapchainImages(viewSwapChain->GetSwapChainHandle(), 0, &viewSwapChain->m_numImages, nullptr);
                 WARN_IF_UNSUCCESSFUL(result);
-                
+
                 viewSwapChain->m_swapChainImageHeaders.resize(viewSwapChain->m_numImages);
                 viewSwapChain->m_swapchainImages.resize(viewSwapChain->m_numImages);
                 for (AZ::u32 j = 0; j < viewSwapChain->m_numImages; ++j)
@@ -233,7 +233,7 @@ namespace OpenXRVk
         auto swapchainFormatIt =
             AZStd::find_first_of(runtimeFormats.begin(), runtimeFormats.end(), AZStd::begin(SupportedColorSwapchainFormats),
                 AZStd::end(SupportedColorSwapchainFormats));
-        if (swapchainFormatIt == runtimeFormats.end()) 
+        if (swapchainFormatIt == runtimeFormats.end())
         {
             AZ_Error("OpenXRVk", false, "No runtime swapchain format supported for color swapchain");
             return VK_FORMAT_UNDEFINED;
@@ -252,7 +252,7 @@ namespace OpenXRVk
         AZ::Vulkan::XRSwapChainDescriptor* xrSwapChainDescriptor = static_cast<AZ::Vulkan::XRSwapChainDescriptor*>(swapchainDescriptor);
         uint32_t swapChainIndex = xrSwapChainDescriptor->m_inputData.m_swapChainIndex;
         uint32_t swapChainImageIndex = xrSwapChainDescriptor->m_inputData.m_swapChainImageIndex;
-        
+
         XR::SwapChain::View* viewSwapChain = GetView(swapChainIndex);
         SwapChain::Image* swapchainImage = static_cast<SwapChain::Image*>(viewSwapChain->m_images[swapChainImageIndex].get());
         xrSwapChainDescriptor->m_outputData.m_nativeImage = swapchainImage->GetNativeImage();

+ 2 - 0
Gems/OpenXRVk/Code/Source/Platform/Android/platform_private_android_files.cmake

@@ -10,4 +10,6 @@ set(FILES
     OpenXRVk_Traits_Android.h
     OpenXRVk_Traits_Android.cpp
     OpenXRVk_Traits_Platform.h
+    ../Common/Default/OculusTouch_Default.cpp
+    ../Common/Default/OculusTouch_Default.h
 )

+ 161 - 0
Gems/OpenXRVk/Code/Source/Platform/Common/Default/OculusTouch_Default.cpp

@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include "OculusTouch_Default.h"
+
+#include <OpenXRVk_Platform.h>
+
+namespace OpenXRVk
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Map of digital button ids keyed by their button bitmask
+    const AzFramework::InputDeviceXRController::Implementation::ButtonIdToBitMaskMap GetButtonIdToBitMaskMap()
+    {
+        using xrc = AzFramework::InputDeviceXRController;
+        return {
+            { xrc::Button::A, (1 << 0) },
+            { xrc::Button::B, (1 << 1) },
+            { xrc::Button::X, (1 << 2) },
+            { xrc::Button::Y, (1 << 3) },
+            { xrc::Button::Home, (1 << 4) },
+            { xrc::Button::Menu, (1 << 5) },
+            { xrc::Button::L3, (1 << 6) },
+            { xrc::Button::R3, (1 << 7) },
+            { xrc::Button::TA, (1 << 8) },
+            { xrc::Button::TB, (1 << 9) },
+            { xrc::Button::TX, (1 << 10) },
+            { xrc::Button::TY, (1 << 11) },
+            { xrc::Button::TLStick, (1 << 12) },
+            { xrc::Button::TRStick, (1 << 13) },
+            { xrc::Button::TLRest, (1 << 14) },
+            { xrc::Button::TRRest, (1 << 15) },
+            { xrc::Button::TLTrig, (1 << 16) },
+            { xrc::Button::TRTrig, (1 << 17) },
+        };
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceOculusTouch::InputDeviceOculusTouch(AzFramework::InputDeviceXRController& inputDevice)
+        : AzFramework::InputDeviceXRController::Implementation(inputDevice)
+        , m_rawControllerState(GetButtonIdToBitMaskMap())
+    {
+        // These are guesses for now
+        m_rawControllerState.m_triggerMaxValue = 1.f;
+        m_rawControllerState.m_triggerDeadZoneValue = 0.1f;
+        m_rawControllerState.m_thumbStickMaxValue = 1.f;
+        m_rawControllerState.m_leftThumbStickDeadZoneValue = 0.1f;
+        m_rawControllerState.m_rightThumbStickDeadZoneValue = 0.1f;
+
+        using xrc = AzFramework::InputDeviceXRController;
+        m_xrPathMap = {
+            { xrc::Button::A, "/user/hand/right/input/a/click" },
+            { xrc::Button::B, "/user/hand/right/input/b/click" },
+            { xrc::Button::X, "/user/hand/left/input/x/click" },
+            { xrc::Button::Y, "/user/hand/left/input/y/click" },
+            { xrc::Button::Home, "/user/hand/right/input/system/click" },
+            { xrc::Button::Menu, "/user/hand/left/input/menu/click" },
+            { xrc::Button::L3, "/user/hand/left/input/thumbstick/click" },
+            { xrc::Button::R3, "/user/hand/right/input/thumbstick/click" },
+            { xrc::Button::TA, "/user/hand/right/input/a/touch" },
+            { xrc::Button::TB, "/user/hand/right/input/b/touch" },
+            { xrc::Button::TX, "/user/hand/left/input/x/touch" },
+            { xrc::Button::TY, "/user/hand/left/input/y/touch" },
+            { xrc::Button::TLStick, "/user/hand/left/input/thumbstick/touch" },
+            { xrc::Button::TRStick, "/user/hand/right/input/thumbstick/touch" },
+            { xrc::Button::TLRest, "/user/hand/left/input/thumbrest/touch" },
+            { xrc::Button::TRRest, "/user/hand/right/input/thumbrest/touch" },
+            { xrc::Button::TLTrig, "/user/hand/left/input/trigger/touch" },
+            { xrc::Button::TRTrig, "/user/hand/right/input/trigger/touch" },
+            { xrc::Trigger::LTrigger, "/user/hand/left/input/trigger/value" },
+            { xrc::Trigger::RTrigger, "/user/hand/right/input/trigger/value" },
+            { xrc::Trigger::LGrip, "/user/hand/left/input/squeeze/value" },
+            { xrc::Trigger::RGrip, "/user/hand/right/input/squeeze/value" },
+            { xrc::ThumbStickAxis1D::LX, "/user/hand/left/input/thumbstick/x" },
+            { xrc::ThumbStickAxis1D::LY, "/user/hand/left/input/thumbstick/y" },
+            { xrc::ThumbStickAxis1D::RX, "/user/hand/right/input/thumbstick/x" },
+            { xrc::ThumbStickAxis1D::RY, "/user/hand/right/input/thumbstick/y" },
+            { xrc::ControllerPosePosition::LPos, "/user/hand/left/input/grip/pose" },
+            { xrc::ControllerPosePosition::RPos, "/user/hand/right/input/grip/pose" },
+            { xrc::ControllerPoseOrientation::LOrient, "/user/hand/left/input/aim/pose" },
+            { xrc::ControllerPoseOrientation::ROrient, "/user/hand/right/input/aim/pose" },
+        };
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceOculusTouch::~InputDeviceOculusTouch() = default;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZStd::string InputDeviceOculusTouch::GetInputChannelPath(const AzFramework::InputChannelId& channelId) const
+    {
+        if (const auto it = m_xrPathMap.find(channelId);
+            it != m_xrPathMap.end())
+        {
+            return{ it->second };
+        }
+        return {};
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZStd::string InputDeviceOculusTouch::GetInputDeviceProfilePath() const
+    {
+        return { "/interaction_profiles/oculus/touch_controller" };
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZStd::string InputDeviceOculusTouch::GetLeftHandSubPath() const
+    {
+        return { "/user/hand/left" };
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    AZStd::string InputDeviceOculusTouch::GetRightHandSubPath() const
+    {
+        return { "/user/hand/right" };
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceOculusTouch::RegisterTickCallback(TickCallbackFn callbackFn)
+    {
+        m_tickCallback = callbackFn;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceOculusTouch::RawXRControllerState& InputDeviceOculusTouch::GetRawState()
+    {
+        return m_rawControllerState;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceOculusTouch::IsConnected() const
+    {
+        return m_isConnected;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceOculusTouch::SetVibration(float leftMotorSpeedNormalized, float rightMotorSpeedNormalized)
+    {
+        if (m_isConnected)
+        {
+            // Set vibration values on the raw data structure, they will be consumed on the next tick
+            m_rawControllerState.m_leftMotorVibrationValue = leftMotorSpeedNormalized;
+            m_rawControllerState.m_rightMotorVibrationValue = rightMotorSpeedNormalized;
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceOculusTouch::TickInputDevice()
+    {
+        if (m_tickCallback)
+        {
+            m_tickCallback();
+
+            ProcessRawControllerState(m_rawControllerState);
+        }
+    }
+
+} // namespace OpenXRVk

+ 70 - 0
Gems/OpenXRVk/Code/Source/Platform/Common/Default/OculusTouch_Default.h

@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <OpenXRVk/InputDeviceXRController.h>
+#include <AzCore/std/containers/unordered_map.h>
+
+
+namespace OpenXRVk
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //! Platform/API-specific implementation for Oculus Touch Controller input device
+    class InputDeviceOculusTouch
+        : public AzFramework::InputDeviceXRController::Implementation
+    {
+    public:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Allocator
+        AZ_CLASS_ALLOCATOR(InputDeviceOculusTouch, AZ::SystemAllocator, 0);
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        explicit InputDeviceOculusTouch(AzFramework::InputDeviceXRController& inputDevice);
+        AZ_DISABLE_COPY_MOVE(InputDeviceOculusTouch);
+        ~InputDeviceOculusTouch() override;
+
+        AZStd::string GetInputChannelPath(const AzFramework::InputChannelId& channelId) const override;
+        AZStd::string GetInputDeviceProfilePath() const override;
+        AZStd::string GetLeftHandSubPath() const override;
+        AZStd::string GetRightHandSubPath() const override;
+        void RegisterTickCallback(TickCallbackFn callbackFn) override;
+        RawXRControllerState& GetRawState() override;
+
+    private:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! @see AzFramework::InputDeviceXRController::Implementation::IsConnected
+        bool IsConnected() const override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! @see AzFramework::InputDeviceXRController::Implementation::SetVibration
+        void SetVibration(float leftMotorSpeedNormalized, float rightMotorSpeedNormalized) override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! @see AzFramework::InputDeviceXRController::Implementation::TickInputDevice
+        void TickInputDevice() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Data
+        RawXRControllerState m_rawControllerState;  //!< The latest raw xr controller input state
+        bool m_isConnected{}; //!< Is the controller(s) currently connected?
+
+        TickCallbackFn m_tickCallback{ nullptr };
+
+        AZStd::unordered_map<AzFramework::InputChannelId, const AZStd::string_view> m_xrPathMap{};
+    };
+
+} // namespace OpenXRVk
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+// static
+inline AzFramework::InputDeviceXRController::Implementation* AzFramework::InputDeviceXRController::Implementation::Create(
+    InputDeviceXRController& inputDevice)
+{
+    return aznew OpenXRVk::InputDeviceOculusTouch(inputDevice);
+}

+ 2 - 0
Gems/OpenXRVk/Code/Source/Platform/Linux/platform_private_linux_files.cmake

@@ -10,4 +10,6 @@ set(FILES
     OpenXRVk_Traits_Linux.h
     OpenXRVk_Traits_Linux.cpp
     OpenXRVk_Traits_Platform.h
+    ../Common/Default/OculusTouch_Default.cpp
+    ../Common/Default/OculusTouch_Default.h
 )

+ 2 - 0
Gems/OpenXRVk/Code/Source/Platform/Windows/platform_private_windows_files.cmake

@@ -10,4 +10,6 @@ set(FILES
     OpenXRVk_Traits_Windows.h
     OpenXRVk_Traits_Windows.cpp
     OpenXRVk_Traits_Platform.h
+    ../Common/Default/OculusTouch_Default.cpp
+    ../Common/Default/OculusTouch_Default.h
 )

+ 2 - 0
Gems/OpenXRVk/Code/openxrvk_private_common_files.cmake

@@ -7,6 +7,7 @@
 #
 
 set(FILES
+    Include/OpenXRVk/InputDeviceXRController.h
     Include/OpenXRVk/OpenXRVkDevice.h
     Include/OpenXRVk/OpenXRVkInput.h
     Include/OpenXRVk/OpenXRVkInstance.h
@@ -16,6 +17,7 @@ set(FILES
     Include/OpenXRVk/OpenXRVkSwapChain.h
     Include/OpenXRVk/OpenXRVkSystemComponent.h
     Include/OpenXRVk/OpenXRVkUtils.h
+    Source/InputDeviceXRController.cpp
     Source/OpenXRVkDevice.cpp
     Source/OpenXRVkInput.cpp
     Source/OpenXRVkInstance.cpp

+ 4 - 4
Gems/XR/Code/Include/XR/XRBase.h

@@ -23,17 +23,17 @@ namespace XR
 
     using StringList = AZStd::vector<AZStd::string>;
     using RawStringList = AZStd::vector<const char*>;
-    
+
     enum class  Side : uint32_t
     {
         Left = 0,
         Right,
         Count
-    }; 
+    };
 
 #define RETURN_RESULTCODE_IF_UNSUCCESSFUL(result) \
-    if (result != AZ::RHI::ResultCode::Success) {\
-        return result;\
+    if ((result) != AZ::RHI::ResultCode::Success) {\
+        return (result);\
     }
 }
 

+ 9 - 10
Gems/XR/Code/Include/XR/XRDevice.h

@@ -18,8 +18,9 @@
 
 namespace XR
 {
-    //! Base XR device class which will provide access to the back-end concrete object
     class Session;
+
+    //! Base XR device class which will provide access to the back-end concrete object
     class Device
         : public XR::Object
     {
@@ -28,7 +29,7 @@ namespace XR
         AZ_RTTI(Device, "{A31B0DC2-BD54-443E-9350-EB1B10670FF9}");
 
         Device() = default;
-        virtual ~Device() = default;
+        ~Device() override = default;
 
         struct Descriptor
         {
@@ -38,10 +39,10 @@ namespace XR
 
         //! Create the xr specific native device object and populate the XRDeviceDescriptor with it.
         virtual AZ::RHI::ResultCode InitDeviceInternal(AZ::RHI::XRDeviceDescriptor* instanceDescriptor) = 0;
-        
+
         //! Returns true if rendering data is valid for the current frame.
         virtual bool ShouldRender() const = 0;
-        
+
         //! Returns fov data for a give view index.
         virtual AZ::RHI::ResultCode GetViewFov(AZ::u32 viewIndex, AZ::RPI::FovData& outFovData) const = 0;
 
@@ -50,7 +51,7 @@ namespace XR
 
         //! Init the XR device.
         AZ::RHI::ResultCode Init(Descriptor descriptor);
-        
+
         //! Signal Begin frame to the underlying back end.
         //! @note This function is called from the thread related to the presentation queue.
         bool BeginFrame();
@@ -68,7 +69,7 @@ namespace XR
 
         //! Register XR session with the device.
         void RegisterSession(Ptr<Session> session);
-    
+
         //! UnRegister XR session with the device.
         void UnRegisterSession();
 
@@ -79,7 +80,6 @@ namespace XR
         Ptr<Session> GetSession() const;
 
     protected:
-    
         //! Called when the device is being shutdown.
         virtual void ShutdownInternal() = 0;
 
@@ -87,7 +87,7 @@ namespace XR
         //! @note This function is called from the thread related to the presentation queue.
         virtual bool BeginFrameInternal() = 0;
 
-        //! Called when the device is ending a frame for processing. 
+        //! Called when the device is ending a frame for processing.
         //! Pass in the active swapchain in order to allow the back end to release the swap chain images
         //! @note This function is called from the thread related to the presentation queue.
         virtual void EndFrameInternal(XR::Ptr<XR::SwapChain>) = 0;
@@ -100,7 +100,6 @@ namespace XR
         virtual bool AcquireSwapChainImageInternal(AZ::u32 viewIndex, XR::SwapChain* baseSwapChain) = 0;
 
     private:
-
         ///////////////////////////////////////////////////////////////////
         // XR::Object
         void Shutdown() override;
@@ -109,4 +108,4 @@ namespace XR
         Ptr<Session> m_session;
         Descriptor m_descriptor;
     };
-}
+}

+ 2 - 7
Gems/XR/Code/Include/XR/XRInput.h

@@ -13,7 +13,7 @@
 #include <XR/XRObject.h>
 
 namespace XR
-{ 
+{
     class Session;
     class Instance;
     class Device;
@@ -27,21 +27,16 @@ namespace XR
         AZ_CLASS_ALLOCATOR(Input, AZ::SystemAllocator, 0);
         AZ_RTTI(Input, "{DCDFC6A7-B457-414B-BC24-0831C2AC628B}");
 
-        Input() = default;
-        virtual ~Input() = default;
-        
         struct Descriptor
         {
             Ptr<Instance> m_instance;
             Ptr<Device> m_device;
             Ptr<Session> m_session;
         };
-        
+
         AZ::RHI::ResultCode Init(Descriptor descriptor);
         const Descriptor& GetDescriptor() const;
 
-        virtual void PollActions() = 0;
-       
     private:
         ///////////////////////////////////////////////////////////////////
         // XR::Object

+ 5 - 6
Gems/XR/Code/Include/XR/XRInstance.h

@@ -24,10 +24,10 @@ namespace XR
         AZ_RTTI(Instance, "{1C457924-56A4-444F-BC72-4D31A097BA70}");
 
         Instance() = default;
-        virtual ~Instance() = default;
+        ~Instance() override = default;
 
-        //! Init the back-end instance. It is responsible for figuring out supported layers and extensions 
-        //! and based on that a xr instance is created. It also has logging support based on validation mode.  
+        //! Init the back-end instance. It is responsible for figuring out supported layers and extensions
+        //! and based on that a xr instance is created. It also has logging support based on validation mode.
         AZ::RHI::ResultCode Init(AZ::RHI::ValidationMode validationMode);
 
         //! API to init the native instance object and populate the XRInstanceDecriptor with it.
@@ -40,7 +40,6 @@ namespace XR
         virtual AZ::RHI::ResultCode GetXRPhysicalDevice(AZ::RHI::XRPhysicalDeviceDescriptor* physicalDeviceDescriptor, int32_t index) = 0;
 
     private:
-
         ///////////////////////////////////////////////////////////////////
         // XR::Object
         void Shutdown() override;
@@ -49,9 +48,9 @@ namespace XR
         //! Called when the XR instance is being shutdown.
         virtual void ShutdownInternal() = 0;
 
-        //! API to allow backend object to initialize native xr instance. 
+        //! API to allow backend object to initialize native xr instance.
         virtual AZ::RHI::ResultCode InitInstanceInternal(AZ::RHI::ValidationMode m_validationMode) = 0;
-       
+
         //Cache validation mode in case the backend object needs to use it.
         AZ::RHI::ValidationMode m_validationMode = AZ::RHI::ValidationMode::Disabled;
     };

+ 1 - 1
Gems/XR/Code/Include/XR/XRObject.h

@@ -31,7 +31,7 @@ namespace XR
     {
     public:
         AZ_RTTI(Object, "{74FCA8BF-CBDA-43EB-A378-89F752ED2FCA}");
-        virtual ~Object() = default;
+        ~Object() override = default;
 
         //! Sets the name of the object.
         void SetName(const AZ::Name& name)

+ 4 - 5
Gems/XR/Code/Include/XR/XRSession.h

@@ -36,8 +36,8 @@ namespace XR
         };
 
         Session() = default;
-        virtual ~Session() = default;
-        
+        ~Session() override = default;
+
         //! Initialize the XrSession object which is responsible for creating
         //! XrInput and XrSpace
         AZ::RHI::ResultCode Init(const Descriptor& descriptor);
@@ -50,7 +50,7 @@ namespace XR
 
         //! Get the Xr Space object
         Space* GetSpace() const;
-        
+
         //! Return true if session is running
         virtual bool IsSessionRunning() const = 0;
 
@@ -59,7 +59,7 @@ namespace XR
 
         //! Return true if a restart is requested
         virtual bool IsRestartRequested() const = 0;
-    
+
         //! Return true if render loop skip is requested
         virtual bool IsExitRenderLoopRequested() const = 0;
 
@@ -112,7 +112,6 @@ namespace XR
         virtual float GetTriggerState(AZ::u32 handIndex) const = 0;
 
     private:
-
         ///////////////////////////////////////////////////////////////////
         // XR::Object
         void Shutdown() override;

+ 2 - 3
Gems/XR/Code/Include/XR/XRSpace.h

@@ -18,7 +18,6 @@ namespace XR
     class Space
         : public ::XR::Object
     {
-	    
     public:
         AZ_CLASS_ALLOCATOR(Space, AZ::SystemAllocator, 0);
         AZ_RTTI(Space, "{A78A37F1-8861-4EB4-8FC6-0E9C11394EF1}");
@@ -30,8 +29,8 @@ namespace XR
 
         AZ::RHI::ResultCode Init(Descriptor descriptor);
         const Space::Descriptor& GetDescriptor() const;
-    private:
 
+    private:
         ///////////////////////////////////////////////////////////////////
         // XR::Object
         void Shutdown() override;
@@ -40,7 +39,7 @@ namespace XR
         //! Called when the XR instance is being shutdown.
         virtual void ShutdownInternal() = 0;
         virtual AZ::RHI::ResultCode InitInternal() = 0;
-       
+
         Descriptor m_descriptor;
     };
 } // namespace XR

+ 11 - 12
Gems/XR/Code/Include/XR/XRSwapChain.h

@@ -22,14 +22,14 @@ namespace XR
     //! This class will be responsible for creating multiple XR::SwapChain::ViewSwapchains
     //! (one per view). Each XR::SwapChain::ViewSwapchain will then be responsible
     //! for manging and synchronizing multiple swap chain images
-    class SwapChain 
+    class SwapChain
         : public XR::Object
     {
     public:
         AZ_CLASS_ALLOCATOR(SwapChain, AZ::SystemAllocator, 0);
         AZ_RTTI(SwapChain, "{0C666E76-E4B7-4097-8D14-713DC2C446EF}");
 
-        class Image 
+        class Image
             : public AZStd::intrusive_base
         {
         public:
@@ -37,10 +37,10 @@ namespace XR
             AZ_RTTI(Image, "{4037835D-F1BB-4407-BC98-2299CC7BE0A3}");
 
             Image() = default;
-            virtual ~Image() = default;
+            ~Image() override = default;
         };
 
-        class View 
+        class View
             : public AZStd::intrusive_base
         {
         public:
@@ -48,7 +48,7 @@ namespace XR
             AZ_RTTI(View, "{774EB724-8261-4684-AA78-EDF6BBECD48A}");
 
             View() = default;
-            virtual ~View() = default;
+            ~View() override = default;
 
             virtual void Shutdown() = 0;
             virtual AZ::u32 GetCurrentImageIndex() const = 0;
@@ -66,7 +66,7 @@ namespace XR
 
             //! Width of the swap chain view.
             AZ::u32 m_width = 0;
-    
+
             //! Height of the swap chain view.
             AZ::u32 m_height = 0;
         };
@@ -89,7 +89,7 @@ namespace XR
         //! Initialize the XR swapchain.
         AZ::RHI::ResultCode Init(const Descriptor& descriptor);
 
-        //! Get the descriptor. 
+        //! Get the descriptor.
         const Descriptor& GetDescriptor() const;
 
         //! Get the number of Xr views
@@ -97,10 +97,10 @@ namespace XR
 
         //! Api to allow the back end object to return the requested native swapchain image
         virtual AZ::RHI::ResultCode GetSwapChainImage(AZ::RHI::XRSwapChainDescriptor* swapchainDescriptor) const = 0;
-       
+
         //! Api to allow the back end to report the recommended swapchain width
         virtual AZ::u32 GetSwapChainWidth(AZ::u32 viewIndex) const = 0;
-        
+
         //! Api to allow the back end to report the recommended swapchain height
         virtual AZ::u32 GetSwapChainHeight(AZ::u32 viewIndex) const = 0;
 
@@ -108,13 +108,12 @@ namespace XR
         virtual AZ::RHI::Format GetSwapChainFormat(AZ::u32 viewIndex) const = 0;
 
     protected:
-        
         //! Number of Xr views
         AZ::u32 m_numViews = 0;
 
         //! Vector to hold all the SwapChain View objects
         AZStd::vector<Ptr<SwapChain::View>> m_viewSwapchains;
-   
+
     private:
         ///////////////////////////////////////////////////////////////////
         // XR::Object override
@@ -124,7 +123,7 @@ namespace XR
         //! Called when the swapchain is being shutdown.
         virtual void ShutdownInternal() = 0;
 
-        //! Api to allow the back end object to initialize  
+        //! Api to allow the back end object to initialize
         virtual AZ::RHI::ResultCode InitInternal() = 0;
 
         Descriptor m_descriptor;

+ 8 - 9
Gems/XR/Code/Include/XR/XRSystem.h

@@ -21,9 +21,9 @@
 
 namespace XR
 {
-    //! This class is the window to everything XR related. It implements 
-    //! RPI::RenderingInterface and RHI::RenderingInterface but
-    //! can be extended to implement other non rendering interfaces if needed. 
+    //! This class is the window to everything XR related.
+    //! It implements RPI::RenderingInterface and RHI::RenderingInterface but
+    //! can be extended to implement other non rendering interfaces if needed.
     class System
         : public AZ::RPI::XRRenderingInterface
         , public AZ::RHI::XRRenderingInterface
@@ -34,10 +34,9 @@ namespace XR
         AZ_CLASS_ALLOCATOR(System, AZ::SystemAllocator, 0);
         AZ_RTTI(System, "{C3E0291D-FB30-4E27-AB0D-14606A8C3C1F}");
 
-        AZ_DISABLE_COPY_MOVE(System);
-
         System() = default;
-        ~System() = default;
+        ~System() override = default;
+        AZ_DISABLE_COPY_MOVE(System);
 
         struct Descriptor
         {
@@ -47,7 +46,7 @@ namespace XR
         //! Init the XRSystem.
         void Init(const Descriptor& descriptor);
 
-        //! Destroy any relevant objects held by this .class 
+        //! Destroy any relevant objects held by this .class
         void Shutdown();
 
         //! Handle XR events and actions
@@ -71,8 +70,8 @@ namespace XR
         float GetControllerScale(AZ::u32 handIndex) const override;
         bool ShouldRender() const override;
         AZ::Matrix4x4 CreateStereoscopicProjection(float angleLeft, float angleRight,
-                                                float angleBottom, float angleTop, 
-                                                float nearDist, float farDist, bool reverseDepth) override;
+                                                   float angleBottom, float angleTop,
+                                                   float nearDist, float farDist, bool reverseDepth) override;
         AZ::RHI::XRRenderingInterface* GetRHIXRRenderingInterface() override;
         float GetXButtonState() const override;
         float GetYButtonState() const override;

+ 1 - 1
Gems/XR/Code/Include/XR/XRSystemComponent.h

@@ -24,7 +24,7 @@ namespace XR
         static void Reflect(AZ::ReflectContext* context);
 
         SystemComponent();
-        ~SystemComponent() = default;
+        ~SystemComponent() override = default;
 
         //////////////////////////////////////////////////////////////////////////
         // Component

+ 4 - 3
Gems/XR/Code/Include/XR/XRUtils.h

@@ -12,11 +12,12 @@
 
 namespace XR
 {
-    //! Creates an off-center projection matrix suitable for VR. It does the following in order to provide a stereoscopic projection
+    //! Creates an off-center projection matrix suitable for VR.
+    //! It does the following in order to provide a stereoscopic projection:
     //! Stretch more horizontally and vertically
     //! Generate asymmetric or off-center projection matrix
-    //! Right handed coord system as Openxr provides data in that system
+    //! Right-handed coord system as OpenXR provides data in that system
     //! Provides support for reverse depth in case we want better depth precision
-    //! Handles the case where farDist is less than nearDist whereby it will place far plane at infinity. 
+    //! Handles the case where farDist is less than nearDist whereby it will place far plane at infinity.
     AZ::Matrix4x4 CreateStereoscopicProjection(float angleLeft, float angleRight, float angleBottom, float angleTop, float nearDist, float farDist, bool reverseDepth);
 }

+ 3 - 3
Gems/XR/Code/Source/XRSession.cpp

@@ -25,7 +25,7 @@ namespace XR
             AZ_Error("XR", result == AZ::RHI::ResultCode::Success, "XR Space was not initialized");
             RETURN_RESULTCODE_IF_UNSUCCESSFUL(result);
         }
-        
+
         m_input = Factory::Get().CreateInput();
         AZ_Error("XR", m_input, "XR Input was not created");
         if (m_input)
@@ -48,7 +48,7 @@ namespace XR
         m_descriptor.m_device->UnRegisterSession();
         ShutdownInternal();
     }
-    
+
     Input* Session::GetInput() const
     {
         return m_input.get();
@@ -57,5 +57,5 @@ namespace XR
     Space* Session::GetSpace() const
     {
         return m_space.get();
-    } 
+    }
 } // namespace XR

+ 0 - 4
Gems/XR/Code/Source/XRSystem.cpp

@@ -135,10 +135,6 @@ namespace XR
     void System::OnSystemTick()
     {
         m_session->PollEvents();
-        if (m_session->IsSessionRunning())
-        {
-            m_session->GetInput()->PollActions();
-        }
     }
     
     void System::BeginFrame()